Skip to content

neoremind/dynamic-proxy

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

16 Commits
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Dynamic proxy for Java

Coverage Status Maven Central Hex.pm

Dynamic proxy is a useful library for Java developers to generate proxy object. This library leverages a wide range of byte-code generation methods, including

1. Guideline

1.1 Prerequisite

Maven dependency:

<dependency>
    <groupId>net.neoremind</groupId>
    <artifactId>dynamicproxy</artifactId>
    <version>1.0.0</version>
</dependency>

Next, let us look at some examples of implementing dynamic proxy.

1.2 Creating invoker

First, let us define an interface.

public interface EchoService {
    String echo(String message);
}

By using runtime code generation technique, you can create an instance implementing some specific interfaces or extending a class without defining the Class.

By initiate ProxyCreator instance directly, you can get the specific code generation creator leveraging ASM, Javassist, ByteBuddy, CGLIB and traditional JDK Dynamic Proxy.

ObjectInvoker is where method’s behavior is defined. createInvokerProxy method takes two arguments, one is the ProxyCreator instance, the other is a var-args which takes multiple interfaces you would like to implement. Note that ByteBuddy currently only supports one interface or class.

Invoker is very useful when implementing a RPC client, caller can depend on an interface, ProxyCreator creates a subclass that takes sufficient information to execute a remote call.

ProxyCreator proxyCreator = new CglibCreator();
//  ProxyCreator proxyCreator = new ASMCreator();
//  ProxyCreator proxyCreator = new JavassistCreator();
//  ProxyCreator proxyCreator = new ByteBuddyCreator();
//  ProxyCreator proxyCreator = new JdkProxyCreator();

ObjectInvoker fixedStringObjInvoker = new ObjectInvoker() {
    @Override
    public Object invoke(Object proxy, Method method, Object... arguments) throws Throwable {
        return "Hello world!";
    }
};

EchoService service = proxyCreator.createInvokerProxy(fixedStringObjInvoker, EchoService.class);

assertThat(service.echo("wow"), Matchers.is("Hello world!"));

1.3 Creating interceptor

Interceptors are used to implement cross-cutting concerns, such as logging, auditing, and security, from the business logic.

Here, you can define an Interceptor instance, for example you can manipulate the returned string by appending a suffix - "Huh!". createInterceptorProxy method which takes three arguments, the first argument is the target object, the second argument is the interceptor, the third argument is the interfaces you would like to proxy.

Note that this time, ProxyCreator is created by DefaultProxyCreator that looks up implementation of ProxyCreator through SPI, we will talk about this later in chapter 1.5.

ProxyCreator proxyCreator = new DefaultProxyCreator();
Interceptor interceptor = new Interceptor() {
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        return invocation.proceed() + ".Huh!";
    }
};
EchoService service =
        proxyCreator.createInterceptorProxy(new EchoServiceImpl(), interceptor, EchoService.class);
assertThat(service.echo("wow"), Matchers.is("wow.Huh!"));

1.4 Creating delegator

Delegator is useful when you do not want to expose the real implementation out to the caller or if you would like to change the implementation, for example here we return a decorated object to the caller, the decorated implementation changes the output string to upper case. Usually this reflects the GoF design pattern.

The decorated EchoService implementation:

class DecoratorEchoService implements EchoService {

    private EchoService delegate;

    public DecoratorEchoService(EchoService delegate) {
        this.delegate = delegate;
    }

    @Override
    public String echo(String message) {
        message = message.toUpperCase();
        return delegate.echo(message);
    }

    public EchoService getDelegate() {
        return delegate;
    }

    public void setDelegate(EchoService delegate) {
        this.delegate = delegate;
    }
}
ProxyCreator proxyCreator = new DefaultProxyCreator();
ObjectProvider objectProvider = new ObjectProvider() {

    @Override
    public Object getObject() {
        return new DecoratorEchoService(new EchoServiceImpl());
    }
};
EchoService service =
        proxyCreator.createDelegatorProxy(objectProvider, EchoService.class);
assertThat(service.echo("wow"), Matchers.is("WOW"));

1.5 Using SPI to lookup the capable ProxyCreator

The SPI class ProxyCreator can be subclassed into classes including:

  • CglibCreator
  • ASMCreator
  • JavassistCreator
  • ByteBuddyCreator
  • JdkProxyCreator

You can specify the implementation by creating a file named net.neoremind.dynamicproxy.ProxyCreator under META-INF/services folder.

The content looks like below, this is also the default configuration.

