简体   繁体   English

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

[英]How can I manually add a Spring CacheInterceptor using Java Config?

I'm trying to work out how I can add caching to method calls on a third party Java class. 我正在尝试找出如何在第三方Java类上的方法调用中添加缓存。 I'm using Spring Boot for my application. 我正在为我的应用程序使用Spring Boot

I've come up with this class in my attempts to get caching working. 我尝试使用此类来实现缓存工作。

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

The hope is that I'd be able create a bean in my Spring config that wraps calls to Greeter.getGreeting(..) and returns cached results if they exist. 希望是我能够在Spring配置中创建一个bean,该bean包装对Greeter.getGreeting(..)调用,并返回缓存的结果(如果存在)。 However no caching is taking place. 但是,不会进行缓存。

Any ideas? 有任何想法吗?

Alright, I have more information for you. 好吧,我有更多信息给您。 But first, I want to address some issues with your code above. 但是首先,我想在上面的代码中解决一些问题。

1) The first issue involves your use of the oscache.interceptor.CacheProxyFactoryBean in the "greeter" @Bean definition of your application @Configuration class (ie "MyApp"). 1)第一个问题涉及在应用程序@Configuration类(即“ MyApp”)的“ greeter” @Bean定义中使用oscache.interceptor.CacheProxyFactoryBean

Anytime you use 1 of Spring's FactoryBeans (eg CacheProxyFactoryBean ) or implement your own, what you return from the @Bean method is the FactoryBean itself, not the product of the FactoryBean . 每当你使用Spring的 1 FactoryBeans (如CacheProxyFactoryBean ),或者实现你自己的,你从返回的内容@Bean方法是FactoryBean本身,而不是对产品FactoryBean So, instead of return factoryBean.getObject() , you would return the FactoryBean , like so... 因此,您将返回FactoryBean ,而不是return factoryBean.getObject() ,就像这样...

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

Here, GreeterFactoryBean implements osbeans.factory.FactoryBean . 在这里, GreeterFactoryBean实现了osbeans.factory.FactoryBean

As Spring's Reference Documentation points out, the Spring container knows to return the product of the FactoryBean (eg a [ Singleton ] Greeter instance) and not the FactoryBean itself, as the "defined" bean in the Spring container for this @Bean method. 正如Spring的 参考文档所指出的那样, Spring容器知道要返回FactoryBean (例如[ Singleton ] Greeter实例)而不是 FactoryBean本身,作为该@Bean方法在Spring容器中的“定义” Bean。 The name of the bean will be the name of the @Bean method if not explicitly defined with @Bean (eg @Bean("Greeter") ). 如果未使用@Bean明确定义,则bean的名称@Bean方法的名称(例如@Bean("Greeter") )。

If the FactoryBean also implements Spring's lifecycle callback interfaces (eg osbeans.factory.InitializingBean or osbeans.factory.DisposableBean , etc), the Spring container will know to invoke those lifecycle callbacks at the "appropriate" time, during the Spring container's initialization process. 如果FactoryBean还实现Spring的生命周期回调接口(例如osbeans.factory.InitializingBeanosbeans.factory.DisposableBean等),则Spring容器将知道在Spring容器的初始化过程中的“适当”时间调用这些生命周期回调。

Therefore, it is unnecessary to invoke either CacheProxyFactoryBean.afterPropertiesSet() or CacheProxyFactoryBean.getObject() inside the "greeter" @Bean definition. 因此,没有必要在“ greeter” @Bean定义内调用CacheProxyFactoryBean.afterPropertiesSet()CacheProxyFactoryBean.getObject() Doing so actually violates the Spring container's initialization contract, and you could run into premature "initialization" problems, especially if the provided FactoryBean implements other Spring container interfaces (eg BeanClassLoaderAware , or EnvironmentAware , and so on and so forth). 这样做实际上违反了Spring容器的初始化约定,您可能会遇到过早的“初始化”问题,尤其是在提供的FactoryBean实现其他Spring容器接口(例如BeanClassLoaderAwareEnvironmentAware等)的情况下。

Be careful! 小心!

2) Second, this is less of an issue/problem with your example than something to just be aware of. 2)其次,这不是您的示例所要解决的问题/问题,而是要意识到的问题。 You stated in this SO post that you are trying to add "caching" behavior to 3rd party library classes. 您在此SO帖子中指出,您正在尝试将“缓存”行为添加到第三方库类。

The approach you use above is applicable only when you are able to instantiate the 3rd party class (eg Greeter ) in your application yourself. 仅当您能够自己在应用程序中实例化第三方类(例如Greeter )时,以上使用的方法才适用。

