代码之家  ›  专栏  ›  技术社区  ›  Programming Guy

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

  •  1
  • Programming Guy  · 技术社区  · 8 年前

    我正在研究如何将缓存添加到第三方Java类的方法调用中。我正在使用 弹簧防尘套 用于我的申请。

    为了让缓存工作起来,我提出了这个类。

    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;
            }
        }
    }
    

    希望我能够在我的 春天 包装调用的配置 Greeter.getGreeting(..) 并返回缓存结果(如果存在)。但是,没有发生缓存。

    有什么想法吗?

    2 回复  |  直到 8 年前
        1
  •  3
  •   John Blum    8 年前

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

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

    只要您使用 春季 FactoryBeans (例如。 CacheProxyFactoryBean )或者实现您自己的,从 @豆子 方法是 FactoryBean 它本身 的产品 FactoryBean公司 . 因此 return factoryBean.getObject() ,您将返回 FactoryBean公司 ,就像这样。。。

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

    在这里 GreeterFactoryBean 机具 o.s.beans.factory.FactoryBean .

    春季 Reference Documentation 指出 春天 集装箱知道退货 FactoryBean公司 (例如a[ 独生子女 ] Greeter 实例)和 这个 FactoryBean公司 本身,作为 春天 此容器 @豆子 方法bean的名称将是 @豆子 方法,如果没有用显式定义 @豆子 (例如。 @Bean("Greeter") ).

    如果 FactoryBean公司 还实施 春季 生命周期回调接口(例如。 o.s.beans.factory.InitializingBean o.s.beans.factory.DisposableBean ,等等) 春天 容器将知道在“适当”的时间调用这些生命周期回调,在 春天 容器的初始化过程。

    因此,没有必要调用 CacheProxyFactoryBean.afterPropertiesSet() CacheProxyFactoryBean.getObject() 在“迎宾者”里面 @豆子 释义这样做实际上违反了 春天 容器的初始化契约,您可能会遇到过早的“初始化”问题,尤其是如果提供了 FactoryBean公司 实施其他 春天 容器接口(例如。 BeanClassLoaderAware EnvironmentAware ,等等)。

    小心!

    2) 其次,这不是你的例子中的问题,而只是需要注意的事情。您在这篇文章中表示,您正试图将“缓存”行为添加到第三方库类中。

    仅当您能够实例化第三方类时(例如。 迎宾员 )在您的应用程序中。

    但是,如果第三方库或框架代表您实例化该类,由于配置库/框架(例如JDBC驱动程序和Hibernate),您将无法在应用程序中向此类引入缓存行为,除非您求助于 Load-Time Weaving (LTW)。阅读文档了解更多详细信息。

    好的,到 解决方案 .

    我写了一个测试来重现这个问题,以便更好地理解系统内部发生的事情 Spring框架 . 你可以找到我完成的测试 here .

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

    为了获得配置方法 CacheProxyFactoryBean 为了工作,我需要 extend the CacheProxyFactoryBean class . 除了 extending 这个 CacheProxyFactoryBean ,我也需要 implement 这个 SmartInitializingSingleton interface BeanFactoryAware interface 由于种种原因,什么会在一瞬间变得显而易见。看见 9 对于 complete implementation .

    内部, Spring框架 o、 s.cache。拦截器。CacheProxyFactoryBean making use o.s.cache.interceptor.CacheInterceptor . 它还将对此进行“初始化” CacheInterceptor 例子 here here . 然而,确实如此 完成初始化,因为 缓存拦截器 此外,间接地, implements 这个 智能初始化Singleton 接口,由 extending CacheAspectSupport . 如果 智能初始化Singleton 已实施 CacheInterceptor.afterSingletonsInstantiated() method 从未调用,则 initialized bit 从不跳闸,任何可缓存操作 will not be cached ,导致 cacheable operation being invoked every single time (因此,忽略任何引入的缓存行为)。

    这就是我扩展 CacheProxyFactoryBean 在我的测试课上 capture “主拦截器”(即 缓存拦截器 )然后打电话给 afterSingletonsInstantiated() method 在适当的时候 春天 容器的初始化阶段,这就是我的 SmartCacheProxyFactoryBean 扩展工具 智能初始化Singleton ,委托给 缓存拦截器。AfterSingletons实例化() 方法

    此外 缓存拦截器 BeanFactoryAware requires 这个 春天 BeanFactory 为了实现其功能,因此我检查这个“主拦截器”并设置 Bean工厂 适当地, here .

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

    在这种配置中,我是 configuring 这个 春天 AOP建议(即 缓存拦截器 )直接从 @豆子 定义方法“cacheInterceptor”,它允许 春天 容器调用生命周期回调。

    然后,我继续 use this Advice cache proxy creation 第三方类别(即 迎宾员 ").

    您应该小心地传入从“”创建的bean cacheInterceptor “bean定义到” greeter “bean定义, like so . 如果你要调用“ 缓存拦截器 “bean中的bean定义方法” 迎宾员 “bean定义,就像许多用户一样 不适当地 for example ),那么你就会放弃 春天 容器生命周期回调!不要这样做!解释了原因 here .

    此外,要阅读有关代理程序创建的更多信息,您应该阅读 this .

    好的,这就涵盖了它。

    我提供的测试等级(即 ProgrammaticCachingWithSpringIntegrationTests

    希望这有帮助!

    干杯

        2
  •  0
  •   John Blum    8 年前

    你可以简单地创建一个 Adapter / Wrapper 类,该类委托给要通过缓存启用的底层第三方类类型。然后,将缓存行为添加到应用程序中 适配器 / 包装器 改为键入。

    这比试图确定适当的AOP切入点来截取第三方类上的方法要容易得多,在第三方类中,您希望使用缓存提供建议。当然,如果您没有对要在其中引入缓存的对象的引用,这种方法将不起作用,在这种情况下,您必须求助于AOP。

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

    如果没有,请澄清,然后我可以帮助您处理AOP。