接口注释不接受 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、方法等,并编写逻辑以查找 属性 以获取相应的字符串。

  1. 在主应用程序上配置 @EnableAspectJAutoProxy 或通过配置 class.

  2. 进行设置
  3. 添加aop依赖:spring-boot-starter-aop

  4. 使用切入点创建 @Aspect

    @Aspect
    public class CustomAnnotationAOP {
    
    
    @Pointcut("@annotation(it.federicogatti.annotationexample.annotationexample.annotation.CustomAnnotation)")
     //define your method with logic to lookup application.properties
    

查看官方指南:Aspect Oriented Programming with Spring

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();
 }
}