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