I have developed a simple Annotation Interface
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface CustomAnnotation {
String foo() default "foo";
}
then I test it annotating a Class
@CustomAnnotation
public class AnnotatedClass {
}
and call it using a method
public void foo() {
CustomAnnotation customAnnotation = AnnotatedClass.class.getAnnotation(CustomAnnotation.class);
logger.info(customAnnotation.foo());
}
and all works fine because it logs foo. I try also change the annotated class to @CustomAnnotation(foo = "123")
and all works fine too, becuase it logs 123.
Now I want that the value passed to the annotation is retrieved by the application.properties
, so I have changed my annotated class to
@CustomAnnotation(foo = "${my.value}")
public class AnnotatedClass {
}
but now the log returns the String ${my.vlaue}
and not the value in application.properties
.
I know that is possible use ${}
instruction in annotation because I always use a @RestController
like this @GetMapping(path = "${path.value:/}")
and all works fine.
My solution on Github repository: https://github.com/federicogatti/annotatedexample
The @interface element is used to declare an annotation. For example: @interface MyAnnotation{}
Spring @Value annotation is used to assign default values to variables and method arguments. We can read spring environment variables as well as system variables using @Value annotation. Spring @Value annotation also supports SpEL. Let's look at some of the examples of using @Value annotation.
Annotation gives you the ability to provide additional metadata alongside a Java entity (such as classes, interfaces, fields and methods). This additional metadata, called annotation, can be read and interrelated by the compiler or other utilities. They can also be stored in the class files.
You can use @Value("${property-name}") from the application. properties if your class is annotated with @Configuration or @Component . You can make use of static method to get the value of the key passed as the parameter.
First off, I want to show you a standalone application that doesn't utilise Spring Boot auto-configurable facilities. I hope you will appreciate how much Spring does for us.
The idea is to have a ConfigurableBeanFactory
set up with StringValueResolver
which will be aware of our context (particularly, of the application.yaml
properties).
class Application {
public static void main(String[] args) {
// read a placeholder from CustomAnnotation#foo
// foo = "${my.value}"
CustomAnnotation customAnnotation = AnnotatedClass.class.getAnnotation(CustomAnnotation.class);
String foo = customAnnotation.foo();
// create a placeholder configurer which also is a properties loader
// load application.properties from the classpath
PropertySourcesPlaceholderConfigurer configurer = new PropertySourcesPlaceholderConfigurer();
configurer.setLocation(new ClassPathResource("application.properties"));
// create a factory which is up to resolve embedded values
// configure it with our placeholder configurer
ConfigurableListableBeanFactory factory = new DefaultListableBeanFactory();
configurer.postProcessBeanFactory(factory);
// resolve the value and print it out
String value = factory.resolveEmbeddedValue(foo);
System.out.println(value);
}
}
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface CustomAnnotation {
String foo() default "foo";
}
@CustomAnnotation(foo = "${my.value}")
class AnnotatedClass {}
Now, I will demonstrate how to do it within your Spring Boot application.
We are going to inject ConfigurableBeanFactory
(which has already been configured) and resolve the value similarly to the previous snippet.
@RestController
@RequestMapping("api")
public class MyController {
// inject the factory by using the constructor
private ConfigurableBeanFactory factory;
public MyController(ConfigurableBeanFactory factory) {
this.factory = factory;
}
@GetMapping(path = "/foo")
public void foo() {
CustomAnnotation customAnnotation = AnnotatedClass.class.getAnnotation(CustomAnnotation.class);
String foo = customAnnotation.foo();
// resolve the value and print it out
String value = factory.resolveEmbeddedValue(foo);
System.out.println(value);
}
}
I don't like mixing up low-level Spring components, such as BeanFactory
, in business logic code, so I strongly suggest we narrow the type to StringValueResolver
and inject it instead.
@Bean
public StringValueResolver getStringValueResolver(ConfigurableBeanFactory factory) {
return new EmbeddedValueResolver(factory);
}
The method to call is resolveStringValue
:
// ...
String value = resolver.resolveStringValue(foo);
System.out.println(value);
We could write a method that generates a proxy based on the interface type; its methods would return resolved values.
Here's a simplified version of the service.
@Service
class CustomAnnotationService {
@Autowired
private StringValueResolver resolver;
public <T extends Annotation> T getAnnotationFromType(Class<T> annotation, Class<?> type) {
return annotation.cast(Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), new Class<?>[]{annotation},
((proxy, method, args) -> {
T originalAnnotation = type.getAnnotation(annotation);
Object originalValue = method.invoke(originalAnnotation);
return resolver.resolveStringValue(originalValue.toString());
})));
}
}
Inject the service and use it as follows:
CustomAnnotation customAnnotation = service.getAnnotationFromType(CustomAnnotation.class, AnnotatedClass.class);
System.out.println(customAnnotation.foo());
You can't do something like directly as an annotation attribute's value must be a constant expression.
What you can do is, you can pass foo value as string like @CustomAnnotation(foo = "my.value")
and create advice AOP to get annotation string value and lookup in application properties.
create AOP with @Pointcut
, @AfterReturn
or provided others to match @annotation
, method etc and write your logic to lookup property for corresponding string.
Configure @EnableAspectJAutoProxy
on main application or setting up by configuration class.
Add aop dependency: spring-boot-starter-aop
Create @Aspect
with pointcut .
@Aspect
public class CustomAnnotationAOP {
@Pointcut("@annotation(it.federicogatti.annotationexample.annotationexample.annotation.CustomAnnotation)")
//define your method with logic to lookup application.properties
Look more in official guide : Aspect Oriented Programming with Spring
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With