What's the difference between #Cacheable Method and #Cacheable Class on ehcache - java

Working with ehcache I noticed that #Cacheable annotation could be used on top of a class decleration or on top of a method decleration, such as;
Cacheable class:
#Cacheable
class CacheableClass{
Long l;
Integer i;
String s;
}
Cacheable method:
class ...
#Cacheable
public List<ToBeCached> getCacheableClassList()
{
...
}
If #Cacheable is on top of a class then you cannot give the name of the chache but if you declare on top of a method you can give the name of the cache declared in the configuration xml. I suppose I miss something since using #Cacheable for class declerations seem obsolute to me.

Using ehcache-spring-annotations :
#com.googlecode.ehcache.annotations.Cacheable(cacheName = "test") if we give this at type level then it gives error saying The annotation #Cacheable is disallowed for this location.
According to documents I ever read Annotation Placement :
On a method.
On an interface or
On a public method on a class
Spring recommends that you only annotate methods of concrete classes with the #Cacheable annotation, as opposed to annotating methods of interfaces.
When using proxies, you should apply the #Cacheable annotation only to methods with public visibility. If you do annotate protected, private or package-visible methods with the #Cacheable annotation, no error is raised, but the annotated method does not exhibit the configured cachable settings.
Using Spring Cache
If you use #org.springframework.cache.annotation.Cacheable(value="test") where value represents the name of the cache. You can specify this at type &/or method level.
You can try this & tell whether you get error or not :-
#com.googlecode.ehcache.annotations.Cacheable(cacheName = "test")
#org.springframework.cache.annotation.Cacheable(value="")
public class PortalDatabaseAdapterImpl{
#com.googlecode.ehcache.annotations.Cacheable(cacheName="test")
#org.springframework.cache.annotation.Cacheable(value="test")
public List<PageControl> getLoginPage() {}
}
If you didn't get error then I have to update myself.

Using #Cacheable at method level means, the results of the method are cached.
Using #Cacheable at interface level is used to define custom annotations as below,
//Custom annotation
#Retention(RetentionPolicy.RUNTIME)
#Target({ElementType.METHOD})
#Cacheable(value="books", key="#isbn")
public #interface SlowService {
}
The below code
#Cacheable(value="books", key="#isbn")
public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed)
can be replaced with
#SlowService
public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed)
But I have never seen applying #Cacheable annotation at Class level.

Related

How can I pass parameters to a constructor of a class that uses #RequiredArgsConstructor?

I am trying to instantiate a class passing a parameter by constructor drMessage, I am using the #RequiredArgsConstructor annotation, and some dependency injections using #Autowired as I show below, the problem is that when using the #RequiredArgsConstructor annotation it implements the default constructors internally, and I tried to build the constructors manually but the IDE tells me that the variable of those constructors has not been initialized, how could I solve it? Thanks
#RequiredArgsConstructor
#Service
public class ServiceImpl implements IService {
#Autowired
private final DtoMapper dtoMapper;
#Autowired
private final CDtoMapper cDtoMapper;
private DrMessage drMessage;
**other sentences**
}
Constructor Injection With Lombok
With Lombok, it's possible to generate a constructor for either all class's fields (with #AllArgsConstructor) or all final class's fields (with #RequiredArgsConstructor). Moreover, if you still need an empty constructor, you can append an additional #NoArgsConstructor annotation.
Let's create a third component, analogous to the previous two:
1.Example
#Component
#RequiredArgsConstructor
public class ThankingService {
private final Translator translator;
public String produce() {
return translator.translate("thank you");
}
}
The above annotation will cause Lombok to generate a constructor for us:
2.Example
#Component
public class ThankingService {
private final Translator translator;
public String thank() {
return translator.translate("thank you");
}
/* Generated by Lombok */
public ThankingService(Translator translator) {
this.translator = translator;
}
}
#Service annotated classes are Spring managed beans. You don't manually instantiate these classes. Spring does it for you.
These #Service annotated beans have default singleton scope. Which means this bean is only initialize once. So they are called stateless beans, or says they have a shared state. You do not crate a state or change the state of these kind of beans.
Note: Read this document about bean scopes.
In your ServiceImpl you only have two final state variables. Which means are the only required filed when ServiceImpl initialize, so only they will be included in #RequiredArgsConstructor.
Lombok works at compile time while Spring works at runtime.
So when you place a Lombok annotation of the constructor (#AllArgsConstructor, #RequiredArgsConstructor, etc) on your class (it can be a regular java class, spring bean, whatever), Lombok creates a constructor for you.
In this case, it will create a constructor for all final fields (see the documentation
As you see lombok doesn't take into consideration the #Autowired annotation,
Since you haven't placed #NonNull annotation on drMessage (check the documentation) it won't generate a constructor parameter for it, so your class will look like:
#RequiredArgsConstructor
#Service
public class ServiceImpl implements IService {
#Autowired
private final DtoMapper dtoMapper;
#Autowired
private final CDtoMapper cDtoMapper;
private DrMessage drMessage;
public ServiceImpl(DtoMapper dtoMapper, cDtoMapper cDtoMapper) {
this.dtoMapper = dtoMapper;
this.cDtoMapper = cDtoMapper;
}
**other sentences**
}
So if you want the lombok to generate a parameter for DrMessage make it final, or put a #NonNull annotation on it.
At this point the job of Lombok is done, in fact, you can exclude it from being available at runtime at all. Now regarding the spring part:
First of all, you say that you by yourself are trying to create the instance of ServiceImpl class, why? It's a spring bean (you've put a #Service annotation on it), so let Spring manage this class.
With this definition, placing #Autowired on the final field won't work in spring.
See this thread, so you should not place #Autowired on these fields.
Luckily Spring in its recent versions is smart enough to understand that if the class has a single constructor (in your case the one that you've generated with Lombok) spring will call it to create the instance of your class, so instead of field injection, you'll use constructor injection.

Get outer class by member annotation

I have a class with custom annotation for one of class field:
public class Test {
#CustomAnnotation
private String name;
...
}
I just want to know if it possible to get Class<Test> by this annotation? Can't find any suitable api..
public Class<?> getOuterClass(CustomAnnotation annotation) {
...
}
#CustomAnnotation is declared as #Retention(RetentionPolicy.RUNTIME)
No, annotation does not store any data about where it was declared.
Also annotation can work just like any normal interface, so someone can implement annotation in class an make instances of it that were never used as annotations.
You need either include that information yourself, by adding parameter to annotation and then using it #CustomAnn(Test.class) or when reading annotations just remember and include that information yourself in some other object.

Does #CacheEvict annotation work on private methods?

I am trying to use #Cachable and #CacheEvict for managing redis cache via spring framework. Can one add #CacheEvict on private methods of the class?
#Cacheable is only evaluated when called between beans, even for public methods. Ie for this:
public class MyBean {
#Cacheable
public String getString(int i) {
return Integer.toString(i);
}
public void myOtherMethod() {
String myString = getString(2);
}
}
the caching will not be triggered.
Therefore, it does not make sense to declare a private method with #Cacheable.
Note that the same is true for Aspects (as suggested in the other solution); those are not triggered when calling intra-class methods either.
Method visibility and #Cacheable/#CachePut/#CacheEvict
When using proxies, you should apply the #Cache* annotations only to methods with public visibility. If you do annotate protected, private or package-visible methods with these annotations, no error is raised, but the annotated method does not exhibit the configured caching settings. Consider the use of AspectJ (see below) if you need to annotate non-public methods as it changes the bytecode itself.
Ref : https://docs.spring.io/spring/docs/3.2.0.RC1/reference/html/cache.html

Spring Cache not working for abstract classes

I'm trying to use Spring Cache within abstract classes but it won't work, because, from what I can see, Spring is searching for CacheNames on the abstract class. I'm having a REST API which uses a service layer and a dao layer. The idea is to have a different cache name for every subclass.
My abstract service class looks like this:
#Service
#Transactional
public abstract class AbstractService<E> {
...
#Cacheable
public List<E> findAll() {
return getDao().findAll();
}
}
An extension of the abstract class would look like this:
#Service
#CacheConfig(cacheNames = "textdocuments")
public class TextdocumentsService extends AbstractService<Textdocuments> {
...
}
So when I start the application with this code, Spring gives me the following exception:
Caused by: java.lang.IllegalStateException: No cache names could be detected on 'public java.util.List foo.bar.AbstractService.findAll()'. Make sure to set the value parameter on the annotation or declare a #CacheConfig at the class-level with the default cache name(s) to use.
at org.springframework.cache.annotation.SpringCacheAnnotationParser.validateCacheOperation(SpringCacheAnnotationParser.java:240) ~[spring-context-4.1.6.RELEASE.jar:?]
I think this happens because Spring is searching for the CacheName on the abstract class, despite it is being declared on the subclass.
Trying to use
#Service
#Transactional
#CacheConfig
public abstract class AbstractService<E> {
}
leads to the same exception; using
#Service
#Transactional
#CacheConfig(cacheNames = "abstractservice")
public abstract class AbstractService<E> {
}
gives no exception, but then Spring Cache uses the same cache name for every subclass and ignores the cache name defined on the subclass. Any Ideas to so solve this?
This problem has been addressed in another question and is less about abstract classes and more about the framework's ability to figure out which cache to use.
Long story short (quoting from Spring documentation) you are missing appropriate CacheResolver that will work with your abstract class hierarchy:
Since Spring 4.1, the value attribute of the cache annotations are no longer mandatory, since this particular information can be provided by the CacheResolver regardless of the content of the annotation.
Therefore, your abstract class should define a caching resolver instead of directly stating the cache name.
abstract class Repository<T> {
// .. some methods omitted for brevity
#Cacheable(cacheResolver = CachingConfiguration.CACHE_RESOLVER_NAME)
public List<T> findAll() {
return getDao().findAll();
}
}
The resolver determines the Cache instance(s) to use for an intercepted method invocation. A very naive implementation can take the target repository bean (by name) and use it as the cache name
class RuntimeCacheResolver
extends SimpleCacheResolver {
protected RuntimeCacheResolver(CacheManager cacheManager) {
super(cacheManager);
}
#Override
protected Collection<String> getCacheNames(CacheOperationInvocationContext<?> context) {
return Arrays.asList(context.getTarget().getClass().getSimpleName());
}
}
Such resolver needs an explicit configuration:
#Configuration
#EnableCaching
class CachingConfiguration extends CachingConfigurerSupport {
final static String CACHE_RESOLVER_NAME = "simpleCacheResolver";
#Bean
#Override
public CacheManager cacheManager() {
return new ConcurrentMapCacheManager();
}
#Bean(CACHE_RESOLVER_NAME)
public CacheResolver cacheResolver(CacheManager cacheManager) {
return new RuntimeCacheResolver(cacheManager);
}
}
I've create a Gist which describes the whole concept in more details.
Disclaimer
The above snippets are just for demonstration and are intended to give direction than to provide a complete solution. The above cache resolver implementation is very naive and doesn't consider many things (like method parameters etc.). I'd never use it in a production environment.
The way Spring handles caching is through proxies, where the #Cacheable annotation declares the cache, together with naming information processed on runtime. The cache is resolved through runtime information provided to cache resolver (no surprise it resembles some similarities to InvocationContext of classical AOP).
public interface CacheOperationInvocationContext<O extends BasicOperation> {
O getOperation();
Object getTarget();
Method getMethod();
Object[] getArgs();
}
Through the getTarget() method it is possible to figure out which bean is proxied, but in real-life, more information should be taken into account, to provide a reliable cache (like method parameters, etc).

Passing annotation properties to meta-annotations

Say I have an annotation with a property:
#Named(name = "Steve")
private Person person
and I want to create a compound annotation with several meta-annotations, including the one that takes a property
#Named
#AnotherAnnotation
#YetAnotherAnnotation
public #interface CompoundAnnotation {
...
}
Is there a way that I can pass properties to the compound annotation to one of the meta annotations?
Eg, something like this:
#CompoundAnnotation(name = "Bob")
private Person person;
that is equivalent to, but much more convenient than
#Named(name = "Bob")
#AnotherAnnotation
#YetAnotherAnnotation
private Person person;
Thanks!
PS apologies for my poor choice of an example annotation - I didn't have the javax.inject.#Named annotation in mind, just some arbitrary annotation that has properties.
Thank you everyone for your answers/comments.
It definitely seems to be the case that this is not possible. However, it just happens that there is a simple work-around for my case-in-point, which I will share in case it helps anyone:
I am working with Spring and want to create my own Annotations that have #Component as a meta-annotation, thus being autodetected by component scanning. However, I also wanted to be able to set the BeanName property (corresponding to the value property in #Component) so I could have custom bean names.
Well it turns out that the thoughtful guys at Spring made it possible to do just that - the AnnotationBeanNameGenerator will take the 'value' property of whatever annotation it is passed and use that as the bean name (and of course, by default, it will only get passed annotations that are #Component or have #Component as a meta-annotation). In retrospect this should have been obvious to me from the start - this is how existing annotations with #Component as a meta-annotation, such as #Service and #Registry, can provide bean names.
Hope that is useful to someone. I still think it's a shame that this is not possible more generally though!
It is a few years later now, and since you are using Spring, what you are asking for is sort of possible now using the #AliasFor annotation.
For example:
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.TYPE)
#SpringApplicationConfiguration
#ActiveProfiles("test")
public #interface SpringContextTest {
#AliasFor(annotation = SpringApplicationConfiguration.class, attribute = "classes")
Class<?>[] value() default {};
#AliasFor("value")
Class<?>[] classes() default {};
}
Now you can annotate your test with #SpringContextTest(MyConfig.class), and the amazing thing is that it actually works the way you would expect.
N.B. When you need to programmatically get the attribute values, the Spring automagical aliasing works only when you use AnnotatedElementUtils instead of AnnotationUtils, as the documentation says:
AnnotatedElementUtils defines the public API for Spring's meta-annotation programming model with support for annotation attribute overrides. If you do not need support for annotation attribute overrides, consider using AnnotationUtils instead.
Example:
final Named namedAnnotation = AnnotatedElementUtils.findMergedAnnotation(Person.class, Named.class);
final String name = namedAnnotation.name();
assertEquals("Steve", name);
Is there a way that I can pass properties to the compound annotation to one of the meta annotations?
I think the simple answer is "no". There is no way to ask Person what annotations it has on it and get #Named for example.
The more complex answer is that you can chain annotations but you would have to investigate these annotations via reflection. For example, the following works:
#Bar
public class Foo {
public static void main(String[] args) {
Annotation[] fooAnnotations = Foo.class.getAnnotations();
assertEquals(1, fooAnnotations.length);
for (Annotation annotation : fooAnnotations) {
Annotation[] annotations =
annotation.annotationType().getAnnotations();
assertEquals(2, annotations.length);
assertEquals(Baz.class, annotations[0].annotationType());
}
}
#Baz
#Retention(RetentionPolicy.RUNTIME)
public #interface Bar {
}
#Retention(RetentionPolicy.RUNTIME)
public #interface Baz {
}
}
However the following statement will return null:
// this always returns null
Baz baz = Foo.class.getAnnotation(Baz.class)
This means that any 3rd party class that is looking for the #Baz annotation won't see it.

Categories

Resources