随手记 AOP 如何避开 BeanNotOfRequir

首先分享之前的所有文章 , 欢迎点赞收藏转发三连下次一定 >>>> 😜😜😜

文章合集 : 🎁 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
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
java复制代码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 拦截的判断

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
java复制代码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 环节会去获取该代理类的对象 ,
拿到的对象如下图所示 :

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

image.png

Step 2 : 判断类的关系入口

1
2
3
4
5
6
7
java复制代码// 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 : 判断类的关系逻辑

1
2
3
4
5
6
7
8
9
10
11
12
13
java复制代码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 创建的对象满足该条件 : 相同 , 或者是超类或者超接口

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

1
2
3
4
5
6
7
8
9
10
11
12
13
java复制代码// 问题原因 : 
我通过实现 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个主要的方法 :

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
33
34
35
36
java复制代码// 代理方法
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 通过重写字节码生成主类达到代理的目的 , 这里来看一下 , 原方法被改写成什么样了

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
java复制代码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

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

0%