JDK dynamic proxy, I believe many people are familiar. However, fewer people probably know how dynamic proxies work and how to code them. Let’s take a look at the basics of JDK dynamic proxies and how to simulate them using Javassist.

JDK dynamic proxy

The sample

The following is an example of Hello World based on JDK dynamic proxies, versions of which can be found in many places.

public class DynamicProxyTest {

    interface IHello {
        void sayHello(a);
    }

    static class Hello implements IHello {
        @Override
        public void sayHello(a) {
            System.out.println("hello world"); }}static class DynamicProxy implements InvocationHandler {

        Object originalObj;

        Object bind(Object originalObj) {
            this.originalObj = originalObj;
            return Proxy.newProxyInstance(originalObj.getClass().getClassLoader(), originalObj.getClass().getInterfaces(), this);
        }

        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        	System.out.println("pre method");
        	Object result = method.invoke(originalObj, args);
    		System.out.println("post method");
    		returnresult; }}public static void main(String[] args) {
    	System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles"."true");
        IHello hello = (IHello) new DynamicProxy().bind(newHello()); hello.sayHello(); }}Copy the code

Generate proxy class source code

By setting the parameters of the sun. The misc. ProxyGenerator. SaveGeneratedFiles to true, after the execution of the main function, we will receive a $Proxy0. Class files, it is the proxy class Hello.

$Proxy0 = $Proxy0;

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

	public $Proxy0(InvocationHandler paramInvocationHandler) {
		super(paramInvocationHandler);
	}

	public final void sayHello(a) {
		try {
			this.h.invoke(this, m3, null);
			return;
		} catch (Error | RuntimeException localError) {
			throw localError;
		} catch (Throwable localThrowable) {
			throw newUndeclaredThrowableException(localThrowable); }}static {
		try {
			m1 = Class.forName("java.lang.Object").getMethod("equals".new Class[] { Class.forName("java.lang.Object")}); m3 = Class.forName("DynamicProxyTest$IHello").getMethod("sayHello".new Class[0]);
			m2 = Class.forName("java.lang.Object").getMethod("toString".new Class[0]);
			m0 = Class.forName("java.lang.Object").getMethod("hashCode".new Class[0]);
		} catch (NoSuchMethodException localNoSuchMethodException) {
			throw new NoSuchMethodError(localNoSuchMethodException.getMessage());
		} catch (ClassNotFoundException localClassNotFoundException) {
			throw newNoClassDefFoundError(localClassNotFoundException.getMessage()); }}}Copy the code

Main implementation principles

  • Dynamically generate a proxy class to implement the IHello interface;
  • The Proxy class inherits the Proxy class and provides an instance variable, InvocationHandler H, to hold the Proxy logic (in addition, Proxy also provides the related Proxy class processing logic).
  • The proxy class declares a set of Method class variables that hold interface-specific reflection methods;
  • The proxy class implements the relevant interface methods. The core logic is to call the Invoke method of InvocationHandler and pass in three parameters: the current proxy class object, the interface reflection method object, and the actual method parameters.

Javassist implements JDK dynamic proxies

In front of the simple analysis of the basic principle of the JDK dynamic Proxy, among them, the most core logic lies in how to generate dynamic Proxy classes, namely Java. Lang. Reflect.. Proxy newProxyInstance (this loader, Class <? >[] interfaces, InvocationHandler h).

Next we will implement the newProxyInstance method step by step through Javassist.

1. Define interfaces

The interface is basically the same as proxy. newProxyInstance. For simplicity, we define only one interface type parameter, Class<? > instead of an array.

public static Object newProxyInstance(ClassLoader loader, Class
        interfaceClass, InvocationHandler h) {... }Copy the code

2. Create a dynamic proxy class

Javassist manipulates the source code through a simple Java API so that classes can be dynamically generated or their behavior modified without knowledge of Java bytecode.

Create a proxy class named NewProxyClass.

ClassPool pool = ClassPool.getDefault();
CtClass proxyCc = pool.makeClass("NewProxyClass");
Copy the code

3. Add the instance variable InvocationHandler

Add an instance variable h of type InvocationHandler.

CtClass handlerCc = pool.get(InvocationHandler.class.getName());
/* Private InvocationHandler h; * /
CtField handlerField = new CtField(handlerCc, "h", proxyCc);
handlerField.setModifiers(AccessFlag.PRIVATE);
proxyCc.addField(handlerField);
Copy the code

4. Add constructors

Create a constructor of type InvocationHandler.

Public NewProxyClass(InvocationHandler h) {this.h = h; }
CtConstructor ctConstructor = new CtConstructor(new CtClass[] { handlerCc }, proxyCc);
ctConstructor.setBody("$0.h = $1;");
proxyCc.addConstructor(ctConstructor);
Copy the code

Where $0 represents this and $1 represents the first argument to the constructor.

5. Implement the IHello interface declaration

Public class NewProxyClass implements IHello
CtClass interfaceCc = pool.get(interfaceClass.getName());
proxyCc.addInterface(interfaceCc);
Copy the code

6. Implement IHello related interface methods

6.1 Interface Traversal Methods

CtMethod[] ctMethods = interfaceCc.getDeclaredMethods();
for(int i = 0; i < ctMethods.length; I++) {// core logic below}Copy the code

6.2 Proxy method implementation

Since a proxy class calling the Invoke Method requires a reflection Method object (Method) passed into the interface, we need to add a reusable Method class variable for each Method.

6.2.1 Reflection method object declaration and initialization

/* Constructor arguments, such as: new Class[] {string.class, boolea.type, object.class} */
String classParamsStr = "new Class[0]";
if (ctMethods[i].getParameterTypes().length > 0) {
	for (CtClass clazz : ctMethods[i].getParameterTypes()) {
		classParamsStr = ((classParamsStr == "new Class[0]")? clazz.getName() : classParamsStr +"," + clazz.getName()) + ".class";
	}
	classParamsStr = "new Class[] {" + classParamsStr + "}";
}
// Field generation template
String methodFieldTpl = "private static java.lang.reflect.Method %s=Class.forName(\"%s\").getDeclaredMethod(\"%s\", %s);";
// Generate statements according to the template generation method and parameter constructor field
String methodFieldBody = String.format(methodFieldTpl, "m" + i, interfaceClass.getName(), ctMethods[i].getName(), classParamsStr);
// Add a reflection method field to the proxy class
CtField methodField = CtField.make(methodFieldBody, proxyCc);
proxyCc.addField(methodField);
Copy the code

Using the above logic, code similar to the following is generated:

private static Method m0 = Class.forName("chapter9.javassistproxy3.IHello").getDeclaredMethod("sayHello".new Class[0]);
private static Method m1 = Class.forName("chapter9.javassistproxy3.IHello").getDeclaredMethod("sayHello2".new Class[] { Integer.TYPE });
private static Method m2 = Class.forName("chapter9.javassistproxy3.IHello").getDeclaredMethod("sayHello3".new Class[] { String.class, Boolean.TYPE, Object.class });
Copy the code

6.2.2 Interface method body implementation

// invoke invoke logic. Where $args is the array of arguments passed in by the actual method
String methodBody = "$0.h.invoke($0, " + methodFieldName + ", $args)";

// If the method has a return type, it needs to be converted to the corresponding type and returned
if(CtPrimitiveType.voidType ! = ctMethods[i].getReturnType()) {// Transform the 8 basic types
	/ / such as: ((Integer)this.h.invoke(this, this.m2, new Object[] { paramString, new Boolean(paramBoolean), paramObject })).intValue();
	if (ctMethods[i].getReturnType() instanceof CtPrimitiveType) {
		CtPrimitiveType ctPrimitiveType = (CtPrimitiveType) ctMethods[i].getReturnType();
		methodBody = "return ((" + ctPrimitiveType.getWrapperName() + ")" + methodBody + ")." + ctPrimitiveType.getGetMethodName() + "()";
	}
	// For non-basic types, direct transformation is ok
	else {
		methodBody = "return (" + ctMethods[i].getReturnType().getName() + ")" + methodBody;
	}
}
methodBody += ";";

/* Adds the method */ for the proxy class
CtMethod newMethod = new CtMethod(ctMethods[i].getReturnType(), ctMethods[i].getName(),
		ctMethods[i].getParameterTypes(), proxyCc);
newMethod.setBody(methodBody);
proxyCc.addMethod(newMethod);
Copy the code

Using the above logic, code similar to the following is generated:

public void sayHello(a) {
    this.h.invoke(this, m0, new Object[0]);
}

public String sayHello2(int paramInt) {
    return (String)this.h.invoke(this, m1, new Object[] { new Integer(paramInt) });
}

public int sayHello3(String paramString, boolean paramBoolean, Object paramObject) {
    return ((Integer)this.h.invoke(this, m2, new Object[] { paramString, new Boolean(paramBoolean), paramObject })).intValue();
}
Copy the code

7. Generate proxy bytecode

The following statement, will be generated proxy class bytecode: D: / TMP/NewProxyClass. Class

proxyCc.writeFile("D:/tmp"); // This step is optional
Copy the code

8. Generate proxy objects

Finally, the InvocationHandler object is passed in by calling the constructor created in step 3 to generate and return the proxy class.

Object proxy = proxyCc.toClass().getConstructor(InvocationHandler.class).newInstance(h);
return proxy;
Copy the code

The complete code

public class ProxyFactory {
	
	public static Object newProxyInstance(ClassLoader loader, Class
        interfaceClass, InvocationHandler h) throws Throwable {
		ClassPool pool = ClassPool.getDefault();
		
		Create the proxy class: public class NewProxyClass
		CtClass proxyCc = pool.makeClass("NewProxyClass");

		Private InvocationHandler h; private InvocationHandler h; * /
		CtClass handlerCc = pool.get(InvocationHandler.class.getName());
		CtField handlerField = new CtField(handlerCc, "h", proxyCc); // CtField(CtClass fieldType, String fieldName, CtClass addToThisClass)
		handlerField.setModifiers(AccessFlag.PRIVATE);
		proxyCc.addField(handlerField);

		Public NewProxyClass(InvocationHandler h) {this.h = h; } * /
		CtConstructor ctConstructor = new CtConstructor(new CtClass[] { handlerCc }, proxyCc);
		ctConstructor.setBody("$0.h = $1;"); // $0 represents this and $1 represents the first argument to the constructor
		proxyCc.addConstructor(ctConstructor);
		
		/* 4. Add corresponding interface methods and implementation */ for the proxy class
		CtClass interfaceCc = pool.get(interfaceClass.getName());
		
		Public class NewProxyClass implements IHello
		proxyCc.addInterface(interfaceCc);
		
		// 4.2 Add corresponding methods and implementations for the proxy class
		CtMethod[] ctMethods = interfaceCc.getDeclaredMethods();
		for (int i = 0; i < ctMethods.length; i++) {
			String methodFieldName = "m" + i; // The new method name

			4.2.1 Adding a reflection method field to the proxy class
			/ / such as: private static Method m1 = Class.forName("chapter9.javassistproxy3.IHello").getDeclaredMethod("sayHello2", new Class[] { Integer.TYPE });
			/* Construct reflection field declarations and assignment statements */
			String classParamsStr = "new Class[0]"; // Multiple parameter types of a method are separated by commas
			if (ctMethods[i].getParameterTypes().length > 0) { // getParameterTypes Gets a list of method parameter types
				for (CtClass clazz : ctMethods[i].getParameterTypes()) {
					classParamsStr = (("new Class[0]".equals(classParamsStr)) ? clazz.getName() : classParamsStr + "," + clazz.getName()) + ".class";
				}
				classParamsStr = "new Class[] {" + classParamsStr + "}";
			}
			String methodFieldTpl = "private static java.lang.reflect.Method %s=Class.forName(\"%s\").getDeclaredMethod(\"%s\", %s);";
			String methodFieldBody = String.format(methodFieldTpl, "m" + i, interfaceClass.getName(), ctMethods[i].getName(), classParamsStr);
			// Add a reflection method field to the proxy class. CtField.make(String sourceCodeText, CtClass addToThisClass)
			CtField methodField = CtField.make(methodFieldBody, proxyCc);
			proxyCc.addField(methodField);
			
			System.out.println("methodFieldBody: " + methodFieldBody);
			
			/* add the method body */ to the method
			/* Constructor body.this.h.i nvoke(this, reflection field name, method argument list); * /
			String methodBody = "$0.h.invoke($0, " + methodFieldName + ", $args)";
			// If a method has a return type, it needs to be converted to the corresponding type and returned, since the invoke method returns type Object
			if(CtPrimitiveType.voidType ! = ctMethods[i].getReturnType()) {// Transform the 8 basic types
				/ / such as: ((Integer)this.h.invoke(this, this.m2, new Object[] { paramString, new Boolean(paramBoolean), paramObject })).intValue();
				if (ctMethods[i].getReturnType() instanceof CtPrimitiveType) {
					CtPrimitiveType ctPrimitiveType = (CtPrimitiveType) ctMethods[i].getReturnType();
					methodBody = "return ((" + ctPrimitiveType.getWrapperName() + ")" + methodBody + ")." + ctPrimitiveType.getGetMethodName() + "()";
				} else { // For non-basic types, direct transformation is ok
					methodBody = "return (" + ctMethods[i].getReturnType().getName() + ")" + methodBody;
				}
			}
			methodBody += ";";
			/* Add a method for the proxy class. CtMethod(CtClass returnType, String methodName, CtClass[] parameterTypes, CtClass addToThisClass) */
			CtMethod newMethod = new CtMethod(ctMethods[i].getReturnType(), ctMethods[i].getName(),
					ctMethods[i].getParameterTypes(), proxyCc);
			newMethod.setBody(methodBody);
			proxyCc.addMethod(newMethod);
			
			System.out.println("Invoke method: " + methodBody);
		}
		
		proxyCc.writeFile("D:/tmp");
		
		// 5. Generate a proxy instance. Set the input parameter InvocationHandler H to the InvocationHandler H variable of the proxy class
		@SuppressWarnings("unchecked")
		Object proxy = proxyCc.toClass().getConstructor(InvocationHandler.class).newInstance(h);

		returnproxy; }}public interface IHello {
	int sayHello3(String a, boolean b, Object c);
}

public class Hello implements IHello {
	@Override
	public int sayHello3(String a, boolean b, Object c) {
		String abc = a + b + c;
		System.out.println("a + b + c=" + abc);
		returnabc.hashCode(); }}public class CustomHandler implements InvocationHandler {

	private Object obj;

	public CustomHandler(Object obj) {
		this.obj = obj;
	}

	@Override
	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
		System.out.println("pre method");
		Object result = method.invoke(obj, args);
		System.out.println("post method");
		returnresult; }}public class ProxyTest {

	public static void main(String[] args) throws Throwable {
		IHello hello = new Hello();
		CustomHandler customHandler = new CustomHandler(hello);
		IHello helloProxy = (IHello) ProxyFactory.newProxyInstance(hello.getClass().getClassLoader(), IHello.class, customHandler);
		System.out.println();
		System.out.println("a+false+Object=" + helloProxy.sayHello3("a".false.newObject())); }}Copy the code

Execution Result:

methodFieldBody: private static java.lang.reflect.Method m0=Class.forName(“chapter9.javassistproxy3.IHello”).getDeclaredMethod(“sayHello3”, new Class[] {java.lang.String.class,boolean.class,java.lang.Object.class});

Invoke method: return ((java.lang.Integer) $0.h.invoke(args)).intValue();

pre method

a + b + c=afalsejava.lang.Object@504bae78

post method

a+false+Object=-903110407


reference

Javassist’s official website: www.javassist.org/

Personal public account

For more articles, please pay attention to the public number: binary road