记一次Spring aop的所遇到的问题
由来
项目中需要实现某个订单的状态改变后然后推送给第三方的功能,由于更改状态的项目和推送的项目不是同一个项目,所以为了不改变原项目的代码,我们考虑用spring的aop来实现。
项目用的是springmvc + spring + mybatis 的架构,我们知道spring实现了两种代理方式:JDK动态代理和CGLB动态代理。所以spring对接口和类都可以实现代理。所以只需要考虑在DAO接口的相关update状态的方法上加aop就可以了。整理了下共有六个地方对订单的status做了update。所以配置如下:
<!-- 声明通知类 -->
<bean id="aspectBean" class="com.info.web.service.BorrowOrderStatusAspect"></bean>
<aop:config>
<aop:aspect id="myAspect" ref="aspectBean">
<aop:pointcut
expression="execution(public * com.info.risk.dao.IRiskCreditUserDao.updateAssetsSuc(..))
|| execution(public * com.info.risk.dao.IRiskCreditUserDao.updateAssetsFail(..))
|| execution(public * com.info.web.dao.IBorrowOrderDao.updateByPrimaryKeySelective(..))
|| execution(public * com.info.web.dao.IBorrowOrderDao.updateByPrimaryKeyWithBLOBs(..))
|| execution(public * com.info.web.dao.IBorrowOrderDao.updateByPrimaryKey(..))"
id="servicePointcut" />
<aop:after-returning method="doAfter"
pointcut-ref="servicePointcut" />
</aop:aspect>
</aop:config>
可是启动项目的时候发现,启动失败,报错信息如下:
……………………………… Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'repaymentService': Injection of autowired dependencies failed; nested exception is org.springframework.beans.factory.BeanCreationException: Could not autowire field: private com.info.web.dao.IBorrowOrderDao com.info.web.service.RepaymentService.borrowOrderDao; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'IBorrowOrderDao': Post-processing of FactoryBean's singleton object failed; nested exception is org.springframework.aop.framework.AopConfigException: Could not generate CGLIB subclass of class [class com.sun.proxy.$Proxy39]: Common causes of this problem include using a final class or a non-visible class; nested exception is java.lang.IllegalArgumentException: Cannot subclass final class class com.sun.proxy.$Proxy39
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessPropertyValues(AutowiredAnnotationBeanPostProcessor.java:334)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1210)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:537)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:476)
at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:303)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:230)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:299)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:194)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.findAutowireCandidates(DefaultListableBeanFactory.java:1120)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1044)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:942)
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:533)
... 37 more
Caused by: org.springframework.beans.factory.BeanCreationException: Could not autowire field: private com.info.web.dao.IBorrowOrderDao com.info.web.service.RepaymentService.borrowOrderDao; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'IBorrowOrderDao': Post-processing of FactoryBean's singleton object failed; nested exception is org.springframework.aop.framework.AopConfigException: Could not generate CGLIB subclass of class [class com.sun.proxy.$Proxy39]: Common causes of this problem include using a final class or a non-visible class; nested exception is java.lang.IllegalArgumentException: Cannot subclass final class class com.sun.proxy.$Proxy39
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:561)
at org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:88)
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessPropertyValues(AutowiredAnnotationBeanPostProcessor.java:331)
... 48 more
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'IBorrowOrderDao': Post-processing of FactoryBean's singleton object failed; nested exception is org.springframework.aop.framework.AopConfigException: Could not generate CGLIB subclass of class [class com.sun.proxy.$Proxy39]: Common causes of this problem include using a final class or a non-visible class; nested exception is java.lang.IllegalArgumentException: Cannot subclass final class class com.sun.proxy.$Proxy39
at org.springframework.beans.factory.support.FactoryBeanRegistrySupport.getObjectFromFactoryBean(FactoryBeanRegistrySupport.java:116)
at org.springframework.beans.factory.support.AbstractBeanFactory.getObjectForBeanInstance(AbstractBeanFactory.java:1517)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:251)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:194)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.findAutowireCandidates(DefaultListableBeanFactory.java:1120)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1044)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:942)
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:533)
... 50 more
Caused by: org.springframework.aop.framework.AopConfigException: Could not generate CGLIB subclass of class [class com.sun.proxy.$Proxy39]: Common causes of this problem include using a final class or a non-visible class; nested exception is java.lang.IllegalArgumentException: Cannot subclass final class class com.sun.proxy.$Proxy39
at org.springframework.aop.framework.CglibAopProxy.getProxy(CglibAopProxy.java:212)
at org.springframework.aop.framework.ProxyFactory.getProxy(ProxyFactory.java:109)
at org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator.createProxy(AbstractAutoProxyCreator.java:447)
at org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator.wrapIfNecessary(AbstractAutoProxyCreator.java:333)
at org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator.postProcessAfterInitialization(AbstractAutoProxyCreator.java:293)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.applyBeanPostProcessorsAfterInitialization(AbstractAutowireCapableBeanFactory.java:422)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.postProcessObjectFromFactoryBean(AbstractAutowireCapableBeanFactory.java:1719)
at org.springframework.beans.factory.support.FactoryBeanRegistrySupport.getObjectFromFactoryBean(FactoryBeanRegistrySupport.java:113)
... 57 more
Caused by: java.lang.IllegalArgumentException: Cannot subclass final class class com.sun.proxy.$Proxy39
at org.springframework.cglib.proxy.Enhancer.generateClass(Enhancer.java:446)
at org.springframework.cglib.transform.TransformingClassGenerator.generateClass(TransformingClassGenerator.java:33)
at org.springframework.cglib.core.DefaultGeneratorStrategy.generate(DefaultGeneratorStrategy.java:25)
at org.springframework.cglib.core.AbstractClassGenerator.create(AbstractClassGenerator.java:216)
at org.springframework.cglib.proxy.Enhancer.createHelper(Enhancer.java:377)
at org.springframework.cglib.proxy.Enhancer.createClass(Enhancer.java:317)
at org.springframework.aop.framework.ObjenesisCglibAopProxy.createProxyClassAndInstance(ObjenesisCglibAopProxy.java:57)
at org.springframework.aop.framework.CglibAopProxy.getProxy(CglibAopProxy.java:202)
... 64 more
分析原因
从报错信息可以了解说是代理了final修饰的类。可是哪里来的final类? 原来,DAO层使用的是mybatis,可以只写接口不用写实现类。而我们项目中就是没有写实现类。但是spring也可以对接口进行代理,继续分析。
Mapper开发规则
- 在mapper.xml中将namespace设置为mapper.java的全限定名
- 将mapper.java接口的方法名和mapper.xml中statement的id保持一致。
- 将mapper.java接口的方法输入参数类型和mapper.xml中statement的parameterType保持一致
- 将mapper.java接口的方法输出 结果类型和mapper.xml中statement的resultType保持一致。
注意遵循上边四点规范!这样抛弃Dao实现类的写法: 具有更好的可扩展性,提高了灵活度。
先来说明下mybatis为何可以只写接口而不写实现类,通过mybatis源码分析可知:
mybatis通过JDK的动态代理方式,在启动加载配置文件时,根据配置mapper的xml去生成Dao的实现。session.getMapper()使用了代理,当调用一次此方法,都会产生一个代理class的instance,看看这个代理class的实现.
public class MapperProxy implements InvocationHandler {
...
public static <T> T newMapperProxy(Class<T> mapperInterface, SqlSession sqlSession) {
ClassLoader classLoader = mapperInterface.getClassLoader();
Class<?>[] interfaces = new Class[]{mapperInterface};
MapperProxy proxy = new MapperProxy(sqlSession);
return (T) Proxy.newProxyInstance(classLoader, interfaces, proxy);
} public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (!OBJECT_METHODS.contains(method.getName())) {
final Class<?> declaringInterface = findDeclaringInterface(proxy, method);
final MapperMethod mapperMethod = new MapperMethod(declaringInterface, method, sqlSession);
final Object result = mapperMethod.execute(args);
if (result == null && method.getReturnType().isPrimitive()) {
throw new BindingException("Mapper method '" + method.getName() + "' (" + method.getDeclaringClass() + ") attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
}
return result;
}
return null;
}
这里是用到了JDK的代理Proxy。 newMapperProxy()可以取得实现interfaces 的class的代理类的实例。
当执行interfaces中的方法的时候,会自动执行invoke()方法,其中public Object invoke(Object proxy, Method method, Object[] args)中 method参数就代表你要执行的方法.
MapperMethod类会使用method方法的methodName 和declaringInterface去取 sqlMapxml 取得对应的sql,也就是拿declaringInterface的类全名加上 sql-id..
因此,dao类被多次代理,第二次aop进行代理的时候拿到的是第一次代理后的对象,这个对象是个final形式的,因此报错。
解决方法:最后我在外层封装了一个service接口和接口的实现类,将dao注入到该service中,最后对该service实现aop,问题就解决了。
总结
动态代理解决问题的检查点:
- 需要AOP拦截的类是否是final的,final类不可使用CGLIB来代理。
- 是否在给BEAN配AOP的时候强制使用CGLIB,如果是则可指定proxyTargetClass属性以让spring强制代理目标类。
- 类是否被多次代理了,如果类被多次代理过,则第二次进行代理的时候拿到的是第一次代理后的对象,这个对象是个final形式的,所以会出现这个错误。
基于第三点要注意,类是否被多次代理不紧紧取决于类是否被配置了多次AOP,如果类实现了某个接口,则还要看类实现的接口是否被aop拦截过。如果类实现了接口且接口也被AOP拦截了,则很可能出现上面的错误(是否出错取决于AOP代理执行的顺序)。
spring配置aop需要注意:
1、proxy-target-class属性值决定是基于接口的还是基于类的代理被创建,启动对@Aspectj的支持 true为cglib(基于类),false为jdk代理(基于接口),不写的话默认为false。为true的话,会导致拦截不了mybatis的mapper
<aop:aspectj-autoproxy proxy-target-class="false" />
2、在类没有实现任何接口,并且没有默认构造函数的情况下,通过构造函数注入时,目前的Spring是无法实现AOP切面拦截的。 参考通过CGLIB实现AOP的浅析(顺便简单对比了一下JDK的动态代理)
记一次Spring aop的所遇到的问题的更多相关文章
- Spring AOP 随记
本周经历各种面试失败后,最后一站张建飞老大的阿里,感觉有着这般年纪不该有的垃圾履历而忧伤中,不过还是要继续加油的,毕竟他说的好,都是经历,无愧初心. 所以为了更加深入理解Spring AOP我又翻起了 ...
- spring aop使用
最近做一个数据库分离的功能,其中用到了spring aop,主要思路就是在service层的方法执行前根据注解(当然也可以根据方法名称,如果方法名称写的比较统一的话)来判断具体使用哪个库.所以想着再回 ...
- Spring AOP AspectJ
本文讲述使用AspectJ框架实现Spring AOP. 再重复一下Spring AOP中的三个概念, Advice:向程序内部注入的代码. Pointcut:注入Advice的位置,切入点,一般为某 ...
- Spring 学习——Spring AOP——AOP配置篇Advice(有参数传递)
声明通知Advice 配置方式(以前置通知为例子) 方式一 <aop:config> <aop:aspect id="ikAspectAop" ref=" ...
- 简单直白的去理解AOP,了解Spring AOP,使用 @AspectJ - 读书笔记
AOP = Aspect Oriental Programing 面向切面编程 文章里不讲AOP术语,什么连接点.切点.切面什么的,这玩意太绕,记不住也罢.旨在以简单.直白的方式理解AOP,理解Sp ...
- 浅析Spring AOP
在正常的业务流程中,往往存在着一些业务逻辑,例如安全审计.日志管理,它们存在于每一个业务中,然而却和实际的业务逻辑没有太强的关联关系. 图1 这些逻辑我们称为横切逻辑.如果把横切的逻辑代码写在业务代码 ...
- Spring aop 简单示例
简单的记录一下spring aop的一个示例 基于两种配置方式: 基于xml配置 基于注解配置 这个例子是模拟对数据库的更改操作添加事物 其实并没有添加,只是简单的输出了一下记录 首先看下整个例子的目 ...
- Hibernate 延迟加载的代理模式 和 Spring AOP的代理模式
Hibernate 延迟加载的代理模式 和 Spring AOP的代理模式 主题 概念 Hibernate 延迟加载的代理模式 Spring AOP的代理模式 区别和联系 静态代理和动态代理 概念 代 ...
- spring AOP详解〇
AOP正在成为软件开发的下一个圣杯.使用AOP,你可以将处理aspect的代码注入主程序,通常主程序的主要目的并不在于处理这些aspect.AOP可以防止代码混乱. 为了理解AOP如何做到这点,考虑一 ...
随机推荐
- icon button样式(类似windows桌面图标)
<Style x:Key="IconButton" TargetType="{x:Type Button}"> <Setter Propert ...
- background:rgba() 兼容ie8 用法
background: rgba(255,255,255,.1);//火狐,谷歌等 filter:progid:DXImageTransform.Microsoft.gradient(startCol ...
- PHP中json_encode与json_decode
一.json_encode() 对变量进行JSON编码, 语法: json_encode ( $value [, $options = 0 ] ) 注意:1.$value为要编码的值,且该函数只对UT ...
- eclipse 下使用git clone
方法一:eclipse安装好git插件后,直接import-git-project from git- clone url-输入github的网址等就可以了方法二:使用git软件,到指定的目录,右击g ...
- PHP将数据导出Excel表中(投机型)
1.简介 如何利用最简单粗糙暴力的方法将数据写入Excel文件中呢? 因为ms word和excel的文档都支持html文本格式,因此我们可以基于这个原理采用html文本格式进行数据的输出. 在htm ...
- Docker学习--->>Docker的认识,安装,及常用命令熟悉
Docker是什么? 在平常的软件开发中,会面临着开发不同的程序或服务需要不同的环境.而在该环境上开发完成后,想要在其他的环境上部署,则需要自己去重新部署,而Docker的出现使得这样的迁移变得容易. ...
- MAC上安装EndNote破解版的链接文件 以及某些安装好后有可能替换执照文件的方法
一款非常好用的论文写作软件不多形容,开整: X7 mac版本(非免破解版本)链接: 点击我 X8 mac版本(大客户版本,免破解非常好用) 点击我 X8 windows版本(大客户版本,免破解非常好用 ...
- Redis架构设计--客户端请求RedisServer时,server端持久化的部分操作
- NodeJS之queryString
前面的话 无论是前端还是后端,经常出现的应用场景是URL中参数的处理.nodeJS的queryString模块提供了一些处理 query strings 的工具.本文将详细介绍nodeJS中的quer ...
- 22. leetcode 242. Valid Anagram(由颠倒字母顺序而构成的字)
22. 242. Valid Anagram(由颠倒字母顺序而构成的字) Given two strings s and t, write a function to determine if t i ...