隨手記 : AOP 如何避開 BeanNotOfRequiredTypeException 及 CGLIB
一 . 前言
今天對 Spring 進(jìn)行深度使用的時(shí)候 , 想仿照 AOP 去實(shí)現(xiàn)對應(yīng)的代理 , 但是卻觸發(fā)了 BeanNotOfRequiredTypeException 異常 , 原因是因?yàn)?Spring 會(huì)進(jìn)行類的校驗(yàn)
于是突然產(chǎn)生了好奇 , 決定研究一下 , AOP 是通過什么方式避開這個(gè)校驗(yàn)過程
二 . 前置知識(shí)
AOP 通過 AopProxy 進(jìn)行代理
SpringBoot 1.5 默認(rèn)使用 JDK Proxy , SpringBoot 2.0 基于自動(dòng)裝配(AopAutoConfiguration)的配置 , 默認(rèn)使用 CGlib
JDK Proxy 和 CGLib 的區(qū)別
老生常談的問題 , 問了完整性(湊字?jǐn)?shù)) , 還是簡單列一下 :
JDK Proxy : 利用攔截器(攔截器必須實(shí)現(xiàn)InvocationHanlder)加上反射機(jī)制生成一個(gè)實(shí)現(xiàn)代理接口的匿名類,在調(diào)用具體方法前調(diào)用InvokeHandler來處理。
CGLIB動(dòng)態(tài)代理:利用ASM開源包,對代理對象類的class文件加載進(jìn)來,通過修改其字節(jié)碼生成子類來處理。
PS : 通過 proxy-target-class 可以進(jìn)行配置
三 . 原理探索
常規(guī)方式是 CGLIB , 所以主流程還是通過這種方式分析 , 有了前置知識(shí)的補(bǔ)充 , 實(shí)現(xiàn)猜測是由于 CGLIB 的特性 , 實(shí)際上被校驗(yàn)出來.
源頭 :autowired 導(dǎo)入得時(shí)候會(huì)校驗(yàn)注入的類是否正確
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 的實(shí)際對象或者代理對象
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;
}
}
復(fù)制代碼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);
}
}
}
復(fù)制代碼3.3 AOP 的使用
看到了攔截的入口 , 那就得看看 AOP 中是如何通過 PostProcessor 進(jìn)行處理的了 , 首先看一下 PostProcessor 鏈表

Step 1 : 當(dāng)對象 A 中字段是 @Autowired 注入的 AOP 代理類時(shí)
此時(shí)我們可以發(fā)現(xiàn) , 在 DefaultListableBeanFactory # doResolveDependency 環(huán)節(jié)會(huì)去獲取該代理類的對象 , 拿到的對象如下圖所示 :
// doResolveDependency 中獲取對象環(huán)節(jié)
if (instanceCandidate instanceof Class) {
// 此時(shí)拿到的對象就是一個(gè) cglib 代理類
instanceCandidate = descriptor.resolveCandidate(autowiredBeanName, type, this);
復(fù)制代碼
Step 2 : 判斷類的關(guān)系入口
// doResolveDependency 中判斷類的關(guān)系 -> true
if (!ClassUtils.isAssignableValue(type, result)) {
throw new BeanNotOfRequiredTypeException(autowiredBeanName, type, instanceCandidate.getClass());
}
// result.getClass()
- name=com.gang.aop.demo.service.StartService$$EnhancerBySpringCGLIB$$d673b902
復(fù)制代碼Step 3 : 判斷類的關(guān)系邏輯
C- ClassUtils
public static boolean isAssignable(Class> lhsType, Class> rhsType) {
// 核心語句 , native 方法 -> public native boolean isAssignableFrom(Class> cls);
if (lhsType.isAssignableFrom(rhsType)) {
return true;
}
//.........
}
// 這里簡單做了一個(gè)繼承類 , ChildService extends ChildService
: ------> ChildService By ParentService :false <-------
: ------> ParentService By ChildService:true <-------
復(fù)制代碼由此可見 cglib 創(chuàng)建的對象滿足該條件 : 相同 , 或者是超類或者超接口
這里回過頭看之前的問題 , 就很簡單了 :
// 問題原因 :
我通過實(shí)現(xiàn) postProcessor 去做了一個(gè)代理
public class AopProxyImpl extends Sourceable {
private Sourceable source;
}
// 修改后 :
public class AopProxyImpl extends Source {
private Sourceable source;
}
// 通過繼承即可解決 BeanNotOfRequiredTypeException ,弄懂了就沒什么難度了
//
復(fù)制代碼四 . 深入原理
那么繼續(xù)回顧下 CGLIB 的創(chuàng)建過程 , 實(shí)際上在編譯的結(jié)果上是可以很直觀的看到代理的對象的 :

關(guān)于 CGLIB 的基礎(chǔ) , 可以看看菜鳥的文檔 CGLIB(Code Generation Library) 介紹與原理 , 寫的很詳細(xì)
4.1 CGLIB 的創(chuàng)建過程
FastClass 的作用
FastClass 就是給每個(gè)方法編號(hào),通過編號(hào)找到方法,這樣可以避免頻繁使用反射導(dǎo)致效率比較低
CGLIB 會(huì)生成2個(gè) fastClass :
xxxx$$FastClassByCGLIB$$xxxx ?:為生成的代理類中的每個(gè)方法建立了索引
xxxx$$EnhancerByCGLIB$$xxxx$$FastClassByCGLIB$$xxxx : 為我們被代理類的所有方法包含其父類的方法建立了索引
原因 : cglib代理基于繼承實(shí)現(xiàn),父類中非public、final的方法無法被繼承,所以需要一個(gè)父類的fastclass來調(diào)用代理不到的方法
FastClass 中有2個(gè)主要的方法 :
// 代理方法
public Object invoke(final int n, final Object o, final Object[] array) throws InvocationTargetException {
final CglibService cglibService = (CglibService)o;
switch (n) {
case 0: {
// 代理對應(yīng)的業(yè)務(wù)方法
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");
}
// 實(shí)例化對象
public Object newInstance(final int n, final Object[] array) throws InvocationTargetException {
switch (n) {
case 0: {
// 此處總結(jié)通過 new 進(jìn)行了實(shí)例化
return new CglibService();
}
}
throw new IllegalArgumentException("Cannot find matching method/constructor");
}
復(fù)制代碼enchance 對象
之前了解到 , cglib 通過重寫字節(jié)碼生成主類達(dá)到代理的目的 , 這里來看一下 , 原方法被改寫成什么樣了
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) {
// 調(diào)用攔截器對象
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;
}
// 沒有攔截器對象 , 則直接調(diào)用
super.run();
}
// 實(shí)際被調(diào)用的攔截器
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
// 這里會(huì)調(diào)用關(guān)聯(lián)類
// 最終通過 super.run 調(diào)用
Object result = proxy.invokeSuper(obj, args);
return result;
}
復(fù)制代碼此處也可以看到映射關(guān)系
總結(jié)

作者:AntBlack
鏈接:https://juejin.cn/post/7015855422049353741
來源:稀土掘金
著作權(quán)歸作者所有。商業(yè)轉(zhuǎn)載請聯(lián)系作者獲得授權(quán),非商業(yè)轉(zhuǎn)載請注明出處。
