我正在尝试弄清楚如何在第三方Java类上为方法调用添加缓存。我正在为我的应用程序使用 Spring Boot 。
我试图让缓存工作,我想出了这个课程。
package test;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.interceptor.CacheOperation;
import org.springframework.cache.interceptor.CacheProxyFactoryBean;
import org.springframework.cache.interceptor.CacheableOperation;
import org.springframework.cache.interceptor.NameMatchCacheOperationSource;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.Collection;
import java.util.Date;
import java.util.HashSet;
@SpringBootApplication
@EnableCaching
@Configuration
public class MyApp {
public static void main(String[] args) {
ConfigurableApplicationContext context = SpringApplication.run(MyApp.class, args);
Greeter greeter = context.getBean(Greeter.class);
System.out.println(new Date() + " : " + greeter.getGreeting("Bob"));
System.out.println(new Date() + " : " +greeter.getGreeting("Fred"));
System.out.println(new Date() + " : " +greeter.getGreeting("Bob"));
System.out.println(new Date() + " : " +greeter.getGreeting("Fred"));
System.out.println(new Date() + " : " +greeter.getGreeting("Bob"));
System.out.println(new Date() + " : " +greeter.getGreeting("Fred"));
}
@Bean
public Greeter greeter() {
final NameMatchCacheOperationSource nameMatchCacheOperationSource = new NameMatchCacheOperationSource();
Collection<CacheOperation> cacheOperations = new HashSet<CacheOperation>();
cacheOperations.add(new CacheableOperation.Builder().build());
nameMatchCacheOperationSource.addCacheMethod("*", cacheOperations);
CacheProxyFactoryBean cacheProxyFactoryBean = new CacheProxyFactoryBean();
cacheProxyFactoryBean.setTarget(new MySlowGreeter());
cacheProxyFactoryBean.setProxyInterfaces(new Class[] {Greeter.class});
cacheProxyFactoryBean.setCacheOperationSources(nameMatchCacheOperationSource);
cacheProxyFactoryBean.afterPropertiesSet();
return (Greeter) cacheProxyFactoryBean.getObject();
}
interface Greeter {
String getGreeting(String name);
}
class MySlowGreeter implements Greeter {
public String getGreeting(String name) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "Hello " + name;
}
}
}
希望我能够在我的 Spring 配置中创建一个bean,它包含对Greeter.getGreeting(..)
的调用,并返回缓存的结果(如果它们存在)。但是没有缓存。
有什么想法吗?
答案 0 :(得分:2)
好的,我有更多的信息给你。但首先,我想解决上面代码中的一些问题。
1)第一个问题涉及您在&#34; greeter&#34;中使用o.s.cache.interceptor.CacheProxyFactoryBean
。您的应用@Bean
类的@Configuration
定义(即&#34; MyApp&#34;)。
任何时候你使用 Spring FactoryBeans
中的一个(例如CacheProxyFactoryBean
)或实现自己的,你从@Bean
方法返回的是FactoryBean
本身,不 FactoryBean
的产品。因此,您将返回return factoryBean.getObject()
,而不是FactoryBean
,而不是{...}}。
@Bean
GreeterFactoryBean greeter() {
GreeterFactoryBean factoryBean = new GreeterFactoryBean();
factoryBean.set...
return factoryBean;
}
此处GreeterFactoryBean
实施o.s.beans.factory.FactoryBean
。
正如 Spring Reference Documentation指出的那样, Spring 容器知道返回FactoryBean
的产品(例如a [ Singleton ] Greeter
实例)和不 FactoryBean
本身,作为&#34;定义&#34;此@Bean
方法的 Spring 容器中的bean。如果未使用@Bean
明确定义(例如@Bean
),则bean的名称将是@Bean("Greeter")
方法的名称。
如果FactoryBean
还实现 Spring 生命周期回调接口(例如o.s.beans.factory.InitializingBean
或o.s.beans.factory.DisposableBean
等), Spring < / em>容器将知道在&#34;适当的&#34;中调用那些生命周期回调。时间,在 Spring 容器的初始化过程中。
因此,没有必要在&#34; greeter&#34;内调用CacheProxyFactoryBean.afterPropertiesSet()
或CacheProxyFactoryBean.getObject()
。 @Bean
定义。这样做实际上违反了 Spring 容器的初始化合同,你可能会遇到过早的&#34;初始化&#34;问题,特别是如果提供的FactoryBean
实现了其他 Spring 容器接口(例如BeanClassLoaderAware
或EnvironmentAware
,依此类推)。
小心!
2)其次,这不是你的例子的问题/问题,而是要注意的事情。你在这篇SO帖子中说过你正试图添加&#34;缓存&#34;对第三方图书馆类的行为。
您上面使用的方法仅适用于您自己在应用程序中实例化第三方类(例如Greeter
)的情况。
但是,如果第三方库或框架代表您实例化该类,则由于配置库/框架(例如,考虑JDBC驱动程序和Hibernate),您将无法在此类中引入缓存行为申请,除非您诉诸Load-Time Weaving(LTW)。阅读文档了解更多详情。
好的,进入解决方案。
我写了一个测试来重现这个问题并更好地理解 Spring Framework 中发生的事情。您可以找到我完成的测试here。
TestConfigurationOne
实际上与您采用的方法相同
以编程方式创建缓存代理,基于我上面讨论的内容进行修改,并且还解决我认为核心 Spring Framework 中的 bug (注意:我正在使用我的测试中的 Spring Framework 5.0.1.RELEASE
。
为了使CacheProxyFactoryBean
的配置方法有效,我需要extend the CacheProxyFactoryBean
class。除了CacheProxyFactoryBean
o.s.cache.interceptor.CacheProxyFactoryBean
之外,我还需要extending implement和SmartInitializingSingleton
interface,原因很明显。有关BeanFactoryAware
interface的信息,请参见9。
在内部, Spring Framework&#39> <{em> o.s.cache.interceptor.CacheInterceptor
是CacheInterceptor
的{{3}}。它也进入&#34;初始化&#34;这个CacheInterceptor
个实例,complete implementation和making use。但是,这样做不完成初始化,因为SmartInitializingSingleton
也是间接here CacheAspectSupport
接口here SmartInitializingSingleton
。如果从未调用CacheProxyFactoryBean
已实施implements,则extending永远不会被触发,任何可缓存的操作CacheInterceptor.afterSingletonsInstantiated()
method都会导致initialized
bit(因此,忽略任何引入的缓存行为)。
这就是我将测试类中的CacheInterceptor
扩展到will not be cached&#34; mainInterceptor&#34;的确切原因。 (即SmartCacheProxyFactoryBean
),然后在 Spring 容器初始化阶段的适当时刻调用cacheable operation being invoked every single time,这就是我的SmartInitializingSingleton
扩展名的原因实现CacheInterceptor.afterSingletonsInstantiated()
,委托CacheInterceptor
方法。
此外,BeanFactoryAware
是BeanFactory
和capture Spring BeanFactory
来执行其功能,因此我检查了这个& #34; mainInterceptor&#34;并适当地设置TestConfigurationTwo
,afterSingletonsInstantiated()
method。
我建议另一种方法是使用CacheInterceptor
。
在此配置中,我直接requires Spring AOP建议(即@Bean
),从Greeter
定义方法返回&## 34; cacheInterceptor&#34;,它允许 Spring 容器适当地调用生命周期回调。
然后,我进入第三方课程here中的configuring(即&#34; cacheInterceptor
&#34;)。
你应该小心传递从&#34; greeter
&#34;创建的bean。 bean定义到&#34; cacheInterceptor
&#34; bean定义,use this Advice。如果您要调用&#34; greeter
&#34; bean中的bean定义方法&#34; {{1}}&#34; bean定义,像许多用户不恰当做(cache proxy creation),然后你会放弃 Spring 容器生命周期回调!不要这样做!解释原因like so。
另外,要阅读有关代理编程创建的更多信息,请阅读for example。
好的,关于它的内容。
我提供的测试课程(即&#34; here&#34;)。如果您有任何后续问题,请随意使用它并告诉我。
希望这有帮助!
干杯!
答案 1 :(得分:0)
您可以简单地创建一个Adapter
/ Wrapper
类,该类委托您要通过缓存启用的基础第三方类类型。然后,将缓存行为添加到您的应用程序Adapter
/ Wrapper
类型中。
这比尝试确定适当的AOP切入点来拦截您想要通过缓存建议的第三方类上的方法要容易得多。当然,如果您没有引用要引入缓存的对象,这种方法将无法工作,在这种情况下,您将不得不求助于AOP。
从您的示例中,我得到的印象是您可能引用了此第三方对象实例???
如果没有,请澄清,然后我可以帮助您完成AOP。