Skip to content

@CacheConfig(cacheNames) broken when used on interface [SPR-14781] #19347

Closed
@spring-projects-issues

Description

@spring-projects-issues

Marc Vanbrabant opened SPR-14781 and commented

Since upgrading from 4.1 to 4.3 we are seeing the following exception thrown in CacheAspectSupport.getCaches when a call to a @Cacheable method occurs:

throw new IllegalStateException("No cache could be resolved for '" +
              context.getOperation() + "' using resolver '" + cacheResolver +
              "'. At least one cache should be provided per cache operation.");

As far as I have been able to trace, it looks like the refactoring from #18054 (59c88eb) might be the cause.

It seems to fail when the @CacheConfig annotation is not on the Impl class of the interface, but on the interface itself.

@CacheConfig( cacheNames = "userCache")
public interface UserService
{
	@Cacheable(key = "('username:' + #username).toLowerCase()")
	User getUserByUsername( String username );
}

public class UserServiceImpl implements UserService {
	@Override
	public User getUserByUsername( String username ) {
		return userRepository.findByUsername( username );
	}
}

We observe the same error if the @Cachable annotation is placed on the UserServiceImpl class:

@CacheConfig( cacheNames = "userCache")
public interface UserService
{
	User getUserByUsername( String username );
}

public class UserServiceImpl implements UserService {
	@Override
        @Cacheable(key = "('username:' + #username).toLowerCase()")
	public User getUserByUsername( String username ) {
		return userRepository.findByUsername( username );
	}
}

Moving down the @CacheConfig down to the UserServiceImpl class circumvents the issue.

The difference compared to 4.1 (as far as I was manage to debug), seem to be that SpringCacheAnnotationParser.parseCacheAnnotations() now uses AnnotatedElementUtils.findAllMergedAnnotations(ae, CacheEvict.class)

In 4.1 SpringCacheAnnotationParser.parseCacheAnnotations() would return null when called with the UserServiceImpl.getUserByUsername as second argument. It would then return null all the way up to AbstractFallbackCacheOperationSource.computeCacheOperations() where it would fallback onto the following check:

if (specificMethod != method) {
     // Fallback is to look at the original method.
     opDef = findCacheOperations(method);
     if (opDef != null) {
          return opDef;
     }
     // Last fallback is the class of the original method.
     opDef = findCacheOperations(method.getDeclaringClass());
     if (opDef != null && ClassUtils.isUserLevelMethod(method)) {
          return opDef;
     }
}

The method here being the interface method.

https://github.com/spring-projects/spring-framework/blob/4.1.x/spring-context/src/main/java/org/springframework/cache/interceptor/AbstractFallbackCacheOperationSource.java#L146

In 4.3 it seems that SpringCacheAnnotationParser.parseCacheAnnotations() does not return null and that the defaultConfig of the interface is not merged with parsed annotations from the Impl method.

Note:
If this is indeed a bug, the workaround is to put the @CacheConfig on the implementing classes, or specify the cacheNames in the @Cacheable annotations


Affects: 4.3.2

Issue Links:

Referenced from: commits 08972ef, 3cca57a

0 votes, 5 watchers

Metadata

Metadata

Assignees

Labels

in: coreIssues in core modules (aop, beans, core, context, expression)type: regressionA bug that is also a regression

Type

No type

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions