Previous articles have covered Java’s reflection mechanism and garbage collection mechanism, but this time we will cover a more interesting mechanism: dynamic proxy. Learn why such a mechanism exists in Java and when it is used.

Static agent

The general proxy pattern consists of the following three parts: functional interfaces

interface IFunction {
	void doAThing(a);
}
Copy the code

Function provider

class FunctionProvider implement IFunction {
	public void doAThing {
		System.out.print("do A"); }}Copy the code

Functional agent

class Proxy implement IFunction {
	private FunctionProvider provider;
	Proxy(FunctionProvider provider) {
		this.provider = provider;
	}

	public voiddoAThing { provider.doAThing(); }}Copy the code

The first two are plain interfaces and implementation classes, and the third is a so-called proxy class. For the consumer, he will let the proxy class perform a task, regardless of the specific runner of the task.

This is a static proxy, which has the advantage of being able to easily adjust and transform the implementation classes without any impact on the consumer.

However, there are drawbacks to this approach: if you have multiple interfaces that need to be propped up, creating a proxy class for each function provider can get bigger and bigger. Moreover, a “static” proxy means that the delegate class being proxied must be known in advance.

Here’s an example:

Statistical function time – static proxy implementation

Now I want to use a proxy class to do time statistics for the methods I am interested in. Static proxies have the following implementation:

interface IAFunc {
	void doA(a);
}
interface IBFunc {
	void doB(a);
}
Copy the code
class TimeConsumeProxy implement IAFunc.IBFunc {
	private AFunc a;
	private BFunc b;
	public(AFunc a, BFunc b) {
		this.a = a;
		this.b = b;
	}
	void doA(a) {
		long start = System.currentMillions();

		a.doA();
		
		System.out.println("Time:" + (System.currentMillions() - start));
	}
	void doB(a) {
		long start = System.currentMillions();

		b.doB();
		
		System.out.println("Time:"+ (System.currentMillions() - start)); }}Copy the code

The downside is obvious: if there are more interfaces, every new function needs to modify the TimeConsumeProxy proxy class: pass in the delegate object, implement the interface, and count the elapsed time before and after the function is executed.

This approach is obviously not sustainable, so let’s take a look at the implementation using dynamic proxies for comparison.

A dynamic proxy

The core idea of dynamic Proxy is to dynamically generate a Proxy object for any incoming object through the Java Proxy class. This Proxy object implements all the interfaces of the original object by default.

It is more direct to illustrate by statistical function time example.

Statistical function time – dynamic proxy implementation

interface IAFunc {
	void doA(a);
}
interface IBFunc {
	void doB(a);
}
Copy the code
class A implement IAFunc {... }class B implement IBFunc {... }Copy the code
class TimeConsumeProxy implements InvocationHandler {
	private Object realObject;

	public Object bind(Object realObject) {
		this.realObject = realObject;
		Object proxyObject = Proxy.newInstance(
			realObject.getClass().getClassLoader(),
			realObject.getClass().getInterfaces(),
			this
		);

		return proxyObject;
	}

	@Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        long start = System.currentMillions();

        Object result = method.invoke(target, args);

        System.out.println("Time:" + (System.currentMillions() - start));

        returnresult; }}Copy the code

Specific use:

public static void main(String[] args) {
	A a = new A();
	IAFunc aProxy = (IAFunc) new TimeConsumeProxy().bind(a);
	aProxy.doA();

	B b = new B();
	IBFunc bProxy = (IBFunc) new TimeConsumeProxy().bind(b);
	bProxy.doB();	
}
Copy the code

The big difference here is that the proxy class and the delegate class are transparent and independent of each other, with no logic coupling and only bound together at run time. This is the main difference between static proxies and dynamic proxies, with the benefit that no matter how many delegate classes there are, the proxy classes are not affected and you do not need to know the specific delegate class at compile time.

Going back to the dynamic proxy itself, the most important thing in the code above is:

Object proxyObject = Proxy.newInstance(
			realObject.getClass().getClassLoader(),
			realObject.getClass().getInterfaces(),
			this
		);
Copy the code

Through the Proxy tool, the real delegate class is converted into a Proxy class. At the beginning, three elements of a Proxy mode are mentioned: function interface, function provider and function agent. This corresponds to realObject.getClass().getinterfaces (), realObject, TimeConsumeProxy.

In fact, dynamic Proxy is not complicated. Through a Proxy tool, a Proxy object is automatically generated for the interface of the delegate class. Subsequent function calls are initiated by this Proxy object and are eventually executed to the InvocationHandler#invoke method. You can also do some other custom logic, such as the runtime statistics above.

Explore dynamic proxy implementation mechanisms

Ok, so we’ve covered the basics of dynamic proxies and why to use them, and we’ve covered a lot of the articles here, but we’re ready to explore them further for interested readers.

Throw up a few questions:

  1. The proxy object generated aboveObject proxyObjectWhat the hell is it? Why can it be transformed intoIAFunc, can also calldoA()Methods?
  2. thisproxyObjectHow is it generated? Is it a class?

I’m going to give you the answer, and then I’m going to take a step back and see how it came to be.

What exactly is a proxyObject -> dynamically generated $proxy0.class file

After calling proxy.newinstance, Java eventually generates A real class file for the delegate class A: $proxy0.class, and proxyObject is an instance of this class.

What does the $Proxy0. Class class look like and what methods it contains? Take a look back at the code:

IAFunc aProxy = (IAFunc) new TimeConsumeProxy().bind(a);
aProxy.doA();
Copy the code

$proxy0.class implements the IAFunc interface. It also implements the doA() method internally, and the point is that this doA() method is invoked at runtime in the TimeConsumeProxy#invoke() method.

Here we go! Let’s take a look at the $proxy0.class file, put it into the IDE decompile, and see the following to verify the conjecture:

final class $Proxy0 extends Proxy implements IAFunc {
    private static Method m1;
    private static Method m3;
    private static Method m2;
    private static Method m0;

    public $Proxy0(InvocationHandler var1) throws  {
        super(var1);
    }

    public final boolean equals(Object var1) throws  {
        / / to omit
    }

