The most detailed dynamic Proxy analysis in Java -- Proxy

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

Keywords: Java Design Pattern source code

Added by mrneilrobinson on Mon, 13 Dec 2021 09:05:22 +0200