Overview of Java Dynamic Agents

1. What is an agent?

The classical meaning is sales agent, which, on the basis of signing a contract, sells certain products or all products for the principal (manufacturer), and has full authority over the price, terms and other transaction conditions.When we buy products from a sales agent, we usually don't know who the client (manufacturer) behind the sales agent is, that is, the "principal" is not visible to us.

Agent, in short, is to provide an agent, who has full authority to handle the affairs of the principal.

In Java, the proxy mode, similarly, provides a proxy object (that is, a proxy) for an object (that is, a principal) and controls access to the original object (that is, the principal) with full control by the proxy object (that is, the proxy).The client is no longer visible to the principal, cannot operate directly, and must operate indirectly through the proxy object.

 

So let's summarize a little bit about the advantages of agents:

Advantage one: Hide the implementation of the delegate class;

Advantages two: decoupling between the client and the delegate class, without modifying the code of the delegate class, you can do some additional processing, such as preprocessing, filtering, forwarding, etc.

------------------------------------------------------------------------------------------------------------

Common scenarios for proxy use:

1. Method enhancement, AOP facet programming using reflection and dynamic proxy, typically Spring AOP

2. Remote Call Proxy (the basis of RPC implementation), such as the RMI of Java standard libraries, other such as hessian, dubbo, remote calls in various web service frameworks

-------------------------------------------------------------------------------------------------------------

 

2. Static Proxy

A static proxy is a proxy class that has been compiled into a.Class file before the program runs.A general static proxy requires a proxy class and a delegate class to implement the same interface or derive from the same parent class.

 

For example, to provide a commodity service.

Commodity Provider Interface:

public interface GoodsProivder {

	// Provide goods
	public void provider();
}

A commodity provider, such as a specific manufacturer, assumes to be a charmer:

public class GoodsProivderImpl implements  GoodsProivder{
	@Override
	public void provider() {
		System.out.println("provider goods : Meizu MX6");
	}
}

A sales agent who provides goods to customers instead of the charm:

public class SalesProxy implements GoodsProivder {

	private final GoodsProivder proivder;

	public SalesProxy(GoodsProivder proivder) {
		this.proivder = proivder;
	}

	// Provide goods
	public void provider() {
		System.out.println("Welcome to our store, you can browse and purchase at will, wish you a pleasant shopping...");
		this.proivder.provider();
		System.out.println("Welcome, bye!...");
	}
}

Test class:

public class Test {

	public static void main(String[] args) {
		// Here the sales agent is visible to the customer and the manufacturer is invisible
		GoodsProivder proxy = new SalesProxy(new GoodsProivderImpl());
		proxy.provider();
	}
}


Output:

 

3. Dynamic Proxy

Dynamic code refers to a proxy class that is generated dynamically by reflection while the program is running.Compared with static proxy, dynamic proxy can handle the proxy process of delegate class more conveniently and uniformly, without modifying the proxy class, and proxy many function methods of delegate class one by one.

 

Why do you say that?

As the example of static proxy above, if a delegate interface is not only a method, but if there are hundreds of methods, when we need to unify hundreds of methods, such as preprocessing, filtering, logging, etc.We have to add or modify logic one by one for hundreds of methods in the proxy class.

 

So let's look at how dynamic proxies are implemented.

4. Implementation of Dynamic Agent

There are many kinds of dynamic proxies, including JDK dynamic proxy, CGLIB, Javassist, ASM, and so on.JDK dynamic proxy refers to the default dynamic proxy implementation of JDK.CGLIB, Javassist, ASM require corresponding third-party class libraries.

To implement dynamic proxy, an InvocationHandler is abstracted here, which is dedicated to uniform call handling.

Basic workflow of dynamic proxy: the implementation of its own method function is handed over to the InvocationHandler role, the outside world calls to each method in the Proxy role, the Proxy role is handled by the InvocationHandler, and the InvocationHandler calls the method of the specific object role.

 

There are about three ways to implement dynamic proxy:

1. In a more intuitive way, both Proxy and function implementation classes implement the same function interface.

2. In a more obscure way, Proxy inherits functionality to implement classes and to implement polymorphism.

3. Actual operation of byte code to achieve dynamic code.(This is not a category of the above working modes)

 

Here JDK takes method 1, CGLIB takes method 2, Javassist and ASM take method 3.

 

The following is a comparison of the different types of dynamic code implementations:

Dynamic Proxy Characteristic Usability
JDK Dynamic Proxy Interface Binding Required Simple and easy
CGLIB No binding interface required, ASM Wrapper Based Simple and easy
Javassist Dynamically generate and change class structure Relatively simple, directly using java encoding, no virtual machine instructions required
ASM Manipulating byte codes Complex, requires a certain understanding of the class organization structure and assembly language

CGLIB or Javassist Bytecode are recommended, which can be tested against JDK and third-party library versions.

(Refer to Section 8)

 

5. JDK Dynamic Proxy

JDK Dynamic Proxy Workflow:

1. Get a list of all interfaces on the feature implementation class XXX;

2. Determine the class name of the proxy class to be generated by default: com.sun.proxy. $ProxyXX;

3. Create the byte code of the Proxy class dynamically in the code according to the interface information that needs to be implemented;

4. Convert the corresponding byte code to the corresponding class object;

5. Create an InvocationHandler instance handler to handle all method calls to Proxy;

6. Proxy's class object instantiates a proxy object with the created handler object as a parameter

 

Let's first see what API s the JDK has:

java.lang.reflect.Proxy: This is the main class of the Java dynamic proxy mechanism, which provides a set of static methods for dynamically generating proxy classes and their objects for a set of interfaces.(

Proxy method list:

// Method 1: This method is used to get the call handler associated with the specified proxy object
static InvocationHandler getInvocationHandler(Object proxy) 
 
// Method 2: This method is used to obtain a class object associated with a dynamic proxy class for a specified class loader and a set of interfaces
static Class getProxyClass(ClassLoader loader, Class[] interfaces) 
 
// Method 3: This method is used to determine if the specified class object is a dynamic proxy class?
static boolean isProxyClass(Class cl) 
 
// Method 4: This method is used to generate a dynamic proxy class instance for a specified class loader, a set of interfaces, and a calling processor
static Object newProxyInstance(ClassLoader loader, Class[] interfaces, 
    InvocationHandler h)

java.lang.reflect.InvocationHandler: This is a call processor interface that customizes an invoke method to centrally handle method calls on dynamic proxy class objects, usually in which proxy access to the delegate class is implemented.(

// This method is responsible for centralizing all method calls on the dynamic proxy class.The first parameter is both an instance of the proxy class and the second is the called method object
// The third parameter is the called method parameter.
Object invoke(Object proxy, Method method, Object[] args)

Or just a static proxy, an example of providing commodity services, the function interface GoodsProivder, and the function implementation class GoodsProivderImpl remain unchanged.

At this point, the Sales Agent SalesProxy class can be written instead of a Sales Call Processor:

import io.flysium.standard.proxy.statics.GoodsProivder;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

/**
 * Sales Agent Caller
 *
 * @author Sven Augustus
 */
public class SalesInvocationHandler implements InvocationHandler {

	private Object target; // Delegate class object;

	public SalesInvocationHandler(Object target) {
		this.target = target;
	}

	@Override
	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
		// You can do some preprocessing here
//		if(!proxy instanceof  GoodsProivder || !"provider".equals(method.getName())) {
//			throw new UnsupportedOperationException("not supported");
//		}
		System.out.println("Welcome to our store, you can browse and purchase at will, wish you a pleasant shopping...");
		Object result = method.invoke(this.target, args);
		System.out.println("Welcome, bye!...");
		return result;
	}
}

Test class:

	public static void main(String[] args) {
//		GoodsProivder proxy = new SalesProxy(new GoodsProivderImpl());
		// Use functionality to implement classes and callers to generate proxy class instances
		GoodsProivder proxy = (GoodsProivder) Proxy.newProxyInstance(GoodsProivder.class.getClassLoader(),
				new Class[]{GoodsProivder.class},
				new SalesInvocationHandler(new GoodsProivderImpl()));
		proxy.provider();
	}

We can see the key points:

1. Write an InvocationHandler implementation that defines how to invoke and process feature implementation classes.

2. Use Proxy's api, load functions to implement class interface definitions, and InvocationHandler instances to create proxy class instances.

 

6,CGLIB

CGLIB, full name Code Generation Library.Unlike the JDK dynamic proxy, no binding interface is required.

Source: https://github.com/cglib/cglib

Workflow:

1. Find method definitions for all non-final public types on XXX;

2. Convert the definitions of these methods into byte codes;

3. Convert the composed byte code into the corresponding proxy class object;

4. Implement the MethodInterceptor interface to handle requests to all methods on the proxy class (this interface has the same functionality and role as the JDK dynamic proxy InvocationHandler)

Package structure for CGLIB:

  • net.sf.cglib.core underlying byte code processing class.

  • net.sf.cglib.transform The classes in this package are used for class file runtime or compile-time conversion.

  • net.sf.cglib.proxy The classes in this package are used to create proxies and method intercepts.

  • net.sf.cglib.reflect The classes in this package are used for quick reflection and provide C#style delegates.

  • net.sf.cglib.util Collection Sorting Tool Class.

  • net.sf.cglib.beans JavaBean tool class.

Using cglib, Maven:

<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>3.2.5</version>
</dependency>

It mainly depends on the ant of ASM and apache.

 

Let's first see what the main API s are for cglib:

net.sf.cglib.proxy.Enhancer. Major enhancement class.

//Set the parent of the generated proxy object
void setSuperclass(java.lang.Class superclass) . 
//Sets an instance of the CallBack interface.Normally MethodInterceptor
void setCallback(Callback callback) 
//Set up instances of multiple CallBack interfaces.Normally MethodInterceptor
void setCallbacks(Callback[] callbacks) 
//Set method callback filter.
void setCallbackFilter(CallbackFilter filter) 
//Create the target object using the default parameterless constructor.
Object create() 
//Create the target object using a parameterized constructor.The parameter Class[] defines the type of the parameter, and the second Object[] is the value of the parameter.
Object create(Class[], Object[]) 

The main method intercept class, net.sf.cglib.proxy.MethodInterceptor, is a subinterface of the Callback interface and requires user implementation.

// This method is responsible for centralizing all method calls on the dynamic proxy class.The first parameter is both an object instance of the proxy class and the second parameter is the called method object
// The third parameter is the called method parameter, and the fourth parameter is the proxy class instance.
Object intercept(Object target, Method method, Object[] args, MethodProxy proxy) throws Throwable

net.sf.cglib.proxy.CallbackFilter has the option of using callbacks on some methods.

 

Or just a static proxy, an example of providing commodity services, the function interface GoodsProivder, and the function implementation class GoodsProivderImpl remain unchanged.

At this point, sales agent SalesProxy class we can write MethodInterceptor instead:

import io.flysium.standard.proxy.statics.GoodsProivder;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

/**
 * Sales method interceptor interface
 *
 * @author Sven Augustus
 */
public class SalesMethodInterceptor implements MethodInterceptor {

	@Override
	public Object intercept(Object target, Method method, Object[] args, MethodProxy proxy)
 throws Throwable {
		// You can do some preprocessing here
//		if(!(target instanceof GoodsProivder) || !"provider".equals(method.getName())) {
//			return null;
//		}
		System.out.println("Welcome to our store, you can browse and purchase at will, wish you a pleasant shopping...");
		//Object result=proxy.invoke(target, args);
		Object result = proxy.invokeSuper(target, args);// Represents an intercepted method that calls the original class.
		System.out.println("Welcome, bye!...");
		return result;
	}
}

Test class:

import net.sf.cglib.proxy.Enhancer;

public static void main(String[] args) {
//		GoodsProivder proxy = new SalesProxy(new GoodsProivderImpl());
		// Use functionality to implement classes and callers to generate proxy class instances
//		GoodsProivder proxy = (GoodsProivder) Proxy.newProxyInstance(GoodsProivder.class.getClassLoader(),
//				new Class[]{GoodsProivder.class},
//				new SalesInvocationHandler(new GoodsProivderImpl()));
		// Intensifier in cglib
		Enhancer enhancer = new Enhancer();
		// Set the class to create a dynamic proxy
		enhancer.setSuperclass(GoodsProivderImpl.class);
		// Set callback, which is equivalent to a call to all methods on the proxy class
		enhancer.setCallback(new SalesMethodInterceptor());
		// Create dynamic proxy class instance
		GoodsProivder proxy = (GoodsProivder) enhancer.create();
		proxy.provider();
	}

 

We can see the key points:

1. Write a MethodInterceptor implementation that defines how to invoke and process functional implementation classes.

2. Create an Enhancer, set the dynamically proxied class, set the callback MethodInterceptor instance, and then create the proxy class instance.

 

7,Javassist

 

Using javassist, Maven:

<dependency>
    <groupId>org.javassist</groupId>
    <artifactId>javassist</artifactId>
    <version>3.21.0-GA</version>
</dependency>

 

javassist.util.proxy.MethodHandler provides a similar method call processor.

// The first parameter is the Javassist dynamically generated proxy class instance, and the second parameter is the invoked method object
// The third parameter is the proxy reference to the method for the generated proxy class, and the fourth parameter is the called method parameter
Object invoke(Object self, Method m, Method proceed, Object[] args) throws Throwable

 

Or just a static proxy, an example of providing commodity services, the function interface GoodsProivder, and the function implementation class GoodsProivderImpl remain unchanged.

At this point, we can write SaleMethodHandler instead of the SalesProxy class of the sales agent:

import javassist.util.proxy.MethodHandler;

import java.lang.reflect.Method;

/**
 * @author Sven Augustus
 */
public class SaleMethodHandler implements MethodHandler {

	public Object invoke(Object self, Method m, Method proceed, Object[] args) throws Throwable {
		System.out.println("Welcome to our store, you can browse and purchase at will, wish you a pleasant shopping...");
		//Object result = m.invoke(delegate, args);
		Object result = proceed.invoke(self, args); // execute the original method.
		System.out.println("Welcome, bye!...");
		return result;
	}
}

Test class:

import javassist.util.proxy.MethodFilter;

import javassist.util.proxy.Proxy;

import javassist.util.proxy.ProxyFactory;

public static void main(String[] args) throws IllegalAccessException, InstantiationException {
		ProxyFactory f = new ProxyFactory();
		// Set Proxied Class
		f.setSuperclass(GoodsProivderImpl.class);
		// Set Method Filter
		f.setFilter(new MethodFilter() {
			public boolean isHandled(Method m) {
				// ignore finalize()
				return !m.getName().equals("finalize");
			}
		});
		// Create Proxy Class
		Class c = f.createClass();
		GoodsProivder proxy = (GoodsProivder) c.newInstance();
		// Set Method Call Processor
		((Proxy) proxy).setHandler(new SaleMethodHandler());
		proxy.provider();
	}

 

8. Performance comparison

Multiple Tests in Two Rounds

First round:

JDK-1.6.0_45, CGLIB-3.2.5, JAVAASSIST-3.21.0.GA

Result:

Create JDK Proxy: 9 ms
Create CGLIB Proxy: 140 ms
Create JAVAASSIST Proxy: 62 ms
Create JAVAASSIST Bytecode Proxy: 98 ms
----------------
Run JDK Proxy Average: 776.0 ms
Run CGLIB Proxy Average: 223.4 ms
Run JAVAASSIST Proxy Average: 828.8 ms
Run JAVAASSIST Bytecode Proxy Average: 76.0 ms

You can see that JAVAASSIST Bytecode under JDK6 is the best, CGLIB dynamic proxy performance is also good, then JDK and JAVAASSIST are similar.

 

Second round:

The versions used are:

JDK-1.7.0_80, CGLIB-3.2.5, JAVAASSIST-3.21.0.GA

Result:

Create JDK Proxy: 19 ms
Create CGLIB Proxy: 256 ms
Create JAVAASSIST Proxy: 124 ms
Create JAVAASSIST Bytecode Proxy: 127 ms
----------------
Run JDK Proxy Average: 117.6 ms
Run CGLIB Proxy Average: 129.2 ms
Run JAVAASSIST Proxy Average: 259.4 ms
Run JAVAASSIST Bytecode Proxy Average: 76.2 ms

You can see that JAVAASSIST Bytecode under JDK7 is the best, and JDK dynamic proxy performance is also good, followed by CGLIB.

 

------------------------------------------------------------------------------------------------

Test conclusion:

1. JAVAASSIST Bytecode byte code is generated very quickly, five times faster than CGLIB.

2. CGLIB is next under JDK6, twice as much as JDK itself.JDK7, they are similar.

3. JAVAASSIST provider dynamic proxy interface is the slowest, slower than JDK's own.

 

Recommend:

If you are familiar with byte code operations, the JAVAASSIST Bytecode byte code method is preferred to implement dynamic proxy.

Otherwise choose CGLIB.

Keywords: JDK Java Maven Programming

Added by waynem801 on Thu, 23 May 2019 20:08:53 +0300