1

我正在尝试研究如何将缓存添加到第三方 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,该 bean 包装调用Greeter.getGreeting(..)并返回缓存结果(如果存在)。但是没有缓存发生。

有任何想法吗?

4

2 回答 2

3

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

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

每当您使用Spring 的 FactoryBeans1 个(例如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实例)的产品而不是自身,作为Spring容器中此方法FactoryBean的“已定义”bean 。如果没有用(例如)明确定义,bean 的名称将是方法的名称。@Bean@Bean@Bean@Bean("Greeter")

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

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

当心!

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

您在上面使用的方法仅适用Greeter于您自己能够在应用程序中实例化第 3 方类(例如 )的情况。

但是,如果第 3 方库或框架代表您实例化类,由于配置库/框架(例如 JDBC 驱动程序和 Hibernate),您将无法在应用程序中向此类引入缓存行为,除非加载时间编织( LTW )。阅读文档以获取更多详细信息。

好的,进入解决方案

我编写了一个测试来重现此问题并更好地了解Spring Framework内部发生的情况。你可以在这里找到我完成的测试。

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

为了让您的配置方法CacheProxyFactoryBean起作用,我需要扩展CacheProxyFactoryBean。除了扩展.之外CacheProxyFactoryBean,我还需要实现SmartInitializingSingleton接口BeanFactoryAware接口,原因稍后将变得显而易见。完整的实现请参见9

在内部,Spring Framework o.s.cache.interceptor.CacheProxyFactoryBean正在使用. o.s.cache.interceptor.CacheInterceptor它还会在此处此处CacheInterceptor“初始化”此实例。但是,这并没有完成初始化,因为也间接地通过扩展实现接口。如果从未调用已实现的方法,则该永远不会被触发,并且任何可缓存操作不会被缓存,从而导致每次都调用可缓存操作CacheInterceptorSmartInitializingSingleton CacheAspectSupportSmartInitializingSingletonCacheInterceptor.afterSingletonsInstantiated()initialized(因此,忽略任何引入的缓存行为)。

CacheProxyFactoryBean这就是我在测试类中扩展的确切原因,以捕获“ mainInterceptor ”(即CacheInterceptor),然后在Spring容器初始化阶段的适当时刻调用该afterSingletonsInstantiated()方法,这就是我的扩展实现的原因方法。SmartCacheProxyFactoryBeanSmartInitializingSingletonCacheInterceptor.afterSingletonsInstantiated()

此外,它CacheInterceptor需要Spring执行其功能,因此我检查此“mainInterceptor”并在此处适当设置的原因。BeanFactoryAware BeanFactoryBeanFactory

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

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

然后,我继续在第 3 方类(即“ ”)的缓存代理创建中使用此建议。Greeter

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

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

好的,这涵盖了它。

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

希望这可以帮助!

干杯!

于 2017-12-12T18:20:03.440 回答
0

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

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

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

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

于 2017-12-06T02:27:36.623 回答