I have created my own annotation type like this:
public #interface NewAnnotationType {}
and attached it to a class:
#NewAnnotationType
public class NewClass {
public void DoSomething() {}
}
and I tried to get the class annotation via reflection like this :
Class newClass = NewClass.class;
for (Annotation annotation : newClass.getDeclaredAnnotations()) {
System.out.println(annotation.toString());
}
but it's not printing anything. What am I doing wrong?
The default retention policy is RetentionPolicy.CLASS which means that, by default, annotation information is not retained at runtime:
Annotations are to be recorded in the class file by the compiler but need not be retained by the VM at run time. This is the default behavior.
Instead, use RetentionPolicy.RUNTIME:
Annotations are to be recorded in the class file by the compiler and retained by the VM at run time, so they may be read reflectively.
...which you specify using the #Retention meta-annotation:
#Retention(RetentionPolicy.RUNTIME)
public #interface NewAnnotationType {
}
Having the default Retention of an annotation does not mean that you can not read it at run-time.
Since
Annotations are to be recorded in the class file by the compiler
but need not be retained by the VM at run time. This is the default behavior.
It is possible to access them reading the .class file directly
This can be accomplished by using the ASM library (handling some corner cases, of course).
Check out its excellent User guide. In particular section 4.2 Annotations.
You may want to refer to the Spring framework's handling of such annotations (it uses shaded asm dependency):
SimpleAnnotationMetadataReadingVisitor
AnnotationMetadataReadingVisitor (deprecated)
Related
In the javadoc for java.lang.annotation.RetentionPolicy it says that:
SOURCE: Annotations are to be discarded by the compiler.
CLASS: Annotations are to be recorded in the class file by the compiler but need not be retained by the VM at run time. This is the default behavior.
RUNTIME: Annotations are to be recorded in the class file by the compiler and retained by the VM at run time, so they may be read reflectively.
I understand, that RUNTIME is used to access it using the reflection API, SOURCE for compiler related information (and maybe documentation) and CLASS, as far as I could find out, for special cases, like bytecode manipulation tools, etc. and also compiler related stuff, like #FunctionInterface.
But why is CLASS the default? I expect most annotations to be annotated with RUNTIME, because I think, that most programmers use annotations to specify metadata, that should be read through the reflection API at runtime, because the average programmer doesn't play around with the generated bytecode (at least I've never done it).
So why is RUNTIME not the default? Is there any use case for CLASS, that at I'm not aware of? Or is this just another case of this decision was made long ago, for now unknown or irrelevant reasons and can't be changed, because that would break stuff.
At least for beginners, it may be very confusing and can lead to bugs, that the code
package test;
import java.lang.annotation.Annotation;
import test.Test.Example;
#Example("example")
public class Test {
public static void main(String[] args) {
Annotation[] annotations = Test.class.getAnnotations();
if (annotations.length == 0) {
System.out.println("Class Test has no annotations");
} else {
System.out.println("Class Test has the following annotations:");
for (Annotation annotation : annotations) {
System.out.println("\t" + annotation.toString());
}
}
}
// #Retention(RetentionPolicy.RUNTIME)
public static #interface Example {
public String value();
}
}
outputs "Class Test has no annotations" without the #Retention meta annotation.
I upgraded spring from version 2.1.1 to 2.2.0 .
Since then I'm facing the following error when I start my app :
Caused by: org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'ParentService' available: expected single matching bean but found 2: MasterService,SlaveService .
ParentService is an interface :
public interface ParentService{
..
}
MasterService :
#Service
#MasterProfile
public class MasterService implements ParentService{
.....
}
SlaveService :
#Service
#SlaveProfile
public class SlaveService implements ParentService{
.....
}
MasterProfile annotation :
#Profile("MASTER")
public #interface MasterProfile {
}
Slave Profile :
#Profile("SLAVE")
public #interface SlaveProfile{
}
I'm passing to my app the profile with the following flag :
-Dspring.profiles.include=MASTER
According to Spring 2.2 release notes, they have done some changes and forks are enabled by default in maven. As a result the only way to pass params is with the parameter -Dspring-boot.run.jvmArguments . I used -Dspring-boot.run.jvmArguments=-Dspring.profiles.include=MASTER but it still fails..
Passing a profile as a parameter depends on how you run your app. Be careful, the doc you mentioned is referring to the maven spring-boot plugin.
With maven plugin : mvn spring-boot:run -Dspring-boot.run.jvmArguments=-Dspring.profiles.include=MASTER
Classic java app : java -Dspring.profiles.include=MASTER -jar ./myapp.jar
In both cmd line, you can pass more than one parameter, if separated by a ,. See the documentation: https://docs.spring.io/spring-boot/docs/current/reference/html/spring-boot-features.html#boot-features-external-config-profile-specific-properties
Since the upgrade, you now have to define your custom profile annotation like this :
#Target({ElementType.TYPE})
#Retention(RetentionPolicy.RUNTIME) // Only this one is really needed
#Profile("SLAVE")
public #interface SlaveProfile {
}
Explaination:
In java, an annotation has a RetentionPolicy, which is similar to a scope. (See this: https://docs.oracle.com/javase/7/docs/api/java/lang/annotation/RetentionPolicy.html).
Without any RetentionPolicy set, the default behavior is an annotation not visible for the JVM (i.e at runtime).
When you want to run your application, you first compile it, which implies converting your .java files into .class file. Your class is only a bunch of byte code, converting your human readable file into a computer language.
Then, when Spring is loading the ApplicationContext, what it does under the hood, among many other things, is reading your .class files. During this process (see class name: org.springframework.asm.ClassReader) Spring loads the annotations that you declare. With what I've said above, during the Runtime, you end up with "two kinds" of annotations :
InvisibleAnnotation: #Retention(RetentionPolicy.COMPILE)
VisibleAnnotation: #Retention(RetentionPolicy.RUNTIME)
To conclude and understand why it was working before:
Spring-boot 2.1.0uses spring-core-5.1.2, which interprets at runtime the visible and invisible annotations, which explain why your #SlaveProfile and #MasterProfile have the expected behaviour.
Spring-boot 2.2.0uses spring-core-5.2.0, which interprets at runtime ONLY the visible annotations, which explain why your #SlaveProfile and #MasterProfile haven't the expected behaviour.
Let's say that Spring "silently" fixed a bug that was reading Invisible Annotation when they shouldn't, but didn't mention it.
Hope it helps!
Adding #Profile will not stop the bean from being instantiated. This is causing the exception. Add #Primary to any beans that the application should not default to.
Ex, add #Primary to the MasterProfile bean.
I have written an annotation:
#Documented
#Retention(RUNTIME)
#Target(TYPE)
/**
* #author christian
*
*/
public #interface HeraklesObject {
}
I use it in an osgi environment (eclipse oxygen with tycho) in a bundle.
In another bundle (a test fragment to be specific) i check classes from the first bundle for this annotation, using:
class.getAnnotation(HeraklesObject.class)
I am sure, that the queried class has this annotation, but I get always null result.
The retention policy seems right to me, but I am not sure if a class loader issue is present and I don't know how I check this. Or do you see another cause? Thanks
Christian
Try to use
#Retention(RetentionPolicy.RUNTIME)
#Target({ ElementType.TYPE })
Also from How to get class annotation in java?
use
Class newClass = X.class;
for (Annotation annotation : newClass.getDeclaredAnnotations()) {
System.out.println(annotation.toString());
}
Does the bundle with the class whose runtime annotations you want to check contain or import the package with the HeraklesObject annotation class?
A class loader will load a class and, if the class of a runtime annotation class is not visible, just ignore the annotation. So you end up with a silent "failure". So check to make sure the package holding the runtime annotation is visible to the bundle holding the annotated class.
If you use Bnd to build the bundle, it should properly import the runtime annotation's package.
Can anyone explain in a clear way the practical differences between the java.lang.annotation.RetentionPolicy constants SOURCE, CLASS, and RUNTIME?
I'm also not exactly sure what the phrase "retaining annotation" means.
RetentionPolicy.SOURCE: Discard during
the compile. These annotations don't
make any sense after the compile has
completed, so they aren't written to
the bytecode.
Example: #Override, #SuppressWarnings
RetentionPolicy.CLASS: Discard during
class load. Useful when doing
bytecode-level post-processing.
Somewhat surprisingly, this is the
default.
RetentionPolicy.RUNTIME: Do not
discard. The annotation should be
available for reflection at runtime.
Example: #Deprecated
Source:
The old URL is dead now
hunter_meta and replaced with hunter-meta-2-098036. In case even this goes down, I am uploading the image of the page.
Image (Right Click and Select 'Open Image in New Tab/Window')
According to your comments about class decompilation, here is how I think it should work:
RetentionPolicy.SOURCE: Won't appear in the decompiled class
RetentionPolicy.CLASS: Appear in the decompiled class, but can't be inspected at run-time with reflection with getAnnotations()
RetentionPolicy.RUNTIME: Appear in the decompiled class, and can be inspected at run-time with reflection with getAnnotations()
Minimal runnable example
Language level:
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
#Retention(RetentionPolicy.SOURCE)
#interface RetentionSource {}
#Retention(RetentionPolicy.CLASS)
#interface RetentionClass {}
#Retention(RetentionPolicy.RUNTIME)
#interface RetentionRuntime {}
public static void main(String[] args) {
#RetentionSource
class B {}
assert B.class.getAnnotations().length == 0;
#RetentionClass
class C {}
assert C.class.getAnnotations().length == 0;
#RetentionRuntime
class D {}
assert D.class.getAnnotations().length == 1;
}
Bytecode level: using javap we observe that the Retention.CLASS annotated class gets a RuntimeInvisible class attribute:
#14 = Utf8 LRetentionClass;
[...]
RuntimeInvisibleAnnotations:
0: #14()
while Retention.RUNTIME annotation gets a RuntimeVisible class attribute:
#14 = Utf8 LRetentionRuntime;
[...]
RuntimeVisibleAnnotations:
0: #14()
and the Runtime.SOURCE annotated .class does not get any annotation.
Examples on GitHub for you to play with.
Retention Policy: A retention policy determines at what point an annotation is discarded. It is s specified using Java's built-in annotations: #Retention[About]
1.SOURCE: annotation retained only in the source file and is discarded
during compilation.
2.CLASS: annotation stored in the .class file during compilation,
not available in the run time.
3.RUNTIME: annotation stored in the .class file and available in the run time.
CLASS
:Annotations are to be recorded in the class file by the compiler but need not be retained by the VM at run time.
RUNTIME
:Annotations are to be recorded in the class file by the compiler and retained by the VM at run time, so they may be read reflectively.
SOURCE
:Annotations are to be discarded by the compiler.
Oracle Doc
I am trying to play with reflection and annotations.
For some reason whenever I add an annotation to a field (or a class or a method), and use reflection to look at the field, I see that its annotations field is null.
For example, this code:
public class Test {
public static void main(String[] args) throws NoSuchFieldException, SecurityException {
System.out.println(Test.class.getField("bl").getAnnotations().length);
}
#anno
public int bl;
public #interface anno {}
}
prints 0.
BTW, Java does not ignore annotations in general, and when (for example) I use the #Deprecated annotation - Eclipse recognizes it and tells me the class is deprecated.
I am using Eclipse Indigo and Java SE Development Kit 7 update 2.
Thanks!
By default, annotations are not available to reflection. In order to do so, you need to change the retention policy, i.e.
#Retention(RetentionPolicy.RUNTIME)
public #interface Anno {}
The various retention policies can be found in the Javadoc. The default (for mysterious reasons that nobody seems to know) is CLASS.
I would check the #Retention e.g.
#Documented
#Retention(RetentionPolicy.RUNTIME)
public #interface Deprecated {
}
If you don't set it, it won't be visible at runtime.