spring-projects / spring-framework

Spring Framework
https://spring.io/projects/spring-framework
Apache License 2.0
56.39k stars 38.05k forks source link

Programmatic creation of caching proxies using CacheProxyFactoryBean does not work [SPR-16295] #20842

Closed spring-projects-issues closed 6 years ago

spring-projects-issues commented 6 years ago

John Blum opened SPR-16295 and commented

When a user attempts to create caching proxies using the o.s.cache.interceptor.CacheProxyFactoryBean, this fails to work.

A user might want to use the CacheProxyFactoryBean to introduce caching behavior and logic to classes from a 3rd party library. This recently came up in a StackOverflow post. Please read the post and the answers (especially this one) for further details.

Ideally, a user would be able to add caching behavior to any class, programmatically (not declaratively) using...

@Bean
CacheProxyFactoryBean someBean() {

    CacheProxyFactoryBean factoryBean = new CacheProxyFactoryBean();

    factoryBean.setCacheOperationSources(...);
    factoryBean.setTarget(new ThirdPartyClass());

    return factoryBean;
}

Unfortunately, this does not work because the internal CacheInterceptor's afterSingletonesInitialized() method never gets called, thereby setting the initialized bit and therefore allowing the caching behavior/logic to be evaluated. As result, the target object's original operation is always invoked, forgoing any caching behavior, making any caching proxy configuration useless.

This can be fixed by having CacheProxyFactoryBean implement the SmartInitializingSingleton interface along with BeanFactoryAware and delegating said operations to the internal CacheInterceptor.

See Pull Request for fix.


Affects: 4.3.13, 5.0.2

Reference URL: https://stackoverflow.com/questions/47665485/how-can-i-manually-add-a-spring-cacheinterceptor-using-java-config

Issue Links:

Referenced from: pull request https://github.com/spring-projects/spring-framework/pull/1624, and commits https://github.com/spring-projects/spring-framework/commit/b160f93495f762b1f59519b66c3e07fe6bf387fd, https://github.com/spring-projects/spring-framework/commit/d53ede9bd3325269411efc67ab248389e02c85aa

Backported to: 4.3.14

spring-projects-issues commented 6 years ago

John Blum commented

Also, as I pointed out in my post on StackOverflow, another way to create programmatic caching proxies, is to declare Spring's ProxyFactory and CacheInterceptor as @Bean definitions in the Spring @Configuration class, like so...

@Configuration
class ApplicationConfiguration {

    @Bean
    CacheInterceptor cacheInterceptor(CacheManager cacheManager) {

        CacheInterceptor cacheInterceptor = new CacheInterceptor();

        cacheInterceptor.setCacheManager(cacheManager);
        cacheInterceptor.setCacheOperationSources(...);

        return cacheInterceptor;
    }

    @Bean
    Object thirdPartyObject(ConfigurableListableBeanFactory beanFactory, 
            CacheInterceptor cacheInterceptor) {

        ProxyFactory proxyFactory = new ProxyFactory();

        proxyFactory.addAdvisor(new DefaultPointcutAdvisor(cacheInterceptor));
        proxyFactory.setInterfaces(ThirdParty.class);
        proxyFactory.setTarget(new ThirdParty());

        return proxyFactory.getProxy(beanFactory.getBeanClassLoader());
    }
}

This is covered in Spring Framework's Reference Documentation, "6.7 - Creating AOP proxies programmatically with the ProxyFactory". Conveniently, a user could use the o.s.aop.framework.ProxyFactoryBean instead of ProxyFactory, directly.

Still, using the o.s.cache.interceptor.CacheProxyFactoryBean is far more convenient and consistent with Spring's policy on complex instantiation and initialization logic nicely encapsulated with a FactoryBean.

One example of alternative methods for programmatically creating caching proxies, for possibly 3rd party library/framework classes, can be found here.

spring-projects-issues commented 6 years ago

John Blum commented

Note: I closed my first PR #1623 in favor of PR #1624.

spring-projects-issues commented 6 years ago

Juergen Hoeller commented

CacheProxyFactoryBean is quite a stepchild here: This class essentially never worked (in particular not since #16941), and nobody seems to have noticed and complained... Since I still consider it a rather secondary configuration variant, I went with the simplest possible revision that does the job: For 4.3.14, simply delegating BeanFactoryAware and SmartInitializatingSingleton; for 5.0.3, also adding the three additional setters from CacheInterceptor for common configuration instead of individual operation settings.

For simplicity's sake, we're just doing integration testing (since that's all that really matters here) and without any custom configuration adaptation in CacheProxyFactoryBean itself, both of which significantly streamline the implementation and make it straightforward to backport. This also keeps things very aligned with TransactionProxyFactoryBean, the original inspiration behind our class here. For those reasons, I went with a custom variant instead of your PR, John. Thanks for pointing this out and for the tests, in any case!