由来

项目中需要实现某个订单的状态改变后然后推送给第三方的功能,由于更改状态的项目和推送的项目不是同一个项目,所以为了不改变原项目的代码,我们考虑用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开发规则

  1. 在mapper.xml中将namespace设置为mapper.java的全限定名
  2. 将mapper.java接口的方法名和mapper.xml中statement的id保持一致。
  3. 将mapper.java接口的方法输入参数类型和mapper.xml中statement的parameterType保持一致
  4. 将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,问题就解决了。

总结

动态代理解决问题的检查点:

  1. 需要AOP拦截的类是否是final的,final类不可使用CGLIB来代理。
  2. 是否在给BEAN配AOP的时候强制使用CGLIB,如果是则可指定proxyTargetClass属性以让spring强制代理目标类。
  3. 类是否被多次代理了,如果类被多次代理过,则第二次进行代理的时候拿到的是第一次代理后的对象,这个对象是个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的所遇到的问题的更多相关文章

  1. Spring AOP 随记

    本周经历各种面试失败后,最后一站张建飞老大的阿里,感觉有着这般年纪不该有的垃圾履历而忧伤中,不过还是要继续加油的,毕竟他说的好,都是经历,无愧初心. 所以为了更加深入理解Spring AOP我又翻起了 ...

  2. spring aop使用

    最近做一个数据库分离的功能,其中用到了spring aop,主要思路就是在service层的方法执行前根据注解(当然也可以根据方法名称,如果方法名称写的比较统一的话)来判断具体使用哪个库.所以想着再回 ...

  3. Spring AOP AspectJ

    本文讲述使用AspectJ框架实现Spring AOP. 再重复一下Spring AOP中的三个概念, Advice:向程序内部注入的代码. Pointcut:注入Advice的位置,切入点,一般为某 ...

  4. Spring 学习——Spring AOP——AOP配置篇Advice(有参数传递)

    声明通知Advice 配置方式(以前置通知为例子) 方式一 <aop:config> <aop:aspect id="ikAspectAop" ref=" ...

  5. 简单直白的去理解AOP,了解Spring AOP,使用 @AspectJ - 读书笔记

    AOP = Aspect Oriental Programing  面向切面编程 文章里不讲AOP术语,什么连接点.切点.切面什么的,这玩意太绕,记不住也罢.旨在以简单.直白的方式理解AOP,理解Sp ...

  6. 浅析Spring AOP

    在正常的业务流程中,往往存在着一些业务逻辑,例如安全审计.日志管理,它们存在于每一个业务中,然而却和实际的业务逻辑没有太强的关联关系. 图1 这些逻辑我们称为横切逻辑.如果把横切的逻辑代码写在业务代码 ...

  7. Spring aop 简单示例

    简单的记录一下spring aop的一个示例 基于两种配置方式: 基于xml配置 基于注解配置 这个例子是模拟对数据库的更改操作添加事物 其实并没有添加,只是简单的输出了一下记录 首先看下整个例子的目 ...

  8. Hibernate 延迟加载的代理模式 和 Spring AOP的代理模式

    Hibernate 延迟加载的代理模式 和 Spring AOP的代理模式 主题 概念 Hibernate 延迟加载的代理模式 Spring AOP的代理模式 区别和联系 静态代理和动态代理 概念 代 ...

  9. spring AOP详解〇

    AOP正在成为软件开发的下一个圣杯.使用AOP,你可以将处理aspect的代码注入主程序,通常主程序的主要目的并不在于处理这些aspect.AOP可以防止代码混乱. 为了理解AOP如何做到这点,考虑一 ...

随机推荐

  1. Codeforces_617E: XOR and Favorite Number(莫队算法)

    题目链接 题意大致是说,给出一个长为n(n<=1e5)的数组,给定一个k(k<=1e6),给出m(m<=1e5)个询问,每组询问中回答 从a_l到a_r有多少个连续的子序列满足异或和 ...

  2. mybaits错误解决:There is no getter for property named 'parentId ' in class 'java.lang.String'

    在使用mybaitis传参数的时候,如果仅传入一个类型为String的参数,那么在 xml文件中应该使用_parameter来代替参数名. 比如mapper中如下方法,只有一个String值 publ ...

  3. css阴影,边框,渐变,背景

    一.box-shadow: 参数1,参数2 阴影位置偏移量 参数3 模糊半径 参数4 扩展半径 负数 0 默认值 正数 参数5 阴影的颜色 参数6 设置内阴影 inset 默认是外阴影 多个阴影使用, ...

  4. CGLib方式对接口实现代理

    JDK实现动态代理需要实现类通过接口定义业务方法,对于没有接口的类,如何实现动态代理呢,这就需要CGLib了.CGLib采用了非常底层的字节码技术,其原理是通过字节码技术为一个类创建子类,并在子类中采 ...

  5. LoadRunner性能测试-LoadRunner工具安装

    Loadrunner11安装 1,在网上下载loadrunner11安装包 (百度可以下载安装包) 2,打开安装文件,点击setup.exe进行安装: 3,win10系统可能会出现如下问题: 提示管理 ...

  6. 如何将1234通过java变成4321,下面介绍几种办法。

    //1 StringBuffer的反转 public static void main(String[] args) { int a=1234; StringBuffer sb = new Strin ...

  7. Scala 令人着迷的类设计

    尽管 Scala 和 Java 有很多相同的地方, 但是在类的声明, 构造, 访问控制上存在很大的差异, 通过本文你也能看到相比较 Java 很多啰嗦的模板代码, Scala 更加的简洁, 使用 Sc ...

  8. python--代码统计(进阶版)

    在上一篇的随笔中发表了代码统计小程序,但是发表后,我发现,以前写的代码怎么办 写了那么多,怎么就从0开始了呢,,,,我还是个孩子啊,不能这么残忍 于是,代码统计进阶版:统计当前目录下所有指定文件类型的 ...

  9. 同步读取各平台StreamingAssets文件

    //Path为StreamingAssets文件后面的路径 public AssetBundle GetstreamingAssets(string Path) { #if UNITY_EDITOR ...

  10. NodeJS基本使用简介

    Node.js 1.Nvm Node的版本管理工具,用于切换node的版本 一.下载nvm,放在纯英文路径 二.用命令行打开nvm.exe 三.打开setting.txt,其中有四个配置. Root: ...