Skip to content

Allow users to inject custom cacheDecorator [SPR-16738] #21279

Closed
@spring-projects-issues

Description

@spring-projects-issues

Petar Tahchiev opened SPR-16738 and commented

In my spring-boot application I need to be able to turn on/off the caching of the app at runtime. I found a very neat way but to make it work I have introduced some changes in the AbstractTransactionSupportingCacheManager (PR will follow in a minute).

First of all in my spring-boot application I define a custom CacheManagerCustomizer:

    @Bean
    public CacheManagerCustomizer defaultSpringCacheManagerCustomizer() {

        return cacheManager -> {
            CacheDecorator cacheDecorator = ((AbstractCacheManager) cacheManager).getCacheDecorator();
            if (cacheDecorator != null) {
                ((AbstractCacheManager) cacheManager).setCacheDecorator(new NemesisCacheKeyAwareCacheDecorator(cacheDecorator));

            } else {
                ((AbstractCacheManager) cacheManager).setCacheDecorator(new NemesisCacheKeyAwareCacheDecorator());
            }
        };
    }

and this works fine - now I plug-in a custom NemesisCacheKeyAwareCacheDecorator which checks at runtime if a given flag is on and so returns the underlying Cache or if it is off and so it returns null denoting that no such cache was found.

However as you can see the AbstractCacheManager now has an attribute cacheDecorator of type CacheDecorator:

public interface CacheDecorator {

	/**
	 * Decorate the given cache if necessary.
	 *
	 * @param cache the cache to decorate
	 * @return the decorated cache
	 */
	Cache decorateCache(Cache cache);

	boolean shouldDecorate();
}

and so the AbstractCacheManager's decorate method becomes:

	protected Cache decorateCache(Cache cache) {
		return this.cacheDecorator != null && this.cacheDecorator.shouldDecorate() ? this.cacheDecorator.decorateCache(cache) : cache;
	}

Then the AbstractTransactionSupportingCacheManager becomes much simpler as the logic of transactionAware is now delegated to a new TransactionAwareCacheDecorator

public class TransactionAwareCacheDecorator implements CacheDecorator {

	/**
	 * Whether this CacheDecorator is transaction aware and should expose transaction-aware Cache objects.
	 * <p>Default is "false". Set this to "true" to synchronize cache put/evict
	 * operations with ongoing Spring-managed transactions, performing the actual cache
	 * put/evict operation only in the after-commit phase of a successful transaction.
	 */
	private boolean transactionAware;

	public TransactionAwareCacheDecorator(boolean transactionAware) {
		this.transactionAware = transactionAware;
	}

	@Override
	public Cache decorateCache(Cache cache) {
		return new TransactionAwareCache(cache);
	}

	@Override
	public boolean shouldDecorate() {
		return this.transactionAware;
	}
}

and the AbstractTransactionSupportingCacheManager becomes:

public abstract class AbstractTransactionSupportingCacheManager extends AbstractCacheManager {

	/**
	 * Constructor that creates CacheManager with transactionAware flag set to false.
	 */
	public AbstractTransactionSupportingCacheManager() {
		this(false);
	}

	/**
	 * Constructor that creates CacheManager with the given transactionAware flag.
	 *
	 * @param transactionAware Set whether this CacheManager should expose transaction-aware Cache objects.
	 *                         <p>Default is "false". Set this to "true" to synchronize cache put/evict
	 *                         operations with ongoing Spring-managed transactions, performing the actual cache
	 *                         put/evict operation only in the after-commit phase of a successful transaction.
	 */
	public AbstractTransactionSupportingCacheManager(boolean transactionAware) {
		super(new TransactionAwareCacheDecorator(transactionAware));
	}
}

Of course JCacheCacheManager and EhCacheCacheManager needs new constructors but I think this is much better than relying on setter methods and manually invoking afterPropertiesSet.

As a result I think with these chages the caching API becomes much more powerful and with absolutely NO changes in spring-boot I am able to fulfil my requirements. I even have a custom actuator endpoint to start/stop the cacheManager at runtime!


2 votes, 1 watchers

Metadata

Metadata

Assignees

Labels

in: coreIssues in core modules (aop, beans, core, context, expression)status: declinedA suggestion or change that we don't feel we should currently applytype: enhancementA general enhancement

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions