随手记 : AOP 如何避开 BeanNotOfRequiredTypeException 及 CGLIB

2,584 阅读4分钟

首先分享之前的所有文章 , 欢迎点赞收藏转发三连下次一定 >>>> 😜😜😜
文章合集 : 🎁 juejin.cn/post/694164…
Github : 👉 github.com/black-ant
CASE 备份 : 👉 gitee.com/antblack/ca…

一 . 前言

今天对 Spring 进行深度使用的时候 , 想仿照 AOP 去实现对应的代理 , 但是却触发了 BeanNotOfRequiredTypeException 异常 , 原因是因为 Spring 会进行类的校验

于是突然产生了好奇 , 决定研究一下 , AOP 是通过什么方式避开这个校验过程

二 . 前置知识

  • AOP 通过 AopProxy 进行代理
  • SpringBoot 1.5 默认使用 JDK Proxy , SpringBoot 2.0 基于自动装配(AopAutoConfiguration)的配置 , 默认使用 CGlib

JDK Proxy 和 CGLib 的区别

老生常谈的问题 , 问了完整性(凑字数) , 还是简单列一下 :

  • JDK Proxy : 利用拦截器(拦截器必须实现InvocationHanlder)加上反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理。
  • CGLIB动态代理:利用ASM开源包,对代理对象类的class文件加载进来,通过修改其字节码生成子类来处理。

PS : 通过 proxy-target-class 可以进行配置

三 . 原理探索

常规方式是 CGLIB , 所以主流程还是通过这种方式分析 , 有了前置知识的补充 , 实现猜测是由于 CGLIB 的特性 , 实际上被校验出来.

  • 源头 :autowired 导入得时候会校验注入的类是否正确

3.1 拦截的入口

  • 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 {

      //............ 以下是主要逻辑 

      if (autowiredBeanNames != null) {
         autowiredBeanNames.add(autowiredBeanName);
      }
      
      // 获取 Autowired 的实际对象或者代理对象
      if (instanceCandidate instanceof Class) {
         instanceCandidate = descriptor.resolveCandidate(autowiredBeanName, type, this);
      }
      
      // 判断该对象是否为null
      Object result = instanceCandidate;
      if (result instanceof NullBean) {
         if (isRequired(descriptor)) {
            raiseNoMatchingBeanFound(type, descriptor.getResolvableType(), descriptor);
         }
         result = null;
      }
      
      // 核心拦截逻辑
      if (!ClassUtils.isAssignableValue(type, result)) {
         throw new BeanNotOfRequiredTypeException(autowiredBeanName, type, instanceCandidate.getClass());
      }
      return result;
   }
}

3.2 拦截的判断

public static boolean isAssignable(Class<?> lhsType, Class<?> rhsType) {
    
    // 类型判断
    if (lhsType.isAssignableFrom(rhsType)) {
        return true;
    } else {
        Class resolvedWrapper;
        
        // 基本类型特殊处理
        if (lhsType.isPrimitive()) {
            resolvedWrapper = (Class)primitiveWrapperTypeMap.get(rhsType);
            return lhsType == resolvedWrapper;
        } else {
            resolvedWrapper = (Class)primitiveTypeToWrapperMap.get(rhsType);
            return resolvedWrapper != null && lhsType.isAssignableFrom(resolvedWrapper);
        }
    }
}

3.3 AOP 的使用

看到了拦截的入口 , 那就得看看 AOP 中是如何通过 PostProcessor 进行处理的了 , 首先看一下 PostProcessor 链表

image.png

Step 1 : 当对象 A 中字段是 @Autowired 注入的 AOP 代理类时

此时我们可以发现 , 在 DefaultListableBeanFactory # doResolveDependency 环节会去获取该代理类的对象 , 拿到的对象如下图所示 :

