由来

项目中需要实现某个订单的状态改变后然后推送给第三方的功能,由于更改状态的项目和推送的项目不是同一个项目,所以为了不改变原项目的代码,我们考虑用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代理Mybatis的DAO所遇到的问题的更多相关文章

  1. Spring框架——AOP代理

    我们知道AOP代理指的就是设计模式中的代理模式.一种是静态代理,高效,但是代码量偏大:另一种就是动态代理,动态代理又分为SDK下的动态代理,还有CGLIB的动态代理.Spring AOP说是实现了AO ...

  2. Spring温故而知新 – AOP代理

    AOP的概念 AOP:Aspect-Oriented Programming(面向切面编程),维基百科的解释如下:Aspect是一种新的模块化机制,用来描述分散在对象.类或者函数中的横切关注点,从关注 ...

  3. Spring的AOP与代理

    spring 支持两种注入方式: setter/constructor 支持多种配置方式: xml/java5注解/java类配置 支持两种事务管理: 声明性/编程性 实际上上述方式只有一个就能保证系 ...

  4. Spring Aop(十一)——编程式的创建Aop代理之ProxyFactory

    转发地址:https://www.iteye.com/blog/elim-2397388 编程式的创建Aop代理之ProxyFactory Spring Aop是基于代理的,ProxyFactory是 ...

  5. Spring通知类型及使用ProxyFactoryBean创建AOP代理

    Spring 通知类型 通过前面的学习可以知道,通知(Advice)其实就是对目标切入点进行增强的内容,Spring AOP 为通知(Advice)提供了 org.aopalliance.aop.Ad ...

  6. 吴裕雄--天生自然JAVA SPRING框架开发学习笔记:Spring通知类型及使用ProxyFactoryBean创建AOP代理

    通知(Advice)其实就是对目标切入点进行增强的内容,Spring AOP 为通知(Advice)提供了 org.aopalliance.aop.Advice 接口. Spring 通知按照在目标类 ...

  7. mybatis源码学习--spring+mybatis注解方式为什么mybatis的dao接口不需要实现类

    相信大家在刚开始学习mybatis注解方式,或者spring+mybatis注解方式的时候,一定会有一个疑问,为什么mybatis的dao接口只需要一个接口,不需要实现类,就可以正常使用,笔者最开始的 ...

  8. Spring:AOP

    摘要 本文内容为我在网上搜集Spring AOP资料的汇总.摘抄. AOP是一种编程思想,其对不同对象进行了横向的抽象,将不同对象的.和主流程无关的公共逻辑抽象出来以方便维护.AOP的实现基础为AOP ...

  9. Spring中AOP相关的API及源码解析

    Spring中AOP相关的API及源码解析 本系列文章: 读源码,我们可以从第一行读起 你知道Spring是怎么解析配置类的吗? 配置类为什么要添加@Configuration注解? 谈谈Spring ...

随机推荐

  1. SQLSERVER中的元数据锁

    SQLSERVER中的元数据锁 网上对于元数据锁的资料真的非常少 元数据锁一般会出现在DDL语句里 下面列出数据库引擎可以锁定的资源 资源 说明 RID 用于锁定堆(heap)中的某一行 KEY 用于 ...

  2. 使用 Azure 资源管理器向 Windows VM 应用策略

    通过使用策略,组织可以在整个企业中强制实施各种约定和规则. 强制实施所需行为有助于消除风险,同时为组织的成功做出贡献. 本文介绍如何使用 Azure 资源管理器策略,为组织中的虚拟机定义所需的行为. ...

  3. sql server 2008 身份验证失败 18456

    双击打开后加上  ;-m  然后以管理员方式  打开 SQLSERVER 2008  就可以已window身份登录  不过还没有完 右键  属性  =>安全性 更改为 sql server 和 ...

  4. javascript中注册和移除事件的4种方式

    对于html中的一些元素注册事件的方式有多种 第一种: 复制代码代码如下: <script> function test() { alert("OK"); } < ...

  5. python给邮箱发送消息

    首先要用到两个模块  并且大同你的发送邮箱smtp 最开始测试没打通了好久 smtplib是提供邮箱smtp服务, email是提供你发送消息的格式之类服务 import smtplib from e ...

  6. orcl 中upper()和lower()和initcap()的用法

    upper(字符串 | 列):输入的字符串变为大写返回: 将 bqh4表里的zym字段信息中含有字母的全部转成大写的方法: select * from bqh4 select upper(zym) f ...

  7. MySQL 在各种程序语音的连接字符串(转)

    一.MySQL Connector/ODBC 2.50 (MyODBC 2.50)连接方式 1.本地数据库连接Driver={MySQL};Server=localhost;Option=16834; ...

  8. RLP(转发注明出处)

    目录 RLP序列化 什么是序列化? 为什么要序列化? RLP序列化处理的两项数据 RLP序列化采取的5项规则: 利用python写的RLP 实际中的使用是个怎么样子? RLP分析 参考目录 @ RLP ...

  9. array_map 巧替 foreach

    1.array_reduce( $arr , callable $callback ) 使用回调函数迭代地将数组简化为单一的值. 其中$arr 为输入数组,$callback($result , $v ...

  10. Linux平台安装Oracle11gR2数据库

    1. 数据库安装先决条件 1.1 认证的操作系统检查确认 o RHEL4,OEL4 - update 7 or greater o RHEL5,OEL5 - 5.2 or greater o RHEL ...