The agent profile

Proxy is a design pattern in which a proxy object accesses a target object and extends or enhances its functionality without changing the target object.

Java proxy

A class goes from being written to being called at run time through these steps

So there are three ways to generate a proxy:

  1. Modify source code at compile time;
  2. Modify the bytecode before loading;
  3. The bytecode of the proxy class is dynamically created after the bytecode is loaded

According to the different generation methods of proxy, proxy can be divided into static proxy and dynamic proxy. Static proxy is to modify the source code before compilation, and dynamic proxy is to dynamically generate the bytecode of proxy class after being loaded by proxy object

Static agent

Static proxies need to write a proxy class that implements the same interface as the propped class and calls the target object’s methods in the proxy class.

Static proxy is easy to understand and code, but it is tedious and not strong in extensibility. Each proxy object needs to be encoded to realize a proxy object, interface method is added, and both the proxy object and the proxy object need to be modified.

Here is an example of a static proxy. We have a “vehicle” interface, the implementation class is “car”, and the proxy object is ProxyCar, which prints logs before and after the methods implemented by “car”.

/ / interface
public interface Vehicle {
    void doSomething(a);
}
Copy the code
/ / implementation
public class Car implements Vehicle{
    @Override
    public void doSomething(a) {
        System.out.println("Running on the highway."); }}Copy the code
// Proxy object
public class ProxyCar implements Vehicle{
    private Car car;

    public ProxyCar(Car car){
        this.car = car;
    }

    @Override
    public void doSomething(a) {
        System.out.println("before car doSomething");
        car.doSomething();
        System.out.println("after car doSomething");
    }

    / / test
    public static void main(String[] args) {
        Car car = new Car();
        ProxyCar proxyCar = newProxyCar(car); proxyCar.doSomething(); }}Copy the code

Test results:

A dynamic proxy

Dynamic proxy is a type of proxy pattern, unlike static proxy, which requires us to manually code proxy classes, dynamic proxy builds proxy objects in memory. In the Spring framework, the JDK dynamic proxy and CGLIB dynamic proxy are commonly used.

JDK dynamic proxy

JDK dynamic proxy principles

  1. The InvocationHandler interface is implemented by getting the class loader (getClassLoade() method) and the interface (getInterfaces() method) of the propped object.
  2. Proxy class bytecode files are generated and proxy class objects are dynamically loaded into the JVM using a class loader.
  3. The proxy class implements the same interface as the proxied class, which uses reflection to invoke the Invoke method of the InvocationHandler interface.

demo

We have a “vehicle” interface with an implementation class of “car” that uses JDK dynamic proxies to generate proxy objects that log before and after the methods implemented by “car”.

/ / interface
public interface Vehicle {
    void doSomething(a);
}
/ / implementation class
public class Car implements Vehicle{
    @Override
    public void doSomething(a) {
        System.out.println("Running on the highway."); }}Copy the code
// JDK dynamic proxy
public class JDKProxyFactory {
    private Object target;

    public JDKProxyFactory(Object target) {
        this.target = target;
    }

    public Object getProxyInstance(a){
        // get classLoader and interface by reflectionClassLoader classLoader = target.getClass().getClassLoader(); Class<? >[] interfaces = target.getClass().getInterfaces();// generate proxy object
        Object proxyObject = Proxy.newProxyInstance(classLoader,interfaces,(proxy,method,args)->{
            System.out.println("Open transaction");
            // Execute the target object method
            Object returnValue = method.invoke(target, args);
            System.out.println("Commit transaction");
            return null;
        });
        return proxyObject;
    }

    // test
    public static void main(String[] args) throws IOException {
        Vehicle car = new Car();
        System.out.println(car.getClass());
        Vehicle proxy = (Vehicle)new JDKProxyFactory(car).getProxyInstance();
        System.out.println(proxy.getClass());
        proxy.doSomething();
        // print proxy class file
        byte[] proxyBytes = ProxyGenerator.generateProxyClass("$Proxy0".new Class[]{proxy.getClass()});
        FileOutputStream os = new FileOutputStream("Proxy0.class"); os.write(proxyBytes); os.close(); }}Copy the code

Here is part of the printed bytecode file

public final class $Proxy0 extends Proxy implements Proxy0 {
    private static Method m1;
    private static Method m7;
    private static Method m3;
    private static Method m2;
    private static Method m6;
    private static Method m11;
    private static Method m13;
    private static Method m0;
    private static Method m10;
    private static Method m12;
    private static Method m5;
    private static Method m9;
    private static Method m4;
    private staticMethod m8; . .// Here is the same method that the proxy class implements on the proxied object's interface
public final void doSomething(a) throws  {
        try {
	   // super.h corresponds to the parent h variable, which is the InvocationHandler parameter in proxy. newProxyInstance
           // So we are actually using our own InvocationHandler to implement the invoke method of our class
            super.h.invoke(this, m3, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw newUndeclaredThrowableException(var3); }}... .// In a static building block, the proxy class gets the details of the propped class through reflection
static {
        try {
            m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
            m7 = Class.forName("com.sun.proxy.$Proxy0").getMethod("getInvocationHandler", Class.forName("java.lang.Object"));
            m3 = Class.forName("com.sun.proxy.$Proxy0").getMethod("doSomething");
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            m6 = Class.forName("com.sun.proxy.$Proxy0").getMethod("getProxyClass", Class.forName("java.lang.ClassLoader"), Class.forName("[Ljava.lang.Class;"));
            m11 = Class.forName("com.sun.proxy.$Proxy0").getMethod("getClass");
            m13 = Class.forName("com.sun.proxy.$Proxy0").getMethod("notifyAll");
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
            m10 = Class.forName("com.sun.proxy.$Proxy0").getMethod("wait");
            m12 = Class.forName("com.sun.proxy.$Proxy0").getMethod("notify");
            m5 = Class.forName("com.sun.proxy.$Proxy0").getMethod("newProxyInstance", Class.forName("java.lang.ClassLoader"), Class.forName("[Ljava.lang.Class;"), Class.forName("java.lang.reflect.InvocationHandler"));
            m9 = Class.forName("com.sun.proxy.$Proxy0").getMethod("wait", Long.TYPE);
            m4 = Class.forName("com.sun.proxy.$Proxy0").getMethod("isProxyClass", Class.forName("java.lang.Class"));
            m8 = Class.forName("com.sun.proxy.$Proxy0").getMethod("wait", Long.TYPE, Integer.TYPE);
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw newNoClassDefFoundError(var3.getMessage()); }}}Copy the code

If you look at the class file of the proxy class, you get the following information:

  1. In the lowest static building block, the class information of the propped object is obtained by reflection.
  2. The proxy object implements the same interface as the proxied object.
  3. When generating the proxy object, we pass in an InvocationHandler object that implements the Invoke method. When the proxy object implements the interface, the invoke method of the InvocationHandler object is invoked through reflection.

CGLIB dynamic proxy

CGLIB(Code Generation Library) is a third party Code Generation class Library. CGLIB dynamic proxy extends the functions of proxied objects by dynamically generating a subclass object in memory at runtime. The bottom layer relies on ASM (an open source Java bytecode editing library) to manipulate bytecode.

Since CGLIB implements dynamic proxies through inheritance, the proxied class cannot be final, and the methods of the target class cannot be final, otherwise the proxy class will call the methods of the target class directly.

demo

We have a proxied object “aircraft”, and also implement an interceptor AirCraftInterceptor, through the CGLIB dynamic proxy generation proxy object, the proxy object is printed out

// Proxy object
public class AirCraft {
    public void doSomething(a) {
        System.out.println("Heaven"); }}Copy the code
// interceptor
public class AirCraftInterceptor implements MethodInterceptor {
    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        System.out.println("before:"+method.getName());
        Object object = proxy.invokeSuper(obj, args);
        System.out.println("after:"+method.getName());
        return object;
    }

    // Test case
    public static void main(String[] args) {
	System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "/Users/******/Desktop/proxyInfo");
        Enhancer enhancer = new Enhancer();
        // Inherits the proxied class
        enhancer.setSuperclass(AirCraft.class);
        // Set the callback
        enhancer.setCallback(new AirCraftInterceptor());
        // Generate the proxy object
        AirCraft airCraft =  (AirCraft)enhancer.create();
        // Methods that call the proxy class are intercepted by the method interceptor we implementedairCraft.doSomething(); }}Copy the code

Test case output

CGLIB debugging enabled, Writing to '/Users/*****/Desktop/proxyInfo' before:doSomething God after:doSomething Process finished with exit code 0Copy the code

The following is part of the bytecode file generated by CGLIB

public class AirCraft$$EnhancerByCGLIB$$13502f9c extends AirCraft implements Factory {
    private boolean CGLIB$BOUND;
    private static final ThreadLocal CGLIB$THREAD_CALLBACKS;
    private static final Callback[] CGLIB$STATIC_CALLBACKS;
    private MethodInterceptor CGLIB$CALLBACK_0;
    private static final Method CGLIB$doSomething$0$Method;
    private static final MethodProxy CGLIB$doSomething$0$Proxy;
    private static final Object[] CGLIB$emptyArgs;
    private static final Method CGLIB$finalize$1$Method;
    private static final MethodProxy CGLIB$finalize$1$Proxy;
    private static final Method CGLIB$equals$2$Method;
    private static final MethodProxy CGLIB$equals$2$Proxy;
    private static final Method CGLIB$toString$3$Method;
    private static final MethodProxy CGLIB$toString$3$Proxy;
    private static final Method CGLIB$hashCode$4$Method;
    private static final MethodProxy CGLIB$hashCode$4$Proxy;
    private static final Method CGLIB$clone$5$Method;
    private static final MethodProxy CGLIB$clone$5$Proxy; .// Override the parent method
public final void doSomething(a) {
        MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
        if (var10000 == null) {
            CGLIB$BIND_CALLBACKS(this);
            var10000 = this.CGLIB$CALLBACK_0;
        }

        if(var10000 ! =null) {
            var10000.intercept(this, CGLIB$doSomething$0$Method, CGLIB$emptyArgs, CGLIB$doSomething$0$Proxy);
        } else {
            super.doSomething(); }}... }Copy the code

If you look at the class file of the proxy class, you get the following information:

  1. The proxy class inherits the proxied class and overrides the proxied class’s methods
  2. In the overridden method, we first determine whether the Intercept method of the MethodInterceptor is implemented, that is, whether an interceptor is implemented
  3. If an interceptor is implemented, the Intercept method we implemented is called.
  4. Instead of implementing an interceptor, methods of the parent class are called directly

Spring AOP and dynamic proxies

Spring AOP is based on a dynamic proxy implementation, according to the Spring Framework 5.x documentation. Spring AOP defaults to using JDK dynamic proxies, or CGLIB proxies if the object does not implement an interface. Of course, you can force the CGLIB proxy.

Spring Boot Dynamic proxy type

If you use Spring Boot 1.5.x, you will find that the JDK dynamic proxy is used by default, while in Spring Boot 2.x, you will find that the CGLIB proxy is used regardless of whether the object implements the interface. The specific control of this behavior is generally controlled by the @aopAutoConfiguration annotation, especially the proxy-target-class configuration item. If you’re interested, take a look at the following analysis.

SpringBoot projects typically have the @SpringBootApplication annotation in their configuration classes. The @SpringBootApplication annotation is a composite annotation, This annotation uses @enableAutoConfiguration to do a lot of auto-assembly.

EnableAutoConfigurationIt is also a composite annotation on which @import is marked as injectedAutoConfigurationImportSelector.

AutoConfigurationImportSelectorImplements the DeferredImportSelector interface.

In the Spring Framework 4.x version, this is an empty interface that simply inherits the ImportSelector interface. 5. X extended the DeferredImportSelector interface and added a getImportGroup method.

The AutoConfigurationGroup class is returned in this method

AutoConfigurationGroupIs an inner class of AutoConfigurationImportSelector, he realized DeferredImportSelector. Group interface.

In SpringBoot 2.x, that isAutoConfigurationImportSelector.AutoConfigurationGroupdTo import the automatic configuration class. To perform this. AutoConfigurationEntries. Add (autoConfigurationEntry), add configuration includingorg.springframework.boot.autoconfigure.aop.AopAutoConfiguration

AopAutoConfiguration, without the proxy – target – class under the condition of configuration items, use CglibAutoProxyConfiguration by default.

So, in SpringBoot2.x, AOP is automatically assembled through AopAutoConfiguration. By default, there is no spring.aop.proxy-target-class configuration item and CGLIB is used for dynamic implementation by default.

Here we paste SpringBoot 1.5.xAopAutoConfigurationAs you can see, the JDK dynamic proxy is still used by default in SpringBoot 1.5.x.

The proxy-taget-class option is configured

There are two configuration modes:

Configuration Mode 1:

// Use spring.aop.proxy-target-class in the application.properties file
spring.aop.proxy-target-class=false
Copy the code

Configuration Mode 2:

// Use annotations in configuration classes
@EnableAspectJAutoProxy(proxyTargetClass = true)
Copy the code

Why does 2.x use CGLIB by default

Spring Boot 2.x uses CGLIB by default to prevent proxy problems during automatic injection.

In general, our coding practices are oriented toward interfaces, but if an object does not implement an interface and uses JDK dynamic proxies by default, or if our reference type is an implementation class instead of an interface, injection will fail.

Suppose we have a UserServiceImpl class that does not implement any interface and we inject it as follows

@Autowired
UserServiceImpl userService;
Copy the code

If we were using the JDK dynamic proxy, we would get an error at startup.

conclusion

  1. Proxy is a design pattern, which can be divided into static proxy and dynamic proxy
  2. The principle of Spring AOP is dynamic proxy, there are JDK dynamic proxy and CGLIB dynamic proxy two ways
  3. JDK dynamic proxies are implemented based on interfaces, and CGLIB dynamic proxies are implemented based on inheritance generation subclasses.