文档章节

Spring aop 内部调用、自调用不生效问题与解决方案

筱龙缘
 筱龙缘
发布于 2016/07/19 10:38
字数 702
阅读 2090
收藏 30

场景

使用 spring cache 框架时 服务类内部方法调用并不触发缓存动作

演示

[[@Service](http://my.oschina.net/service)](http://my.oschina.net/service)
public class CacheTestService {


    @Cacheable(value = "test_cache", key = "'method'")
    public String method() {
        System.out.println("method");
        return method1(1) + "_" +
                method2(2) + "_" +
                method3(3) + "_" +
                method4(4)
                ;
    }

    @Cacheable(value = "test_cache", key = "'method'+#i")
    public String method1(int i) {
        System.out.println("method1");
        return "method1_" + i;
    }

    @Cacheable(value = "test_cache", key = "'method'+#i")
    protected String method2(int i) {
        System.out.println("method2");
        return "method2_" + i;
    }

    @Cacheable(value = "test_cache", key = "'method'+#i")
    String method3(int i) {
        System.out.println("method3");
        return "method3_" + i;
    }

    @Cacheable(value = "test_cache", key = "'method'+#i")
    private String method4(int i) {
        System.out.println("method4");
        return "method4_" + i;
    }
}

查看redis


127.0.0.1:6379> keys cache:*
1) "cache://test_cache:method"

可以看到 method1、method2、method3、method4 方法的缓存并没有生效

原因:

注意和限制 基于 proxy 的 spring aop 带来的内部调用问题 上面介绍过 spring cache 的原理,即它是基于动态生成的 proxy 代理机制来对方法的调用进行切面,这里关键点是对象的引用问题,如果对象的方法是内部调用 (即 this 引用)而不是外部引用,则会导致 proxy 失效,那么我们的切面就失效,也就是说上面定义的各种注释包括 @Cacheable、@CachePut 和 @CacheEvict 都会失效

解决方案


public interface BeanSelfAware {
    void setSelf(Object proxyBean);  
}  
@Component
public class InjectBeanSelfProcessor implements BeanPostProcessor, ApplicationContextAware {
    private ApplicationContext context;
    //① 注入ApplicationContext  
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.context = applicationContext;  
    }  
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {  
        if(!(bean instanceof BeanSelfAware)) { //② 如果Bean没有实现BeanSelfAware标识接口 跳过  
            return bean;  
        }  
        if(AopUtils.isAopProxy(bean)) { //③ 如果当前对象是AOP代理对象,直接注入
            ((BeanSelfAware) bean).setSelf(bean);  
        } else {  
            //④ 如果当前对象不是AOP代理,则通过context.getBean(beanName)获取代理对象并注入  
            //此种方式不适合解决prototype Bean的代理对象注入  
            ((BeanSelfAware)bean).setSelf(context.getBean(beanName));  
        }  
        return bean;  
    }  
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {  
        return bean;  
    }  
}  

服务类:

@Service
public class CacheTestService implements BeanSelfAware {


    @Cacheable(value = "test_cache", key = "'method'")
    public String method() {
        System.out.println("method");
        return proxySelf.method1(1) + "_" +
                proxySelf.method2(2) + "_" +
                proxySelf.method3(3) + "_" +
                proxySelf.method4(4)
                ;
    }

    @Cacheable(value = "test_cache", key = "'method'+#i")
    public String method1(int i) {
        System.out.println("method1");
        return "method1_" + i;
    }

    @Cacheable(value = "test_cache", key = "'method'+#i")
    protected String method2(int i) {
        System.out.println("method2");
        return "method2_" + i;
    }

    @Cacheable(value = "test_cache", key = "'method'+#i")
    String method3(int i) {
        System.out.println("method3");
        return "method3_" + i;
    }

    @Cacheable(value = "test_cache", key = "'method'+#i")
    private String method4(int i) {
        System.out.println("method4");
        return "method4_" + i;
    }


    CacheTestService proxySelf;

    @Override
    public void setSelf(Object proxyBean) {
        this.proxySelf = (CacheTestService) proxyBean;
    }
}

再次查看 redis


127.0.0.1:6379> keys cache:*
1) "cache://test_cache:method1"
2) "cache://test_cache:method"

方法 method1 缓存动作成功执行,但是以protected、默认、private修饰的方法签名并没有生效 因此 此方案还需注意使用public修饰被调用方法

...

骚年,你以为这样就完了? 不 还有坑!

启用 aop 时 请使用

    <!-- 用这个 -->
    <aop:aspectj-autoproxy proxy-target-class="true" /> 
   <!-- 不要用这个 -->
    <!--<aop:config proxy-target-class="true"  />-->

over

参考资料

© 著作权归作者所有

筱龙缘

筱龙缘

粉丝 20
博文 29
码字总数 13048
作品 1
南京
程序员
私信 提问
加载中

评论(5)

筱龙缘
筱龙缘 博主

引用来自“Gillian_Male”的评论

还有,直接用currentProxy()先拿到代理不就好了么?虽然有可能拿不到代理对象
所以咯

这方面详细可以看 http://jinnianshilongnian.iteye.com 大神的
筱龙缘
筱龙缘 博主

引用来自“Gillian_Male”的评论

启用 aop 时 请使用

<!-- 用这个 -->
<aop:aspectj-autoproxy proxy-target-class="true" />
<!-- 不要用这个 -->
<!--<aop:config proxy-target-class="true" />-->

为啥呢?
坑啊 掉进一次
我 spring 4.2 亲测
Gillian_Male
Gillian_Male
还有,直接用currentProxy()先拿到代理不就好了么?虽然有可能拿不到代理对象
Gillian_Male
Gillian_Male
启用 aop 时 请使用

<!-- 用这个 -->
<aop:aspectj-autoproxy proxy-target-class="true" />
<!-- 不要用这个 -->
<!--<aop:config proxy-target-class="true" />-->

为啥呢?
引鸩怼孑
引鸩怼孑
骚年 果汁很厉害
Spring @Async/@Transactional 失效的原因及解决方案

本来上一篇说要写关于AOP的实践的,但是这周刚好遇到了使用@Transactional和@Async的不生效的问题,这篇就进行一个回顾和记录。 之前提到实现AOP的方法有动态代理、编译期,类加载期织入等等...

陶源0111
2018/06/25
0
0
Spring基础系列-Spring事务不生效的问题与循环依赖问题

原创作品,可以转载,但是请标注出处地址:https://www.cnblogs.com/V1haoge/p/9476550.html 一、提出问题   不知道你是否遇到过这样的情况,在ssm框架中开发web引用,或者使用springboot开...

唯一浩哥
2018/08/14
0
0
spring aop类内部调用不拦截原因及解决方案

spring对应java web开发的同学来说,都不陌生,其中事务@Transactional在service层更是常常使用。 aop类内部调用不拦截原因 细心的同学也许早就发现当service中的某个没标注@Transactional的...

乾坤刀
2018/07/19
0
0
Spring AOP中自我调用的问题

前几天在做项目的时候同事说,在使用AOP进行拦截的时候发现有些方法有时候能输出拦截的日志有时候不输出拦截的日志。发现在单独调用这些方法的时候是有日志输出,在被同一个类中的方法调用的...

木叶之荣
2017/05/21
0
0
Spring进阶(六):can not find proxy: set exposeproxy property on advised to make it available

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/sunhuaqiang1/article/details/82594886 Spring进阶(六):can not find proxy: set exposeproxy property ...

孙华强
2018/09/10
0
0

没有更多内容

加载失败,请刷新页面

加载更多

OSChina 周日乱弹 —— 我,小小编辑,食人族酋长

Osc乱弹歌单(2019)请戳(这里) 【今日歌曲】 @宇辰OSC :分享娃娃的单曲《飘洋过海来看你》: #今日歌曲推荐# 《飘洋过海来看你》- 娃娃 手机党少年们想听歌,请使劲儿戳(这里) @宇辰OSC...

小小编辑
今天
446
10
MongoDB系列-- SpringBoot 中对 MongoDB 的 基本操作

SpringBoot 中对 MongoDB 的 基本操作 Database 库的创建 首先 在MongoDB 操作客户端 Robo 3T 中 创建数据库: 增加用户User: 创建 Collections 集合(类似mysql 中的 表): 后面我们大部分都...

TcWong
今天
4
0
spring cloud

一、从面试题入手 1.1、什么事微服务 1.2、微服务之间如何独立通讯的 1.3、springCloud和Dubbo有哪些区别 1.通信机制:DUbbo基于RPC远程过程调用;微服务cloud基于http restFUL API 1.4、spr...

榴莲黑芝麻糊
今天
3
0
Executor线程池原理与源码解读

线程池为线程生命周期的开销和资源不足问题提供了解决方 案。通过对多个任务重用线程,线程创建的开销被分摊到了多个任务上。 线程实现方式 Thread、Runnable、Callable //实现Runnable接口的...

小强的进阶之路
昨天
7
0
maven 环境隔离

解决问题 即 在 resource 文件夹下面 ,新增对应的资源配置文件夹,对应 开发,测试,生产的不同的配置内容 <resources> <resource> <directory>src/main/resources.${deplo......

之渊
昨天
8
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部