    public final void doA(a) throws  {
        try {
        	/ / to highlight
            super.h.invoke(this, m3, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw newUndeclaredThrowableException(var3); }}public final String toString(a) throws  {
        / / to omit
    }

    public final int hashCode(a) throws  {
        / / to omit
    }

    static {
        try {
        	/ / to highlight
            m3 = Class.forName("proxy.IAFunc").getMethod("doA".new Class[0]);

            m1 = Class.forName("java.lang.Object").getMethod("equals".new Class[]{Class.forName("java.lang.Object")});
            m2 = Class.forName("java.lang.Object").getMethod("toString".new Class[0]);
            m0 = Class.forName("java.lang.Object").getMethod("hashCode".new Class[0]);
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw newNoClassDefFoundError(var3.getMessage()); }}}Copy the code

Yes, you were right! Implements the IAFunc interface and doA() method, but what the hell is in doA()?

super.h.invoke(this, m3, (Object[])null);
Copy the code

Looking back, the Invoke method inside TimeConsumeProxy, what is its function signature?

public Object invoke(Object proxy, Method method, Object[] args);
Copy the code

Yes, what doA() does is call the TimeConsumeProxy#invoke() method.

In other words, the following code executes as follows:

IAFunc aProxy = (IAFunc) new TimeConsumeProxy().bind(a);
aProxy.doA();
Copy the code
  1. Based on the incoming delegate classA, generate a$Proxy0.classFile;
  2. To create a$Proxy0.classObject, transformed toIAFuncInterface;
  3. callaProxy.doA()Is automatically called inside TimeConsumeProxyinvokeMethods.

$Proxy0. Class -> $Proxy0

Having just looked at the result from the end, let’s go back to the beginning of the code:

Object proxyObject = Proxy.newInstance(
			realObject.getClass().getClassLoader(),
			realObject.getClass().getInterfaces(),
			this
		);
Copy the code

Ready, let’s start reading the source code. I’ll take important code and comment it out.

Look at the Proxy. NewInstance () :

public static Object newProxyInstance(ClassLoader loader, Class
       [] interfaces, InvocationHandler h) {
    // Copy the interface to proxy
	finalClass<? >[] intfs = interfaces.clone();$Proxy0. Class = $Proxy0Class<? > cl = getProxyClass0(loader, intfs);// Generate an instance of 'proxyObject' for $proxy0.class
	finalConstructor<? > cons = cl.getConstructor(constructorParams);return cons.newInstance(new Object[]{h});
}
Copy the code

Proxyclass0 = proxyClass0;

@Override public Class<? > apply(ClassLoader loader, Class<? >[] interfaces) {// The parameters are ClassLoader and Map<Class<? >, Boolean> interfaceSet = new IdentityHashMap<>(interfaces.length); // 1. Verify the validity of the ClassLoader and interfacefor(Class<? > intf: interfaces) {// validate the classLoader correctness Class<? > interfaceClass = Class.forName(intf.getName(),false, loader);
		if(interfaceClass ! = intf) { throw new IllegalArgumentException( intf +" is not visible from class loader"); } // Verify that the incoming interface class is validif(! interfaceClass.isInterface()) { ... } // Verify that the interface is duplicatedif(interfaceSet.put(interfaceClass, Boolean.TRUE) ! = null) { ... }} // 2. Create package name and class name$Proxy0.class
	proxyPkg = ReflectUtil.PROXY_PACKAGE + "."; long num = nextUniqueNumber.getAndIncrement(); String proxyName = proxyPkg + proxyClassNamePrefix + num; / / 3. Create a class bytecode content byte [] proxyClassFile = ProxyGenerator. GenerateProxyClass (proxyName, interfaces, accessFlags); // 4. Generate Class<? > objectreturn defineClass0(loader, proxyName, proxyClassFile, 0, proxyClassFile.length);
}
Copy the code

Look at the first three steps to generate the class contents ProxyGenerator. GenerateProxyClass:

// Add the hashCode equals toString method
addProxyMethod(hashCodeMethod, Object.class);
addProxyMethod(equalsMethod, Object.class);
addProxyMethod(toStringMethod, Object.class);

// Add the interface implementation of the delegate class
for (int i = 0; i < interfaces.length; i++) {
    Method[] methods = interfaces[i].getMethods();
    for (int j = 0; j < methods.length; j++) { addProxyMethod(methods[j], interfaces[i]); }}// Add the constructor
methods.add(this.generateConstructor());
Copy the code

Now that the contents of the class are constructed: add the necessary functions, implement interfaces, constructors, etc., it’s time to write $proxy0.class as you saw in the previous step.

 ByteArrayOutputStream bout = new ByteArrayOutputStream();
 DataOutputStream dout = new DataOutputStream(bout);
 dout.writeInt(0xCAFEBABE); . dout.writeShort(ACC_PUBLIC | ACC_FINAL | ACC_SUPER); .return bout.toByteArray();
Copy the code

$Proxy0. Class = $Proxy0.

Dynamic proxy summary

As you can see from the above, dynamic proxies can delegate to any delegate class at any time, and can retrieve runtime information at InvocationHandler#invoke and do some cutting.

This proxy class implements the interface of the delegate class and forwards the interface call to InvocationHandler# Invoke, eventually calling the corresponding method of the real delegate class.

Dynamic proxy mechanism isolates the delegate class from the proxy class and improves the extensibility.

Java dynamic proxy with Python decorator

This is an interesting language feature that the Java language provides, but Python also provides a similar feature: decorators, which can achieve a similar aspect programming idea. I’ll compare the two next time, but I’ll stop there.

thank you

wingjay

For more of my articles, please follow: