【问题标题】:How can I manually add a Spring CacheInterceptor using Java Config?如何使用 Java Config 手动添加 Spring CacheInterceptor?
【发布时间】:2017-12-06 01:40:42
【问题描述】:

我正在尝试研究如何将缓存添加到第三方 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(..) 的调用并返回缓存的结果(如果存在)。但是没有进行缓存。

有什么想法吗?

【问题讨论】:

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


    【解决方案1】:

    好的,我有更多信息给你。但首先,我想解决您上面代码的一些问题。

    1) 第一个问题涉及您在应用程序@Configuration 类(即“MyApp”)的“greeter”@Bean 定义中使用o.s.cache.interceptor.CacheProxyFactoryBean

    任何时候你使用 Spring 的 FactoryBeans 之一(例如CacheProxyFactoryBean)或实现你自己的,你从@Bean 方法返回的是FactoryBean 本身,不是 FactoryBean 的产物。因此,您将返回FactoryBean,而不是return factoryBean.getObject(),就像这样......

    @Bean
    GreeterFactoryBean greeter() {
      GreeterFactoryBean factoryBean = new GreeterFactoryBean();
      factoryBean.set...
      return factoryBean;
    }
    

    在这里,GreeterFactoryBean 实现了o.s.beans.factory.FactoryBean

    正如 Spring 的 Reference Documentation 指出的那样,Spring 容器知道返回 FactoryBean 的乘积(例如 [Singleton ] Greeter 实例)并且不是 FactoryBean 本身,作为此@Bean 方法的Spring 容器中的“已定义”bean。如果没有用@Bean 显式定义(例如@Bean("Greeter")),bean 的名称将是@Bean 方法的名称。

    如果FactoryBean 还实现了Spring 的 生命周期回调接口(例如o.s.beans.factory.InitializingBeano.s.beans.factory.DisposableBean 等),Spring 容器将知道调用这些生命周期在 Spring 容器的初始化过程中,在“适当的”时间回调。

    因此,没有必要在“greeter”@Bean 定义中调用CacheProxyFactoryBean.afterPropertiesSet()CacheProxyFactoryBean.getObject()。这样做实际上违反了 Spring 容器的初始化契约,并且您可能会遇到过早的“初始化”问题,特别是如果提供的 FactoryBean 实现了其他 Spring 容器接口(例如 @ 987654387@,或EnvironmentAware,依此类推)。

    小心!

    2) 其次,这不是您的示例的问题/问题,而是需要注意的问题。您在此 SO 帖子中声明您正在尝试将“缓存”行为添加到 3rd 方库类。

    仅当您能够在自己的应用程序中实例化第 3 方类(例如 Greeter)时,您使用的上述方法才适用。

    但是,如果第 3 方库或框架代表您实例化该类,由于配置库/框架(例如 JDBC 驱动程序和 Hibernate),您将无法在您的申请,除非您求助于Load-Time Weaving (LTW)。阅读文档了解更多详情。

    好的,进入解决方案

    我编写了一个测试来重现此问题并更好地了解 Spring 框架 中发生的情况。你可以找到我完成的测试here

    TestConfigurationOne 实际上与您使用的方法相同 以编程方式创建缓存代理,并根据我上面讨论的内容进行修改,同时解决我认为核心 Spring Framework 中的 bug(注意:我使用的是Spring 框架 5.0.1.RELEASE 在我的测试中)。

    为了让CacheProxyFactoryBean 的配置方法能够正常工作,我需要extend the CacheProxyFactoryBean class。除了extendingCacheProxyFactoryBean,我还需要implementSmartInitializingSingleton interfaceBeanFactoryAware interface,原因将在稍后变得显而易见。对于complete implementation,请参阅9

    在内部,Spring Framework 的o.s.cache.interceptor.CacheProxyFactoryBeanmaking useo.s.cache.interceptor.CacheInterceptor。它还会“初始化”这个CacheInterceptor 实例、herehere。但是,这不会完成初始化,因为CacheInterceptor 也间接地,implementsSmartInitializingSingleton 接口,通过extending CacheAspectSupport。如果从未调用实现的SmartInitializingSingleton CacheInterceptor.afterSingletonsInstantiated() method,则永远不会触发initialized bit,并且任何可缓存操作will not be cached,都会导致cacheable operation being invoked every single time(因此,忽略任何引入的缓存行为)。

    这正是我在测试类中将CacheProxyFactoryBean 扩展为capture“mainInterceptor”(即CacheInterceptor)然后在afterSingletonsInstantiated() method 的确切原因>Spring 容器的初始化阶段,这就是为什么我的SmartCacheProxyFactoryBean 扩展实现SmartInitializingSingleton,以委托给CacheInterceptor.afterSingletonsInstantiated() 方法。

    另外,CacheInterceptorBeanFactoryAwarerequires Spring BeanFactory 来执行它的功能,因此我检查这个“mainInterceptor”并设置BeanFactory适当地,here

    我推荐的另一种方法是使用TestConfigurationTwo

    在这个配置中,我是configuringSpring直接AOP Advice(即CacheInterceptor),从@Bean定义方法“cacheInterceptor”返回它,它允许Spring 容器适当地调用生命周期回调。

    然后,我在第三方班级的cache proxy creation 中进入use this Advice(即“Greeter”)。

    您应该小心将从“cacheInterceptor”bean 定义创建的 bean 传递给“greeter”bean 定义like so。如果您要从您的 bean“greeter”bean 定义中调用“cacheInterceptor”bean 定义方法,就像许多用户不恰当地所做的那样 (for example),那么您将放弃Spring 容器生命周期回调!不要这样做!原因在here解释。

    另外,要阅读有关以编程方式创建代理的更多信息,您应该阅读this

    好的,到此为止。

    我提供的测试类(即“ProgrammaticCachingWithSpringIntegrationTests”)。随意尝试一下,如果您有任何后续问题,请告诉我。

    希望这会有所帮助!

    干杯!

    【讨论】:

    【解决方案2】:

    您可以简单地创建一个Adapter/Wrapper 类,该类代表您希望通过缓存启用的底层第 3 方类类型。然后,将缓存行为添加到您的应用程序 Adapter/Wrapper 类型中。

    这比尝试确定适当的 AOP 切入点来拦截您想要建议缓存的第 3 方类上的方法要容易得多。当然,如果您没有对要在其中引入缓存的对象的引用,这种方法将不起作用,在这种情况下,您将不得不求助于 AOP。

    从您的示例中,我得到的印象是您可能引用了此第 3 方对象实例???

    如果不是,请澄清一下,然后我可以帮助您处理 AOP。

    【讨论】:

    • 我考虑编写一个包装器并在其上的方法调用上使用注释,但我认为在不创建代理每个方法调用的额外类的情况下做事可能会更干净。我目前的尝试还有多远?如果要让它工作的工作量不合理,我会改用包装器。
    • 嗨,彼得-我目前在 SpringOne,我会在我发表演讲后跟进并给予更多/适当的关注。
    • 感谢您抽出宝贵的时间
    猜你喜欢
    • 2012-07-01
    • 2018-07-06
    • 2013-06-21
    • 2014-03-19
    • 2019-08-13
    • 2012-11-23
    • 2018-10-30
    • 1970-01-01
    • 2014-06-15
    相关资源
    最近更新 更多