Spring Framework AliasFor annotation dilema - java

I am using spring boot (1.3.4.RELEASE) and have a question regarding the new #AliasFor annotation introduced spring framework in 4.2
Consider the following annotations:
View
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.TYPE)
#Component
public #interface View {
String name() default "view";
}
Composite
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.TYPE)
#View
public #interface Composite {
#AliasFor(annotation = View.class, attribute = "name")
String value() default "composite";
}
We then annotate a simple class as follows
#Composite(value = "model")
public class Model {
}
When running the following code
ConfigurableApplicationContext context = SpringApplication.run(App.class, args);
String[] beanNames = context.getBeanNamesForAnnotation(View.class);
for (String beanName : beanNames) {
View annotationOnBean = context.findAnnotationOnBean(beanName, View.class);
System.out.println(annotationOnBean.name());
}
I am expecting the output to be model, but it's view.
From my understanding, shouldn't #AliasFor (among other things) allow you to override attributes from meta-annotations (in this case #View)?
Can someone explain to me what am I doing wrong?
Thank you

Take a look at the documentation for #AliasFor, and you will see this quite in the requirements for using the annotation:
Like with any annotation in Java, the mere presence of #AliasFor on its own will not enforce alias semantics.
So, trying to extract the #View annotation from your bean is not going to work as expected. This annotation does exist on the bean class, but its attributes were not explicitly set, so they cannot be retrieved in the traditional way. Spring offers a couple utility classes for working with meta-annotations, such as these. In this case, the best option is to use AnnotatedElementUtils:
ConfigurableApplicationContext context = SpringApplication.run(App.class, args);
String[] beanNames = context.getBeanNamesForAnnotation(View.class);
for (String beanName : beanNames) {
Object bean = context.getBean(beanName);
View annotationOnBean = AnnotatedElementUtils.findMergedAnnotation(bean, View.class);
System.out.println(annotationOnBean.name());
}

Related

How to retrieve custom annotation fields?

I would like to implement a custom annotation that could be applied to a class (once inside an app), to enable a feature (Access to remote resources). If this annotation is placed on any config class, it will set the access for the whole app. So far it isn't that hard (see example below), but I want to include some definition fields in the #interface that will be used in the access establishing process.
As an example, Spring has something very similar: #EnableJpaRepositories. Access is enabled to the DB, with parameters in the annotation containing definitions. For example: #EnableJpaRepositories(bootstrapMode = BootstrapMode.DEFERRED)
So far, I have:
To create only the access I'm using something like that:
#Target(ElementType.TYPE)
#Retention(RetentionPolicy.RUNTIME)
#Import(AccessHandlerConfiguration.class)
public #interface EnableAccessHandlerAutoconfigure {
String name() default "";
}
Using it:
#EnableAccessHandlerAutoconfigure{name="yoni"}
#Configuration
public class config {}
AccessHandlerConfiguration is a configuration class that contains beans that establish the connection.
The problem I'm having is that I don't know how to retrieve the field name's value. What should I do?
Retrieving the value may be accomplished as follows:
this.getClass().getAnnotation(EnableAccessHandlerAutoconfigure.class).name()
To expand on my comment with an actual example configuration class that uses this:
#EnableAccessHandlerAutoconfigure(name="yoni")
#Configuration
public class SomeConfiguration {
#Bean
SomeBean makeSomeBean() {
return new SomeBean(this.getClass().getAnnotation(EnableAccessHandlerAutoconfigure.class).name());
}
}
This is how you get the value of name, as to what you are going to do next, that depends on you.
After a long research, I found a way: There is a method in Spring's ApplicationContext that retrieves bean names according to their annotations getBeanNamesForAnnotation, then get the annotation itself findAnnotationOnBean, and then simply use the field getter.
#Configuration
public class AccessHandlerConfiguration {
private final ApplicationContext applicationContext;
public AccessHandlerConfiguration(ApplicationContext applicationContext) {
this.applicationContext = applicationContext;
String[] beansWithTheAnnotation = applicationContext.getBeanNamesForAnnotation(EnableRabbitAutoconfigure.class);
for (String beanName : beansWithTheAnnotation) {
EnableRabbitAutoconfigure annotationOnBean = applicationContext.findAnnotationOnBean(beanName, EnableRabbitAutoconfigure.class);
System.out.println("**********" + beanName + "*********************" + annotationOnBean.name() + "*******************");
}
}
}
Results:
**********config*********************yoni*******************

