104 lines
3.6 KiB
Plaintext
104 lines
3.6 KiB
Plaintext
== Why is this an issue?
|
|
|
|
Annotating interfaces or interface methods with ``++@Cache*++`` annotations is not recommended by the official Spring documentation:
|
|
----
|
|
Spring recommends that you only annotate concrete classes (and methods of concrete classes) with the @Cache* annotations, as opposed to annotating interfaces. You certainly can place an @Cache* annotation on an interface (or an interface method), but this works only if you use the proxy mode (mode="proxy"). If you use the weaving-based aspect (mode="aspectj"), the caching settings are not recognized on interface-level declarations by the weaving infrastructure.
|
|
----
|
|
|
|
Also, when a method is annotated as cacheable inside an interface, if two different implementations of that method exist, the first one to be invoked will populate the cache.
|
|
Subsequent calls will always return the cached value, even if it's the other implementation being called.
|
|
|
|
=== What is the potential impact?
|
|
|
|
* *Confusing Code*: Developers may mistakenly believe that caching is in effect, leading to confusion and incorrect assumptions about application performance.
|
|
* *Unreliable Code*: Annotating interface methods as ``++@Cacheable++`` hides the cache name from the implementing classes, making it hard to detect where a conflict of names might occur, causing unexpected results at runtime.
|
|
|
|
This rule raises an issue when an interface or an interface method is annotated with a ``++@Cache*++`` annotation.
|
|
|
|
== How to fix it
|
|
|
|
Move ``++@Cache*++`` annotation from interface or interface method to the concrete class.
|
|
|
|
=== Code examples
|
|
|
|
==== Noncompliant code example
|
|
|
|
[source,java,diff-id=1,diff-type=noncompliant]
|
|
----
|
|
public interface ExampleService {
|
|
|
|
@Cacheable("exampleCache") //non compliant, interface method is annotated with @Cacheable
|
|
String getData(String id);
|
|
}
|
|
----
|
|
|
|
In the following example, if our application has two different rest APIs to query the most popular animal in two different zoos, the first zoo to be queried will populate the cache.
|
|
|
|
Calls to a different API to query the other zoo will produce the same cached output, invalidating our application's business logic.
|
|
|
|
[source,java,diff-id=2,diff-type=noncompliant]
|
|
----
|
|
public interface Zoo {
|
|
@Cacheable("popAnimal") //non compliant, interface method is annotated with @Cacheable
|
|
Animal getMostPopularAnimal();
|
|
}
|
|
|
|
public class SanDiegoZoo implements Zoo {
|
|
@Override
|
|
public Animal getMostPopularAnimal() {
|
|
return new Lion();
|
|
}
|
|
}
|
|
|
|
public class RomeBioparc implements Zoo {
|
|
@Override
|
|
public Animal getMostPopularAnimal() {
|
|
return new Pantegana();
|
|
}
|
|
}
|
|
----
|
|
|
|
==== Compliant solution
|
|
|
|
[source,java,diff-id=1,diff-type=compliant]
|
|
----
|
|
@Service
|
|
public class ExampleServiceImpl implements ExampleService {
|
|
|
|
@Cacheable("exampleCache")
|
|
@Override
|
|
public String getData(String id) {
|
|
// Implementation here
|
|
}
|
|
}
|
|
----
|
|
|
|
With the following solution, we are granted that the two implementations will have separate caches.
|
|
|
|
[source,java,diff-id=2,diff-type=compliant]
|
|
----
|
|
public interface Zoo {
|
|
Animal getMostPopularAnimal();
|
|
}
|
|
|
|
public class SanDiegoZoo implements Zoo {
|
|
@Override
|
|
@Cacheable("sanDiegoPopAnimal")
|
|
public Animal getMostPopularAnimal() {
|
|
return new Lion();
|
|
}
|
|
}
|
|
|
|
public class RomeBioparc implements Zoo {
|
|
@Override
|
|
@Cacheable("romePopAnimal")
|
|
public Animal getMostPopularAnimal() {
|
|
return new Pantegana();
|
|
}
|
|
}
|
|
----
|
|
|
|
== Resources
|
|
=== Documentation
|
|
* Spring - https://docs.spring.io/spring-framework/reference/integration/cache/annotations.html#cache-annotation-enable[Declarative Annotation-based Caching]
|