Here is an example
#Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.TYPE, ElementType.ANNOTATION_TYPE})
#Retention(RetentionPolicy.RUNTIME)
#Qualifier
public #interface Annotation {
}
#Configuration
public class Configuration {
#Bean
#Annotation
public Test getTest() {
return new Test();
}
}
public class Test() {
public void test() {
// how can get the annotation `#Annotation` here?
}
}
Here is what I have tried getClass().getAnnotations() but this returns empty array. I can see why since getClass() return Test.class which does not have the annotation. How can I get the method that creates this instance and then get the annotation?
You could, in theory, inspect the current Thread stack to determine the name of your caller, then look up the class definition, locate the method, and read its annotations:
var t = Thread.currentThread().getStackTrace()[2];
var className = t.getClassName();
Class<?> clazz;
try {
clazz = Test.class.getClassLoader().loadClass(className);
} catch (ClassNotFoundException e) {
throw new RuntimeException("Caller was loaded by a different ClassLoader :-(");
}
for (var method : clazz.getDeclaredMethods()) {
if (method.getName().equals(t.getMethodName())) {
return method.getAnnotation(YourAnnotation.class).value();
}
}
throw new RuntimeException("Method not found - I might have found the wrong class definition");
However:
inspecting the stack is rather slow, in particular if the stack is deep
inspecting the stack is brittle with respect to refactorings (people don't expect that factoring out code into a utility method will change behaviour)
the compiler can not check that the caller provides the required annotation
this only works reliably if all code is loaded by the same ClassLoader
this can not distinguish overloaded methods
This is therefore a rather brittle hack. Are you sure that there is no better option? For instance, requiring the caller to pass the value as a method parameter would have none of these shortcomings ...
You can use ConfigurableListableBeanFactory to get metadata about any Bean by name. Use BeanNameAware interface to retrieve Bean name.
#Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.TYPE, ElementType.ANNOTATION_TYPE})
#Retention(RetentionPolicy.RUNTIME)
#Qualifier
public #interface CustomAnnotation {
}
#org.springframework.context.annotation.Configuration
public static class ContextConfiguration {
#Bean(name = "TEST")
#CustomAnnotation
public TestObject getTest() {
return new TestObject();
}
}
public class TestObject implements BeanNameAware {
private String beanName;
#Autowired
ConfigurableListableBeanFactory beanFactory;
#Override
public void setBeanName(String name) {
this.beanName = name;
}
public void test() {
CustomAnnotation customAnnotation = (CustomAnnotation) getBeanAnnotation(beanName, CustomAnnotation.class);
}
private Annotation getBeanAnnotation(String beanName, java.lang.Class<? extends Annotation> clazz) {
Annotation annotation = null;
BeanDefinition beanDefinition = beanFactory.getBeanDefinition(beanName);
if( beanDefinition != null && beanDefinition.getSource() instanceof StandardMethodMetadata) {
StandardMethodMetadata metadata = (StandardMethodMetadata) beanDefinition.getSource();
annotation = Arrays.stream(metadata.getIntrospectedMethod().getDeclaredAnnotations()).filter(annot -> annot.annotationType().equals(clazz)).findFirst().orElse(null);
}
return annotation;
}
}
Related
I have a CMS (Content management System) to store property key values.
public interface CMSConfig {
#Property(propertyName = "someConfig")
String getSomeConfig();
}
And the Configuration annotation code is
#Target({ElementType.TYPE})
#Retention(RetentionPolicy.RUNTIME)
public #interface Configuration {
String configName() default "";
}
And the way to call this CMS is
#Service
public class MockitoService {
#ManagedConfiguration
private CMSConfig cmsConfig;
public String method() {
return "Hello!" + cmsConfig.getSomeConfig();
}
}
ManagedConfiguration.java
#Retention(RetentionPolicy.RUNTIME)
#Target({ElementType.FIELD, ElementType.METHOD})
public #interface ManagedConfiguration {
}
I am looking to do the UnitTest for CMSConfig
I am not able to Mock or create the bean of CMSConfig as it's an interface. I have tried multiple approaches.
My UnitTest case file is
#ExtendWith(MockitoExtension.class)
public class MockitoServiceTest {
#Autowired
MockitoService mockitoService;
#ManagedConfiguration
private CMSConfig cmsConfig;
#BeforeAll
public static void before() {
System.setProperty("cms.configs.dir", Paths.get("src", "main", "resources").toFile().getAbsolutePath());
}
#Test
public void testCMS(){
assertEquals(cmsConfig.getDummy(),"dummyvalueUatRes");
}
}
Please help if there's any way to do this.
My mockitoService and cmsConfig are null while running the test case.
I'am trying to get the methods that are using a custom annotation, but when i get the method, i can't get any annotation from it, every paramter that cites "annotation" is null.
My annotation:
#Target(ElementType.METHOD)
#Retention(RetentionPolicy.RUNTIME)
public #interface MyAnnotation {
String value() default "";
}
Class using annotation:
public interface Interface {
void doSomething();
}
#Repository
public class ImplementationClass implements Interface {
#Override
#MyAnnotation("some_value")
public void doSomething() {
}
}
Getting annotation:
#Configuration
public class MyAnnotationScanner implements ApplicationContextAware {
#Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
for (String beanName : applicationContext.getBeanNamesForAnnotation(Repository.class)) {
Object bean = applicationContext.getBean(beanName);
for (Method beanMethod : bean.getClass().getDeclaredMethods()) {
if (beanMethod.isAnnotationPresent(MyAnnotation.class))
// do something
}
}
}
}
I'm able to get the correct method, but when i check with intellij it has no annotations and the "isAnnotationPresent" method always returns false.
I found a solution in using the AnnotationUtils.findAnnotation() method.
Basically for every beanMethod, you get the annotation using the findAnnotation method.
var annotation = AnnotationUtils.findAnnotation(beanMethod, MyAnnotation.class);
I was using isAnnotationPresent method but it was always returning false. I have then used AnnotationUtils.findAnnotation(method, <Annotation.class>)
Here is how I am finding method that is using custom annotation:
for (String beanName : applicationContext.getBeanDefinitionNames()) {
Object bean = applicationContext.getBean(beanName);
Class<?> objClass = bean.getClass();
for (Method m : objClass.getDeclaredMethods()) {
Annotation a = AnnotationUtils.findAnnotation(m, <Annotation_NAME>.class);
if (a != null) {
#Component
public MyClass{
private MyOtherClass myOtherClass;
#Autowired
public MyClass(MyOtherClass myOtherClass){
this.myOtherClass = myOtherClass;
}
}
#Component
#Scope("prototype")// OR
#Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE,
proxyMode = ScopedProxyMode.TARGET_CLASS)
public MyOtherClass{
}
I am writing a custom plugin to detect classes which declare variable of type MyOtherClass and give a warning because MyOtherClass is of type prototype.
Basically I need to get field from MyClass and need to get Annotation on the field(MyOtherClass) class and need to find if the annotation value contains prototype
#Override
public void visitNode(Tree tree) {
org.sonar.plugins.java.api.tree.VariableTree variableTree = (VariableTree) tree;
if (variableTree.type().symbolType().symbol().metadata().isAnnotatedWith(SCOPE_ANNOTATION_FQN)) {
System.out.println("prototype annotation found " + variableTree.symbol().metadata().toString());
reportIssue(variableTree.simpleName(), "This spring bean is of type PROTOTYPE");
}
}
Found a way to read annotations on a variable class.
Is this your answer?
public class MyClass {
private MyOtherClass myOtherClass;
public MyClass(MyOtherClass myOtherClass){
this.myOtherClass = myOtherClass;
}
public static void main(String[] args) {
Field[] declaredFields = MyClass.class.getDeclaredFields();
for(Field field:declaredFields){
Scope scope = field.getType().getAnnotation(Scope.class);
if(scope!=null){
String value = scope.value();
System.out.println(value);
}
}
}
}
#Scope("prototype")
public class MyOtherClass {
}
#Target(ElementType.TYPE)
#Retention(RetentionPolicy.RUNTIME)
public #interface Scope {
String value();
}
I cannot get bean what I want, when using CDI and Annotation #Qualifier
#Qualifier #interface for Type
#Repeatable(Type.List.class)
#Target({TYPE})
#Retention(RetentionPolicy.RUNTIME)
#Qualifier
public #interface Type {
String value();
#Target({TYPE})
#Retention(RetentionPolicy.RUNTIME)
#interface List {
Type[] value();
}
}
and AnnotationLiteral implementation
public class TypeAL extends AnnotationLiteral<Type> implements Type {
private final String type;
public TypeAL(String type) {
this.type = type;
}
#Override
public String value() {
return type;
}
}
#Qualifier #interface for Related
#Target({TYPE})
#Retention(RetentionPolicy.RUNTIME)
#Qualifier
public #interface Related {
Class value();
}
and AnnotationLiteral implementation
public class RelatedAL extends AnnotationLiteral<Related> implements Related {
private final Class clazz;
public RelatedAL(Class clazz) {
this.clazz = clazz;
}
#Override
public Class value() {
return clazz;
}
}
When I annotated sth like this:
#Type(TYPE_ONE)
#Type(TYPE_TWO)
#Related(RelatedClassWhichWillDoLogic.class)
public class LogicToRelatedClass implements BaseLogic {}
and when I'd like to get CDI.current().select(BaseLogic.class, new TypeAL(TYPE_ONE), new RelatedAL(RelatedClassWhichWillDoLogic.class)) i go nothing...
Why is that?
what is the version of your CDI? I think repeating qualifiers has been supported from version 2.
https://issues.jboss.org/browse/CDI-471
https://docs.google.com/document/d/1KUaxXIXJ_r-h5UJGIij6I4vrLS7uXkeeeZr2SaRipWQ/edit#
To choose one implementation between different implementations. we could use qualifier members to narrow the list of possible beans. the injection point must be completely matched with the qualifiers on the bean(if you want the exact one). you have two Type annotations on your bean class but use one of them in your CDI.current().select method call.
Instance<BaseLogic> findedBeans = CDI.current().select(BaseLogic.class, new TypeAL("TYPE_ONE"), new TypeAL("TYPE_TWO"), new RelatedAL(RelatedClassWhichWillDoLogic.class));
I tested it In one Weld Java SE program. you can download it from WELD (CDI) + JPA
just in the main method, in App class, add the following line of the code.
UserApplication userApplication = container.instance()
.select(UserApplication.class)
.get();
Instance<BaseLogic> type_one = CDI.current().select(BaseLogic.class, new TypeAL("TYPE_ONE"), new TypeAL("TYPE_TWO"), new RelatedAL(RelatedClassWhichWillDoLogic.class));
For a simple POJO:
#Component
public class Foo
{
private final String string;
public Foo()
{
this("Secondary ComponentScan??");
}
public Foo(String string)
{
this.string = string;
}
#Override
public String toString()
{
return string;
}
}
and this configuration
#Configuration
#ComponentScan(basePackageClasses = Foo.class)
public class TestConfiguration
{
#Primary
#Bean
public Foo foo()
{
return new Foo("Primary bean!!");
}
}
I would expect the following test
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = TestConfiguration.class)
public class Test
{
#Autowired
private Foo foo;
#Test
public void test()
{
System.out.println(foo);
}
}
to print out Primary Bean!! but it returns Secondary ComponentScan?? instead...
How come? Nowhere does the documentation for #Primary say it fails against component-scanned beans!
The reason is that both beans actually have the same name foo, so internally one bean definition is getting overridden with the other one, essentially the one with #Bean is getting overridden by the one being scanned by #ComponentScan.
The fix is simply to give one of them a different name and you should see the correct behavior of the #Primary bean getting injected.
#Primary
#Bean
public Foo foo1()
{
return new Foo("Primary bean!!");
}
OR
#Component("foo1")
public class Foo
{
..