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

共 3666字,需浏览 8分钟

 ·

2021-10-12 13:49

一 . 前言

今天对 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 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 链表

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

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

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

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 的创建过程 , 实际上在编译的结果上是可以很直观的看到代理的对象的 :

关于 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;
}


复制代码

此处也可以看到映射关系

总结


作者:AntBlack
链接:https://juejin.cn/post/7015855422049353741
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。



浏览 30
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报