Аннотация интерфейса не принимает значение application.properties - PullRequest
0 голосов
/ 26 сентября 2018

Я разработал простой Аннотация Интерфейс

@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 голосов
/ 09 октября 2018

Чтобы прочитать свойство из 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();
 }
}
0 голосов
/ 08 октября 2018

Вы можете посмотреть на Spring 100 *, чтобы увидеть, как они это делают, который использует 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, и он работал как положено.

Имейте в виду, что это минимальный пример.Вы бы хотели обработать ошибки, связанные с отсутствием аннотаций в классе и отсутствием разрешенного значения (если значение не установлено и по умолчанию нет).

0 голосов
/ 08 октября 2018

Подход на основе 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);
    }

}

Я не люблю смешивать низкоуровневыеКомпоненты 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());
0 голосов
/ 04 октября 2018

Вы не можете сделать что-то вроде непосредственно как 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 с pointcut.

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

Подробнее в официальном руководстве: Аспектно-ориентированное программирование с пружиной

0 голосов
/ 04 октября 2018

Вы можете использовать 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);
0 голосов
/ 26 сентября 2018

Убедитесь, что у аннотированного класса есть аннотация @Component вместе с @CustomAnnotation(foo = "${my.value}"), тогда Spring распознает этот класс как компонент Spring и выполнит необходимые конфигурации для вставки значения.

...