繁体   English   中英

Spring 自定义@Value注解

[英]Spring Custom @Value Annotation

我正在寻找创建一个自定义接口来注入这样的属性......

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使用我的自定义注释进行注释的字段进行了这项工作。 这更像是一种 hack,因为 spring 完成了所有的注入,然后我们通过反射更新了该字段,因此当您注释构造函数参数时这不起作用。 为此,我使用了本指南https://www.baeldung.com/spring-annotation-bean-pre-processor

所以我的问题是,有没有办法为 spring 实现工厂 object 我可以编写代码来读取注释并基于它注入一个实现,所以我不需要使用反射,无论我在哪里注入它都会工作因为它将成为弹簧正常注射生命周期的一部分?

所以我找到了一种方法来实现 BeanDefinitionRegistryPostProcessor 使用 org.reflections 库来查找我的属性 object 上的所有关键注释。

然后我可以为每个键创建自定义 bean 定义,然后我可以使用 Key 作为限定符进行注册,以允许 spring 注入我的所有属性对象。

所以第一件事就是将 Qualifer 注释添加到我的 Key 注释中。

@Qualifier
@interface Key {
  String name();
  String fallback() default "";
}

接下来是创建 BeanDefinitionRegistryPostProcessor 接口的实现,这将注册一个 bean 定义,其中包含要在运行时注入的 Property 接口的具体实现、构造函数参数和通过使用反射扫描包找到的键注释中的限定符

(这是替换使用反射的关键,从在我的 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 可以触发的工厂,而不是使用反射库,因为这不是最快的

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM