With Spring, you can have some kind of composed annotations. A prominent example is the #SpringBootApplication-annotation, which is a composite of an #Configuration, #EnableAutoConfiguration and #ComponentScan.
I am trying to get all Beans that are affected by a certain annotation, i.e. ComponentScan.
Following this answer, I am using the following code:
for (T o : applicationContext.getBeansWithAnnotation(ComponentScan.class).values()) {
ComponentScan ann = (ComponentScan) o.getClass().getAnnotation(ComponentScan.class);
...
}
which does not work, since not all beans, returned by getBeansWithAnnotation(ComponentScan.class) are indeed annotated with that annotation, since those that are e.g. annotated with #SpringBootApplication are (usually) not.
Now I am looking for some kind of generic way, to retrieve the value of an annotation, even when it is only added as piece of another annotation.
How can I do this?
It turns out, there is a utility set AnnotatedElementUtils which allows you to handle those merged annotations.
for (Object annotated : context.getBeansWithAnnotation(ComponentScan.class).values()) {
Class clazz = ClassUtils.getUserClass(annotated) // thank you jin!
ComponentScan mergedAnnotation = AnnotatedElementUtils.getMergedAnnotation(clazz, ComponentScan.class);
if (mergedAnnotation != null) { // For some reasons, this might still be null.
// TODO: useful stuff.
}
}
it may be CglibProxy. so you can not directly get the Annotation.
ClassUtils.isCglibProxyClass(o)
for more see this
edit,you can add your logic code. find the ComponentScan.
if (ClassUtils.isCglibProxyClass(o.getClass())) {
Annotation[] annotations = ClassUtils.getUserClass(o).getAnnotations();
for (Annotation annotation : annotations) {
ComponentScan annotation1 = annotation.annotationType().getAnnotation(ComponentScan.class);
// in my test code , ComponentScan can get here.for #SpringBootApplication
System.out.println(annotation1);
}
}
Related
Is it possible to use a system-property for the value of the #WebappConfiguration annotation? I have tried something like:
#WebAppConfiguration("#{systemProperties['webapproot'] ?: 'war'}")
But it doesn't seem to work at all. Is there any other way to do this through spring? I don't want to do this through our build tools as it would break executing our integration tests from our IDEs.
#WebAppConfiguration doesn't seems to support neither SpEL nor placeholder resolving.
I checked it in the following way: I tried to inject system property and resolve placeholder using #Value. When I tried to inject a nonexistent property #Value failed throwing SpelEvaluationException: EL1008E: Property or field 'asd' cannot be found on object of type 'java.util.Properties' and IllegalArgumentException: Could not resolve placeholder 'nonexistent_property' in value "${nonexistent_property}" respectively while #WebAppConfiguration's value had simply #{systemProperties.asd} and ${nonexistent_property} intialized as simple String's.
No, that is unfortunately not supported.
The value supplied to #WebAppConfiguration must be an an explicit resource path as stated in the class-level JavaDoc.
If you would like for us to consider dynamic evaluation of the value attribute, please feel free to open a JIRA issue to request such support.
Regards,
Sam (author of the Spring TestContext Framework)
I have found a solution to this problem. I wrote my own ContextBootsTraper by extending WebTestContextBootstrapper which Spring uses to load the WebAppConfiguration-Annotation value.
I extended the functionality to include a check if a certain system-property exists and overwrite the annotations value if it does:
public class SystemPropTestContextBootstrapper extends WebTestContextBootstrapper {
#Override
protected MergedContextConfiguration processMergedContextConfiguration(MergedContextConfiguration mergedConfig) {
WebAppConfiguration webAppConfiguration = AnnotationUtils.findAnnotation(mergedConfig.getTestClass(),
WebAppConfiguration.class);
if (webAppConfiguration != null) {
//implementation ommited
String webappDir = loadWebappDirFromSystemProperty();
if(webappDir == null) {
webappDir = webAppConfiguration.value();
}
return new WebMergedContextConfiguration(mergedConfig, webappDir);
}
return mergedConfig;
}
}
This class can then be used with the #BootstrapWith-Annotation:
#RunWith(SpringJUnit4ClassRunner.class)
#BootstrapWith(SystemPropTestContextBootstrapper.class)
#WebAppConfiguration("standardDir")
public class SomeTest {
}
This solution enables me to run the tests from my build-tool while maintaining the ability to run the tests from my IDE, which is great.
In Weld, I can do the following to get dynamically inject config values from some source:
#Qualifier
#Retention(RetentionPolicy.RUNTIME)
#Target({ TYPE, METHOD, FIELD, PARAMETER })
public static #interface ConfigValue {
#Nonbinding
String value();
}
#Produces
#Dependent
#ConfigValue("")
public String stringValue(InjectionPoint ip) {
ConfigValue configValue = ip.getAnnotated().getAnnotation(ConfigValue.class);
return myConfigMap.get(configValue.value());
}
The equivalent Spring, however, matches based on the value of the #ConfigValue annotation.
I would like Spring to call a method to allow me to inject custom values for all fields annotated with #ConfigValue.
I'm aware of this: http://joshlong.com/jl/blogPost/supporting_your_own_field_or_method_injection_annotation_processors_in_spring.html
However that's a very complex solution for a seemingly simple problem. I'm wondering if there's a simpler solution...
Spring has InjectionPoint class. The catch is that your #ConfigValue annotation should not have Qualifier annotation. Then you can have a single method to produces various values.
However, if you choose to have #Qualifier on your #ConfigValue annotation, then you will have to have multiple producing methods one for each value of #ConfigValue. I dont seem to have come across anything equivalent to #Nonbinding though.
I have tested this for user object, not String.
PS: another approach is to have one "defining" annotation and one "non-defining" annotation on the injection point to serve your need.
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.
I have an annotation #MyAnnotation and I can annotate any type (class) with it. Then I have a class called AnnotatedClassRegister and I would like it to register all classes annotated with #MyAnnotation so I can access them later. And I'd like to register these classes automatically upon creation of the AnnotatedClassRegister if possible, and most importantly before the annotated classes are instantiated.
I have AspectJ and Guice at my disposal. The only solution I came up with so far is to use Guice to inject a singleton instance of the AnnotatedClassRegister to an aspect, which searches for all classes annotated with #MyAnnotation and it adds the code needed to register such class in its constructor. The downside of this solution is that I need to instantiate every annotated class in order for the code added by AOP to be actually run, therefore I cannot utilize lazy instantiation of these classes.
Simplified pseudo-code example of my solution:
// This is the class where annotated types are registered
public class AnnotatedClassRegister {
public void registerClass(Class<?> clz) {
...
}
}
// This is the aspect which adds registration code to constructors of annotated
// classes
public aspect AutomaticRegistrationAspect {
#Inject
AnnotatedClassRegister register;
pointcutWhichPicksConstructorsOfAnnotatedClasses(Object annotatedType) :
execution(/* Pointcut definition */) && args(this)
after(Object annotatedType) :
pointcutWhichPicksConstructorsOfAnnotatedClasses(annotatedType) {
// registering the class of object whose constructor was picked
// by the pointcut
register.registerClass(annotatedType.getClass())
}
}
What approach should I use to address this problem? Is there any simple way to get all such annotated classes in classpath via reflection so I wouldn't need to use AOP at all? Or any other solution?
Any ideas are much appreciated, thanks!
It's possible:
Get all paths in a classpath. Parse System.getProperties().getProperty("java.class.path", null) to get all paths.
Use ClassLoader.getResources(path) to get all resources and check for classes: http://snippets.dzone.com/posts/show/4831
It isn't simple that much is sure, but I'd do it in a Pure Java way:
Get your application's Jar location from the classpath
Create a JarFile object with this location, iterate over the entries
for every entry that ends with .class do a Class.forName() to get the Class object
read the annotation by reflection. If it's present, store the class in a List or Set
Aspects won't help you there, because aspects only work on code that's actually executed.
But annotation processing may be an Option, create a Processor that records all annotated classes and creates a class that provides a List of these classes
Well, if your AnnotatedClassRegister.registerClass() doesn't have to be called immediately at AnnotatedClassRegister creation time, but it could wait until a class is first instantiated, then I would consider using a Guice TypeListener, registered with a Matcher that checks if a class is annotated with #MyAnnotation.
That way, you don't need to search for all those classes, they will be registered just before being used. Note that this will work only for classes that get instantiated by Guice.
I would use the staticinitialization() pointcut in AspectJ and amend classes to your register as they are loaded, like so:
after() : staticinitialization(#MyAnnotation *) {
register.registerClass(thisJoinPointStaticPart.getSignature().getDeclaringType());
}
Piece of cake, very simple and elegant.
You can use the ClassGraph package like so:
Java:
try (ScanResult scanResult = new ClassGraph().enableAnnotationInfo().scan()) {
for (ClassInfo classInfo = scanResult.getClassesWithAnnotation(classOf[MyAnnotation].getName()) {
System.out.println(String.format("classInfo = %s", classInfo.getName()));
}
}
Scala:
Using(new ClassGraph().enableAnnotationInfo.scan) { scanResult =>
for (classInfo <- scanResult.getClassesWithAnnotation(classOf[MyAnnotation].getName).asScala) {
println(s"classInfo = ${classInfo.getName}")
}
}
I have a annotation that does include several other annotations, pretty much like this one here:
#Component // Spring Component
#Interface OsgiService { boolean isFactory() }
meaning that all classes annotated with #OsgiService shall automatically also be annotated as #Component. Which works fine.
Now however, I'd like to add another annotation, that has a parameter which is dependent of the isFactory parameter of #OsgiService.
#Component // Spring Component
#Scope(isFactory() ? "prototype" : "singleton")
#Interface OsgiService { boolean isFactory() }
Which does not work. However, as isFactory property of an annotation requires to be a static value, shouldn't it be possible to have something like this?
I don't think this is possible.
You can create two annotations: #OsgiService and #OsgiServiceFactory