我希望创建一个自定义接口来注入属性,如下所示...
interface Property<T> { T get(); }
然后我想使用一个自定义注释来设置get()
调用的返回值,比如...
@interface Key { String name(); String fallback() default ""; }
然后在我的应用程序中使用它,比如...
@key(name = "my.string.property", fallback = "some default value")
Property<String> myStringProperty;
@key(name = "my.number.property", fallback = "1")
Property<Integer> myNumberProperty;
我们之所以这样做,而不是使用@Value
注释,是为了使用一个新的PropertyChanged事件将这些对象连接到我们先前存在的系统事件中,该事件可以更新get()
方法的返回值(我们还将持久化这些更新,因为我们正在运行一个可以随时创建新节点的分布式系统),并且还将在我们的UI系统管理页面中公开这些属性。
我已经设法使用我自己的BeanPostProcessor#postProcessBeforeInitialization
实现中的ReflectionUtils#doWithFields
对使用我的自定义注释的字段进行了处理。这更像是一种破解,因为spring做了所有的注入,然后我们通过反射更新字段,所以当你注释构造函数参数时,这不起作用。我使用了这个指南,https://www.baeldung.com/spring-annotation-bean-pre-processor。
所以我的问题是,有没有办法为spring实现一个工厂对象,在那里我可以编写代码来读取注释并基于此注入实现,这样我就不需要使用反射,无论我在哪里注入,它都可以工作,因为它将是spring正常注入生命周期的一部分。
发布于 2020-12-21 02:42:35
因此,我找到了一种方法来实现这一点,使用org.reflections库来查找我的Property对象上的所有关键注释。
然后,我可以为每个键创建自定义bean定义,然后可以使用键作为限定符进行注册,以允许spring注入我所有的属性对象。
因此,第一件事就是将限定符注释添加到我的键注释中。
@Qualifier
@interface Key {
String name();
String fallback() default "";
}
下一步是创建BeanDefinitionRegistryPostProcessor接口的实现,这将使用要在运行时注入的属性接口的具体实现注册一个bean定义、构造函数参数和通过使用反射扫描包而找到的键注释中的限定符
(这是替换反射使用的关键,从设置bean中的对象到只使用它来动态查找键/属性并使其可用于注入)
@Component
public class PropertyBeanPostProcessor implements BeanDefinitionRegistryPostProcessor {
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
}
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
Reflections reflections = new Reflections(ClasspathHelper.forPackage("com.package.to.scan"),
new FieldAnnotationsScanner(), new MethodParameterScanner());
registerBeansForConstructors(registry, reflections.getConstructorsWithAnyParamAnnotated(Key.class));
registerBeansForMethods(registry, reflections.getMethodsWithAnyParamAnnotated(Key.class));
registerBeansForFields(registry, reflections.getFieldsAnnotatedWith(Key.class));
}
private void registerBeansForFields(BeanDefinitionRegistry registry, Set<Field> fields) {
for (Field field : fields) {
Class<?> parameterType = field.getType();
Annotation[] annotations = field.getDeclaredAnnotations();
Type genericType = field.getGenericType();
registerBeanIfPropertyType(registry, parameterType, genericType, annotations);
}
}
private void registerBeansForMethods(BeanDefinitionRegistry registry, Set<Method> methods) {
for (Method method : methods) {
Class<?>[] parameterTypes = method.getParameterTypes();
Annotation[][] annotations = method.getParameterAnnotations();
Type[] genericTypes = method.getGenericParameterTypes();
registerBeansForParameters(registry, parameterTypes, annotations, genericTypes);
}
}
private void registerBeansForConstructors(BeanDefinitionRegistry registry, Set<Constructor> constructors) {
for (Constructor constructor : constructors) {
Class<?>[] parameterTypes = constructor.getParameterTypes();
Annotation[][] annotations = constructor.getParameterAnnotations();
Type[] genericTypes = constructor.getGenericParameterTypes();
registerBeansForParameters(registry, parameterTypes, annotations, genericTypes);
}
}
private void registerBeansForParameters(BeanDefinitionRegistry registry, Class<?>[] parameterTypes, Annotation[][] annotations, Type[] genericTypes) {
for (int i = 0; i < parameterTypes.length; i++) {
Class<?> parameterType = parameterTypes[i];
Annotation[] parameterAnnotations = annotations[i];
Type genericType = genericTypes[i];
registerBeanIfPropertyType(registry, parameterType, genericType, parameterAnnotations);
}
}
private void registerBeanIfPropertyType(BeanDefinitionRegistry registry, Class<?> parameterType, Type genericType, Annotation[] parameterAnnotations) {
if (!Property.class.isAssignableFrom(parameterType)) {
return;
}
Arrays.stream(parameterAnnotations)
.filter(annotation -> Key.class.isAssignableFrom(annotation.annotationType()))
.map(Key.class::cast)
.findFirst()
.ifPresent(key -> register(registry, key, genericType));
}
private void register(BeanDefinitionRegistry registry, Key key, Type type) {
registry.registerBeanDefinition(key.name(), createDefinition(key, type));
log.info("registered property: {}", key);
}
public static BeanDefinition createDefinition(Key key, Type type) {
GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
beanDefinition.setBeanClass(PropertyImpl.class);
beanDefinition.setConstructorArgumentValues(createConstructorArgumentValues(key, type));
beanDefinition.addQualifier(createAutowireCandidateQualifier(key));
return beanDefinition;
}
private static AutowireCandidateQualifier createAutowireCandidateQualifier(Key key) {
AutowireCandidateQualifier autowireCandidateQualifier = new AutowireCandidateQualifier(Key.class);
autowireCandidateQualifier.setAttribute("name", key.name());
autowireCandidateQualifier.setAttribute("fallback", key.fallback());
return autowireCandidateQualifier;
}
private static ConstructorArgumentValues createConstructorArgumentValues(Key key, Type type) {
ConstructorArgumentValues constructorArgumentValues = new ConstructorArgumentValues();
constructorArgumentValues.addIndexedArgumentValue(1, key);
constructorArgumentValues.addIndexedArgumentValue(2, getPropertyType(type));
return constructorArgumentValues;
}
private static Class<?> getPropertyType(Type type) {
if (!(type instanceof ParameterizedType)) {
throw new RuntimeException("field " + type.getTypeName() + " is not parameterised");
}
ParameterizedType parameterizedType = (ParameterizedType) type;
Type[] actualGenericTypeArguments = parameterizedType.getActualTypeArguments();
if (actualGenericTypeArguments.length != 1) {
throw new RuntimeException("invalid number of generics: " + Arrays.stream(actualGenericTypeArguments).map(Type::getTypeName).collect(Collectors.toList()));
}
return TypeToken.of(actualGenericTypeArguments[0]).getRawType();
}
最后,我发现这对我来说是可行的,但可能有一种更好的方法来创建一个spring可以触发的工厂,而不是使用反射库,因为这不是最快的
https://stackoverflow.com/questions/65376331
复制相似问题