spring aop注解失效之谜
来源:互联网 发布:项目数据分析师含金量 编辑:程序博客网 时间:2024/05/20 00:39
问题:
在spring 中使用 @Transactional 、 @Cacheable 或 自定义 AOP 注解时,会发现个问题:
在对象内部的方法中调用该对象的其他使用aop机制的方法,被调用方法的aop注解失效。
这句话可能说的有点拗口,那么我们来看几个 aop 失效的例子吧
- 事物失效
public class TicketService{ //买火车票 @Transactional public void buyTrainTicket(Ticket ticket){ System.out.println("买到了火车票"); try { //在同一个类中的方法,调用 aop注解(@Transactional 注解也是aop 注解) 的方法,会使aop 注解失效. //此时如果 sendMessage()的发送消息动作失败抛出异常,“消息存入数据库“动作不会回滚。 sendMessage(); } catch (Exception e) { logger.warn("发送消息异常"); } } //买到车票后发送消息 @Transactional public void sendMessage(){ System.out.println("消息存入数据库"); System.out.println("执行发送消息动作"); }}
- 缓存失效
//使用缓存,查询时先查询缓存,缓存中查询不到时,调用数据库。@Cacheable(value = "User")public User getUserById(Integer id){ System.out.println("查询数据库"); return UserDao.getUserById(id);}//在同一个类中的方法,调用 aop注解(@Cacheable 注解也是aop 注解) 的方法,会使aop 注解失效 public User getUser(Integer id){ //此时注解失效,getUserById 方法不会去缓存中查询数据,会直接查询数据库。 return getUserById(id);}
还有自定义AOP 注解 在同一个类中的方法级别调用也会导致 aop 注解失效。
原因
好了失效的例子已经看过了。那么为什么会产生这种情况呢?我门来探究一下原因吧。
spring AOP 使用Java动态代理和 cglib 代理 来创建AOP代理,没有接口的类 使用cglib 代理。关于 spring aop 的java动态代理原理,请看这片博客:利用java 的动态代理模拟spring的AOP
熟悉一下 aop 的原理注意看m.invoke(target, args);
部分(我门讨论的问题实际上就是m中调用同类的其他方法)。
我门知道当方法被代理时,其实是 动态生成了一个代理对象,代理对象去执行 invoke方法,在调用被代理对象的方法的时候执行了一些其他的动作。
所以当在被代理对象的方法中调用被代理对象的其他方法时。其实是没有用代理调用,是用了被代理对象本身调用的。
- 例如事物的例子:
当我门调用buyTrainTicket(Ticket ticket)方法时,spring 的动态代理已经帮我们动态生成了一个代理的对象,暂且我就叫他 $TicketService1
。
所以调用buyTrainTicket(Ticket ticket) 方法实际上是代理对象$TicketService1
调用的。$TicketService1.buyTrainTicket(Ticket ticket)
但是在buyTrainTicket 方法内调用同一个类的另外一个注解方法sendMessage()时,实际上是this.sendMessage()
这个this 指的是TicketService 对象,并不是$TicketService1
代理对象,没有走代理。所以 注解失效。
解决
通过分析原因我门知道注解失效是因为 执行方法时没有走代理,所以在同一个类的方法中调用其他注解方法,应该使用代理对象 调用。
spring 解决方案
//通过AopContext.currentProxy()获取当前代理对象。AopContext.currentProxy();
修改范例
修改XML 新增如下语句;先开启cglib代理,开启 exposeProxy = true,暴露代理对象
<aop:aspectj-autoproxy proxy-target-class="true" expose-proxy="true"/>
public class TicketService{ //买火车票 @Transactional public void buyTrainTicket(Ticket ticket){ System.out.println("买到了火车票"); try {//通过代理对象去调用sendMessage()方法 (TicketService)AopContext.currentProxy().sendMessage(); } catch (Exception e) { logger.warn("发送消息异常"); } } @Transactional public void sendMessage(){ System.out.println("消息存入数据库"); System.out.println("执行发送消息动作"); }}
当然最好的解决方案就是避免在对象内部调用
springboot 解决方案
springboot 我用的是1.3.0但是我发现 @EnableAspectJAutoProxy的 expose-proxy=”true” 方法都不存在,在spring4.3 以后注解才有 exposeProxy() 方法(spring 源码传送门 )。所以使用 AopContext 获取代理对象的方法就流产了。下面是 EnableAspectJAutoProxy 的源码。在
@Target(ElementType.TYPE)@Retention(RetentionPolicy.RUNTIME)@Documented@Import(AspectJAutoProxyRegistrar.class)public @interface EnableAspectJAutoProxy { /** * Indicate whether subclass-based (CGLIB) proxies are to be created as opposed * to standard Java interface-based proxies. The default is {@code false}. */ boolean proxyTargetClass() default false;}
既然在AopContext取不到,我门只好去ApplicationContext 中取我门的代理对象了。
- 新建获取代理对象的工具类SpringUtil
import org.springframework.beans.BeansException;import org.springframework.context.ApplicationContext;import org.springframework.context.ApplicationContextAware;import org.springframework.stereotype.Component;@Componentpublic class SpringUtil implements ApplicationContextAware { private static ApplicationContext applicationContext; @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { if (SpringUtil.applicationContext == null) { SpringUtil.applicationContext = applicationContext; } } //获取applicationContext public static ApplicationContext getApplicationContext() { return applicationContext; } //通过name获取 Bean. public static Object getBean(String name){ return getApplicationContext().getBean(name); } //通过class获取Bean. public static <T> T getBean(Class<T> clazz){ return getApplicationContext().getBean(clazz); } //通过name,以及Clazz返回指定的Bean public static <T> T getBean(String name,Class<T> clazz){ return getApplicationContext().getBean(name, clazz); }}
- 修改范例
public class TicketService{ //买火车票 @Transactional public void buyTrainTicket(Ticket ticket){ System.out.println("买到了火车票"); try {//通过代理对象去调用sendMessage()方法 SpringUtil.getBean(this.getClass()).sendMessage(); } catch (Exception e) { logger.warn("发送消息异常"); } } @Transactional public void sendMessage(){ System.out.println("消息存入数据库"); System.out.println("执行发送消息动作"); }}
- spring aop注解失效之谜
- Spring aop之注解方式
- Spring AOP之通过注解编写AOP
- spring注解时AOP失效,XML配置<bean>正常
- Spring AOP注解为什么失效?90%Java程序员不知道
- spring aop 失效问题
- Spring Aop实例之AspectJ注解配置
- Spring-AOP之aspectj注解方式
- 【Spring五】AOP之使用注解配置
- Spring Aop实例之AspectJ注解配置
- Spring学习之使用注解配置AOP
- spring之基于aspectj注解aop使用
- Spring 之AOP 使用@AspectJ注解方式
- AOP之Spring使用注解方式实现AOP(入门)
- Spring注解事务失效
- spring aop之对象内部方法间的嵌套失效
- spring aop之对象内部方法间的嵌套失效
- spring(AOP) 注解实现aop
- 用mybatis编写DAO
- LeetCode 67. Add Binary
- linux下使用udpsocket时遇到的问题
- Android apk去广告
- matlab指派问题 线性规划
- spring aop注解失效之谜
- LeetCode字符串(一)
- DTW Barycenter Averaging(DBA)——平均序列求法
- Cygwin安装步骤
- 安卓android破解方法
- Linux学习进阶路线图
- Webstorm安装、破解、使用
- 函数的调用过程(栈帧)
- oracle函数row_number() over ( )的简单使用