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

时间:2017-12-06 01:40:43

标签: java spring spring-boot spring-cache spring-config

我正在尝试弄清楚如何在第三方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(..)的调用,并返回缓存的结果(如果它们存在)。但是没有缓存。

有什么想法吗?

2 个答案:

答案 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.InitializingBeano.s.beans.factory.DisposableBean等), Spring < / em>容器将知道在&#34;适当的&#34;中调用那些生命周期回调。时间,在 Spring 容器的初始化过程中。

因此,没有必要在&#34; greeter&#34;内调用CacheProxyFactoryBean.afterPropertiesSet()CacheProxyFactoryBean.getObject()@Bean定义。这样做实际上违反了 Spring 容器的初始化合同,你可能会遇到过早的&#34;初始化&#34;问题,特别是如果提供的FactoryBean实现了其他 Spring 容器接口(例如BeanClassLoaderAwareEnvironmentAware,依此类推)。

小心!

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 implementSmartInitializingSingleton interface,原因很明显。有关BeanFactoryAware interface的信息,请参见9

在内部, Spring Framework&#39> <{em> o.s.cache.interceptor.CacheInterceptorCacheInterceptor的{​​{3}}。它也进入&#34;初始化&#34;这个CacheInterceptor个实例,complete implementationmaking 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方法。

此外,BeanFactoryAwareBeanFactorycapture Spring BeanFactory来执行其功能,因此我检查了这个& #34; mainInterceptor&#34;并适当地设置TestConfigurationTwoafterSingletonsInstantiated() 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。