如何使用 Java Config 手动添加 Spring CacheInterceptor



我正在尝试研究如何将缓存添加到第三方 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.InitializingBeano.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()方法。

此外,CacheInterceptorBeanFactoryAware的,需要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。

相关内容

  • 没有找到相关文章

最新更新