博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
记一次Spring的aop代理Mybatis的DAO所遇到的问题
阅读量:5280 次
发布时间:2019-06-14

本文共 10528 字,大约阅读时间需要 35 分钟。

由来

项目中需要实现某个订单的状态改变后然后推送给第三方的功能,由于更改状态的项目和推送的项目不是同一个项目,所以为了不改变原项目的代码,我们考虑用spring的aop来实现。

项目用的是springmvc + spring + mybatis 的架构,我们知道spring实现了两种代理方式:JDK动态代理和CGLB动态代理。所以spring对接口和类都可以实现代理。所以只需要考虑在DAO接口的相关update状态的方法上加aop就可以了。整理了下共有六个地方对订单的status做了update。所以配置如下:

 可是启动项目的时候发现,启动失败,报错信息如下:

………………………………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.$Proxy39at 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 moreCaused 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.$Proxy39at 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 moreCaused 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.$Proxy39at 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 moreCaused 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.$Proxy39at 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 moreCaused by: java.lang.IllegalArgumentException: Cannot subclass final class class com.sun.proxy.$Proxy39at 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的实现.

1 public class MapperProxy implements InvocationHandler {  2 ...  3 public static 
T newMapperProxy(Class
mapperInterface, SqlSession sqlSession) { 4 ClassLoader classLoader = mapperInterface.getClassLoader(); 5 Class
[] interfaces = new Class[]{mapperInterface}; 6 MapperProxy proxy = new MapperProxy(sqlSession); 7 return (T) Proxy.newProxyInstance(classLoader, interfaces, proxy); 8 } 9 10 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 11 if (!OBJECT_METHODS.contains(method.getName())) { 12 final Class
declaringInterface = findDeclaringInterface(proxy, method); 13 final MapperMethod mapperMethod = new MapperMethod(declaringInterface, method, sqlSession); 14 final Object result = mapperMethod.execute(args); 15 if (result == null && method.getReturnType().isPrimitive()) { 16 throw new BindingException("Mapper method '" + method.getName() + "' (" + method.getDeclaringClass() + ") attempted to return null from a method with a primitive return type (" + method.getReturnType() + ")."); 17 } 18 return result; 19 } 20 return null; 21 }

这里是用到了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

2、在类没有实现任何接口,并且没有默认构造函数的情况下,通过构造函数注入时,目前的Spring是无法实现AOP切面拦截的。 参考

 

转载于:https://www.cnblogs.com/study-everyday/p/7429298.html

你可能感兴趣的文章
【C语言】07-基本语句和运算
查看>>
实验八:程序结构与数组 4、循环实训
查看>>
Git 常用命令汇总
查看>>
Struts2学习笔记②
查看>>
Java文件上传与下载
查看>>
ftp与/usr/bin/ftp
查看>>
git创建版本库以及使用
查看>>
ANF框架小结、网络概念步骤详情及开发源码
查看>>
【模板】可持久化线段树 1(主席树)
查看>>
动态规划 洛谷P2285 [HNOI2004] 打鼹鼠
查看>>
用MPMoviePlayerController做在线音乐播放
查看>>
maven的安装
查看>>
poj 1684 Lazy Math Instructor(字符串)
查看>>
20172322 2017-2018-2《程序设计与数据结构》(上)课程总结
查看>>
什么是EDID,EDID能做什么,EDID基本介绍
查看>>
JavaScript面向对象
查看>>
matlab中生成随机数的相关知识
查看>>
let与const心智模型
查看>>
1009. Product of Polynomials (25)
查看>>
【dp 背包变形】 poj 1837
查看>>