JVM Bytecode manipulation

 

When reflection is simply not enough

Piotr Lewandowski

Not an expert

 

not even a newbie...

Manipulation

is the skillful handling, controlling or using of someone

Bytecode manipulation

  • Modify method body
  • Replace statements
  • Generate classes

instrumentation

It's everywhere

we make Java more like JavaScript

Hot Reload

Java Debugger API since 2002

Works good when

  1. Modify method body
  2. No new classes
  3. No annotation changes

Not good for

  1. Method definition change
  2. New fields declarations
  3. Spring beans changes
 
 


class Framework {
    public void configure() {
        // Configure beans, set-up endpoints
    }
}


class Framework implements Listener {
    public void configure() {
        // ...
    }
}


CtClass framework
    = cp.get("com.zt.Framework");


framework.addInterface(
    cp.get("com.zt.jrebel.Listener"));
framework.addMethod(
    CtNewMethod.make(
        "public void onEvent() {" +
        "    configure();" +
        "}",
        framework
));


class Framework implements Listener {
    public void configure() {
        // ...
    }

    public void onEvent() {
        configure();
    }
}

Javassist

Result

It's useful

and necessary

Monitoring and profiling

Generating classes from metadata

Caching

Obfuscation

Mocking

java.lang.NullPointerException: null
	at net.piotrl.NewsService.get(NewsService.java:50)
	at net.piotrl.NewsService$$FastClassBySpringCGLIB$$a842897a.invoke(<generated>)
	at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:204)
	at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:717)
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:157)
	at org.springframework.transaction.interceptor.TransactionInterceptor$1.proceedWithInvocation(TransactionInterceptor.java:99)
	at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:281)
	at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:96)
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
	at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:653)
	at net.piotrl.NewsService$$EnhancerBySpringCGLIB$$b6cab172.get(<generated>)
	at net.piotrl.NewsController.get(NewsController.java:46)

Proxy Class

Reflection

/* Handle how proxy behaves */

InvocationHandler handler = (proxy, method, methodArgs) -> {
    if (method.getName().equals("get")) {
        return 42;
    } else {
        throw new UnsupportedOperationException(
          "Unsupported method: " + method.getName());
    }
/* Create proxy for Map */

Map mapProxy = (Map) Proxy.newProxyInstance(
  DynamicProxyTest.class.getClassLoader(), 
  new Class[] { Map.class }, 
  handler
);
/* Result */

proxyInstance.get("hello"); // 42
proxyInstance.put("hello", "world"); // exception

Proxy Class

Reflection

/* Create proxy */

YourEntity mapProxy = (YourEntity) Proxy.newProxyInstance(
  DynamicProxyTest.class.getClassLoader(), 
  new Class[] { YourEntity.class }, 
  handler
);
/* Result */

java.lang.IllegalArgumentException: 
    DynamicProxyTest$1YourEntity is not an interface
@Entity
class YourEntity {
    // getters ...
}
public class JavassistLazyInitializer extends BasicLazyInitializer
        implements MethodHandler {

    final JavassistLazyInitializer instance = new JavassistLazyInitializer(...);

    ProxyFactory factory = new ProxyFactory();
    // factory set-up class metadata



    Class cl = factory.createClass();
    final HibernateProxy proxy = ( HibernateProxy ) cl.newInstance();
    ( (Proxy) proxy ).setHandler( instance );

    return proxy;

InvocationHandler handler = (proxy, method, methodArgs) -> {
        if (method.getName().equals("get")) {
            return 42;
        } else {
            throw new UnsupportedOperationException(
              "Unsupported method: " + method.getName()
            );
        }

Proxy Class

Javassist

Libraries

Javassist

  • Write code in strings
  • What You See Is What You Get
  • API similar to reflection

Javassist

 

Demo

Further reading

Bytecode manipulation with Javassist

By Piotr Lewandowski

Bytecode manipulation with Javassist

Na prezentacji opowiem o tym gdzie i w jaki sposób się manipuluje bytecodem oraz pokażę możliwości biblioteki Javassist która dostarcza API pozwalające na modyfikację bytecode bez narzutu znajomości bytecode samego w sobie. O czym nie będzie prezentacji: To nie będzie deep dive, nie będziemy hackować JVM ani szczegółowo omawiać fragmentów bytecode.

  • 483