随手记 : 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
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。