簡體   English   中英

Interface Annotation不接受application.properties值

[英]Interface Annotation does not accept application.properties value

我開發了一個簡單的注釋界面

@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 {
}

但現在日志返回String ${my.vlaue}而不是application.properties的值。

我知道可以在注釋中使用${}指令,因為我總是使用像@RestController這樣的@GetMapping(path = "${path.value:/}")並且一切正常。


我在Github存儲庫上的解決方案: https//github.com/federicogatti/annotatedexample

您不能直接執行某些操作,因為annotation attribute's value must be a constant expression.

您可以做的是,您可以將foo值作為字符串傳遞給@CustomAnnotation(foo = "my.value")並創建建議AOP以獲取注釋字符串值並在應用程序屬性中查找。

創建AOP與@Pointcut@AfterReturn或提供他人匹配@annotation ,方法等,並寫你的邏輯來查找屬性對應的字符串。

  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 

在官方指南中查看更多內容: 使用Spring進行面向方面編程

基於Spring Core的方法

首先,我想向您展示一個不使用Spring Boot自動配置工具的獨立應用程序。 我希望你能體會到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 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());

確保Annotated Class具有@Component注釋以及@CustomAnnotation(foo = "${my.value}") ,然后Spring會將此類識別為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);

您可以查看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測試了它,它按預期工作。

請記住,這是一個最小的例子。 您可能希望處理類上缺少注釋和缺少已解析值的錯誤情況(如果未設置該值且沒有默認值)。

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

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM