How to use Java annotations rention policy for CLASS - java

I'm using annotations for generating documentation for an API that I'm publishing. I have it defined like this:
#Documented
#Target(ElementType.METHOD)
#Retention(RetentionPolicy.RUNTIME)
public #interface PropertyInfo {
String description();
String since() default "5.8";
String link() default "";
}
Now this works fine when I process the classes using reflection. I can get the list of annotations on the method. The issue I have is that it only works if I instantiate a new instance of the object I'm processing. I would prefer not to have to instantiate them to get the annotation. I tried RetentionPolicy.CLASS but it doesn't work.
Any ideas?

You don't need to instantiate an object, you just need the class. Here is an example:
public class Snippet {
#PropertyInfo(description = "test")
public void testMethod() {
}
public static void main(String[] args) {
for (Method m : Snippet.class.getMethods()) {
if (m.isAnnotationPresent(PropertyInfo.class)) {
System.out.println("The method "+m.getName()+
" has an annotation " + m.getAnnotation(PropertyInfo.class).description());
}
}
}
}

Starting from Java5, classes are loaded lazily.
There are somes rules that determine if a class should be loaded.
The first active use of a class occurs when one of the following occurs:
An instance of that class is created
An instance of one of its subclasses is initialized
One of its static fields is initialized
So, in your case, merely referencing its name for reflection purposes is not enough to trigger its loading, and you cannot see the annotations.

You can get the annotations for a class using bean introspection:
Class<?> mappedClass;
BeanInfo info = Introspector.getBeanInfo(mappedClass);
PropertyDescriptor[] descriptors = info.getPropertyDescriptors();
for (PropertyDescriptor descriptor : descriptors) {
Method readMethod = descriptor.getReadMethod();
PropertyInfo annotation = readMethod.getAnnotation(PropertyInfo.class);
if (annotation != null) {
System.out.println(annotation.description());
}
}

Related

Java Annotation how to get current ElemenType of specific annotation

My annotation:
#Target({ElementType.TYPE, ElementType.FIELD})
#Retention(RetentionPolicy.RUNTIME)
public #interface ObjectName {
String name() default "";
String field() default "";
}
Some class with my annotation
#ObjectName("a_")
public class A {
#ObjectName("field_")
String filed;
}
Problem - when i get all my "ObjectName" annotations from class above, how can i get annotation's ElementType value (field, class or method type)?
So i want something like this
public void process(Class<?> clazz) {
Annotation[] annotations = clazz.getAnnotations();
for (Annotation anno : annotations) {
if (anno instanceof ObjectName) {
ObjectName annObjName = (ObjectName) anno;
Target target = anno.getAnnotation(Target.class);
if (target.getType().equals(ElementType.TYPE)
doThat(annObjName.name());
else if (target.getType().equals(ElementType.FIELD)
doThis(annObjName.field());
}
}
}
Can i even do this?
How can i do this or how can i find out if this annotation declared on filed or class?
You can't.
All you can do is look at where you call getAnnotations(), because you seem to incorrectly believe that clazz.getAnnotations() will return all annotations on everything in the class. That is false. When you call clazz.getAnnotations(), you will only get the annotations directly on the class. To get annotations on fields, you must call clazz.getFields(), and then call getAnnotations() on the Field elements. So there's no risk of getting them mixed up as long as you keep those straight.

Does JDK automatically generate proxy classes for custom annotations?

Here is my code(JDK1.8):
#Retention(RetentionPolicy.RUNTIME)
#Target(value={ElementType.TYPE})
#Documented
public #interface MyAnnotation {
}
#MyAnnotation
public class Main {
public static void main(String[] args) {
Class<Main> cls = Main.class;
Annotation[] annotations = cls.getDeclaredAnnotations();
Arrays.stream(annotations).forEach(an -> {
if (an instanceof MyAnnotation) {
System.out.println("proxy");
} else {
System.out.println("???");
}
System.out.println(an.getClass().getName());
});
}
}
i test on windows,centos7 and ubuntu got the same result:
proxy
com.sun.proxy.$Proxy1
My question: Is this my environment problem or JDK will automatically generate a proxy class? if it is the latter,why? Thanks in advance.
See 9.6. Annotation Types:
An annotation type declaration specifies a new annotation type, a special kind of interface type."
As interfaces cannot be instatiated directly, there has to be some kind of object implementing that interface that is returned from the reflection API methods. Whether that is a proxy or some other kind of anonymous class is up to the implementation.

#ConditionalOnProperty for lists or arrays?

I'm using Spring Boot 1.4.3 #AutoConfiguration where I create beans automatically based on properties user specifies. User can specify an array of services, where name and version are required fields:
service[0].name=myServiceA
service[0].version=1.0
service[1].name=myServiceB
service[1].version=1.2
...
If the user forgets to specify a required field on even just one service, I want to back-off and not create any beans. Can I accomplish this with #ConditionalOnProperty? I want something like:
#Configuration
#ConditionalOnProperty({"service[i].name", "service[i].version"})
class AutoConfigureServices {
....
}
This is the custom Condition I created. It needs some polishing to be more generic (ie not hardcoding strings), but worked great for me.
To use, I annotated my Configuration class with #Conditional(RequiredRepeatablePropertiesCondition.class)
public class RequiredRepeatablePropertiesCondition extends SpringBootCondition {
private static final Logger LOGGER = LoggerFactory.getLogger(RequiredRepeatablePropertiesCondition.class.getName());
public static final String[] REQUIRED_KEYS = {
"my.services[i].version",
"my.services[i].name"
};
#Override
public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
List<String> missingProperties = new ArrayList<>();
RelaxedPropertyResolver resolver = new RelaxedPropertyResolver(context.getEnvironment());
Map<String, Object> services = resolver.getSubProperties("my.services");
if (services.size() == 0) {
missingProperties.addAll(Arrays.asList(REQUIRED_KEYS));
return getConditionOutcome(missingProperties);
}
//gather indexes to check: [0], [1], [3], etc
Pattern p = Pattern.compile("\\[(\\d+)\\]");
Set<String> uniqueIndexes = new HashSet<String>();
for (String key : services.keySet()) {
Matcher m = p.matcher(key);
if (m.find()) {
uniqueIndexes.add(m.group(1));
}
}
//loop each index and check required props
uniqueIndexes.forEach(index -> {
for (String genericKey : REQUIRED_KEYS) {
String multiServiceKey = genericKey.replace("[i]", "[" + index + "]");
if (!resolver.containsProperty(multiServiceKey)) {
missingProperties.add(multiServiceKey);
}
}
});
return getConditionOutcome(missingProperties);
}
private ConditionOutcome getConditionOutcome(List<String> missingProperties) {
if (missingProperties.isEmpty()) {
return ConditionOutcome.match(ConditionMessage.forCondition(RequiredRepeatablePropertiesCondition.class.getCanonicalName())
.found("property", "properties")
.items(Arrays.asList(REQUIRED_KEYS)));
}
return ConditionOutcome.noMatch(
ConditionMessage.forCondition(RequiredRepeatablePropertiesCondition.class.getCanonicalName())
.didNotFind("property", "properties")
.items(missingProperties)
);
}
}
Old question, but I hope my answer will help for Spring2.x:
Thanks to #Brian, I checked migration guide, where I was inspired by example code. This code works for me:
final List<String> services = Binder.get(context.getEnvironment()).bind("my.services", List.class).orElse(null);
I did try to get List of POJO (as AutoConfigureService) but my class differs from AutoConfigureServices. For that purpose, I used:
final Services services = Binder.get(context.getEnvironment()).bind("my.services", Services.class).orElse(null);
Well, keep playing :-D
Here's my take on this issue with the use of custom conditions in Spring autoconfiguration. Somewhat similar to what #Strumbels proposed but more reusable.
#Conditional annotations are executed very early in during the application startup. Properties sources are already loaded but ConfgurationProperties beans are not yet created. However we can work around that issue by binding properties to Java POJO ourselves.
First I introduce a functional interface which will enable us to define any custom logic checking if properties are in fact present or not. In your case this method will take care of checking if the property List is empty/null and if all items within are valid.
public interface OptionalProperties {
boolean isPresent();
}
Now let's create an annotation which will be metannotated with Spring #Conditional and allow us to define custom parameters. prefix represents the property namespace and targetClass represents the configuration properties model class to which properties should be mapped.
#Target({ElementType.TYPE, ElementType.METHOD})
#Retention(RetentionPolicy.RUNTIME)
#Documented
#Conditional(OnConfigurationPropertiesCondition.class)
public #interface ConditionalOnConfigurationProperties {
String prefix();
Class<? extends OptionalProperties> targetClass();
}
And now the main part. The custom condition implementation.
public class OnConfigurationPropertiesCondition extends SpringBootCondition {
#Override
public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
MergedAnnotation<ConditionalOnConfigurationProperties> mergedAnnotation = metadata.getAnnotations().get(ConditionalOnConfigurationProperties.class);
String prefix = mergedAnnotation.getString("prefix");
Class<?> targetClass = mergedAnnotation.getClass("targetClass");
// type precondition
if (!OptionalProperties.class.isAssignableFrom(targetClass)) {
return ConditionOutcome.noMatch("Target type does not implement the OptionalProperties interface.");
}
// the crux of this solution, binding properties to Java POJO
Object bean = Binder.get(context.getEnvironment()).bind(prefix, targetClass).orElse(null);
// if properties are not present at all return no match
if (bean == null) {
return ConditionOutcome.noMatch("Binding properties to target type resulted in null value.");
}
OptionalProperties props = (OptionalProperties) bean;
// execute method from OptionalProperties interface
// to check if condition should be matched or not
// can include any custom logic using property values in a type safe manner
if (props.isPresent()) {
return ConditionOutcome.match();
} else {
return ConditionOutcome.noMatch("Properties are not present.");
}
}
}
Now you should create your own configuration properties class implementing OptionalProperties interface.
#ConfigurationProperties("your.property.prefix")
#ConstructorBinding
public class YourConfigurationProperties implements OptionalProperties {
// Service is your POJO representing the name and version subproperties
private final List<Service> services;
#Override
public boolean isPresent() {
return services != null && services.stream().all(Service::isValid);
}
}
And then in Spring #Configuration class.
#Configuration
#ConditionalOnConfigurationProperties(prefix = "", targetClass = YourConfigurationProperties.class)
class AutoConfigureServices {
....
}
There are two downsides to this solution:
Property prefix must be specified in two locations: on #ConfigurationProperties annotation and on #ConditionalOnConfigurationProperties annotation. This can partially be alleviated by defining a public static final String PREFIX = "namespace" in your configuration properties POJO.
Property binding process is executed separately for each use of our custom conditional annotation and then once again to create the configuration properties bean itself. It happens only during app startup so it shouldn't be an issue but it still is an inefficiency.
You can leverage the org.springframework.boot.autoconfigure.condition.OnPropertyListCondition class. For example, given you want to check for the service property having at least one value:
class MyListCondition extends OnPropertyListCondition {
MyListCondition() {
super("service", () -> ConditionMessage.forCondition("service"));
}
}
#Configuration
#Condition(MyListCondition.class)
class AutoConfigureServices {
}
See the org.springframework.boot.autoconfigure.webservices.OnWsdlLocationsCondition used on org.springframework.boot.autoconfigure.webservices.WebServicesAutoConfiguration#wsdlDefinitionBeanFactoryPostProcessor for an example within Spring itself.

How to access annotation variables from interceptor

Lets say I have simple annotation
#InterceptorBinding
#Retention(RUNTIME) #Target({TYPE, METHOD})
public #interface CheckUserRole {
#Nonbinding String[] allowedRoles() default "";
}
Next, I have interceptor defined like this:
#Interceptor
#CheckUserRole
public class CheckUserRoleInterceptor
How can I access allowedRoles from annotation? I'm doing it this way:
CheckUserRole checkUserRoleAnnotation = ctx.getMethod().getAnnotation(CheckUserRole.class);
if(checkUserRoleAnnotation != null){
String[] allowedRoles = checkUserRoleAnnotation.allowedRoles();
}
But this only works if I use annotation on method in my class. If I want to use my annotation on whole class, checkUserRoleAnnotation is null, since my method ins't annotated in code with it.
How can I access those variables when whole class is annotated?
I've found good solution for this problem. To access annotation from method or class (if there is no annotation on method level) just use my function:
// Returns annotation from method or class (if does not exist, returns null)
#SuppressWarnings({ "unchecked", "rawtypes" })
protected Object getAnnotationClass(Class clazz, InvocationContext ctx){
Object annotationClass = ctx.getMethod().getAnnotation(clazz);
// Try to find annotation on class level
if(annotationClass == null){
annotationClass = ctx.getTarget().getClass().getSuperclass().getAnnotation(clazz);
}
return annotationClass;
}
My function does:
Check if there is annotation on method level
If not, check if there is annotation on class level
If still not, return null
Note using .getSuperclass() since .getClass() returns proxy.
Example of usage:
CheckUserRole annotationClass = (CheckUserRole) getAnnotationClass(CheckUserRole.class, ctx);
if(annotationClass != null){
String[] allowedRoles = annotationClass.allowedRoles();
}
Use :
#Context
ResourceInfo resourceInfo;
in your interceptor class.With ResourceInfo object you will be able to get associated class as well as method(and other useful info).
after that use
Annotationabc abc = resourceInfo.getResourceMethod().getAnnotation(Annotationabc .class)
to get the annotation on top of ur method

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