>>>> 😜😜😜 Github: 👉 github.com/black-ant CASE Backup: 👉 gitee.com/antblack/ca…

A. The preface

Today on the depth of the Spring to use, want to imitate the AOP to implement the corresponding agent, but triggered BeanNotOfRequiredTypeException is unusual, because Spring will check for class

Suddenly curious, I decided to investigate how AOP circumvents this validation process

2. Prior knowledge

  • AOP proxies through AopProxy
  • SpringBoot 1.5 uses JDK Proxy by default. SpringBoot 2.0 uses CGlib by default based on AopAutoConfiguration

Differences between JDK Proxy and CGLib

It’s an old question, but it’s a short one:

  • JDK Proxy: Use an interceptor (the interceptor must implement InvocationHanlder) plus reflection to generate an anonymous class that implements the Proxy interface and InvokeHandler before invoking the specific method.
  • CGLIB dynamic proxy: using the ASM open source package, load in the class file of the proxy object class, and modify its bytecode generation subclass to handle.

PS: You can configure this parameter using proxy-target-class

3. Principle exploration

The conventional method is CGLIB, so the main process is still analyzed in this way, with the supplement of the prior knowledge, the realization of the guess is due to the characteristics of CGLIB, which is actually verified.

  • Source: AutoWired verifies that the injected classes are correct when imported

3.1 Entry point for interception

  • Step 1 : AbstractAutowireCapableBeanFactory # populateBean
  • Step 2 : AutowiredAnnotationBeanPostProcessor # postProcessProperties
  • Step 3 : InjectionMetadata # inject
  • Step 4 : AutowiredAnnotationBeanPostProcessor # inject
  • Step 5 : DefaultListableBeanFactory # doResolveDependency
public Object doResolveDependency(DependencyDescriptor descriptor, @Nullable String beanName,
      @Nullable Set<String> autowiredBeanNames, @Nullable TypeConverter typeConverter) throws BeansException {

      / /... Here is the main logic

      if(autowiredBeanNames ! =null) {
         autowiredBeanNames.add(autowiredBeanName);
      }
      
      // Get the actual or proxy object for Autowired
      if (instanceCandidate instanceof Class) {
         instanceCandidate = descriptor.resolveCandidate(autowiredBeanName, type, this);
      }
      
      // Check whether the object is null
      Object result = instanceCandidate;
      if (result instanceof NullBean) {
         if (isRequired(descriptor)) {
            raiseNoMatchingBeanFound(type, descriptor.getResolvableType(), descriptor);
         }
         result = null;
      }
      
      // Core interception logic
      if(! ClassUtils.isAssignableValue(type, result)) {throw new BeanNotOfRequiredTypeException(autowiredBeanName, type, instanceCandidate.getClass());
      }
      returnresult; }}Copy the code

3.2 Judgment of interception

public static boolean isAssignable(Class
        lhsType, Class
        rhsType) {
    
    // Type judgment
    if (lhsType.isAssignableFrom(rhsType)) {
        return true;
    } else {
        Class resolvedWrapper;
        
        // Basic type special processing
        if (lhsType.isPrimitive()) {
            resolvedWrapper = (Class)primitiveWrapperTypeMap.get(rhsType);
            return lhsType == resolvedWrapper;
        } else {
            resolvedWrapper = (Class)primitiveTypeToWrapperMap.get(rhsType);
            returnresolvedWrapper ! =null&& lhsType.isAssignableFrom(resolvedWrapper); }}}Copy the code

3.3 Use of AOP

Seeing the entry point for interception, it’s time to take a look at how AOP is handled by PostProcessor

Step 1: When the field in object A is an AOP proxy class injected by @AutoWired

At this point we can see that in DefaultListableBeanFactory # doResolveDependency link will go to get the proxy class object, get the object as shown in the figure below:

// doResolveDependency gets objects
if (instanceCandidate instanceof Class) { 
   // The object is a Cglib proxy class
   instanceCandidate = descriptor.resolveCandidate(autowiredBeanName, type, this);
Copy the code

Step 2: Judge the relationship entry of the class

// doResolveDependency determines class relationships -> true
if(! ClassUtils.isAssignableValue(type, result)) {throw new BeanNotOfRequiredTypeException(autowiredBeanName, type, instanceCandidate.getClass());
}

// result.getClass()
- name=com.gang.aop.demo.service.StartService$$EnhancerBySpringCGLIB$$d673b902
Copy the code

Step 3: Determine the relational logic of the class

C- ClassUtils
public static boolean isAssignable(Class
        lhsType, Class
        rhsType) {
   
   Public native Boolean isAssignableFrom(Class
       cls);
   if (lhsType.isAssignableFrom(rhsType)) {
      return true;
   }
   / /...
}

// This is a simple extension. ChildService extends ChildService
: ------> ChildService By ParentService :false <-------
: ------> ParentService By ChildService:true <-------

Copy the code

Cglib creates objects that meet this condition: identical, or superclass or superinterface

It’s easy to go back to the previous question:

// Cause of the problem:I made a proxy by implementing postProcessorpublic class AopProxyImpl extends Sourceable {
    private Sourceable source;
}  

// After modification:
public class AopProxyImpl extends Source {
    private Sourceable source;
}

/ / by inheritance can solve BeanNotOfRequiredTypeException, understand the hard
// 
    
Copy the code

4. In-depth principle

Going back to the CGLIB creation process, it is actually quite straightforward to see the proxy object in the result of compilation:

For the basics of CGLIB, you can see the rookie documentation CGLIB(Code Generation Library) introduction and principles, written in detail

4.1 CGLIB creation Process

The role of FastClass

FastClass just numbers each method and finds the method by numbering it, so you don’t have to use reflection too often to be inefficient

CGLIB generates two Fastclasses:

  • XXXX $$FastClassByCGLIB$$XXXX: Each method in the generated proxy class is indexed
  • XXXX $$EnhancerByCGLIB$$XXXX $$FastClassByCGLIB$$XXXX: indexes all methods of our propped class including their parent

The cglib proxy is based on inheritance. Methods that are not public or final in the parent class cannot be inherited, so a fastClass of the parent class is required to call methods that the proxy cannot

There are two main methods in FastClass:

// Proxy method
public Object invoke(final int n, final Object o, final Object[] array) throws InvocationTargetException {
    final CglibService cglibService = (CglibService)o;
    switch (n) {
        case 0: {
            // The business method corresponding to the broker
            cglibService.run();
            return null;
        }
        case 1: {
            // Proxy equeals method
            return new Boolean(cglibService.equals(array[0]));
        }
        case 2: {
            // Proxy toString method
            return cglibService.toString();
        }
        case 3: {
            // Proxy the hashCode method
            return newInteger(cglibService.hashCode()); }}throw new IllegalArgumentException("Cannot find matching method/constructor");
}
    
    
// instantiate the object
public Object newInstance(final int n, final Object[] array) throws InvocationTargetException {
    switch (n) {
        case 0: {
            // Here the summary is instantiated with new
            return newCglibService(); }}throw new IllegalArgumentException("Cannot find matching method/constructor");
}
Copy the code

Enchance object

Cglib does this by overwriting the bytecode to generate the main class

final void CGLIB$run$0() {
    super.run();
}
    
public final void run(a) {
    MethodInterceptor cglib$CALLBACK_2;
    MethodInterceptor cglib$CALLBACK_0;
    if ((cglib$CALLBACK_0 = (cglib$CALLBACK_2 = this.CGLIB$CALLBACK_0)) == null) {
        CGLIB$BIND_CALLBACKS(this);
        cglib$CALLBACK_2 = (cglib$CALLBACK_0 = this.CGLIB$CALLBACK_0);
    }
    if(cglib$CALLBACK_0 ! =null) {
        // Call the interceptor object
        cglib$CALLBACK_2.intercept((Object)this, CglibService$$EnhancerByCGLIB$$7aba7860.CGLIB$run$0$Method, CglibService$$EnhancerByCGLIB$$7aba7860.CGLIB$emptyArgs, CglibService$$EnhancerByCGLIB$$7aba7860.CGLIB$run$0$Proxy);
        return;
    }
    // If there is no interceptor object, call it directly
    super.run();
}


// The interceptor that is actually called
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
    
    // The associated class is called here
    // Finally called via super.run
    Object result = proxy.invokeSuper(obj, args);     

    return result;
}


Copy the code

You can also see the mapping here

conclusion