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:
- Modify source code at compile time;
- Modify the bytecode before loading;
- 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
- The InvocationHandler interface is implemented by getting the class loader (getClassLoade() method) and the interface (getInterfaces() method) of the propped object.
- Proxy class bytecode files are generated and proxy class objects are dynamically loaded into the JVM using a class loader.
- 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:
- In the lowest static building block, the class information of the propped object is obtained by reflection.
- The proxy object implements the same interface as the proxied object.
- 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:
- The proxy class inherits the proxied class and overrides the proxied class’s methods
- In the overridden method, we first determine whether the Intercept method of the MethodInterceptor is implemented, that is, whether an interceptor is implemented
- If an interceptor is implemented, the Intercept method we implemented is called.
- 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.
EnableAutoConfiguration
It is also a composite annotation on which @import is marked as injectedAutoConfigurationImportSelector
.
AutoConfigurationImportSelector
Implements 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
AutoConfigurationGroup
Is an inner class of AutoConfigurationImportSelector, he realized DeferredImportSelector. Group interface.
In SpringBoot 2.x, that isAutoConfigurationImportSelector.AutoConfigurationGroupd
To 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.xAopAutoConfiguration
As 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
- Proxy is a design pattern, which can be divided into static proxy and dynamic proxy
- The principle of Spring AOP is dynamic proxy, there are JDK dynamic proxy and CGLIB dynamic proxy two ways
- JDK dynamic proxies are implemented based on interfaces, and CGLIB dynamic proxies are implemented based on inheritance generation subclasses.