Я разработал простой интерфейс Аннотации
@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
Хорошо, я хочу определить свой индивидуальная обработка, чтобы получить свою цель, но я не понимаю, как
Вы выполняете logger.info(customAnnotation.foo()), однако, прежде чем вы это сделаете, вам нужно будет заменить значение фактическим значением. Таким образом, вы должны передать его через PropertyResolver, например Environment, вызвав метод resolvePlaceholders и использовать результат этого метода для регистрации. Вам придется сделать это самостоятельно, Spring ничего не может сделать для вас, чтобы справиться с этим.
Спасибо за ответ, я попытался найти ссылку, используя ваше предложение, но пока не понимаю, как его реализовать. У вас есть предложения?
Можно попробовать добавить в класс аннотацию @PropertySource("classpath:application.properties").




Убедитесь, что аннотированный класс имеет аннотацию @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);
Привет, я воспроизвел ваш ответ, но продолжаю получать строку $ {my.value}, а не содержимое свойства приложения. Кажется, потому что, когда он называется logger.info(customAnnotation.foo()), область видимости не находится внутри аннотированного класса, где реализована логика, а просто войдите в интерфейс аннотации и верните значение.
Вы не можете сделать что-то подобное напрямую как annotation attribute's value must be a constant expression.
Что вы можете сделать, так это передать значение foo в виде строки, такой как @CustomAnnotation(foo = "my.value"), и создать AOP совета для получения значения строки аннотации и поиска в свойствах приложения.
создайте АОП с @Pointcut, @AfterReturn или предоставленными другими для сопоставления @annotation, метода и т. д. и запишите свою логику в свойство поиска для соответствующей строки.
Настройте @EnableAspectJAutoProxy в основном приложении или настройте классом конфигурации.
Добавить зависимость aop: spring-boot-starter-aop
Создайте @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 не управляет им. Мне любопытно, как бы вы написали совет
@EnableAspectJAutoProxy может включить аспект
@EnableAspcetJAutoProxy не работает в моем примере, но достаточно использовать аннотацию @Component в аспекте, чтобы создать bean-компонент.
@ kj007, но из @Aspect не получится Spring bean
В конфигурации создайте компонент для класса аспекта
@ kj007 ваши инструкции по созданию @Aspect неуместны, вопрос в том, как этот @Aspect может помочь. Вам не хватает реализации pointcut и примера его использования.
@AndrewTobilko AOP может помочь найти строку в свойствах приложения, и это то, что я уже объяснил, поскольку Федерико позже спросил меня, как можно создать AOp, поэтому я привел пример, конечно, ему нужно посмотреть на свои потребности, используя аннотации, после возврат и т. д.
Прежде всего, я хочу показать вам автономное приложение, которое не использует возможности автоматической настройки 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.
Мы собираемся внедрить 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, для его реализации? Или есть другие способы лучше?
@FedericoGatti, вы не можете вставить эту логику в «аннотацию», потому что аннотация - это не что иное, как метаинформация, которую можно получить в какой-то момент.
@FedericoGatti некоторые классы обычно снабжены аннотацией для обеспечения логики. Spring предоставляет вам классы, которые могут читать файлы .properties и анализировать выражения ${...}.
@FedericoGatti Эти классы прочно интегрированы в экосистему Spring. Поэтому вам нужно либо добавить свои части к полностью упакованным компонентам Spring Boot (мой второй пример), либо написать собственный процесс, тщательно подбирая необходимые компоненты Spring (мой первый пример).
@FedericoGatti Я не знаю, как мы можем применить здесь АОП. Звучит как чрезмерная инженерия, но мы могли бы написать прокси (я могу показать, как это сделать, если вы хотите)
Я перепутал 2 решения github.com/federicogatti/annotatedexample, посмотрите, по вашему мнению, все выглядит нормально. Теперь аннотация - ElementType.METHOD.
@FedericoGatti выглядит хорошо, я не знал, что у вас может быть аннотация к методам
@FedericoGatti, однако, решение заключается во взаимодействии с методами, аннотированными вашей аннотацией, а не в изменении поведения методов аннотации (как вы изначально хотели).
@FedericoGatti еще один вопрос: если вы используете АОП, как бы вы получили доступ к разрешенным значениям в аннотированном методе?
Думаю, что можно сделать это с помощью @Around вместо @Before
@FedericoGatti @Around повлияет на возвращаемое значение аннотированного метода, он по-прежнему будет недоступен для использования внутри метода
Позвольте нам продолжить обсуждение в чате.
Вы можете посмотреть на 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();
}
}
Тот факт, что он работает с
@GetMapping, не означает, что он работает для всего.@GetMappingобрабатываются Spring, и он знает, чтоpath(и другие) могут содержать выражение SpEL или выражение значения. Это делается во время выполнения при фактической обработке аннотаций. Значения в аннотациях должны быть статичными! поэтому, если вы не выполняете никакой специальной обработки аннотации и получаете только значение, он всегда будет читать то, что вы туда поместили.