Аннотация интерфейса не принимает значение application.properties

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

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

и все работает нормально, потому что он регистрирует фу. Я пытаюсь также изменить аннотированный класс на @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

Тот факт, что он работает с @GetMapping, не означает, что он работает для всего. @GetMapping обрабатываются Spring, и он знает, что path (и другие) могут содержать выражение SpEL или выражение значения. Это делается во время выполнения при фактической обработке аннотаций. Значения в аннотациях должны быть статичными! поэтому, если вы не выполняете никакой специальной обработки аннотации и получаете только значение, он всегда будет читать то, что вы туда поместили.

M. Deinum 26.09.2018 12:26

Хорошо, я хочу определить свой индивидуальная обработка, чтобы получить свою цель, но я не понимаю, как

Federico Gatti 26.09.2018 12:59

Вы выполняете logger.info(customAnnotation.foo()), однако, прежде чем вы это сделаете, вам нужно будет заменить значение фактическим значением. Таким образом, вы должны передать его через PropertyResolver, например Environment, вызвав метод resolvePlaceholders и использовать результат этого метода для регистрации. Вам придется сделать это самостоятельно, Spring ничего не может сделать для вас, чтобы справиться с этим.

M. Deinum 26.09.2018 13:01

Спасибо за ответ, я попытался найти ссылку, используя ваше предложение, но пока не понимаю, как его реализовать. У вас есть предложения?

Federico Gatti 03.10.2018 18:05

Можно попробовать добавить в класс аннотацию @PropertySource("classpath:application.properties").

Alessandro 04.10.2018 10:57
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
6
5
3 070
6
Перейти к ответу Данный вопрос помечен как решенный

Ответы 6

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

Вы пробуете свое решение? Потому что у меня это не работает

Federico Gatti 27.09.2018 09:05

Вы можете использовать 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);

Привет, я воспроизвел ваш ответ, но продолжаю получать строку $ {my.value}, а не содержимое свойства приложения. Кажется, потому что, когда он называется logger.info(customAnnotation.foo()), область видимости не находится внутри аннотированного класса, где реализована логика, а просто войдите в интерфейс аннотации и верните значение.

Federico Gatti 08.10.2018 13:03
Ответ принят как подходящий

Вы не можете сделать что-то подобное напрямую как annotation attribute's value must be a constant expression.

Что вы можете сделать, так это передать значение foo в виде строки, такой как @CustomAnnotation(foo = "my.value"), и создать 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
    

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

AFAIK, CustomAnnotation#foo нельзя проксировать, потому что Spring не управляет им. Мне любопытно, как бы вы написали совет

Andrew Tobilko 08.10.2018 21:49

@EnableAspectJAutoProxy может включить аспект

kj007 09.10.2018 04:07
@EnableAspcetJAutoProxy не работает в моем примере, но достаточно использовать аннотацию @Component в аспекте, чтобы создать bean-компонент.
Federico Gatti 09.10.2018 09:55

@ kj007, но из @Aspect не получится Spring bean

Andrew Tobilko 09.10.2018 10:49

В конфигурации создайте компонент для класса аспекта

kj007 09.10.2018 10:56

@ kj007 ваши инструкции по созданию @Aspect неуместны, вопрос в том, как этот @Aspect может помочь. Вам не хватает реализации pointcut и примера его использования.

Andrew Tobilko 09.10.2018 12:29

@AndrewTobilko AOP может помочь найти строку в свойствах приложения, и это то, что я уже объяснил, поскольку Федерико позже спросил меня, как можно создать AOp, поэтому я привел пример, конечно, ему нужно посмотреть на свои потребности, используя аннотации, после возврат и т. д.

kj007 09.10.2018 15:15

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

Это моя цель, но я хочу вставить эту логику в «аннотацию», возможно, используя автоматически загружаемый класс поддержки. Вы предлагаете использовать АОП (с использованием @Aspect), например, @ kj007 suggets, для его реализации? Или есть другие способы лучше?

Federico Gatti 08.10.2018 15:33

@FedericoGatti, вы не можете вставить эту логику в «аннотацию», потому что аннотация - это не что иное, как метаинформация, которую можно получить в какой-то момент.

Andrew Tobilko 08.10.2018 15:45

@FedericoGatti некоторые классы обычно снабжены аннотацией для обеспечения логики. Spring предоставляет вам классы, которые могут читать файлы .properties и анализировать выражения ${...}.

Andrew Tobilko 08.10.2018 15:54

@FedericoGatti Эти классы прочно интегрированы в экосистему Spring. Поэтому вам нужно либо добавить свои части к полностью упакованным компонентам Spring Boot (мой второй пример), либо написать собственный процесс, тщательно подбирая необходимые компоненты Spring (мой первый пример).

Andrew Tobilko 08.10.2018 15:58

@FedericoGatti Я не знаю, как мы можем применить здесь АОП. Звучит как чрезмерная инженерия, но мы могли бы написать прокси (я могу показать, как это сделать, если вы хотите)

Andrew Tobilko 08.10.2018 23:42

Я перепутал 2 решения github.com/federicogatti/annotatedexample, посмотрите, по вашему мнению, все выглядит нормально. Теперь аннотация - ElementType.METHOD.

Federico Gatti 09.10.2018 09:52

@FedericoGatti выглядит хорошо, я не знал, что у вас может быть аннотация к методам

Andrew Tobilko 09.10.2018 10:16

@FedericoGatti, однако, решение заключается во взаимодействии с методами, аннотированными вашей аннотацией, а не в изменении поведения методов аннотации (как вы изначально хотели).

Andrew Tobilko 09.10.2018 10:21

@FedericoGatti еще один вопрос: если вы используете АОП, как бы вы получили доступ к разрешенным значениям в аннотированном методе?

Andrew Tobilko 09.10.2018 10:53

Думаю, что можно сделать это с помощью @Around вместо @Before

Federico Gatti 09.10.2018 11:41

@FedericoGatti @Around повлияет на возвращаемое значение аннотированного метода, он по-прежнему будет недоступен для использования внутри метода

Andrew Tobilko 09.10.2018 12:01

Позвольте нам продолжить обсуждение в чате.

Federico Gatti 09.10.2018 12:46

Вы можете посмотреть на 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();
 }
}

Другие вопросы по теме