• Stars
    star
    116
  • Rank 293,559 (Top 6 %)
  • Language
    Java
  • Created almost 8 years ago
  • Updated almost 8 years ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

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

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

More Repositories

1

fluent-validator

A Java validation framework leveraging fluent interface style and JSR 303 specification
Java
1,012
star
2

kraps-rpc

A RPC framework leveraging Spark RPC module
Scala
212
star
3

2018-polar-race

1st AliCloud Database Performance Competition in 2018 - Java rank No.1 source code 阿里云2018年第一届PolarDB数据库性能大赛Java排名第一源码
Java
197
star
4

navi-pbrpc

A protobuf based high performance rpc framework leveraging full-duplexing and asynchronous io with netty
Java
165
star
5

coddding

My sample code repository
Java
149
star
6

easy-mapper

Easy-mapper is a simple, light-weighted, high performance java bean mapping framework
Java
114
star
7

navi

Navi is a distributed service framework that provides cluster management and high performance RPC
Java
93
star
8

fountain

Fountain is a Java based toolkit for syncing MySQL binlog and provide an easy API to process/publish events.
Java
83
star
9

biz-framework

针对复杂业务逻辑的Java实现系统,抽象出一套编程框架,借鉴领域模型的设计方法,使得开发体验更加环保、更加友好,大大提高代码的后期可维护性
Java
23
star
10

app-on-yarn-demo

Demo for service oriented application hosted on Hadoop YARN cluster for HA and scheduling
Java
22
star
11

llama2.java

Inference Llama 2 in one file of pure Java
Java
14
star
12

flume-byday-file-sink

A customized Flume file sink which enables you to persist incrementals to file in a rolling by-day and by-type manner
Java
12
star
13

scala-dsl-rpc-client

Scala
7
star
14

io_benchmark

Maximize the disk IO bandwidth for NVMe SSD by sequential read and write.
C++
4
star
15

kafka-example

Kafka example
Java
2
star
16

maven-plugin-archetype

maven check-style & p3c插件在编译打包时做代码规范检查
Java
2
star
17

rest-api-framework

API framework for building RESTful Web Services following JAX-RS and OPEN-API spec, also leveraging JSR330 implementation - Google Guice
Java
2
star
18

spring-boot-yarn-example

Spring boot YARN example
Java
1
star
19

scala-learn

Scala learning arena
Scala
1
star
20

result-util

Result code and message utility for Java programmer
Java
1
star
21

fast_innochecksum

Faster innochecksum
C++
1
star
22

big-traffic-jam-solver

A algorithm for solving the big traffic jam problem
Java
1
star
23

flink-exercises

Flink exercises
Java
1
star
24

hbase-coprocessor-example

HBase coprocessor example
Java
1
star