首页>>后端>>java->AOP如何避开BeanNotOfRequiredTypeException及CGLIB

AOP如何避开BeanNotOfRequiredTypeException及CGLIB

时间:2023-11-30 本站 点击:1

一 . 前言

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

publicObjectdoResolveDependency(DependencyDescriptordescriptor,@NullableStringbeanName,@NullableSet<String>autowiredBeanNames,@NullableTypeConvertertypeConverter)throwsBeansException{//............以下是主要逻辑if(autowiredBeanNames!=null){autowiredBeanNames.add(autowiredBeanName);}//获取Autowired的实际对象或者代理对象if(instanceCandidateinstanceofClass){instanceCandidate=descriptor.resolveCandidate(autowiredBeanName,type,this);}//判断该对象是否为nullObjectresult=instanceCandidate;if(resultinstanceofNullBean){if(isRequired(descriptor)){raiseNoMatchingBeanFound(type,descriptor.getResolvableType(),descriptor);}result=null;}//核心拦截逻辑if(!ClassUtils.isAssignableValue(type,result)){thrownewBeanNotOfRequiredTypeException(autowiredBeanName,type,instanceCandidate.getClass());}returnresult;}}

3.2 拦截的判断

publicstaticbooleanisAssignable(Class<?>lhsType,Class<?>rhsType){//类型判断if(lhsType.isAssignableFrom(rhsType)){returntrue;}else{ClassresolvedWrapper;//基本类型特殊处理if(lhsType.isPrimitive()){resolvedWrapper=(Class)primitiveWrapperTypeMap.get(rhsType);returnlhsType==resolvedWrapper;}else{resolvedWrapper=(Class)primitiveTypeToWrapperMap.get(rhsType);returnresolvedWrapper!=null&&lhsType.isAssignableFrom(resolvedWrapper);}}}

3.3 AOP 的使用

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

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

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

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

Step 2 : 判断类的关系入口

//doResolveDependency中判断类的关系->trueif(!ClassUtils.isAssignableValue(type,result)){thrownewBeanNotOfRequiredTypeException(autowiredBeanName,type,instanceCandidate.getClass());}//result.getClass()-name=com.gang.aop.demo.service.StartService$$EnhancerBySpringCGLIB$$d673b902

Step 3 : 判断类的关系逻辑

C-ClassUtilspublicstaticbooleanisAssignable(Class<?>lhsType,Class<?>rhsType){//核心语句,native方法->publicnativebooleanisAssignableFrom(Class<?>cls);if(lhsType.isAssignableFrom(rhsType)){returntrue;}//.........}//这里简单做了一个继承类,ChildServiceextendsChildService:------>ChildServiceByParentService:false<-------:------>ParentServiceByChildService:true<-------

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

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

//问题原因:我通过实现postProcessor去做了一个代理publicclassAopProxyImplextendsSourceable{privateSourceablesource;}//修改后:publicclassAopProxyImplextendsSource{privateSourceablesource;}//通过继承即可解决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个主要的方法 :

//代理方法publicObjectinvoke(finalintn,finalObjecto,finalObject[]array)throwsInvocationTargetException{finalCglibServicecglibService=(CglibService)o;switch(n){case0:{//代理对应的业务方法cglibService.run();returnnull;}case1:{//代理equeals方法returnnewBoolean(cglibService.equals(array[0]));}case2:{//代理toString方法returncglibService.toString();}case3:{//代理hashCode方法returnnewInteger(cglibService.hashCode());}}thrownewIllegalArgumentException("Cannotfindmatchingmethod/constructor");}//实例化对象publicObjectnewInstance(finalintn,finalObject[]array)throwsInvocationTargetException{switch(n){case0:{//此处总结通过new进行了实例化returnnewCglibService();}}thrownewIllegalArgumentException("Cannotfindmatchingmethod/constructor");}

enchance 对象

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

finalvoidCGLIB$run$0(){super.run();}publicfinalvoidrun(){MethodInterceptorcglib$CALLBACK_2;MethodInterceptorcglib$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();}//实际被调用的拦截器publicObjectintercept(Objectobj,Methodmethod,Object[]args,MethodProxyproxy)throwsThrowable{//这里会调用关联类//最终通过super.run调用Objectresult=proxy.invokeSuper(obj,args);returnresult;}

此处也可以看到映射关系

总结


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:/java/4958.html