However, if the 3rd party library or framework instantiates the class on your behalf, as a result of configuring the library/framework (eg think JDBC Driver and Hibernate), you lose the ability to introduce caching behavior to this class in your application, unless your resort to Load-Time Weaving (LTW). 但是,如果第三方库或框架代表您实例化该类,则由于配置了库/框架(例如,认为JDBC Driver和Hibernate),您将无法在应用程序中向此类引入缓存行为,除非您可以使用加载时编织 (LTW)。 Read the docs for more details. 阅读文档以获取更多详细信息。

Alright, onto the solution . 好了,到解决方案了

I wrote a test to reproduce this problem and to better understand what is happening inside the Spring Framework . 我编写了一个测试来重现此问题并更好地了解Spring Framework内部发生的情况。 You can find my completed test here . 您可以在这里找到我完成的考试。

TestConfigurationOne is effectively the same approach you employed to create caching proxies programmatically, with modifications based on what I discussed above, and also, to address what I think is a bug in the core Spring Framework (NOTE: I was using Spring Framework 5.0.1.RELEASE in my test). 实际上, TestConfigurationOne与您以编程方式创建缓存代理所采用的方法相同,它基于我在上面讨论的内容进行了修改,并且还解决了我认为是核心Spring Framework中错误的地方(注意:我正在使用Spring Framework 5.0.1.RELEASE在我的测试中)。

In order to get your configuration approach with CacheProxyFactoryBean to work, I needed to extend the CacheProxyFactoryBean class . 为了使您的CacheProxyFactoryBean配置方法起作用,我需要扩展CacheProxyFactoryBean In addition to extending the CacheProxyFactoryBean , I also needed to implement the SmartInitializingSingleton interface and the BeanFactoryAware interface for reasons what will become apparent in a moment. 除了扩展 CacheProxyFactoryBean ,我还需要实现 SmartInitializingSingleton接口BeanFactoryAware接口 ,其原因SmartInitializingSingleton就会显现出来。 See 9 for the complete implementation . 完整的实现请参见9

Internally, Spring Framework's oscache.interceptor.CacheProxyFactoryBean is making use of a oscache.interceptor.CacheInterceptor . 在内部, Spring Framework的 oscache.interceptor.CacheProxyFactoryBean 使用 oscache.interceptor.CacheInterceptor It also goes onto "initialize" this CacheInterceptor instance, here and here . 它还在此处此处继续“初始化”此CacheInterceptor实例。 However, this does not complete the initialization since the CacheInterceptor also, indirectly, implements the SmartInitializingSingleton interface, by extending CacheAspectSupport . 但是,由于CacheInterceptor还通过扩展 CacheAspectSupport间接实现 SmartInitializingSingleton接口,因此这还不能完成初始化。 If the SmartInitializingSingleton implemented CacheInterceptor.afterSingletonsInstantiated() method is never called, then the initialized bit is never tripped, and any cacheable operations will not be cached , resulting in the cacheable operation being invoked every single time (thus, ignoring any introduced caching behavior). 如果SmartInitializingSingleton实施CacheInterceptor.afterSingletonsInstantiated()方法不会被调用,则initialized永远不会跳闸,任何缓存操作被缓存 ,导致缓存的操作被调用每一次 (因此,忽略任何介绍缓存行为) 。

This is the exact reason why I extended the CacheProxyFactoryBean in my test class, to capture the "mainInterceptor" (ie the CacheInterceptor ) and then call the afterSingletonsInstantiated() method at the appropriate moment during the Spring container's initialization phase, which is why my SmartCacheProxyFactoryBean extension implements SmartInitializingSingleton , to delegate to the CacheInterceptor.afterSingletonsInstantiated() method. 这就是为什么我在测试类中扩展CacheProxyFactoryBean捕获 “ mainInterceptor”(即CacheInterceptor ),然后在Spring容器的初始化阶段的适当时候调用afterSingletonsInstantiated()方法的确切原因,这就是为什么我的SmartCacheProxyFactoryBean扩展程序实现SmartInitializingSingleton ,以委派给CacheInterceptor.afterSingletonsInstantiated()方法。

Additionally, the CacheInterceptor is BeanFactoryAware and requires the Spring BeanFactory to carry out its function, hence the reason I inspect this "mainInterceptor" and set the BeanFactory appropriately, here . 此外, CacheInterceptorBeanFactoryAware ,并要求 春节 BeanFactory执行其功能,因此原因,我检查一下“mainInterceptor”,并设置BeanFactory恰如其分, 在这里

Another approach, which I recommend, is to use TestConfigurationTwo . 我推荐的另一种方法是使用TestConfigurationTwo

In this configuration, I am configuring the Spring AOP Advice (ie the CacheInterceptor ) directly, returning it from the @Bean definition method, "cacheInterceptor", which allows the Spring container to invoked the lifecycle callbacks, appropriately. 在此配置中,我直接配置 Spring AOP咨询(即CacheInterceptor ),并从@Bean定义方法“ cacheInterceptor”返回它,该方法允许Spring容器适当地调用生命周期回调。

Then, I go onto use this Advice in the cache proxy creation of the 3rd party class (ie " Greeter "). 然后,在第三方类(即“ Greeter ”)的缓存代理创建中继续使用此建议

You should be careful to pass in the bean created from the " cacheInterceptor " bean definition to the " greeter " bean definition, like so . 您应该小心地将从“ cacheInterceptor ” bean定义创建的bean传递给“ greeter ” bean定义, 就像这样 If you were to invoke the " cacheInterceptor " bean definition method from within your bean " greeter " bean definition, like so many users inappropriately do ( for example ), then you would forgo the Spring container lifecycle callbacks! 如果你要调用“ cacheInterceptor从你的bean中” bean定义方法“ greeter ” bean定义,像这么多的用户不适当的 (做例如 ),那么你会放弃Spring容器生命周期回调! Don't do this! 不要这样! The reasons for this are explained here . 造成这种情况的原因进行了说明这里

Also, to read more information on the programmatic creation of proxies, you should read this . 此外,要阅读有关以编程方式创建代理的更多信息,您应该阅读此书

Ok, that about covers it. 好的,关于它的覆盖。

The test class I provided (ie " ProgrammaticCachingWithSpringIntegrationTests "). 我提供的测试类(即“ ProgrammaticCachingWithSpringIntegrationTests ”)。 Feel free to play around with it and let me know if you have any follow-up questions. 随时尝试使用它,如果您有任何后续问题,请告诉我。

Hope this helps! 希望这可以帮助!

Cheers! 干杯!

You could simply create an Adapter / Wrapper class that delegates to the underlying 3rd party class type that you want to enable with caching. 您可以简单地创建一个Adapter / Wrapper类,该类委派给您要启用缓存的基础第三方类类型。 Then, add caching behavior to your application Adapter / Wrapper type instead. 然后,将缓存行为添加到您的应用程序Adapter / Wrapper类型。

That would be far easier to do than trying to determine the appropriate AOP Pointcut to intercept the method on the 3rd party class in which you want to advise with caching. 这比尝试确定适当的AOP切入点来拦截要在其中建议缓存的第三方类中的方法要容易得多。 Of course, this approach won't work if you do not have a reference to a the object in which you want to introduce caching, in which case you will have to resort to AOP. 当然,如果您没有引用要在其中引入缓存的对象,则这种方法将行不通,在这种情况下,您将不得不求助于AOP。

From your example, I get the impression that you may have a reference to this 3rd party object instance??? 从您的示例中,我得到的印象是您可能对此第三方对象实例具有引用???

If not, please clarify and then I can help you with the AOP. 如果没有,请澄清一下,然后我可以为您提供AOP帮助。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 如何使用 YML 和 Java 配置在 Spring 中实例化相互依赖的 Bean 列表? - How can I Instantiate interdependent lists of Beans in Spring using YML and Java Config? 使用Spring,如何手动触发AuthenticationSuccessHandler - Using Spring, how can I manually fire AuthenticationSuccessHandler 为什么我不能使用“-Dspring.config.location”将配置位置添加到 windows 上的 Spring 可执行文件 jar 中? - Why can I not add config location to Spring exectuable jar on windows using “-Dspring.config.location”? 如何使用JSESSIONID手动加载Java会话? - How can I manually load a Java session using a JSESSIONID? 如何注册一个自定义CacheInterceptor而不是Spring中打包的CacheInterceptor? - How to register a custom CacheInterceptor instead of the one packaged in Spring? Spring MVC找不到CacheInterceptor - Spring mvc cannot find CacheInterceptor 如何使用 Java API 添加 SCRAM-SHA-512 kafka 配置? - How can i add SCRAM-SHA-512 kafka config using Java API? 如何在春季使用Java配置设置bean - How to set bean using java config in spring 如何使用Java中的Spring数据JPA将附件(文件)添加到Couchbase中的文档中? - How can I add attachments (Files) to doccuments in couchbase using Spring data JPA in java? 自定义 CacheInterceptor 被默认 Spring 的 CacheInterceptor 覆盖 - Custom CacheInterceptor is being overridden by default Spring's CacheInterceptor
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM