You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

非Spring托管类使用@Cacheable(Ehcache)失效问题求助

解决非Spring托管类使用Ehcache @Cacheable不生效的问题

你遇到的核心问题是:Spring的缓存注解(如@Cacheable)依赖AOP代理生效,但直接new出来的对象不会被Spring自动生成代理——哪怕调用了initializeBean,这个方法只处理Bean的初始化回调(比如@PostConstruct),不会触发AOP代理的创建。加上你的类不能做成单例(因为有实例级变量deleteFilesAfterRead),所以无法用@Component让Spring托管,得手动处理代理生成或者改用编程式缓存。

下面给你两种可行的解决方案:

方案一:手动触发Spring AOP代理生成

通过Spring的AutowireCapableBeanFactory,在初始化Bean后手动应用BeanPostProcessor(负责生成AOP代理的组件),这样就能让@Cacheable注解生效。

修改后的ReaderUtility代码

@Component
public class ReaderUtility {

    @Autowired
    private ApplicationContext applicationContext;

    @Bean(name="readers")
    public List<IReader> determineReader() {
        // 创建实例并设置不同的属性值
        DirectoryReader directoryReader1 = new DirectoryReader();
        directoryReader1.setDeleteFilesAfterRead(true);

        DirectoryReader directoryReader2 = new DirectoryReader();
        directoryReader2.setDeleteFilesAfterRead(false);

        AutowireCapableBeanFactory beanFactory = applicationContext.getAutowireCapableBeanFactory();

        // 初始化并生成代理
        directoryReader1 = initializeAndProxy(directoryReader1, "directoryReader1", beanFactory);
        directoryReader2 = initializeAndProxy(directoryReader2, "directoryReader2", beanFactory);

        return Arrays.asList(directoryReader1, directoryReader2);
    }

    private DirectoryReader initializeAndProxy(DirectoryReader bean, String beanName, AutowireCapableBeanFactory beanFactory) {
        // 1. 自动装配Bean的依赖(如果你的类有@Autowired注解的字段)
        beanFactory.autowireBean(bean);
        // 2. 执行初始化回调(如@PostConstruct方法)
        beanFactory.initializeBean(bean, beanName);
        // 3. 应用BeanPostProcessor,生成包含缓存切面的AOP代理
        return (DirectoryReader) beanFactory.applyBeanPostProcessorsAfterInitialization(bean, beanName);
    }
}

关键说明

  • Spring的缓存注解是通过AnnotationAwareAspectJAutoProxyCreator这个BeanPostProcessor实现的,调用applyBeanPostProcessorsAfterInitialization会触发它为带有@Cacheable的方法生成代理。
  • 如果你的DirectoryReader实现了接口IReader,Spring默认生成JDK动态代理,所以建议用IReader类型引用实例(就像你代码里的List<IReader>),避免类型转换问题。如果需要用类本身引用,需要在Spring配置中开启proxy-target-class=true(使用CGLIB代理),但要确保类和方法不是final的。

方案二:改用编程式缓存(不依赖Spring AOP)

如果不想依赖Spring的代理机制,直接使用Ehcache的API手动实现缓存逻辑,灵活性更高,也完全适配你的非单例场景。

修改后的DirectoryReader代码

public class DirectoryReader implements IReader {
    private boolean deleteFilesAfterRead;
    private Cache directoryContentCache;

    // 通过构造方法注入Ehcache的CacheManager
    public DirectoryReader(CacheManager cacheManager) {
        this.directoryContentCache = cacheManager.getCache("directoryContent");
        // 确保缓存存在,避免空指针
        if (this.directoryContentCache == null) {
            throw new IllegalArgumentException("Cache 'directoryContent' not found in Ehcache configuration");
        }
    }

    @Override
    public String getContent() {
        // 自定义缓存键,这里可以根据实际场景调整(比如文件路径等)
        String cacheKey = "directory_content_key";

        // 1. 从缓存获取数据
        Cache.Entry<String, String> cacheEntry = directoryContentCache.get(cacheKey);
        if (cacheEntry != null) {
            return cacheEntry.getValue();
        }

        // 2. 读取文件内容的业务逻辑
        String content = readFileContent();

        // 3. 按照原注解的unless条件(result.length() > 0不缓存)存入缓存
        if (content.length() <= 0) {
            directoryContentCache.put(cacheKey, content);
        }

        // 4. 执行删除文件的逻辑(如果配置了的话)
        if (deleteFilesAfterRead) {
            deleteReadFile();
        }

        return content;
    }

    // 辅助方法:读取文件内容
    private String readFileContent() {
        // 你的文件读取逻辑
        return "";
    }

    // 辅助方法:删除已读取的文件
    private void deleteReadFile() {
        // 你的文件删除逻辑
    }

    // getter/setter
    public boolean isDeleteFilesAfterRead() {
        return deleteFilesAfterRead;
    }

    public void setDeleteFilesAfterRead(boolean deleteFilesAfterRead) {
        this.deleteFilesAfterRead = deleteFilesAfterRead;
    }
}

修改后的ReaderUtility代码

@Component
public class ReaderUtility {

    @Autowired
    private CacheManager cacheManager;

    @Bean(name="readers")
    public List<IReader> determineReader() {
        DirectoryReader directoryReader1 = new DirectoryReader(cacheManager);
        directoryReader1.setDeleteFilesAfterRead(true);

        DirectoryReader directoryReader2 = new DirectoryReader(cacheManager);
        directoryReader2.setDeleteFilesAfterRead(false);

        return Arrays.asList(directoryReader1, directoryReader2);
    }
}

关键说明

  • 这种方式完全脱离Spring AOP的依赖,直接操作Ehcache的API,缓存逻辑完全由你控制,适合不能被Spring代理的类。
  • 注意缓存键的设计:如果不同实例的缓存需要隔离,可以把实例的唯一标识(比如ID)加入缓存键,避免不同实例的缓存互相干扰。

方案选择建议

  • 如果你想保留注解式缓存的简洁性,不想修改业务代码,选方案一。
  • 如果你需要更灵活的缓存控制,或者不想依赖Spring的AOP机制,选方案二。

最后确认一下:确保你的Ehcache配置文件中已经正确定义了directoryContent这个缓存,否则两种方案都会失效。

内容的提问来源于stack exchange,提问作者Pasupathi Rajamanickam

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.27 09:29:48