Slide 1

Slide 1 text

Having fun with Javassist JEEConf 2017 @antonarhipov

Slide 2

Slide 2 text

whoami Anton Arhipov @antonarhipov Javassist inside!

Slide 3

Slide 3 text

No content

Slide 4

Slide 4 text

@Entity
 @Table(name = "owners")
 public class Owner extends Person {
 @Column(name = "address")
 @NotEmpty
 private String address;
 
 @Column(name = "city")
 @NotEmpty
 private String city;
 
 @Column(name = "telephone")
 @NotEmpty
 @Digits(fraction = 0, integer = 10)
 private String telephone;
 
 @OneToMany(cascade = CascadeType.ALL, mappedBy = "owner")
 private Set pets;

Slide 5

Slide 5 text

public class JavassistLazyInitializer extends BasicLazyInitializer implements MethodHandler { final JavassistLazyInitializer instance = new JavassistLazyInitializer(…); 
 ProxyFactory factory = new ProxyFactory();
 factory.setSuperclass(interfaces.length == 1?persistentClass:null);
 factory.setInterfaces(interfaces);
 factory.setFilter(FINALIZE_FILTER); 
 Class cl = factory.createClass();
 final HibernateProxy proxy = (HibernateProxy) cl.newInstance();
 ((ProxyObject)proxy).setHandler(instance);
 instance.constructed = true;
 return proxy;

Slide 6

Slide 6 text

public class JavassistLazyInitializer extends BasicLazyInitializer implements MethodHandler { final JavassistLazyInitializer instance = new JavassistLazyInitializer(…); 
 ProxyFactory factory = new ProxyFactory();
 factory.setSuperclass(interfaces.length == 1?persistentClass:null);
 factory.setInterfaces(interfaces);
 factory.setFilter(FINALIZE_FILTER); 
 Class cl = factory.createClass();
 final HibernateProxy proxy = (HibernateProxy) cl.newInstance();
 ((ProxyObject)proxy).setHandler(instance);
 instance.constructed = true;
 return proxy;

Slide 7

Slide 7 text

The main use case for bytecode generation in Java frameworks is to generate proxies

Slide 8

Slide 8 text

No content

Slide 9

Slide 9 text

Why? Programming model (AOP, ORM, итд) Monitoring agent (NewRelic, XRebel) Specialized debuggers Developer tools (JRebel) Obfuscation (Proguard)

Slide 10

Slide 10 text

Javassist 101 www.javassist.org

Slide 11

Slide 11 text

API ClassPool CtClass CtClass CtClass CtClass CtField CtMethod CtConst CtMethod insertBefore insertAfter instrument

Slide 12

Slide 12 text

https://speakerdeck.com/antonarhipov

Slide 13

Slide 13 text

public static void main(String[] args) throws Exception { ClassPool cp = ClassPool.getDefault(); CtClass ct = cp.makeClass("com.zt.A", cp.get("com.zt.Clazz")); CtMethod[] methods = ct.getMethods();
 for (CtMethod method : methods) {
 //… } ct.writeFile("/output"); }

Slide 14

Slide 14 text

public static void main(String[] args) throws Exception { ClassPool cp = ClassPool.getDefault(); CtClass ct = cp.makeClass("com.zt.A", cp.get("com.zt.Clazz")); CtMethod[] methods = ct.getMethods();
 for (CtMethod method : methods) {
 //… } ct.writeFile("/output"); } ClassPool cp = new ClassPool(null); cp.appendSystemPath();

Slide 15

Slide 15 text

public static void main(String[] args) throws Exception { ClassPool cp = ClassPool.getDefault(); CtClass ct = cp.makeClass("com.zt.A", cp.get("com.zt.Clazz")); CtMethod[] methods = ct.getMethods();
 for (CtMethod method : methods) {
 //… } ct.writeFile("/output"); } public class A extends Clazz { 
 public A() {
 }
 }

Slide 16

Slide 16 text

public static void main(String[] args) throws Exception { ClassPool cp = ClassPool.getDefault(); CtClass ct = cp.makeClass("com.zt.A", cp.get("com.zt.Clazz")); CtMethod[] methods = ct.getMethods();
 for (CtMethod method : methods) {
 //… } ct.writeFile("/output"); }

Slide 17

Slide 17 text

public static void main(String[] args) throws Exception { ClassPool cp = ClassPool.getDefault(); CtClass ct = cp.makeClass("com.zt.A", cp.get("com.zt.Clazz")); CtMethod[] methods = ct.getMethods();
 for (CtMethod method : methods) {
 //… } ct.writeFile("/output"); } mars:output anton$ javap -c com/zt/A.class public class com.zt.A extends com.zt.Clazz { public com.zt.A(); Code: 0: aload_0 1: invokespecial #10 4: return

Slide 18

Slide 18 text

public static void main(String[] args) throws Exception { ClassPool cp = ClassPool.getDefault(); CtClass ct = cp.makeClass("com.zt.A", cp.get("com.zt.Clazz")); CtMethod[] methods = ct.getMethods();
 for (CtMethod method : methods) {
 //… } ct.writeFile("/output"); } Can generate classes from metadata at build time

Slide 19

Slide 19 text

public static void main(String[] args) throws Exception { ClassPool cp = ClassPool.getDefault(); cp.appendClassPath(new ClassPath(){ … }); CtClass ct = cp.get("com.zt.A"); CtMethod[] methods = ct.getMethods();
 for (CtMethod method : methods) {
 //… } ct.writeFile("/output"); } … or you can post process the compiled classes

Slide 20

Slide 20 text

public static void main(String[] args) throws Exception { ClassPool cp = ClassPool.getDefault(); CtClass ctClass = cp.get("com.zt.A"); CtMethod foo = ctClass.getMethod("foo", "()V"); foo.insertBefore("System.out.println();"); Class c = ctClass.toClass();
 A a = (A) c.newInstance();
 a.foo("Hello");
 }

Slide 21

Slide 21 text

public static void main(String[] args) throws Exception { ClassPool cp = ClassPool.getDefault(); CtClass ctClass = cp.get("com.zt.A"); CtMethod foo = ctClass.getMethod("foo", "()V"); foo.insertBefore("System.out.println();"); Class c = ctClass.toClass();
 A a = (A) c.newInstance();
 a.foo("Hello");
 }

Slide 22

Slide 22 text

public static void main(String[] args) throws Exception { ClassPool cp = ClassPool.getDefault(); CtClass ctClass = cp.get("com.zt.A"); CtMethod foo = ctClass.getMethod("foo", "()V"); foo.insertBefore("System.out.println();"); Class c = ctClass.toClass();
 A a = (A) c.newInstance();
 a.foo("Hello");
 } public void foo() { 
 }

Slide 23

Slide 23 text

public static void main(String[] args) throws Exception { ClassPool cp = ClassPool.getDefault(); CtClass ctClass = cp.get("com.zt.A"); CtMethod foo = ctClass.getMethod("foo", "(Ljava/lang/String;)V"); foo.insertBefore("System.out.println();"); Class c = ctClass.toClass();
 A a = (A) c.newInstance();
 a.foo("Hello");
 } public void foo(String s) { 
 }

Slide 24

Slide 24 text

public static void main(String[] args) throws Exception { ClassPool cp = ClassPool.getDefault(); CtClass ctClass = cp.get("com.zt.A"); CtMethod foo = ctClass.getMethod("foo", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lja foo.insertBefore("System.out.println();"); Class c = ctClass.toClass();
 A a = (A) c.newInstance();
 a.foo("Hello");
 } Descriptors might get quite long ;)

Slide 25

Slide 25 text

public static void main(String[] args) throws Exception { ClassPool cp = ClassPool.getDefault(); CtClass ctClass = cp.get("com.zt.A"); CtMethod foo = ctClass.getMethod("foo", "(Ljava/lang/String;)V"); foo.insertBefore("System.out.println($1)"); Class c = ctClass.toClass();
 A a = (A) c.newInstance();
 a.foo("Hello");
 } $1, $2, $3 — local variables $0 — this for non-static methods

Slide 26

Slide 26 text

public static void main(String[] args) throws Exception { ClassPool cp = ClassPool.getDefault(); CtClass ctClass = cp.get("com.zt.A"); CtMethod foo = ctClass.getMethod("foo", "(Ljava/lang/String;)V"); foo.insertBefore("System.out.println($1)"); Class c = ctClass.toClass();
 A a = (A) c.newInstance();
 a.foo("Hello");
 } Exception in thread "main" javassist.CannotCompileException: [source error] ; is missing at javassist.CtBehavior.insertBefore(CtBehavior.java:774) at javassist.CtBehavior.insertBefore(CtBehavior.java:734) at com.zt.basics.Ex.main(Ex.java:35)

Slide 27

Slide 27 text

public static void main(String[] args) throws Exception { ClassPool cp = ClassPool.getDefault(); CtClass ctClass = cp.get("com.zt.A"); CtMethod foo = ctClass.getMethod("foo", "(Ljava/lang/String;)V"); foo.insertBefore("System.out.println($1);"); Class c = ctClass.toClass();
 A a = (A) c.newInstance();
 a.foo("Hello");
 }

Slide 28

Slide 28 text

CtMethod foo = … foo.insertBefore(…); foo.insertAfter(…); Can implement tracing

Slide 29

Slide 29 text

… or add logging CtMethod foo = … foo.insertBefore(…); foo.insertAfter(…);

Slide 30

Slide 30 text

… or implement AOP CtMethod foo = … foo.insertBefore(…); foo.insertAfter(…);

Slide 31

Slide 31 text

CtMethod foo = … foo.instrument(new ExprEditor() { @Override
 public void edit(NewExpr e) throws CannotCompileException {
 e.replace("{" +
 "$_ = $proceed($$);" +
 "System.out.println($_);" +
 "}");
 } });

Slide 32

Slide 32 text

CtMethod foo = … foo.instrument(new ExprEditor() { @Override
 public void edit(NewExpr e) throws CannotCompileException {
 e.replace("{" +
 "$_ = $proceed($$);" +
 "System.out.println($_);" +
 "}");
 } });

Slide 33

Slide 33 text

CtMethod foo = … foo.instrument(new ExprEditor() { @Override
 public void edit(NewExpr e) throws CannotCompileException {
 e.replace("{" +
 "$_ = $proceed($$);" +
 "System.out.println($_);" +
 "}");
 } }); Intercept new instances

Slide 34

Slide 34 text

CtMethod foo = … foo.instrument(new ExprEditor() { @Override
 public void edit(NewExpr e) throws CannotCompileException {
 e.replace("{" +
 "$_ = $proceed($$);" +
 "System.out.println($_);" +
 "}");
 } }); Intercept new instances

Slide 35

Slide 35 text

CtMethod foo = … foo.instrument(new ExprEditor() { @Override
 public void edit(MethodCall m) throws CannotCompileException {
 if(m.getMethodName().contains("println")) {
 m.replace("{}");
 } } }); Remove unwanted invocations

Slide 36

Slide 36 text

CtMethod foo = … foo.instrument(new ExprEditor() { @Override
 public void edit(FieldAccess f) throws CannotCompileException {
 if (f.isWriter()) {
 CtField field = f.getField();
 String setterName = findSetter(field);
 f.replace("{" + "$0." + setterName + "($$);" + "}");
 } } }); Replace direct field access with setter calls

Slide 37

Slide 37 text

java.lang.ClassFormatError: LVTT entry for 'callbackTypes' in class file com/google/inject/internal/ProxyFactory does not match any LVT entry

Slide 38

Slide 38 text

This slide is intentionally left blank

Slide 39

Slide 39 text

Java agents

Slide 40

Slide 40 text

Java Agent import java.lang.instrument.ClassFileTransformer; import java.lang.instrument.Instrumentation; public class Agent { public static void premain(String args, Instrumentation inst) throws Exception { inst.addTransformer(new ClassFileTransformer { // here be code }); } } META-INF/MANIFEST.MF Premain-Class: Agent $> java –javaagent:agent.jar application.Main

Slide 41

Slide 41 text

ClassFileTransformer new ClassFileTransformer() { public byte[] transform(ClassLoader loader, String className, Class> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer){ ClassPool cp = ClassPool.getDefault(); CtClass ct = cp.makeClass(new ByteArrayInputStream(classfileBuffer)); // here we can do all the things to ‘ct’ return ct.toBytecode(); } }

Slide 42

Slide 42 text

new ClassFileTransformer() { public byte[] transform(ClassLoader loader, String className, Class> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer){ ClassPool cp = ClassPool.getDefault(); CtClass ct = cp.makeClass(new ByteArrayInputStream(classfileBuffer)); // here we can do all the things to ‘ct’ return ct.toBytecode(); } } ClassFileTransformer

Slide 43

Slide 43 text

new ClassFileTransformer() { public byte[] transform(ClassLoader loader, String className, Class> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer){ ClassPool cp = ClassPool.getDefault(); CtClass ct = cp.makeClass(new ByteArrayInputStream(classfileBuffer)); // here we can do all the things to ‘ct’ return ct.toBytecode(); } } ClassFileTransformer

Slide 44

Slide 44 text

new ClassFileTransformer() { public byte[] transform(ClassLoader loader, String className, Class> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer){ ClassPool cp = ClassPool.getDefault(); CtClass ct = cp.makeClass(new ByteArrayInputStream(classfileBuffer)); // here we can do all the things to ‘ct’ return ct.toBytecode(); } } ClassFileTransformer

Slide 45

Slide 45 text

No content

Slide 46

Slide 46 text

No content

Slide 47

Slide 47 text

No content

Slide 48

Slide 48 text

No content

Slide 49

Slide 49 text

No content

Slide 50

Slide 50 text

https://github.com/zeroturnaround/callspy

Slide 51

Slide 51 text

Javassist in

Slide 52

Slide 52 text

JRebel core Spring plugin Hibernate plugin EJB plugin

Slide 53

Slide 53 text

JRebel core Spring plugin Hibernate plugin EJB plugin jrebel.jar

Slide 54

Slide 54 text

Reloads classes JRebel core Spring plugin Hibernate plugin EJB plugin

Slide 55

Slide 55 text

Notifies plugins JRebel core Spring plugin Hibernate plugin EJB plugin

Slide 56

Slide 56 text

JRebel core Spring plugin Hibernate plugin EJB plugin Refresh configurations

Slide 57

Slide 57 text

Javassist lives here Spring plugin Hibernate plugin EJB plugin JRebel core

Slide 58

Slide 58 text

Spring Hibernate OpenEJB JRebel core Spring plugin Hibernate plugin EJB plugin

Slide 59

Slide 59 text

class Framework { public void configure(){ //…
 } } CtClass framework = cp.get("com.zt.Framework"); framework.addInterface( cp.get("com.zt.jrebel.Listener")); class Framework implements Listener { public void configure(){
 } } framework.addMethod( CtNewMethod.make( "public void onEvent(){" + " configure();" + "}", framework ));

Slide 60

Slide 60 text

https://github.com/antonarhipov/jpoint HowItWorks

Slide 61

Slide 61 text

This slide is intentionally left blank

Slide 62

Slide 62 text

Your task Javassist

Slide 63

Slide 63 text

https://speakerdeck.com/antonarhipov http://www.slideshare.net/arhan @antonarhipov anton@zeroturnaround.com