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
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
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());
确保Annotated Class具有@Component
注释以及@CustomAnnotation(foo = "${my.value}")
,然后Spring会将此类识别为Spring组件,并进行必要的配置以插入值。
You can use ConfigurableBeanFactory.resolveEmbeddedValue
to resolve ${my.value}
into the value in application.properties .
@CustomAnnotation(foo="${my.value}")
@lombok.extern.slf4j.Slf4j
@Service
public class AnnotatedClass {
@Autowired
private ConfigurableBeanFactory beanFactory;
public void foo() {
CustomAnnotation customAnnotation = AnnotatedClass.class.getAnnotation(CustomAnnotation.class);
String fooValue = customAnnotation.foo().toString();
String value = beanFactory.resolveEmbeddedValue(fooValue);
log.info(value);
}
}
If you also want to resolve expressions you should consider using EmbeddedValueResolver
.
EmbeddedValueResolver resolver = new EmbeddedValueResolver(beanFactory);
final String value = resolver.resolveStringValue(fooValue);
You can look at Spring's RequestMappingHandlerMapping
to see how they do it, which is using a EmbeddedValueResolver
. You can inject the bean factory into any spring component and then use it to build your own resolver:
@Autowired
public void setBeanFactory(ConfigurableBeanFactory beanFactory)
{
this.embeddedValueResolver = new EmbeddedValueResolver(beanFactory);
CustomAnnotation customAnnotation = AnnotatedClass.class.getAnnotation(CustomAnnotation.class);
String fooValue = customAnnotation.foo();
System.out.println("fooValue = " + fooValue);
String resolvedValue = embeddedValueResolver.resolveStringValue(fooValue);
System.out.println("resolvedValue = " + resolvedValue);
}
Assuming you set foo.value=hello
in your properties, the output would look something like:
fooValue = ${foo.value}
resolvedValue = hello
I tested this with Spring Boot 2.0.2 and it worked as expected.
Keep in mind this is a minimal example. You would want to handle the error cases of missing annotations on the class and missing resolved value (if the value isn't set and there's no default).
To read property from application.propertie
, one need to define PropertyPlaceholderConfigurer
and map it with properties file.
XML based configuration:
<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="ignoreUnresolvablePlaceholders" value="true"/>
<property name="locations" value="classpath:application.properties" />
</bean>
For annotation based: one can use as below:
@Configuration
@PropertySource(
value{"classpath:properties/application.properties"},ignoreResourceNotFound=true)
public class Config {
/**
* Property placeholder configurer needed to process @Value annotations
*/
@Bean
public static PropertySourcesPlaceholderConfigurer propertyConfigurer() {
return new PropertySourcesPlaceholderConfigurer();
}
}
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.