net.neoremind.dynamicproxy.impl.CglibCreator
net.neoremind.dynamicproxy.impl.ByteBuddyCreator
net.neoremind.dynamicproxy.impl.ASMCreator
net.neoremind.dynamicproxy.impl.JdkProxyCreator
net.neoremind.dynamicproxy.impl.JavassistCreator

When calling ProxyUtil.getInstance(), the capable ProxyCreator implementation will be look up automatically.

ProxyCreator proxyCreator = ProxyUtil.getInstance();
EchoService service = proxyCreator.createInvokerProxy(new ObjectInvoker() {
    @Override
    public Object invoke(Object proxy, Method method, Object... arguments) throws Throwable {
        return arguments[0];
    }
}, EchoService.class);
assertThat(service.echo("wow"), Matchers.is("wow"));

2. Performace test

The following performance tests are based on Invoker mode. To view source code, please visit here.

Test execution environment:

CPU: Intel(R) Core(TM) i5-4278U CPU @ 2.60GHz
MEM: 8G
JVM Args: -Xms512m -Xmx512m -XX:PermSize=128M -XX:MaxPermSize=128M

###2.1 Invocation performance test result ####Java6 and 7

-------------------------------------
| Invoke number:     1000000        |
-------------------------------------
|   ProxyCreator   |    time cost   |
-------------------------------------
| ByteBuddyCreator |       8ms      |
|       ASMCreator |     718ms      |
|     CglibCreator |      12ms      |
| JavassistCreator |     959ms      |
|  JdkProxyCreator |      20ms      |
-------------------------------------

The performance rank is ByteBuddy > CGLIB > JDKProxy > ASM > Javassist. It has to be highlighted here ASM shouldn’t be so slow, it is because the way invoker is implemented where reflection API is used, that means even ASM is very efficient but byte code that uses reflection API is generated. Using the reflection API is slower than a hard-coded method invocation, JVM needs to perform a rather expensive method lookup to get hold of an object that describes a specific method. And when a method is invoked, this requires the JVM to run native code, which requires long run time compared to a direct invocation.

Moreover, Javassist itself provides two levels of API: source level and bytecode level. Here source level is used, Javassist generates byte code and loads into the JVM at runtime. It is rather slow than bytecode level which implements by manipulating opcodes directly. So JavassistCreator's performance is not very good.

####Java8

1.8
-------------------------------------
| Invoke number:     1000000        |
-------------------------------------
|   ProxyCreator   |    time cost   |
-------------------------------------
| ByteBuddyCreator |      13ms      |
|       ASMCreator |     281ms      |
|     CglibCreator |      15ms      |
| JavassistCreator |    1048ms      |
|  JdkProxyCreator |      18ms      |
-------------------------------------

Here ASMCreator performs way better under Java8 compared with Java6/7. That is because modern JVM knows a concept called inflation where the JNI-based method invocation is replaced by generated byte code that is injected into a dynamically created class. (Even the JVM itself uses code generation!)

2.2 Proxy creation performance test result

-------------------------------------
| Create proxy number:   10000      |
-------------------------------------
|   ProxyCreator   |    time cost   |
-------------------------------------
| ByteBuddyCreator |    7567ms      |
|       ASMCreator |      52ms      |
|     CglibCreator |     275ms      |
| JavassistCreator |     102ms      |
|  JdkProxyCreator |      53ms      |
-------------------------------------

Result shows no big difference on Java6,7,8.

3. Dependencies

By default, the following dependencies will bring into your project.

[INFO] +- org.apache.commons:commons-lang3:jar:3.2.1:compile
[INFO] +- commons-collections:commons-collections:jar:3.2.1:compile
[INFO] +- com.google.guava:guava:jar:18.0:compile
[INFO] +- org.javassist:javassist:jar:3.18.1-GA:compile
[INFO] +- cglib:cglib-nodep:jar:2.2.2:compile
[INFO] +- cn.wensiqun:asmsupport-core:jar:0.4.3:compile
[INFO] |  \- cn.wensiqun:asmsupport-standard:jar:0.4.3:compile
[INFO] |     \- cn.wensiqun:asmsupport-third:jar:0.4.3:compile
[INFO] +- org.ow2.asm:asm-commons:jar:5.0.4:compile
[INFO] |  \- org.ow2.asm:asm-tree:jar:5.0.4:compile
[INFO] +- org.ow2.asm:asm:jar:5.0.4:compile

##4. Acknowledgment

The development of dynamic-proxy was inspired by Apache commons-proxy and made some performance improvements like leveraging cache and introducing ByteBuddy into the library.

5. Appendix

An useful introduction to bytecode tools

About

Dynamic proxy library leveraging ASM, CGLIB, ByteBuddy, Javassist and JDKDynamicProxy techniques

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages