Article before us detailed introduces the JDK’s own API provided a dynamic agent, its implementation is relatively simple, but it has a very fatal defects, and is only for the interface agent method is complete, the delegate class or my way in the parent class method can’t be agent.

CGLIB is a high-performance code generation framework based on the ASM framework. It perfectly solves the problem that JDK version dynamic proxy can only be used as interface method proxy.

Dynamic proxy mechanism of CGLIB

Before we go into more detail about CGLIB, let’s run through a complete example. After all, purposeful learning is not easy to give up.

The Student class is our delegate class, which itself inherits from the Father class and implements the Person interface.

CGLIB’s interceptor is a bit like the processor in the JDK’s dynamic proxy.

As you can see, the proxy class created by CGLIB is a subclass of the delegate class, so it can be strongly converted to the delegate class type.

As you can see from the output, all methods get proxies.

This is one of the simplest applications of CGLIB, so you can copy the code and run it yourself, and then we’ll look at it bit by bit.

Let’s start by looking at the structure of the proxy class generated by CGLIB by setting system properties:

System. SetProperty (DebuggingClassWriter DEBUG_LOCATION_PROPERTY, local disk path)Copy the code

You can specify CGLIB to save dynamically generated proxy classes to a specified disk path. Next, we decompile the proxy Class. There are many excellent third-party decompiler tools. Here I recommend a website that can decompile a Class file directly for us.

JAVA reverse engineering network

You can then find the proxy class CGLIB saved for you in the disk directory you specify. You can just upload it to the web site and get a decompilated Java file.

Let’s start with the proxy class inheritance:

Student is the delegate type that we need to delegate, and the resulting proxy class directly inherits from the delegate class. This small design perfectly solves the JDK dynamic proxy that the single proxy defect, inherits the delegate class, can reflect the delegate class interface all methods, all methods in the parent class, all methods defined by itself, the implementation of these methods to complete the proxy of all methods in the delegate class.

The Factory interface defines several methods for setting and getting callbacks, called interceptors, which we’ll talk about later.

And then this part of the program reflects the parent class, the delegate class, all the methods, including the methods in the parent class and the parent interface of the delegate class.

The last section overrides all the methods of the parent class. Here’s an example of a method.

Obviously, the proxy class overrides all the methods in the parent class, and the logic of these methods is simple, passing the current method signature as a parameter to the interceptor, also known as a “callback.”

So, in this sense, CGLIB method calls are similar to JDK dynamic proxies in that they rely on a callback that is called an interceptor and called a handler in the JDK.

But I want to remind you that each method in the proxy class has two versions, one of which is the original overridden method and the other is the corresponding method that does not pass through the interceptor. This is a result of the FastClass mechanism in CGLIB, but I just want to draw your attention to it later.

So far, we’ve looked at the basic structure of proxy classes, which are roughly similar to JDK dynamic proxies, except that CGLIB generates a proxy class that directly inherits from our delegate class and is able to delegate all the methods in the delegate class.

Since all method calls in a proxy class are passed to the interceptor, let’s take a look at what the interceptor parameters mean.

Customizing interceptors is as simple as implementing our MethodInterceptor interface and overriding its Intercept method. This method takes four parameters, and let’s see what they represent.

  • Obj: This represents the instance object of our proxy class
  • Method: A reference to the currently invoked method
  • Arg: formal argument to call the method
  • Proxy: It also represents a reference to the current method, based on the FastClass mechanism

We know that Method calls are based on reflection, but reflection is always less efficient than direct Method calls. MethodProxy indexes methods directly based on FastClass mechanism, and locates and calls methods directly through the index, which is a bit of performance improvement.

Here’s a factory method for a MethodProxy instance:

public static MethodProxy create(Class c1, Class c2, String desc, String name1, String name2) {
    MethodProxy proxy = new MethodProxy();
    proxy.sig1 = new Signature(name1, desc);
    proxy.sig2 = new Signature(name2, desc);
    proxy.createInfo = new MethodProxy.CreateInfo(c1, c2);
    return proxy;
}
Copy the code

Where the formal parameter desc represents the method descriptor of a method, c1 represents the class to which the method belongs, the value is usually our delegate class, and c2 represents the value of our generated proxy class. Name1 is the name of the method in the delegate class and name2 is the name of the method in the proxy class.

Here’s an example:

var1 = Class.forName("Main.Student");
var0 = Class.forName("Main.Student$$EnhancerByCGLIB$$56e20d66");
MethodProxy.create(var1, var0, "()V"."sayHello"."CGLIB$sayHello$3");
Copy the code

Var1 is our delegate class, var0 is its proxy class, “()V” is the sayHello method signature, and “CGLIB$sayHello$3” is the sayHello method name in the proxy class.

With these parameters, MethodProxy initializes a FastClassInfo.

private static class FastClassInfo {
    FastClass f1;
    FastClass f2;
    int i1;
    int i2;
    private FastClassInfo() {}}Copy the code

And FastClass is what, in fact, the interior is a bit complicated, here is a simple to tell you.

FastClass is a bit of a decorator pattern. It contains a Class object and indexes all the methods in it, so external calls to any method need only provide an index value and FastClass can quickly locate the specific method.

F1 will inner wrap our delegate class, f2 will inner wrap our proxy class, i1 is the index value of the current method in F1, i2 is the index value of the current method in F2.

As a result, FastClass-based method calls are also simple, with an index specified in an invoke method instead of the traditional reflection method, which requires passing a caller to the Invoke method and then invoking the method called through reflection.

In summary, one MethodProxy instance corresponds to two FastClass instances, one wrapping the delegate class and exposing the method index, and the other wrapping the proxy class and exposing the method index in the proxy class.

Ok, now test everybody:

Which method is called by the Invoke and invokeSuper methods in MethodProxy? In the proxy class? Or in a delegate class?

The answer is: The invoke method calls the latter, and the invokeSuper calls the former.

A FastClass instance binds to a Class type and indexes all the methods in that Class.

So f1 binds to our delegate class and F2 binds to our proxy class, and whether you call the invoke method with F1 or F2, you need to pass in an instance of obj, which is our proxy class. F1.i1 corresponds to a method signature “public final void run”, while f2.i2 corresponds to a method signature “final void CGLIB$0”.

So F1.i1. invoke and F2.i2. invoke invoke invoke different methods of the same instance, which explains why CGLIB created a proxy class with two forms for each method, but I find this design somewhat futile and prone to an endless loop that makes it harder to understand.

The FastClass Invoke method isn’t all that mysterious either:

Don’t get too complicated. A FastClass instance simply scans the base method of the inner Class type and lists the switch-case option in the Invoke method. Each invoke call matches the index and then has the target object call the target method directly.

So there’s a problem here, an endless loop. Our interceptors are written like this:

System.out.println("Before:" + method);
Object object = proxy.invokeSuper(obj, arg);
System.out.println("After:" + method);
return object;
Copy the code

InvokeSuper calls the “final void CGLIB$0” method, indirectly calling the corresponding method of the delegate class. And if you change to invoke, something like this:

System.out.println("Before:" + method);
Object object = proxy.invoke(obj, arg);
System.out.println("After:" + method);
return object;
Copy the code

The result is an endless loop. Why?

The invoke method invokes a method with the same signature as the method in the delegate class, and when it finally gets to our proxy class, it passes through an interceptor again, which in turn calls back again and again, in an endless loop.

At this point, I think I’ve covered the basics of CGLIB, and you need to sort out the logic and understand how it works from start to finish.

The lack of additional

CGLIB solves the fatal dynamic proxy problem of the JDK, a single proxy mechanism. It can proxy methods in its parent class and in its parent interface, but notice THAT I’m not saying that all methods can proxy.

The biggest drawback of CGLIB is that it needs to inherit from our delegate class, so if the delegate class is modified to be final, that means that the CGLIB class cannot be proxied.

Naturally, a class with a final modified method cannot be propped even if it is not final. CGLIB generates a proxy class that overrides all methods in the delegate class, and a method that is modified as final is not allowed to be overridden.

Overall, CGLIB has been excellent, despite its flaws. Almost the mainstream framework in the market are inevitable to use CGLIB, later will take you to analyze the framework source code, then we see CGLIB!


All the code, images and files in this article are stored in the cloud on my GitHub:

(https://github.com/SingleYam/overview_java)

Welcome to follow the wechat official account: OneJavaCoder. All articles will be synchronized on the official account.