Spring Boot annotation attribute overriding doesn't work on beans

I'm trying to create a custom annotation for enabling functionality based on feature flags. I'm basing my approach on the ConditionalOnProperty annotation, but to enable code reuse I want to define a common prefix that can be used wherever I need this functionality.
I have defined my annotation as below.
#Retention(RetentionPolicy.RUNTIME)
#Target({ ElementType.TYPE, ElementType.METHOD })
#Documented
#ConditionalOnProperty(prefix = "foo.bar")
public #interface ConditionalOnFeature {
#AliasFor(annotation = ConditionalOnProperty.class, attribute = "value")
String[] value();
}
I'm using the AliasFor annotation to set the value attribute in the ConditionalOnProperty annotation, and hardcoding my prefix as foo.bar (all my features will sit under this property). As far as I understand Spring annotation overriding, this should work.
However when I use this on a class (for example a controller), the code fails to compile, stating that The name or value attribute of #ConditionalOnProperty must be specified.
#RestController
#ConditionalOnFeature("enable-fancy-controller")
public class FancyController {
#RequestMapping(value = "/fancy-method", method = RequestMethod.GET)
public String fancyMethod() {
return "Fancy";
}
}
However it works perfectly fine when I instead use my annotation on a method. Compiles successfully and behaves as intended.
#RestController
public class FancierController {
#ConditionalOnFeature("enable-fancier-method")
#RequestMapping(value = "/fancier-method", method = RequestMethod.GET)
public String fancierMethod() {
return "Fancier";
}
}
Have I configured my annotation wrong? Or is this some bug with Spring?

How do I prevent Spring Boot AOP from removing type annotations?

I am pretty new to Spring Boot and its flavor of AOP, but not new to programming in other languages and AOP frameworks. This one challenge I am not sure how to solve.
I have a simple metadata decorator:
#Target(ElementType.TYPE)
#Retention(RetentionPolicy.RUNTIME)
public #interface GreetingsMeta {
public float version() default 0;
public String name() default "";
}
It works just fine with dependency injection:
public GreetingController(List<IGreetingService> greetings) throws Exception {
this.greetings = new HashMap<>();
greetings.forEach(m -> {
Class<?> clazz = m.getClass();
if (clazz.isAnnotationPresent(GreetingsMeta.class)) {
GreetingsMeta[] s = clazz.getAnnotationsByType(GreetingsMeta.class);
this.greetings.put(s[0].name(), m);
}
});
}
Until I applied a standard logging aspect:
#Aspect
#Component
public class LoggingAspect {
#Around("execution(* com.firm..*(..)))")
public Object profileAllMethods(ProceedingJoinPoint joinPoint) throws Throwable {
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
String methodName = methodSignature.getName();
final StopWatch stopWatch = new StopWatch();
stopWatch.start();
Object result = joinPoint.proceed();
stopWatch.stop();
LogManager.getLogger(methodSignature.getDeclaringType())
.info(methodName + " " + (stopWatch.getTotalTimeSeconds() * 1000) + " µs");
return result;
}
}
Then the list of annotationsData becomes empty, even the #Component annotation is gone.
Sample meta-decorated class:
#Component
#GreetingsMeta(name = "Default", version = 1.0f)
public class DefaultGreetingsService implements IGreetingService {
#Override
public String message(String content) {
return "Hello, " + content;
}
}
How should I troubleshoot?
How do I prevent Spring Boot AOP from removing type annotations?
Spring Boot does not remove anything, but for Spring AOP is uses dynamic proxies generated during runtime, i.e. subclasses or interface implementations with event hooks (joinpoints) for aspect advice code wired in via pointcuts. By default, annotations are not inherited, so this is just a JVM feature.
There is one exception for subclasses inheriting annotations from parent classes: You can add the meta annotation #Inherited to your own annotation class GreetingsMeta. The effect will be that if you annotate any class with it, all subclasses (also dynamic proxies created by Spring AOP) will inherit the annotation and your original code should run as expected.
So in this case there is no need to use AnnotationUtils as suggested by JC Carrillo. His approach works too, of course. It is just more complicated because AnnotationUtils uses a lot of reflection magic and lots of helper classes internally in order to compute results. Thus, I would only use AnnotationUtils in cases where you don't directly annotate a class but e.g. methods or interfaces because #Inherited has no effect on them as documented. Or if you rely on a hierarchy of Spring (or own) meta annotations (annotations on annotations) and you need to get information from them all merged into one, AnnotationUtils or MergedAnnotations are appropriate.
You may want to look into AnnotationUtils
Method method = methodSignature.getMethod();
GreetingsMeta greetingsMeta = AnnotationUtils.findAnnotation(method, GreetingsMeta.class);

