随手记AOP如何避开BeanNotOfRequiredTypeException及CGLIB
一 . 前言
今天对 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 # populateBeanStep 2 : AutowiredAnnotationBeanPostProcessor # postProcessPropertiesStep 3 : InjectionMetadata # injectStep 4 : AutowiredAnnotationBeanPostProcessor # injectStep 5 : DefaultListableBeanFactory # doResolveDependencypublic 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; }
此处也可以看到映射关系
总结
炒币者极度深寒不止凉了,还冻上了炒币的巨大风险,正从币圈向外蔓延,加速侵蚀炒币客的金融信用,甚至危及正常生活。今年以来,国内虚拟货币监管愈发严厉,币价频繁暴跌,炒币客深陷亏损无力自拔。除了账面财富灰飞烟灭外,有人
iQOONeo5活力版评测LCD永不为奴iQOONeo5活力版在5月23日低调地上架了。对比前代iQOONeo3,新机主要升级是换了骁龙865和LPDDR5内存。发布价虽然是8128版2299元,8256版2499元,1
有哪些牌子的手机用的是OLED屏幕?OLED屏幕具有色域广,反应速率快,轻薄省电,自发光的特点,是一种价格较高的高端显示屏。在中小尺寸的OLED中,三星的AMOLED面板占有90以上的市场份额。也就是说,目前市面上绝
买iPhone是买128g的好还是买256g的好?如果资金预算充足的情况下,建议你购买256G或者512G版本,听说iPhone12将会在今晚9月9日凌晨发布,如果是5G手机,而且手机拍摄视频逐步的1080P以及4K成为了主流,甚
苹果供应链名单曝光,新增12家中国大陆厂商(原标题刚刚!苹果供应链名单曝光,这些A股公司新入列!这家被踢出果链,市值蒸发超300亿)中国的工厂,在苹果供应链中的分量无疑越来越重。近日,科技巨头苹果公司在官网公布了2021年
麒麟980还能在撑几年呢?麒麟980在2018年发布,华为余大嘴声成业界第一个7nm制成的soc,集成69亿晶体管如今现在怎么样了呢?海思麒麟980由4个a76核心(2。6hz)加4个a55的核心(1。8)
轻薄外观超级快充,荣耀Play5用户好评率达99,将成618爆款?自5月25日首销开启以来,荣耀Play5的第一批用户评价已经出炉。大家是如何评价这款新机的?小编盘点了一下荣耀商城好评率高达99苏宁好评率99京东好评率98天猫方面有4。9分的好评
华为5G被排除半年后,最想哭的是爱立信我们将失去中国5G市场点击关注,每天精彩不断!导读华为5G被排除半年后,最想哭的是爱立信我们将失去中国5G市场!众所周知,自从进入21世纪以后,移动互联网就开始快速的发展起来,如今网络已经渗透到了我们生
vivoS1正式开售升降式摄像头零界全面屏2298元手机中国新闻在预热了多日之后,vivoS1今日正式开售。vivoS1采用了和vivoX27系列相同的升降式摄像头零界全面屏,拥有光感自拍加持,在外观设计上可谓是走在了众多厂商的前列
霸权?Google阻止我写Web浏览器Metastream创始人SamuelMaddock在其博客中称过去的两年中,我一直致力于一个网页浏览器的开发,却被谷歌阻止。开源浏览器Chrome的创建者竟不允许将数字版权授予开
13个新职业公布!最亮的却不是电子竞技员近年来,伴随人工智能电子竞技等新兴产业的发展,新职业也层出不穷。今天,13个新职业正式公布了!今年初初步确定15个拟发布新职业后,4月1日,人社部发布通知,正式确认了13个新职业信