Next is the source code analysis. Before analysis, you must understand the agent mode.
What is the proxy model
Proxy pattern is a common design pattern in java. Its feature is that the proxy class has the same interface as the delegate class. If other classes want to access this class, they must first pass through the proxy class.
In some cases, a client wants to do some fixed operations before and after accessing an object, so the proxy mode is suitable for this time.
More common: intermediary, express / takeout brother, scalper, etc
Static proxy and dynamic proxy in JAVA
We use code to demonstrate static agent and dynamic agent respectively
1. First learn about static agents
//Top level interface public interface Person { void sayHello(); } //If the proxy factory class has one more interface, it also needs one more proxy factory implementation class public class PersonProxy implements Person { private Person person; public PersonProxy(Person person) { this.person = person; } //Static proxy needs to write repeated code for each additional method public void sayHello() { System.out.println("Before calling"); person.sayHello(); System.out.println("After call"); } } //Implementation class public class PersonBoyImpl implements Person { public void sayHello() { System.out.println("hello I am a boy"); } } //main method call public static void main(String[] args) { new PersonProxy(new PersonBoyImpl()).sayHello(); } //Results of execution printing Before calling hello I am a boy After call
The above is the implementation of static proxy. The obvious disadvantage is that each interface needs a corresponding proxy factory class, and each method needs to write repeated code
2. Demonstration of two ways of dynamic agent
There are two ways to generate agents in Java. Let's look at the first JDK agent (JDK dynamic agent must have an implementation interface)
2.1 JDK dynamic agent
- The interface Person and implementation class BoyPerson in the above static proxy remain unchanged
- New dynamic proxy Handler class
//Dynamic proxy Handler class public class PersonPoxyHandler implements InvocationHandler { //Keep original class Object target; //Create proxy class public Object getInstance(Object target){ this.target = target; Class<?> clazz = target.getClass(); //1. Generate a new class with the same method name. The JDK proxy object is the interface //2. Create by implementing the interface //3. If the callback class passes this and this class implements InvocationHandler, the invoke of the proxy class will be transferred to the current class return Proxy.newProxyInstance(clazz.getClassLoader(),clazz.getInterfaces(),this); } //Proxy call class public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("[JDK]Before proxy call"); //Proxy method execution passes in the original class here Object result = method.invoke(target, args); System.out.println("[JDK]After proxy call"); return result; } }
- Next, let's look at the Main method
public static void main(String[] args) throws Exception { //When the implementation class is passed in, the returned object is actually a proxy object //If it is other different classes, you can get the proxy object by passing in different interface implementation classes Person person = (Person) new PersonPoxyHandler().getInstance(new BoyPerson()); person.sayHello(); } //Print results. See the following for the specific principle [JDK]Before proxy call hello I am xxxx [JDK]After proxy call
Because it is already a proxy class, every time you call the method, you will go to the invoke method of the proxy handler class
2.2 CGLIB dynamic agent
Cglib dynamic agent is a class generated based on bytecode, and cglib package needs to be introduced.
<dependency> <groupId>cglib</groupId> <artifactId>cglib</artifactId> <version>3.2.5</version> </dependency>
- Create a new class GirlPerson type
//Create a new class with only one method public class GirlPerson{ public void sayHello() { System.out.println("hello I am Girl"); } }
- The handler class of Cglib implements MethodInterceptor
/** * cglib Dynamic proxy generates classes based on bytecode * The cglib package needs to be introduced */ public class CglibProxyHandler implements MethodInterceptor { //Create proxy object by bytecode public Object getInstance(Class<?> clazz){ Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(clazz); //Callback enhancer.setCallback(this); return enhancer.create(); } //Callback, because this is also passed during creation public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { System.out.println("[CGLIB]Before proxy call"); //Proxy method execution o = original class methodProxy / proxy class method call parent class Object result = methodProxy.invokeSuper(o, objects); System.out.println("[CGLIB]After proxy call"); return result; } }
- main method execution
public static void main(String[] args) throws Exception { //The proxy class created in the form of inheritance does not need interface implementation GirgPerson instance = (GirgPerson)new CglibProxyHandler().getInstance(new GirgPerson().getClass()); instance.sayWorld(); } //Print results. See the following for the specific principle [CGLIB]Before proxy call hello I am Girl [CGLIB]After proxy call
2.3 difference between the two
- JDK proxy creates a proxy class through the implementation interface and saves the original class at the same time. When calling the same method through the proxy class, first go through the proxy object invoke logic, and then call the corresponding method through the original class in the proxy object
- During CGLIB proxy, the bytecode is reset. The internal code of the generated object has changed
It doesn't matter what you don't understand above. I have prepared the source code of proxy class generation!
3.JDK dynamic agent source code analysis
- Let's start by creating a proxy object
//Look directly at proxy Newproxyinstance this method public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException { Objects.requireNonNull(h); //Clone the interface final Class<?>[] intfs = interfaces.clone(); final SecurityManager sm = System.getSecurityManager(); if (sm != null) { checkProxyAccess(Reflection.getCallerClass(), loader, intfs); } /* * Look up or generate the designated proxy class. */ //Get a proxy class by implementing the interface Class<?> cl = getProxyClass0(loader, intfs); /* * Invoke its constructor with the designated invocation handler. */ try { if (sm != null) { checkNewProxyPermission(Reflection.getCallerClass(), cl); } //Gets the constructor of the proxy class final Constructor<?> cons = cl.getConstructor(constructorParams); final InvocationHandler ih = h; if (!Modifier.isPublic(cl.getModifiers())) { AccessController.doPrivileged(new PrivilegedAction<Void>() { public Void run() { cons.setAccessible(true); return null; } }); } //Instantiate the proxy class and pass parameters to InvocationHandler through the constructor return cons.newInstance(new Object[]{h}); } catch (IllegalAccessException|InstantiationException e) { throw new InternalError(e.toString(), e); } catch (InvocationTargetException e) { Throwable t = e.getCause(); if (t instanceof RuntimeException) { throw (RuntimeException) t; } else { throw new InternalError(t.toString(), t); } } catch (NoSuchMethodException e) { throw new InternalError(e.toString(), e); } }
- Execute these lines of code in the main method to save the generated proxy class
public static void main(String[] args) throws Exception { Person person1 = new BoyPerson(); Class<?>[] interfaces = person1.getClass().getInterfaces(); byte[] bytes = ProxyGenerator.generateProxyClass("$Proxy", interfaces); FileOutputStream stream = new FileOutputStream(new File("D:/$Proxy.class")); stream.write(bytes); stream.flush(); stream.close(); }
- Next, let's look at $proxy What is the content of class?
//What we actually get is the proxy object public final class $Proxy extends Proxy implements Person { private static Method m1; private static Method m3; private static Method m2; private static Method m0; //The constructor class directly passes the InvocationHandler parameter to the parent class through newInstance(h) public $Proxy(InvocationHandler var1) throws UndeclaredThrowableException { super(var1); } //Focus here public final void sayHello() { try { //Invoke the h of the parent class and then call the invoke object. super.h.invoke(this, m3, (Object[])null); } catch (Throwable var3) { throw new UndeclaredThrowableException(var3); } } static { try { m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object")); m3 = Class.forName("com.xxx.demo.Dynamic agent.Person").getMethod("sayHello"); m2 = Class.forName("java.lang.Object").getMethod("toString"); m0 = Class.forName("java.lang.Object").getMethod("hashCode"); } catch (NoSuchMethodException var2) { throw new NoSuchMethodError(var2.getMessage()); } catch (ClassNotFoundException var3) { throw new NoClassDefFoundError(var3.getMessage()); } } }
From $proxy Class proxy class can see that the callback class (handler) is passed into the proxy class through the construction method, and then the proxy will pass into the parent class Super. Then the actual execution of the method call is super.h.invoke(), so you can naturally call the invoke method implemented by the PersonPoxyHandler class
4. Analysis of cglib dynamic agent principle
- Instead of looking at the source code generated by the proxy class, CGLIB uses the bytecode generated dynamically by inheriting the original class and directly looks at the generated proxy class
public static void main(String[] args) throws Exception { //Add this code to the first line of the main method to save to the specified directory //Storage agent class System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "D:\\code"); //The proxy class created in the form of inheritance does not need interface implementation GirgPerson instance = (GirgPerson)new CglibProxyHandler().getInstance(new GirgPerson().getClass()); instance.sayWorld(); }
- Next, look at the generated proxy class. There will be many methods with the same name, mainly focusing on this
//Proxy class object calling method public final void sayHello() { MethodInterceptor var10000 = this.CGLIB$CALLBACK_0; if (this.CGLIB$CALLBACK_0 == null) { CGLIB$BIND_CALLBACKS(this); var10000 = this.CGLIB$CALLBACK_0; } if (var10000 != null) { //There are too many implementation methods in the callback class to expand var10000.intercept(this, CGLIB$sayHello$1$Method, CGLIB$emptyArgs, CGLIB$sayHello$1$Proxy); } else { super.sayHello(); } } //The set callback method is handler public void setCallbacks(Callback[] var1) { this.CGLIB$CALLBACK_0 = (MethodInterceptor)var1[0]; }
The two methods and principles of dynamic agent have been analyzed. That's all. I also want to study deeper partners to understand by myself! Here's a point. Dynamic agent plays a vital role in future code analysis. You must learn it well!
That is the whole content of this chapter.
Previous: Use of thread pool worker ForkJoin
Next: Simple implementation of SpringMvc - IOC container and DI dependency injection
Reading breaks thousands of volumes, and writing is like God