6

我开发了一个简单的注释接口

@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

4

6 回答 6

4

基于 Spring Core 的方法

首先,我想向您展示一个不使用 Spring Boot 自动配置工具的独立应用程序。我希望你会感激春天为我们做了多少。

我们的想法是建立一个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);
    }

}

我不喜欢在业务逻辑代码中混合低级 Spring 组件,例如BeanFactory,所以我强烈建议我们将类型缩小到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());
于 2018-10-08T13:06:45.323 回答
2

你不能直接做类似的事情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
    

在官方指南中查看更多内容:Aspect Oriented Programming with Spring

于 2018-10-04T16:00:07.180 回答
1

确保 Annotated Class@Component与 一起具有注解@CustomAnnotation(foo = "${my.value}"),然后 Spring 会将此类识别为 Spring 组件并进行必要的配置以将值插入。

于 2018-09-26T09:35:58.893 回答
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);
于 2018-10-04T10:11:32.610 回答
0

您可以查看 SpringRequestMappingHandlerMapping以了解他们是如何做到的,它使用EmbeddedValueResolver. 您可以将 bean factory 注入到任何 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 对此进行了测试,它按预期工作。

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

于 2018-10-08T13:06:54.503 回答
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();
 }
}
于 2018-10-09T05:44:00.857 回答