Shine and Poverty of 

Java Proxies

Yegor Bondar

Purposes

  • Revisit Java code build and work cycle
  • An attempt to understand Java proxy mechanism
  • Dig deeper into frameworks architecture tricks

outline

  • Long Story Short: Bytecode, ClassLoader, Java Agent, Bytecode manipulation
  • Java Proxy world: Dynamic Proxy, Javassist, CGLib
  • Proxied Frameworks: Hibernate, Spring, Mocking 

J(XX)

What is java bytecode?

Java Class Loading quizz

class Parent{
 {
  System.out.println("Parent init block before ctor");
 }

  String toString = this.toString();

  public static String var = "static variable";

  static {
    System.out.println("Parent static block");
  }

  public Parent(){
    System.out.println("Parent ctor");
  }

  {
   System.out.println("Parent init block after ctor");
  }

  @Override
  public String toString(){
     System.out.println("Parent toString call");
     return "toString()";
  }
}
class Child extends Parent{

static {
  System.out.println("Child static block");
}

{
  System.out.println("Child init block before ctor");
}

int hash = this.hashCode();

public Child(){
 System.out.println("Child ctor");
}

{
  System.out.println("Child init block after ctor");
}

@Override
public int hashCode(){
  System.out.println("Child hashCode call");
  return new Random().nextInt();
}
}

Java Class Loading quizz

    public static void main(String[] args) {
        new Child();
        new Child();
    }

1. Parent static block

3. Parent init block before ctor

4. Parent toString call

5. Parent init block after ctor

6. Parent ctor

7. Child init block before ctor

8. Child hashCode call

9. Child init block after ctor

10. Child ctor

2. Child static block

What is java classloader?

Responsibilities:

  • Dynamically load classes
  • Perform class cache actuality
  • Verify correctness of bytecode

 

What is java classloader?

How java classloader works

java.lang.ClassCastException

public abstract class ClassLoader {
}

class NetworkClassLoader extends ClassLoader {
   String host;
   int port;
 
   public Class findClass(String name) {
     byte[] b = loadClassData(name);
     Class<?> result = cache.get(name);
     if(result == null)
        result = super.findSystemClass(name);
     if(result == null)
        result = defineClass(name, b, 0, b.length);
     return result;
   }
 
   private byte[] loadClassData(String name) {
     // load the class data from the connection
  }
}

What is java classloader?

What is java Agent?

DIg deeper into java agent - Entry point

public class DemoAgent {
    public static void premain(String agentArguments,
                               Instrumentation instrumentation){

        System.out.println("Demo JavaAgent Entry point. Args: " + agentArguments);

        instrumentation.addTransformer(new DemoClassTransformer());

    }
}

Exec command:

java -javaagent:/to/agent.jar to/java/program/EntryPoint

Added ability to modify application code w/o modifying source code.

public class DemoClassTransformer implements ClassFileTransformer {

    @Override
    public byte[] transform(ClassLoader loader,
                            String className,
                            Class<?> classBeingRedefined,
                            ProtectionDomain protectionDomain,
                            byte[] classfileBuffer) throws IllegalClassFormatException {

        System.out.println("Transformation candidate: " + className);

        return classfileBuffer;

    }
}

DIg deeper into java agent - Class Transformer

Why Should I know All these Strange stuff?

Program analysis

  •   find bugs in code
  •   examine code complexity generation

 

Class generation

  •   proxies
  •   remove access to certain APIs
  •   compiler for another language like Scala

 

Transform classes without Java source code

  •   profilers
  •   optimization and obfuscation
  •   additional logging

Frameworks usage

  • Spring : AOP, Java Config
  • Hibernate : Lazy Proxies
  • Mocking frameworks : Mocks & Stubs
  • JRebel, XRebel, NewRelic, Profiling tools : Statistics & HotSwap
  • Groovy, Clojure, Scala : compilation mixtures

Proxy concept

Java Proxy - Problems solved

  • Event publishing - on method x(), transparently call y() or send message z.
  • Transaction management (for db connections or other transactional ops)
  • Thread management - thread out expensive operations transparently.
  • Performance tracking - timing operations checked by a CountdownLatch, for example.
  • Connection management - improve API that require some operations before actual action performed
  • Changing method parameters - in case you want to pass default values for nulls, if that's your sort of thing.

Libraries overview

Complexity and barrier to entry:

Libraries overview

LIBRARY OVERVIEW - java Dynamic proxy

  • They are based on runtime implementations of interfaces 
  • They are public, final and not abstract 
  • They extend java.lang.reflect.Proxy
  • All method invocations go through java.lang.reflect.InvocationHandler

Java Dynamic Proxies are:

LIBRARY OVERVIEW - java Dynamic proxy

public class NoOpAddInvocationHandler implements InvocationHandler {
 
  private final List proxied;
 
  public NoOpAddInvocationHandler(List proxied) {
 
    this.proxied = proxied;
  }
 
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
 
    if (method.getName().startsWith("add")) {
 
      return false;
    }
 
    return method.invoke(proxied, args);
  }
}
List proxy = (List) Proxy.newProxyInstance(
  NoOpAddInvocationHandlerTest.class.getClassLoader(),
  new Class[] { List.class },
  new NoOpAddInvocationHandler(list));

Library overview - Javassist

  • Subproject of JBoss
  • Javassist version 3.19.0-GA is available (Jan. 6, 2015). 
  • MPL, LGPL and Apache License

Library overview - Javassist bytecode manipulation

Input transformed = (Input) BytecodeTransformer
                    .wrapAround("org.edu.proxy.Input", "method")
                    .getDeclaredConstructor(String.class)
                    .newInstance("Hello world!");
    public static Class wrapAround(String className, String methodName){
        Class result = null;
        ClassPool cp = ClassPool.getDefault();
        try {
            CtClass targetClass = cp.get(className);
            CtMethod targetMethod = targetClass.getDeclaredMethod(methodName);
            targetMethod.insertBefore("{System.out.println(field);};");
            result = targetClass.toClass();
        } catch (Exception e) {
            e.printStackTrace();
        }

        return result;
    }

Library overview - Javassist Proxy

public static <T> T create(Class<T> classs) throws Exception {
	ProxyFactory factory = new ProxyFactory();
	factory.setSuperclass(classs);
	Class clazz = factory.createClass();
	MethodHandler handler = new MethodHandler() {

		@Override
		public Object invoke(Object self, Method overridden, Method forwarder,
				Object[] args) throws Throwable {
			System.out.println("do something "+overridden.getName());
			return forwarder.invoke(self, args);
		}
	};
	Object instance = clazz.newInstance();
	((ProxyObject) instance).setHandler(handler);
	return (T) instance;
}

Library overview - Byte Buddy

Byte Buddy is a code generation library for creating Java classes during the runtime of a Java application and without the help of a compiler.

library overview - byte buddy

Class<? extends MyService> serviceClass =
                buddy.subclass(MyService.class)
                .method(MethodMatchers.named("sayFoo")
                .or(MethodMatchers.named("sayBar")))
                .intercept(MethodDelegation.to(new MyServiceInterceptor()))
                .make()
                .load(Main.class.getClassLoader(), 
                        ClassLoadingStrategy.Default.WRAPPER)
                .getLoaded();

MyService service = serviceClass.newInstance();

library overview - byte buddy hotswap

ByteBuddyAgent.installOnOpenJDK();

Foo foo = new Foo();

new ByteBuddy()
  .redefine(Bar.class)
  .name(Foo.class.getName())
  .make()
  .load(Foo.class.getClassLoader(), ClassReloadingStrategy.fromInstalledAgent());

assertThat(foo.m(), is("bar"));

Frameworks Usage - hibernate

  • Javaassist for all proxy stuff
  • Hibernate provides some BuildTime tasks

Why do we need a proxy?

  1. Is the entry dirty? Have we changed any data?
  2. What fields of the entity have been loaded?

Frameworks Usage - hibernate

LazyInitializationException

Main class: org.hibernate.proxy.pojo.javassist.JavassistLazyInitializer

Enhancement Task

Frameworks Usage - hibernate


EntityEntry entry = (ManagedEntity)entity.$$_hibernate_getEntityEntry();
  • Implement ManagedEntity Interface
  • Build-time instrument
  • Runtime instrument

Frameworks Usage - Spring

  • CGLib & JDK Proxy for all proxy stuff
  • ASM for working with Java Config approach

Why do we need a proxy & bytecode manipulation?

  1. AOP
  2. Java Config approach

Frameworks Usage - Spring aop

  • If target class has an interface  then JDK Dynamic Proxy
  • If target class does not have an interface then CGLib Proxy (proxy-target-class="true")
  • If we enable AspectJ weaver in the config then AspectJ LTW(load-time weaver) or CTW(compile-time weaver) 
public class ExampleRunner implements Runnable {
    @Transactional
    public void run(){
        //Some transactional stuff
    }
    
    public String internalRun(){
        run();
    }
}
<tx:annotation-driven mode="aspectj"/>

AspectJ weaving requires spring-aspects.jar on the classpath, as well as load-time weaving (or compile-time weaving) enabled.

Frameworks Usage - Spring aop

Frameworks usage - Spring Java config

@Configuration
@ComponentScan("org.edu")
class Application {

    @Bean
    RestTemplate restTemplate(){
        return new RestTemplate();
    }

    @Bean
    AsyncRestTemplate asyncRestTemplate(){
        return new AsyncRestTemplate();
    }

}
  1. Scan all annotations
  2. Build Spring Bean definitions
  3. Define Spring Beans in the Context

Frameworks usage - Spring Java config

package org.springframework.context.annotation;
/**
 * A component provider that scans the classpath from a base package.
 * It then applies exclude and include filters to the resulting
 * classes to find candidates.
 *
 * <p>This implementation is based on Spring's
 * {@link org.springframework.core.type.classreading.MetadataReader
 * MetadataReader}
 * facility, backed by an ASM
 * {@linkorg.springframework.asm.ClassReader ClassReader}.
 */
public class ClassPathScanningCandidateComponentProvider implements
EnvironmentCapable, ResourceLoaderAware {

Frameworks usage - Spring Java config

ASM Stuff Inside

Frameworks Usage - Mockito & Co.

  • CGLib for all Mock, Stub and Spy stuff
  • Spock uses JDK Proxy if an interface, CGLib if Class
  • PowerMock uses Bytecode Instrumentation for mocking Static methods

Conlusions

  • Bytecode manipulation is very powerful instrument
  • Proxies are everywhere
  • It is good to know your framework under the hood
javap -v /to/my/best/class/MyBestClass

Usefull Resources

Thank you!

Made with Slides.com