Design pattern [3.3] - Interpretation of cglib dynamic agent source code

Qinhuai grocery store 2021-11-25 18:04:56

<img src=“ https://markdownpicture.oss-cn-qingdao.aliyuncs.com/blog/20210925233820.png” width = “500” height = “400” alt=“ Image name ” align=center />

cglib A dynamic proxy

cglib Introduce

CGLIB It's an open source project , A powerful high-performance and high-quality code generation library , It can be expanded during operation Java class , Realization Java Interface, etc. . The bottom layer is to use a small and fast bytecode processing framework ASM, To convert bytecode and generate new classes .

In theory, we can also use ASM To directly generate code , But ask us to JVM Inside ,class File format , And the instruction set of bytecode .

This thing is not here JDK Inside the bag , You need to download, import or Maven Coordinate import .

I choose Maven Import , Add to pom.xml file :

<dependencies>
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>
</dependencies>

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.

Student.java:

public class Student {
public void learn() {
System.out.println(" I am a student , I want to learn. ");
}
}

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.

MyProxy.java( proxy class )


import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
public class StudentProxy implements MethodInterceptor {
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
// TODO Auto-generated method stub
System.out.println(" Before the agency -------");
proxy.invokeSuper(obj, args);
System.out.println(" After agency -------");
return null;
}
}

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.

Test class (Test.java


import net.sf.cglib.core.DebuggingClassWriter;
import net.sf.cglib.proxy.Enhancer;
public class Test {
public static void main(String[] args) {
// proxy class class The file is saved to the local disk for us to decompile and view the source code 
System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "/Users/aphysia/Desktop");
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(Student.class);
enhancer.setCallback(new StudentProxy());
Student student = (Student) enhancer.create();
student.learn();
}
}

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.

The result of running is :

CGLIB debugging enabled, writing to '/Users/xuwenhao/Desktop'
Before the agency -------
I am a student , I want to learn.
After agency -------

  • 1.
  • 2.
  • 3.
  • 4.

In the folder we chose , Generated the code of the proxy class :

 Design patterns 【3.3】-- CGLIB Dynamic agent source code interpretation _ proxy class

Source code analysis

Let's first proxy the class , Need to achieve MethodInterceptor( method interceptors ) Interface , This interface has only one method intercept, The parameters are :

  • obj: Objects that need to be enhanced
  • method: The way to intercept
  • args: Method parameters to be intercepted
  • proxy: Represents the method object to trigger the parent class
package net.sf.cglib.proxy;
import java.lang.reflect.Method;
public interface MethodInterceptor extends Callback {
public Object intercept(Object obj, java.lang.reflect.Method method, Object[] args,
MethodProxy proxy) throws Throwable;
}

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.

Let's go back to the method we want to create the proxy class enhancer.create(), The meaning of this method : if necessary , Generate a new class , And use the specified callback ( If any ) To create a new object instance . Parameterless constructor using superclass .

 public Object create() {
classOnly = false;
argumentTypes = null;
return createHelper();
}

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.

The main method logic we have to look at createHelper(), Besides checking , It's called KEY_FACTORY.newInstance() Method generation EnhancerKey object ,KEY_FACTORY Is static EnhancerKey Interface ,newInstance() Is a method in the interface , A focus on super.create(key) Inside , It's called the method of the parent class :

 private Object createHelper() {
// check 
preValidate();
Object key = KEY_FACTORY.newInstance((superclass != null) ? superclass.getName() : null,
ReflectUtils.getNames(interfaces),
filter == ALL_ZERO ? null : new WeakCacheKey<CallbackFilter>(filter),
callbackTypes,
useFactory,
interceptDuringConstruction,
serialVersionUID);
this.currentKey = key;
Object result = super.create(key);
return result;
}

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.

AbstractClassGenerator yes Enhancer Parent class of ,create(key) The main logic of the method is to get the class loader , Cache get class load data , Then reflect the construction object , There are two methods to create instance objects :

  • fistInstance(): This method should not be invoked in the regular stream. . Technically speaking ,{@link #wrapCachedClass(Class)} Use {@link EnhancerFactoryData} As cache value , The latter supports faster instantiation than normal old reflection lookups and calls . For backward compatibility reasons , This method remains unchanged : Just in case it's ever been used .( My understanding is that the current logic will not go to this branch , Because it's busy , But for compatibility , This case Still remain in the ), Internal logic actually uses ReflectUtils.newInstance(type).
  • nextInstance(): Real class to create proxy object
 protected Object create(Object key) {
try {
ClassLoader loader = getClassLoader();
Map<ClassLoader, ClassLoaderData> cache = CACHE;
ClassLoaderData data = cache.get(loader);
if (data == null) {
synchronized (AbstractClassGenerator.class) {
cache = CACHE;
data = cache.get(loader);
if (data == null) {
Map<ClassLoader, ClassLoaderData> newCache = new WeakHashMap<ClassLoader, ClassLoaderData>(cache);
data = new ClassLoaderData(loader);
newCache.put(loader, data);
CACHE = newCache;
}
}
}
this.key = key;
Object obj = data.get(this, getUseCache());
if (obj instanceof Class) {
return firstInstance((Class) obj);
}
// The way to actually create an object 
return nextInstance(obj);
} catch (RuntimeException e) {
throw e;
} catch (Error e) {
throw e;
} catch (Exception e) {
throw new CodeGenerationException(e);
}
}

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.

This method is defined in AbstractClassGenerator, But it's actually calling subclasses Enhancer The implementation of the , Mainly by obtaining parameter types , Parameters , And callback objects , Use these parameters to generate proxy objects .

 protected Object nextInstance(Object instance) {
EnhancerFactoryData data = (EnhancerFactoryData) instance;
if (classOnly) {
return data.generatedClass;
}
Class[] argumentTypes = this.argumentTypes;
Object[] arguments = this.arguments;
if (argumentTypes == null) {
argumentTypes = Constants.EMPTY_CLASS_ARRAY;
arguments = null;
}
// structure 
return data.newInstance(argumentTypes, arguments, callbacks);
}

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.

Internal implementation logic , All the calls are ReflectUtils.newInstance(), The parameter types are different :

 public Object newInstance(Class[] argumentTypes, Object[] arguments, Callback[] callbacks) {
setThreadCallbacks(callbacks);
try {
if (primaryConstructorArgTypes == argumentTypes ||
Arrays.equals(primaryConstructorArgTypes, argumentTypes)) {
return ReflectUtils.newInstance(primaryConstructor, arguments);
}
return ReflectUtils.newInstance(generatedClass, argumentTypes, arguments);
} finally {
setThreadCallbacks(null);
}
}

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.

Follow in to the end , Is to get the constructor method , Construct proxy objects by reflection , The final call is JDK Methods provided :

 public static Object newInstance(Class type, Class[] parameterTypes, Object[] args) {
return newInstance(getConstructor(type, parameterTypes), args);
}
public static Object newInstance(final Constructor cstruct, final Object[] args) {
boolean flag = cstruct.isAccessible();
try {
if (!flag) {
cstruct.setAccessible(true);
}
Object result = cstruct.newInstance(args);
return result;
} catch (InstantiationException e) {
throw new CodeGenerationException(e);
} catch (IllegalAccessException e) {
throw new CodeGenerationException(e);
} catch (InvocationTargetException e) {
throw new CodeGenerationException(e.getTargetException());
} finally {
if (!flag) {
cstruct.setAccessible(flag);
}
}
}

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.

Open the automatically generated proxy class file and have a look , You will find that it is actually generating those methods , Some enhancement methods are added :

 Design patterns 【3.3】-- CGLIB Dynamic agent source code interpretation _ A dynamic proxy _02

The generated proxy class inherits the original class :

public class Student$$EnhancerByCGLIB$$929cb5fe extends Student implements Factory {
...
}

  • 1.
  • 2.
  • 3.

Look at the generated enhancements , In fact, it calls intercept() Method , This method is implemented by ourselves , Therefore, the enhanced function of proxy object is completed :

 public final void learn() {
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$learn$0$Method, CGLIB$emptyArgs, CGLIB$learn$0$Proxy);
} else {
super.learn();
}
}

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.

cglib and jdk What's the difference between dynamic agents

  1. jdk Dynamic proxy is an anonymous class that uses interceptors and reflection to generate a proxy interface , Give it to me when you execute the method InvokeHandler Handle .CGLIB Dynamic proxy uses ASM frame , Modify the original bytecode , Then generate new subclasses to handle .
  2. JDK The agent needs to implement the interface , however CGLIB Don't force .
  3. stay JDK1.6 Before ,cglib Because bytecode generation technology is used , Higher than reflection efficiency , But after that, jdk There are also some optimizations , Efficiency has improved .

【 Author's brief introduction 】
Qin Huai , official account 【 Qin Huai grocery store 】 author , The road to technology is not in the moment , one's nobility lasts forever , Even slowly , Go on and on . Personal writing direction :Java The source code parsing ,JDBC,Mybatis,Spring,redis, Distributed , The finger of the sword Offer,LeetCode etc. , Write every article carefully , I don't like the title party , I don't like fancy things , Most of them write series , There's no guarantee that everything I write is right , But I promise that what I have written has been put into practice or searched for information . Omission or error , I hope you can correct me. .

  The finger of the sword Offer All the answers PDF

 2020 What did I write in 2000 ?

  Open source programming notes

Please bring the original link to reprint ,thank
Similar articles

2021-11-25

2021-11-25

2021-11-25

2021-11-25