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