接口注释不接受application.properties值

时间:2018-09-26 08:49:27

标签: java spring spring-boot annotations property-placeholder

我已经开发了一个简单的 注释 界面

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface CustomAnnotation {
    String foo() default "foo";
}

然后我对它进行注释并对其进行测试

@CustomAnnotation
public class AnnotatedClass {
}

并使用方法调用它

public void foo()  {
    CustomAnnotation customAnnotation = AnnotatedClass.class.getAnnotation(CustomAnnotation.class);
    logger.info(customAnnotation.foo());
}

,并且一切正常,因为它记录了 foo 。我还尝试将带注释的类更改为@CustomAnnotation(foo = "123"),并且也都可以正常工作,因为它记录了 123

现在,我希望传递给注释的值由application.properties检索,因此我将带注释的类更改为

@CustomAnnotation(foo = "${my.value}")
public class AnnotatedClass {
}

,但是现在日志返回字符串${my.vlaue},而不返回application.properties中的值。

我知道可以在注释中使用${}指令,因为我总是使用@RestController这样的@GetMapping(path = "${path.value:/}")并且一切正常。


我在Github存储库上的解决方案:https://github.com/federicogatti/annotatedexample

6 个答案:

答案 0 :(得分:2)

您不能直接作为annotation attribute's value must be a constant expression.

您可以做的是,可以将foo值作为@CustomAnnotation(foo = "my.value")之类的字符串传递,并创建建议AOP以获取注释字符串值并在应用程序属性中查找。

使用@Pointcut@AfterReturn创建AOP或提供其他匹配@annotation,方法等的AOP,并将逻辑写入相应字符串的lookup属性。

  1. 在主应用程序上配置@EnableAspectJAutoProxy或通过配置类进行设置。

  2. 添加aop依赖项:spring-boot-starter-aop

  3. 使用切入点创建@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

答案 1 :(得分:2)

基于Spring Core的方法

首先,我想向您展示一个不使用Spring Boot自动配置功能的独立应用程序。希望您能体会到Spring为我们所做的工作。

我们的想法是使用ConfigurableBeanFactory设置StringValueResolver,它将了解我们的上下文(尤其是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 Boot的方法

现在,我将演示如何在您的Spring Boot应用程序中执行此操作。

我们将注入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);
    }

}

我不喜欢在业务逻辑代码中混合使用诸如BeanFactory之类的低级Spring组件,因此我强烈建议我们将类型缩小为StringValueResolver并注入它。

@Bean
public StringValueResolver getStringValueResolver(ConfigurableBeanFactory factory) {
    return new EmbeddedValueResolver(factory);
}

调用方法为resolveStringValue

// ...
String value = resolver.resolveStringValue(foo);
System.out.println(value);

基于代理的方法

我们可以编写一个根据接口类型生成代理的方法;其方法将返回解析值。

这是该服务的简化版本。

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

答案 2 :(得分:0)

确保带注释的类具有 <ItemGroup> <None Remove="ClientApp\components\componentname\componentname.ts" /> </ItemGroup> @Component注释,然后Spring会将此类识别为Spring组件,并进行必要的配置以将值插入其中。

答案 3 :(得分:0)

您可以使用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);

答案 4 :(得分:0)

您可以查看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对此进行了测试,它按预期工作。

请记住,这是一个最小的示例。您可能要处理类缺少注释和解析值(如果未设置值且没有默认值)的错误情况。

答案 5 :(得分:0)

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