spring -- behavior analysis of proxy object during nested call of Java Proxy and Cglib methods

spring -- failure analysis of Java Proxy and Cglib method nested call proxy

This paper mainly analyzes the different reactions of the two proxy methods when methods are nested. This paper is the last one Spring -- configuration class parsing process Configure derivatives. This article is not an expository text used by Java proxy and Cglib.

Of course, the beginning of the article also has to start with examples.

At the beginning of the article, it is recommended to read it first

Jdk dynamic agent analysis

example

Interface:

public interface Action {
	String run(String name);
}

Implementation class

public class TestBean implements Action{
	@Override
	public String run(String name) {
		System.out.println("TestBean.run");
		return this.getClass().getName() + ":" + name;
	}
}

Let's start with this example.

Java Proxy

I've already said about the way java implements proxies. Show the demo first

public class ProxyAnalysis {
    public static void main(String[] args) {
        System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles","true"); // Add this class object that can save the generated class,   
       // Generate proxy object
        Action action = (Action) Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),
                new Class<?>[]{Action.class}, new InvocationHandler() {
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        System.out.println(proxy.getClass().getName());
                        return null;
                    }
                });
        System.out.println(action.run("s"));
    }
}

The result is:

Look at what the generated proxy class looks like

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package com.sun.proxy;

import com.lc.cglib.Action;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;

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

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

    public final boolean equals(Object var1) throws  {
        try {
            return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }

    public final String toString() throws  {
        try {
            return (String)super.h.invoke(this, m2, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final String run(String var1) throws  {
        try {
            return (String)super.h.invoke(this, m3, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }

    public final int hashCode() throws  {
        try {
            return (Integer)super.h.invoke(this, m0, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    static {
        try {
            m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            m3 = Class.forName("com.lc.cglib.Action").getMethod("run", Class.forName("java.lang.String"));
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}

You can see that the generated Proxy class inherits the Proxy and implements the interface to be implemented. Therefore, in the above demo, we can force the conversion to success. In addition, when calling the Method of the interface, the InvocationHandle will be called directly to pass this, the previously saved Method, and the parameters passed by calling this Method. This is how Java Proxy is implemented.

However, the above example does not reflect the classic form of proxy. The target method is operated before and after. Take a look at the following writing.

The red boxes are new codes and results. First determine the target of the proxy (TestBean), and call the method of the proxy target (method.invoke(testBean,args)) when calling. On this basis, operate before and after. This is the standard way of writing. There is no problem with this way of writing.

But there is a question: what does the proxy in the invoke method do? What would it look like if it were written in the following way

The stack overflows directly. The proxy in the invoke method is the proxy object. Think about the bytecode of the proxy object above. this object is passed. this method call will be nested all the time, resulting in stack overflow. If it is directly System.out.println(proxy); And?

Stack overflow, why? Because the proxy class overrides the toString method of Object, it also overrides the hashCode and equals methods.

In other words, the relationship between the proxy object generated by Java proxy and the proxy object is shown in the following class diagram

TestBean and Proxy0 are implementation classes of Action, which are associated through ProxyAnalysis. If not ProxyAnalysis, there is no direct relationship between the two.

Now let's look at method nested calls

If a jump() method is added to the current interface, TestBean and Proxy0 will be implemented accordingly. Now in TestBean. The run method called the jump method. Now create the proxy object in the correct way as described above, and call the TestBean method in the invoke method. Will there be two calls to the proxy object at this time?

First of all, the run method must have been called. This has been analyzed before. Will the jump method in the run method be called? No, definitely not. Since the run method has been called, the following execution has taken place in TestBean and has nothing to do with the proxy object.

Cglib Proxy

As we all know, cglib implements the effect of proxy through inheritance. Because it is through inheritance, there is a later MethodProxy that can call the methods of the parent class. Can it be called in the way above? What does o in intercept do? Method is a method and Objects is a parameter. What does the following MethodProxy do?

Stack overflow, because the generated proxy class also overrides those methods. System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, ""); Specify the address where the generated class is saved. Look at the generated proxy class. There are a lot of code for this proxy class. Here we only look at some key code

 public final String run(String var1) {
        // This is the MethodInterceptor set when Enhancer is created.
        MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
        if (var10000 == null) {
            CGLIB$BIND_CALLBACKS(this);
            var10000 = this.CGLIB$CALLBACK_0;
        }
       // Call directly. The idea here is the same as that of java proxy.
        return var10000 != null ? (String)var10000.intercept(this, CGLIB$run$0$Method, new Object[]{var1}, CGLIB$run$0$Proxy) : super.run(var1);
    }

You can see that the first parameter in the intercept Method is also the this object. The second is the Method object, the third is the input parameter of the Method, and the fourth is CGLIB$run CGLIB$run $0 $proxy $Proxy and CGLIB$run 0 0 Where is 0Proxy set up?

You can see that each method has a corresponding MethodProxy. And it is still a static field.

I can't understand the detailed code logic here. I don't understand.

Back to the subject, what is the relationship between the proxy object and the proxy object?

So what happens to the nesting of methods in Cglib.

Nested method calls in TestBean,

The result shows that begin is called twice, and the last null is because I returned a null.

Nested calls to methods do not invalidate the proxy. They are called twice. Why?

Analyze the reason why the Cglib agent did not fail

First, there is a generalization relationship between the inherited class and the inherited class. When calling run, it will call the intercept method. When calling the parent class method in intercept, it will call TestBean. When calling jump in the run method of TestBean, it will call the jump method of the proxy class. (the proxy class is a subclass of the proxy class, enhancer.create()) For the returned proxy class, test is called at the beginning, and run starts from the proxy class, that is, the subclass). Because the subclass overrides the methods of the parent class, calling jump in run will call jump in the subclass at this time.

As long as you get to the jump of the subclass, you will go to intercept. If you continue to call the parent class, you will call the jump method in TestBean. This is the reason why it is not implemented. This method reminds me of internal substitution.

ending

Based on the above analysis, I think this is the biggest difference between Java Proxy and Cglib Proxy. Of course, there are other things. For example, you can use MethodFilter to find out which MethodCallBack to use. But I think the biggest thing is this. Cglib Proxy will not make nested calls to methods in an object, resulting in proxy operation failure. Polymorphism mechanism is used.

That's it. It's over.

About the blog, I take it as my notes. There are many contents in it that reflect my thinking process. Because my thinking is limited, there are some differences in some contents. If you have any questions, please point them out. Discuss them together. Thank you.

Tags: Java Spring

Posted on Sun, 05 Dec 2021 20:53:49 -0500 by Crowly