java - Spring Boot EnableCaching 和 Cacheable 注释不起作用

标签 java spring caching spring-boot redis

我想缓存主数据到Redis。

所以,我写了这些代码。

@Configuration
@EnableCaching
public class AppConfig extends CachingConfigurerSupport {
    @Bean
    @Autowired
    public CacheManager cacheManager(RedisTemplate<Object, Object> redisTemplate) {
        RedisCacheManager cacheManager = new RedisCacheManager(redisTemplate);
        Map<String, Long> expires = new HashMap<>();
        expires.put("cache.day", new Long(24 * 60 * 60));
        cacheManager.setExpires(expires);
        return cacheManager;
    }
}

package com.taisho.artifacts.repository.impl;

import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Repository;

import java.util.ArrayList;
import java.util.List;

@Repository
public class TestRepository {

    @Cacheable(value = "cache.day", key = "'cache.test'")
    public List<String> getTest() {
        List<String> list = new ArrayList<>();
        list.add("test");
        list.add("sample");
        return list;
    }

    public void printTest() {
        System.out.println(getTest());
    }
}

和 ymlfile

spring:
  redis:
    host: 127.0.0.1
    port: 26379

但是,缓存不工作...

每当我调用 printTest 方法时,“getTest”方法就会执行。 Redis没有数据... 我的代码有什么问题?

注意事项

SpringBoot版本为1.4.0

依赖是

compile("org.springframework.boot:spring-boot-starter-web:${springBootVersion}")
compile("org.springframework.boot:spring-boot-starter-data-redis:${springBootVersion}")
compile("org.springframework.boot:spring-boot-autoconfigure:${springBootVersion}")

最佳答案

TL;博士

Spring AOP 是基于代理的,因此当您从 printTest() 方法调用 getTest() 方法时,getTest() 方法将在 this 引用上调用,而不是能够执行缓存操作的代理版本。通常这是一种设计味道,您最好重新考虑您当前的设计。但作为解决方法,您可以使用 AopContext:

public void printTest() {
    System.out.println(((TestRepository) AopContext.currentProxy()).getTest());
}

详细解答

假设您有一个客户端代码可以通过依赖注入(inject)访问TestRepository:

@Component
class SomeUnfortunateClient {
    // I know field injection is evil!
    @Autowired TestRepository testRepository;

    void youAreGoingToBeSurprised() {
        testRepository.printTest();
    }
}

TestRepository 是一个 Spring 管理的存储库,为了向 TestRepository 添加额外的功能,例如缓存,Spring会为其创建一个Proxy。这意味着对 testRepository 对象引用的方法调用将是对代理的调用,因此代理将能够委托(delegate)给与该特定方法调用相关的所有拦截器(通知) .在您的情况下,这些建议将检查缓存条目是否存在。

然而,一旦调用最终到达目标对象,在本例中为 TestRepository 引用,它可能对其自身进行的任何方法调用,例如 System.out.println( getTest());,将针对 this 引用而不是代理调用。 这意味着自调用不会导致与方法调用相关的建议有机会执行。

作为Spring Documentation状态:

Okay, so what is to be done about this? The best approach (the term best is used loosely here) is to refactor your code such that the self-invocation does not happen. For sure, this does entail some work on your part, but it is the best, least-invasive approach. The next approach is absolutely horrendous, and I am almost reticent to point it out precisely because it is so horrendous. You can (choke!) totally tie the logic within your class to Spring AOP by doing this:

public class SimplePojo implements Pojo {

    public void foo() {
        // this works, but... gah!
        ((Pojo) AopContext.currentProxy()).bar();
    }

    public void bar() {
        // some logic...
    }
}

This totally couples your code to Spring AOP, and it makes the class itself aware of the fact that it is being used in an AOP context, which flies in the face of AOP. It also requires some additional configuration when the proxy is being created.

进一步阅读

这个答案主要基于 Spring 文档,因此对于(甚至!)更详细的讨论,您绝对应该查看 Understanding AOP proxies Spring 文档中的部分。

关于java - Spring Boot EnableCaching 和 Cacheable 注释不起作用,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/41534493/

相关文章:

java - 在提交之前验证属性/YAML 文件中的值

java - java对象转json

java - Spring事务回滚 `UPDATE`和 `INSERT`

javascript - HInclude Internet Explorer 上的缓存问题

java - 正则表达式 "either a semicolon or the end of the string"

java - JWT 无法将访问 token 转换为 JSON

spring - 无法使用 Cucumber 生成 Spring 休息文档

spring - 使用aspectj在Spring Boot中重试死锁事务

c# - 给定单例缓存模式的缺点

caching - ASNetworkImageNode缓存