我正在尝试研究如何将缓存添加到第三方 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(..)
的调用并返回缓存的结果(如果存在)。但是,不会发生缓存。
有什么想法吗?
好的,我有更多信息要告诉你。 但首先,我想解决上面代码的一些问题。
1)第一个问题涉及您在应用程序@Configuration
类(即"MyApp")的"欢迎员"@Bean
定义中使用o.s.cache.interceptor.CacheProxyFactoryBean
。
任何时候您使用SpringFactoryBeans
中的 1 个(例如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 的参考文档所指出的那样,Spring 容器知道返回FactoryBean
的乘积(例如 [Singleton]Greeter
实例),而不是FactoryBean
本身,作为Spring容器中此@Bean
方法的"定义"bean。如果没有用@Bean
显式定义,则 bean 的名称将是@Bean
方法的名称(例如@Bean("Greeter")
)。
如果FactoryBean
还实现了Spring 的生命周期回调接口(例如o.s.beans.factory.InitializingBean
或o.s.beans.factory.DisposableBean
等),Spring容器将知道在Spring容器初始化过程中的"适当"时间调用这些生命周期回调。
因此,没有必要在"greeter"@Bean
定义中调用CacheProxyFactoryBean.afterPropertiesSet()
或CacheProxyFactoryBean.getObject()
。 这样做实际上违反了 Spring 容器的初始化合约,您可能会遇到过早的"初始化"问题,特别是如果提供的FactoryBean
实现了其他Spring容器接口(例如BeanClassLoaderAware
,或EnvironmentAware
,等等)。
小心!
2)其次,这与其说是你的例子的问题/问题,不如说是需要注意的事情。 您在这篇文章中指出,您正在尝试将"缓存"行为添加到第三方库类中。
您上面使用的方法仅适用于您能够实例化第三方类的情况(例如Greeter
) 在您的应用程序中自己。
但是,如果第三方库或框架代表您实例化该类,由于配置了库/框架(例如,考虑 JDBC 驱动程序和休眠),您将失去在应用程序中向此类引入缓存行为的能力,除非您求助于加载时编织(LTW)。 阅读文档了解更多详情。
好了,进入解决方案。
我写了一个测试来重现这个问题,并更好地了解Spring 框架中发生的事情。 你可以在这里找到我完成的测试。
TestConfigurationOne
实际上与您采用的方法相同 以编程方式创建缓存代理,并根据我上面讨论的内容进行修改,并解决我认为核心 Spring 框架中的错误(注意:我在测试中使用了Spring 框架5.0.1.RELEASE
)。
为了使您的配置方法与CacheProxyFactoryBean
起作用,我需要扩展CacheProxyFactoryBean
类。 除了扩展CacheProxyFactoryBean
之外,我还需要实现SmartInitializingSingleton
接口和BeanFactoryAware
接口,其原因将在一会儿变得明显。 有关完整实现,请参见 9。
在内部,Spring Frameworko.s.cache.interceptor.CacheProxyFactoryBean
正在使用o.s.cache.interceptor.CacheInterceptor
。 它还在这里和这里"初始化"这个CacheInterceptor
实例。 但是,这并不能完成初始化,因为CacheInterceptor
还通过扩展CacheAspectSupport
间接实现了SmartInitializingSingleton
接口。 如果从不调用SmartInitializingSingleton
实现的CacheInterceptor.afterSingletonsInstantiated()
方法,则永远不会跳闸initialized
位,并且不会缓存任何可缓存的操作,从而导致每次都调用可缓存操作(因此,忽略任何引入的缓存行为)。
这就是为什么我在测试类中扩展CacheProxyFactoryBean
以捕获"mainInterceptor"(即CacheInterceptor
),然后在Spring容器初始化阶段的适当时刻调用afterSingletonsInstantiated()
方法的确切原因,这就是为什么我的SmartCacheProxyFactoryBean
扩展实现SmartInitializingSingleton
,委托给CacheInterceptor.afterSingletonsInstantiated()
方法。
此外,CacheInterceptor
是BeanFactoryAware
的,需要SpringBeanFactory
来执行其功能,因此我在这里检查这个"mainInterceptor"并适当地设置BeanFactory
的原因。
我推荐的另一种方法是使用TestConfigurationTwo
.
在此配置中,我直接配置 SpringAOP 建议(即CacheInterceptor
),从@Bean
定义方法"cacheInterceptor"返回它,该方法允许Spring容器适当地调用生命周期回调。
然后,我继续在第三方类的缓存代理创建中使用此建议(即"Greeter
")。
你应该小心地将创建的 bean 从"cacheInterceptor
"bean 定义传递到"greeter
"bean 定义,就像这样。 如果你要从你的bean"greeter
"bean定义中调用"cacheInterceptor
"bean定义方法,就像许多用户不恰当地做的那样(例如),那么你将放弃Spring容器生命周期回调! 别这样! 这里解释了其中的原因。
此外,要阅读有关代理编程创建的更多信息,您应该阅读此内容。
好的,关于覆盖它。
我提供的测试类(即"ProgrammaticCachingWithSpringIntegrationTests
")。 请随时使用它,如果您有任何后续问题,请告诉我。
希望这有帮助!
干杯!
您可以简单地创建一个Adapter
/Wrapper
类,该类委托给要使用缓存启用的基础第三方类类型。 然后,改为将缓存行为添加到应用程序Adapter
/Wrapper
类型。
这比尝试确定适当的AOP切入点来拦截要建议缓存的第三方类上的方法要容易得多。 当然,如果您没有对要引入缓存的对象的引用,则此方法将不起作用,在这种情况下,您将不得不求助于 AOP。
从您的示例中,我的印象是您可能引用了这个第三方对象实例???
如果没有,请澄清,然后我可以帮助您处理AOP。