接口注释不接受 application.properties 值
Interface Annotation does not accept application.properties value
我开发了一个简单的注解界面
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface CustomAnnotation {
String foo() default "foo";
}
然后我测试它注释 Class
@CustomAnnotation
public class AnnotatedClass {
}
并使用方法调用它
public void foo() {
CustomAnnotation customAnnotation = AnnotatedClass.class.getAnnotation(CustomAnnotation.class);
logger.info(customAnnotation.foo());
}
并且一切正常,因为它记录了 foo。我也尝试将带注释的 class 更改为 @CustomAnnotation(foo = "123")
并且一切正常,因为它记录 123.
现在我希望 application.properties
检索传递给注释的值,所以我将注释 class 更改为
@CustomAnnotation(foo = "${my.value}")
public class AnnotatedClass {
}
但现在日志 returns 字符串 ${my.vlaue}
而不是 application.properties
中的值。
我知道可以在注释中使用 ${}
指令,因为我总是像这样使用 @RestController
@GetMapping(path = "${path.value:/}")
并且一切正常。
我在 Github 存储库上的解决方案:https://github.com/federicogatti/annotatedexample
确保带注释的 Class 具有 @Component
注释以及 @CustomAnnotation(foo = "${my.value}")
,然后 Spring 会将此 class 识别为 Spring 组件,并且进行必要的配置以插入值。
您可以使用ConfigurableBeanFactory.resolveEmbeddedValue
将${my.value}
解析为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);
}
}
如果您还想解析表达式,您应该考虑使用 EmbeddedValueResolver
。
EmbeddedValueResolver resolver = new EmbeddedValueResolver(beanFactory);
final String value = resolver.resolveStringValue(fooValue);
你不能直接作为 annotation attribute's value must be a constant expression.
您可以做的是,您可以将 foo 值作为字符串传递,如 @CustomAnnotation(foo = "my.value")
并创建建议 AOP 以获取注释字符串值并在应用程序属性中查找。
使用 @Pointcut
、@AfterReturn
创建 AOP 或提供其他匹配 @annotation
、方法等,并编写逻辑以查找 属性 以获取相应的字符串。
在主应用程序上配置 @EnableAspectJAutoProxy
或通过配置 class.
进行设置
添加aop依赖:spring-boot-starter-aop
使用切入点创建 @Aspect
。
@Aspect
public class CustomAnnotationAOP {
@Pointcut("@annotation(it.federicogatti.annotationexample.annotationexample.annotation.CustomAnnotation)")
//define your method with logic to lookup application.properties
Spring 基于核心的方法
首先,我想向您展示一个不使用 Spring 引导自动配置功能的独立应用程序。我希望您会感激 Spring 为我们所做的一切。
我们的想法是用 StringValueResolver
设置一个 ConfigurableBeanFactory
,它将知道我们的上下文(特别是 application.yaml
属性)。
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 {}
Spring 基于引导的方法
现在,我将演示如何在您的 Spring 启动应用程序中执行此操作。
我们将注入 ConfigurableBeanFactory
(已配置)并解析与前面代码段类似的值。
@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);
}
}
我不喜欢在业务逻辑代码中混合低级 Spring 组件,例如 BeanFactory
,因此我强烈建议我们将类型缩小到 StringValueResolver
并且而是注入它。
@Bean
public StringValueResolver getStringValueResolver(ConfigurableBeanFactory factory) {
return new EmbeddedValueResolver(factory);
}
调用方法为resolveStringValue
:
// ...
String value = resolver.resolveStringValue(foo);
System.out.println(value);
基于代理的方法
我们可以编写一个根据接口类型生成代理的方法;它的方法将 return 解析值。
这是该服务的简化版本。
@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());
})));
}
}
注入服务并按如下方式使用:
CustomAnnotation customAnnotation = service.getAnnotationFromType(CustomAnnotation.class, AnnotatedClass.class);
System.out.println(customAnnotation.foo());
您可以查看 Spring 的 RequestMappingHandlerMapping
以了解他们是如何使用 EmbeddedValueResolver
的。您可以将 bean 工厂注入任何 spring 组件,然后使用它来构建您自己的解析器:
@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);
}
假设您在属性中设置 foo.value=hello
,输出将类似于:
fooValue = ${foo.value}
resolvedValue = hello
我用 Spring Boot 2.0.2 测试了它,它按预期工作。
请记住,这只是一个最小的示例。您可能希望处理 class 上缺少注释和缺少已解析值(如果未设置该值且没有默认值)的错误情况。
要从 application.propertie
读取 属性,需要定义 PropertyPlaceholderConfigurer
并将其映射到属性文件。
XML基础配置:
<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="ignoreUnresolvablePlaceholders" value="true"/>
<property name="locations" value="classpath:application.properties" />
</bean>
对于基于注解的:可以如下使用:
@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();
}
}
我开发了一个简单的注解界面
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface CustomAnnotation {
String foo() default "foo";
}
然后我测试它注释 Class
@CustomAnnotation
public class AnnotatedClass {
}
并使用方法调用它
public void foo() {
CustomAnnotation customAnnotation = AnnotatedClass.class.getAnnotation(CustomAnnotation.class);
logger.info(customAnnotation.foo());
}
并且一切正常,因为它记录了 foo。我也尝试将带注释的 class 更改为 @CustomAnnotation(foo = "123")
并且一切正常,因为它记录 123.
现在我希望 application.properties
检索传递给注释的值,所以我将注释 class 更改为
@CustomAnnotation(foo = "${my.value}")
public class AnnotatedClass {
}
但现在日志 returns 字符串 ${my.vlaue}
而不是 application.properties
中的值。
我知道可以在注释中使用 ${}
指令,因为我总是像这样使用 @RestController
@GetMapping(path = "${path.value:/}")
并且一切正常。
我在 Github 存储库上的解决方案:https://github.com/federicogatti/annotatedexample
确保带注释的 Class 具有 @Component
注释以及 @CustomAnnotation(foo = "${my.value}")
,然后 Spring 会将此 class 识别为 Spring 组件,并且进行必要的配置以插入值。
您可以使用ConfigurableBeanFactory.resolveEmbeddedValue
将${my.value}
解析为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);
}
}
如果您还想解析表达式,您应该考虑使用 EmbeddedValueResolver
。
EmbeddedValueResolver resolver = new EmbeddedValueResolver(beanFactory);
final String value = resolver.resolveStringValue(fooValue);
你不能直接作为 annotation attribute's value must be a constant expression.
您可以做的是,您可以将 foo 值作为字符串传递,如 @CustomAnnotation(foo = "my.value")
并创建建议 AOP 以获取注释字符串值并在应用程序属性中查找。
使用 @Pointcut
、@AfterReturn
创建 AOP 或提供其他匹配 @annotation
、方法等,并编写逻辑以查找 属性 以获取相应的字符串。
在主应用程序上配置
@EnableAspectJAutoProxy
或通过配置 class. 进行设置
添加aop依赖:
spring-boot-starter-aop
使用切入点创建
@Aspect
。@Aspect public class CustomAnnotationAOP { @Pointcut("@annotation(it.federicogatti.annotationexample.annotationexample.annotation.CustomAnnotation)") //define your method with logic to lookup application.properties
Spring 基于核心的方法
首先,我想向您展示一个不使用 Spring 引导自动配置功能的独立应用程序。我希望您会感激 Spring 为我们所做的一切。
我们的想法是用 StringValueResolver
设置一个 ConfigurableBeanFactory
,它将知道我们的上下文(特别是 application.yaml
属性)。
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 {}
Spring 基于引导的方法
现在,我将演示如何在您的 Spring 启动应用程序中执行此操作。
我们将注入 ConfigurableBeanFactory
(已配置)并解析与前面代码段类似的值。
@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);
}
}
我不喜欢在业务逻辑代码中混合低级 Spring 组件,例如 BeanFactory
,因此我强烈建议我们将类型缩小到 StringValueResolver
并且而是注入它。
@Bean
public StringValueResolver getStringValueResolver(ConfigurableBeanFactory factory) {
return new EmbeddedValueResolver(factory);
}
调用方法为resolveStringValue
:
// ...
String value = resolver.resolveStringValue(foo);
System.out.println(value);
基于代理的方法
我们可以编写一个根据接口类型生成代理的方法;它的方法将 return 解析值。
这是该服务的简化版本。
@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());
})));
}
}
注入服务并按如下方式使用:
CustomAnnotation customAnnotation = service.getAnnotationFromType(CustomAnnotation.class, AnnotatedClass.class);
System.out.println(customAnnotation.foo());
您可以查看 Spring 的 RequestMappingHandlerMapping
以了解他们是如何使用 EmbeddedValueResolver
的。您可以将 bean 工厂注入任何 spring 组件,然后使用它来构建您自己的解析器:
@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);
}
假设您在属性中设置 foo.value=hello
,输出将类似于:
fooValue = ${foo.value}
resolvedValue = hello
我用 Spring Boot 2.0.2 测试了它,它按预期工作。
请记住,这只是一个最小的示例。您可能希望处理 class 上缺少注释和缺少已解析值(如果未设置该值且没有默认值)的错误情况。
要从 application.propertie
读取 属性,需要定义 PropertyPlaceholderConfigurer
并将其映射到属性文件。
XML基础配置:
<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="ignoreUnresolvablePlaceholders" value="true"/>
<property name="locations" value="classpath:application.properties" />
</bean>
对于基于注解的:可以如下使用:
@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();
}
}