Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduce qualifier in @Cacheable for cache manager name [SPR-8696] #13338

Closed
spring-projects-issues opened this issue Sep 14, 2011 · 12 comments
Closed
Assignees
Labels
has: votes-jira Issues migrated from JIRA with more than 10 votes at the time of import in: core Issues in core modules (aop, beans, core, context, expression) type: enhancement A general enhancement
Milestone

Comments

@spring-projects-issues
Copy link
Collaborator

spring-projects-issues commented Sep 14, 2011

Gaetan Pitteloud opened SPR-8696 and commented

A qualifier attribute on @Cacheable would help to specify a CacheManager in the scenario described in the referenced forum post and summarized here:

Two modules define their own CacheManager and use @Cacheable together with <cache:annotation-driven cache-manager="_some_name_"/> in their respective XML configuration files.

When the XML configuration files from the two modules are used to build a single ApplicationContext, the second CacheManager is ignored as only one cache interceptor is registered by the namespace.


Affects: 3.1 M2

Reference URL: http://forum.springsource.org/showthread.php?110853

Issue Links:

Referenced from: commits 2b89c1a, f06cad9

10 votes, 12 watchers

@spring-projects-issues
Copy link
Collaborator Author

Costin Leau commented

A quick solution would be to use a decorating cache manager - CompositeCacheManager (which works as long as the cache definitions have different names).

@spring-projects-issues
Copy link
Collaborator Author

spring-projects-issues commented May 28, 2012

Chris Beams commented

This is somewhat similar to the work in #11513 to allow executor qualification in @Async methods. Also similar to transaction manager qualification in @Transactional.

@spring-projects-issues
Copy link
Collaborator Author

Vincent DEVILLERS commented

In order to fix this issue on one of my project, I did something like the AbstractRoutingDataSource:

/**
 * {@link CacheManager} implementation that routes {@link #getCache(String name)}
 * calls to one of various target CacheManagers based on the cacheName. A
 * default {@link CacheManager} can be defined, default is
 * {@link NoOpCacheManager} (and return {@code null}).
 * 
 * @author Vincent Devillers
 * @since 3.1
 */
public class RoutingCacheManager implements InitializingBean, CacheManager {

	private ConcurrentMap<String, CacheManager> targetCacheManagers = new ConcurrentHashMap<String, CacheManager>();

	private CacheManager defaultTargetCacheManager = new NoOpCacheManager();

	public void afterPropertiesSet() {
	}

	@Override
	public Cache getCache(String name) {
		CacheManager cacheManager = targetCacheManagers.get(name);
		if (cacheManager == null) {
			cacheManager = defaultTargetCacheManager;
		}
		return cacheManager.getCache(name);
	}

	@Override
	public Collection<String> getCacheNames() {
		Set<String> cacheNames = new LinkedHashSet<String>();
		for (Entry<String, CacheManager> entry : targetCacheManagers.entrySet()) {
			cacheNames.add(entry.getKey());
		}
		return Collections.unmodifiableSet(cacheNames);
	}

	public ConcurrentMap<String, CacheManager> getTargetCacheManagers() {
		return targetCacheManagers;
	}

	public void setTargetCacheManagers(
			ConcurrentMap<String, CacheManager> targetCacheManagers) {
		this.targetCacheManagers = targetCacheManagers;
	}

	public CacheManager getDefaultTargetCacheManager() {
		return defaultTargetCacheManager;
	}

	public void setDefaultTargetCacheManager(
			CacheManager defaultTargetCacheManager) {
		this.defaultTargetCacheManager = defaultTargetCacheManager;
	}

}

@spring-projects-issues
Copy link
Collaborator Author

@spring-projects-issues
Copy link
Collaborator Author

Stéphane Nicoll commented

See PR #441

@spring-projects-issues
Copy link
Collaborator Author

Stéphane Nicoll commented

Working on this issue from a different angle I am wondering if it wouldn't be more flexible to fix it differently. The issue here is that we want to be able per cache operation to resolve the right cache. The cache manager is actually only used for that.

How about a CacheResolver interface that gets called for each operation to retrieve the cache to use. That way the implementation can bring a different cache manager or even take decision based on runtime information.

How does that sound?

@spring-projects-issues
Copy link
Collaborator Author

Sam Brannen commented

For situations like this, I find it's often beneficial to provide both a declarative and programmatic means for such functionality.

For example, consider @ActiveProfiles in the TestContext framework. Initially (in Spring 3.1) we only provided declarative support via the value and profiles attributes, but then the community requested a programmatic means. So we introduced the ActiveProfilesResolver strategy (set via the resolver attribute in @ActiveProfiles) in Spring 4.0.

Of course, for caching it would make sense to be able to set the qualifier or resolver at the class level, not just at the method level.

My $0.02,

Sam

@spring-projects-issues
Copy link
Collaborator Author

Stéphane Nicoll commented

If I understand you correctly, you mean keeping three attributes then: the name of the cache(s), the ref of the cacheManager and a ref to a CacheResolver implementation that would resolve the caches programmatically.

The problem here is that I don't really see how it relates to your example. Specifying the cacheManager is exclusively used to resolve the specific cache name(s) against a different cache managers. I see it more has a third solution between @ActiveProfiles and ActiveProfilesResolver.

If your point is that users wants to be able to manage different cache managers only declaratively, then yes, it makes sense to keep the 3 attributes.

Setting at the class level is already implemented and pending review (but I asked some delay on the review as the work on JSR-107 is slightly changing things there).

Thanks for the feedback Sam!

@spring-projects-issues
Copy link
Collaborator Author

Sam Brannen commented

With regard to @ActiveProfiles, that annotation is used on a test class, and the test instance is not a bean in an ApplicationContext, so a static declaration via profiles is the best we can offer other than the programmatic approach of a custom ActiveProfilesResolver.

However, in the case of caching, each annotated class is actually a bean in the ApplicationContext. So I'm wondering if it wouldn't make sense to evaluate the value attributes of @Cacheable, @CachePut, and @CacheEvict as SpEL expressions. That would enable developers to make programmatic, run-time decisions for which cache to use. This would essentially remove the need for an explicit CacheResolver API in Spring, since the SpEL expression could delegate to a bean in the context for this purpose (which may happen to implement a CacheResolver but wouldn't necessarily have to due to the dynamic method invocation nature of SpEL expressions).

And if you introduce a cacheManager attribute in @Cacheable, @CachePut, and @CacheEvict, I would also recommend that it simply be a String that can be evaluated as a SpEL expression, the result of which is the name of the cache manager bean to use. Like in the paragraph above, using a SpEL expression would avoid the need for any kind of special cache manager resolver API.

@spring-projects-issues
Copy link
Collaborator Author

Sam Brannen commented

If your point is that users wants to be able to manage different cache managers only declaratively, then yes, it makes sense to keep the 3 attributes.

Evaluating those attributes as SpEL expressions basically kills two birds with one stone. Whether or not we introduce dedicated resolver APIs is a different question.

My main point in my original comment was that it is often a good idea to provide both a declarative and a programmatic mechanism for configuring such features, and these mechanisms should be available at the class-level as well as at the method-level, whereby method-level configuration overrides class-level configuration.

Furthermore, any defaults configured via the XML namespace or @Enable annotation should be overridable via annotations at the class-level and method-level.

So in the end you have three locations for declaring configuration for the cache manager, cache name resolution, and cache key generation. And this constellation represents a hierarchy like this:

  1. XML namespace or @Enable annotation
  2. Class-level within an annotated component
  3. Method-level within an annotated component

Lower levels of configuration override higher levels.

Regards,

Sam

@spring-projects-issues
Copy link
Collaborator Author

spring-projects-issues commented Feb 27, 2014

Stéphane Nicoll commented

Sam, please see #16115 for the programmatic mechanism.

Thanks!

@spring-projects-issues
Copy link
Collaborator Author

Stéphane Nicoll commented

It is now possible to specify the CacheManager to use per operation. The related cache annotation now has an extra attribute that defines the name of the CacheManager bean to use. The cache manager that was previously used is therefore a 'default' cache manager (i.e. the one to use if no custom cache manager has been set on the operation).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
has: votes-jira Issues migrated from JIRA with more than 10 votes at the time of import in: core Issues in core modules (aop, beans, core, context, expression) type: enhancement A general enhancement
Projects
None yet
Development

No branches or pull requests

2 participants