非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
相关产品推荐
相关产品推荐

