spring aop注解失效之谜

原创 2017年08月18日 00:53:26

问题:

在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;

@Component
public 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依赖注入失效或不成功的一种情况和解决办法

题目如此啊,但不一定每个人的情况都会和我的一样,感觉这是个非常蛋疼的问题,困扰了我好几天。 我在做一个SSH集成CXF用于实现web service的服务器框架。实际上是用spring+hibern...
  • u014403008
  • u014403008
  • 2015年04月02日 19:52
  • 6006

SpringMVC中使用aop注解无效的问题

aop注解无效的问题
  • u010483289
  • u010483289
  • 2016年10月03日 00:10
  • 2675

方法上spring事物注解失效问题

1.问题: 场景一:通过spring注入对象方式调用方法时,当调用的调用的第一个方法methodA没有事物注解。那么通过methodA调用同一个类中的methodB,methodB方法上加了事物注解...
  • tensorzhl
  • tensorzhl
  • 2016年10月26日 13:59
  • 859

spring aop自动代理注解配置失效问题及原理机制整理总结

spring aop自动代理注解配置失效问题整理总结
  • gufachongyang02
  • gufachongyang02
  • 2016年02月05日 16:13
  • 4417

Android AOP之路二 Android上的注解

一、简介啥是注解,不懂的可以先看我上一篇文章。在android 里面 注解主要用来干这么几件事: 和编译器一起给你一些提示警告信息。 配合一些ide 可以更加方便快捷 安全有效的编写java代码。谷歌...
  • niubitianping
  • niubitianping
  • 2017年03月06日 13:58
  • 1820

SpringMVC中使用aop注解无效的问题

这两天学习Spring,用了SpringMVC的注解,然后配置事务和AOP,发现没用。和别的人讨论了下,大致得出了一个结论:这大约是Spring上下文的问题。 详细情况是这样的:项目引用了Sp...
  • yyd19921214
  • yyd19921214
  • 2015年07月09日 22:32
  • 5225

spring aop拦截自定义注解的切入点表达式

@within(com.cxh.study.aop.controller.UserAccessAnnotation) 表示拦截含有com.cxh.study.aop.controller.UserA...
  • cxh5060
  • cxh5060
  • 2015年04月20日 16:24
  • 7674

spring 注解aop不生效

首先基础要理解spring配置文件和springmvc配置文件 spring注解不生效可从以下几个方面找原因 1 springmvc的配置文件中只配置扫描@Controller的注解,spri...
  • zhaoyachao123
  • zhaoyachao123
  • 2017年06月28日 09:50
  • 1861

Spring MVC中AOP无效、不起作用,解决方案之一

最近由于项目原因,在Spring+SpringMVC+Mybatis的环境中,增加对controller或是service的日志监控,在网上搜了半天,按照SpringAop的配置出不了结果,在按照网上...
  • tianjun2012
  • tianjun2012
  • 2015年08月20日 17:06
  • 14294

利用springaop导致基于实现类注入失败的原因及解决方案

http://www.iteye.com/topic/1131444
  • yrpting
  • yrpting
  • 2016年12月02日 17:58
  • 1556
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:spring aop注解失效之谜
举报原因:
原因补充:

(最多只允许输入30个字)