Can we have enumerated or predefined annotations, which could be code-reused?

Let us define a annotation as below:
#Target(ElementType.METHOD)
#Retention(RetentionPolicy.RUNTIME)
public #interface CheckSomething {
String validIdentity() default "";
String validUserGroup() default "google";
}
I have some set of annotations which is possible in my applicable and I would like to use them.
For example:
2 annotations below:
HumanCheckSomething, RobotCheckSomething - calling these for identification
#CheckSomething(validIdentity = "IAMSTRING", validUserGroup = "HUMANS")
#CheckSomething(validIdentify = "123", validUserGroup = "ROBOTS")
Now, I would like to create an enum using above annotations - HumanCheckSomething, RobotCheckSomething and reuse in my code, to annotate some methods.
#MyEnum.HumanCheckSomething
void allowOnlyHumans(){}
#MyEnum.RobotCheckSomething
void allowAll(){}
instead of
#CheckSomething(validIdentity = "IAMSTRING", validUserGroup = "HUMANS")
void allowOnlyHumans(){}
#CheckSomething(validIdentify = "123", validUserGroup = "ROBOTS")
void allowAll(){}
No, that's not possible.
But many frameworks use "meta-annotations" to solve this problem: you define your own annotation (CheckHumanfor example), which is itself annotated with the original annotation (#CheckSomething(validIdentity = "IAMSTRING", validUserGroup = "HUMANS") for example). Then, when you annotate some class (or field, or method, or whatever) with #CheckHuman, the framework does the same thing as if it was annotated directly with the original annotation.
See the Spring's GetMapping annotation, for example, which is a meta-annotation for #RequestMapping(method=GET).
Another advantage of this pattern is that it allows combining several annotations in one. For example #RestController is a meta-annotation which combines #Controller and #ResponseBody.

How to auto-generate docs for classes annotated spring jmx annotations

I have some code that uses these spring annotations:
org.springframework.jmx.export.annotation.ManagedAttribute;
org.springframework.jmx.export.annotation.ManagedOperation;
org.springframework.jmx.export.annotation.ManagedOperationParameter;
org.springframework.jmx.export.annotation.ManagedOperationParameters;
org.springframework.jmx.export.annotation.ManagedResource;
I want to generate some documentation (even just javadocs) using the comments in the annotations, for example consider the following method?
#ManagedOperation(description="Does foo to bar")
#ManagedOperationParameters({
#ManagedOperationParameter(name = "bar", description = "The bar you want to foo.")})
public long fooBar( Bar bar) throws Exception {
...
}
Is there some way I can automatically generate docs for this, or will I have to duplicate all the annotation strings in javadoc in addition to it?
First, create a custom AnnotationMbeanExporter with a public method that delegates to getRegisteredObjectNames(). Use this as your mbeanExporter.
For example:
#Component
// This is a copy of the AnnotationMBeanExporter with a public version of getRegisteredObjectNames()
public class AnnotationMBeanExporter extends MBeanExporter {
#Autowired
MBeanServer mbeanServer;
AnnotationJmxAttributeSource annotationSource = new AnnotationJmxAttributeSource();
AnnotationMBeanExporter() {
setServer(mbeanServer);
setNamingStrategy(new MetadataNamingStrategy(annotationSource));
setAssembler(new MetadataMBeanInfoAssembler(annotationSource));
setAutodetectMode(MBeanExporter.AUTODETECT_ALL);
}
public ObjectName[] getExportedObjectNames() {
return getRegisteredObjectNames();
}
}
Then for your report, iterate over the object names returned from getExportedObjectNames() and get the relevant metadata for each JMX bean.
For example:
for (ObjectName objectName: mbeanExporter.getExportedObjectNames()) {
MBeanInfo mbeanInfo = mbeanServer.getMBeanInfo(objectName);
MBeanOperationInfo[] operations = mbeanInfo.getOperations();
// etc.
}

Categories