// doResolveDependency 中获取对象环节
if (instanceCandidate instanceof Class) { 
   // 此时拿到的对象就是一个 cglib 代理类
   instanceCandidate = descriptor.resolveCandidate(autowiredBeanName, type, this);

image.png

Step 2 : 判断类的关系入口

// doResolveDependency 中判断类的关系 -> true
if (!ClassUtils.isAssignableValue(type, result)) {
   throw new BeanNotOfRequiredTypeException(autowiredBeanName, type, instanceCandidate.getClass());
}

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

Step 3 : 判断类的关系逻辑

C- ClassUtils
public static boolean isAssignable(Class<?> lhsType, Class<?> rhsType) {
   
   // 核心语句 , native 方法 -> public native boolean isAssignableFrom(Class<?> cls);
   if (lhsType.isAssignableFrom(rhsType)) {
      return true;
   }
   //.........
}

// 这里简单做了一个继承类 , ChildService extends ChildService
: ------> ChildService By ParentService :false <-------
: ------> ParentService By ChildService:true <-------

由此可见 cglib 创建的对象满足该条件 : 相同 , 或者是超类或者超接口

这里回过头看之前的问题 , 就很简单了 :

// 问题原因 : 
我通过实现 postProcessor 去做了一个代理 
public class AopProxyImpl extends Sourceable {
    private Sourceable source;
}  

// 修改后 : 
public class AopProxyImpl extends Source {
    private Sourceable source;
}

// 通过继承即可解决 BeanNotOfRequiredTypeException ,弄懂了就没什么难度了
// 
    

四 . 深入原理

那么继续回顾下 CGLIB 的创建过程 , 实际上在编译的结果上是可以很直观的看到代理的对象的 :

image.png

关于 CGLIB 的基础 , 可以看看菜鸟的文档 CGLIB(Code Generation Library) 介绍与原理 , 写的很详细

4.1 CGLIB 的创建过程

FastClass 的作用

FastClass 就是给每个方法编号,通过编号找到方法,这样可以避免频繁使用反射导致效率比较低

CGLIB 会生成2个 fastClass :

  • xxxx$$FastClassByCGLIB$$xxxx :为生成的代理类中的每个方法建立了索引
  • xxxx$$EnhancerByCGLIB$$xxxx$$FastClassByCGLIB$$xxxx : 为我们被代理类的所有方法包含其父类的方法建立了索引

原因 : cglib代理基于继承实现,父类中非public、final的方法无法被继承,所以需要一个父类的fastclass来调用代理不到的方法

FastClass 中有2个主要的方法 :

// 代理方法
public Object invoke(final int n, final Object o, final Object[] array) throws InvocationTargetException {
    final CglibService cglibService = (CglibService)o;
    switch (n) {
        case 0: {
            // 代理对应的业务方法
            cglibService.run();
            return null;
        }
        case 1: {
            // 代理 equeals 方法
            return new Boolean(cglibService.equals(array[0]));
        }
        case 2: {
            // 代理 toString 方法
            return cglibService.toString();
        }
        case 3: {
            // 代理 hashCode 方法 
            return new Integer(cglibService.hashCode());
        }
    }
    throw new IllegalArgumentException("Cannot find matching method/constructor");
}
    
    
// 实例化对象 
public Object newInstance(final int n, final Object[] array) throws InvocationTargetException {
    switch (n) {
        case 0: {
            // 此处总结通过 new 进行了实例化
            return new CglibService();
        }
    }
    throw new IllegalArgumentException("Cannot find matching method/constructor");
}

enchance 对象

之前了解到 , cglib 通过重写字节码生成主类达到代理的目的 , 这里来看一下 , 原方法被改写成什么样了

final void CGLIB$run$0() {
    super.run();
}
    
public final void run() {
    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) {
        // 调用拦截器对象
        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;
    }
    // 没有拦截器对象 , 则直接调用
    super.run();
}


// 实际被调用的拦截器
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
    
    // 这里会调用关联类
    // 最终通过 super.run 调用
    Object result = proxy.invokeSuper(obj, args);     

    return result;
}


此处也可以看到映射关系 image.png

总结

cglib.jpg