简体   繁体   English

Spring 自定义@Value注解

[英]Spring Custom @Value Annotation

I'm looking to create a custom interface to inject properties like so...我正在寻找创建一个自定义接口来注入这样的属性......

interface Property<T> { T get(); }

I would like to then set the return value of the get() call using a custom annotation like...然后我想使用自定义注释设置get()调用的返回值,例如...

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

Then uses this throughout my application like...然后在我的整个应用程序中使用它,比如......

@key(name = "my.string.property", fallback = "some default value")
Property<String> myStringProperty;

@key(name = "my.number.property", fallback = "1")
Property<Integer> myNumberProperty;

The reason we want to do this rather than using the @Value annotation is to hook these objects into our pre-existing system events with a new PropertyChanged event which can update the return value of the get() method (we will also persist these updates as we're running a distributed system which can create new nodes at anytime) and will also expose these properties in our UIs system admin page.我们想要这样做而不是使用@Value注释的原因是使用新的 PropertyChanged 事件将这些对象挂钩到我们预先存在的系统事件中,该事件可以更新get()方法的返回值(我们还将持久化这些更新因为我们正在运行一个可以随时创建新节点的分布式系统)并且还将在我们的 UI 系统管理页面中公开这些属性。

I've managed to get this working for fields annotated with my custom annotation using ReflectionUtils#doWithFields from my own implementation of BeanPostProcessor#postProcessBeforeInitialization .我已经设法使用我自己的BeanPostProcessor#postProcessBeforeInitialization实现中的ReflectionUtils#doWithFields使用我的自定义注释进行注释的字段进行了这项工作。 This is more of a hack as spring does all it's injection and then we're updating the field via reflection so this doesn't work when you annotate the constructor param.这更像是一种 hack,因为 spring 完成了所有的注入,然后我们通过反射更新了该字段,因此当您注释构造函数参数时这不起作用。 I used this guide for that, https://www.baeldung.com/spring-annotation-bean-pre-processor .为此,我使用了本指南https://www.baeldung.com/spring-annotation-bean-pre-processor

so my question is, is there a way to implement a factory object for spring where i can write code to read the annotation and inject an implementation based on that so i don't need to use reflection and it will work no matter where i inject as it'll be a part of the springs normal injection life cycle?所以我的问题是,有没有办法为 spring 实现工厂 object 我可以编写代码来读取注释并基于它注入一个实现,所以我不需要使用反射,无论我在哪里注入它都会工作因为它将成为弹簧正常注射生命周期的一部分?

So i found a way to do this implementing the BeanDefinitionRegistryPostProcessor using the org.reflections lib to find all the Key annotations on my Property object.所以我找到了一种方法来实现 BeanDefinitionRegistryPostProcessor 使用 org.reflections 库来查找我的属性 object 上的所有关键注释。

I was then able create a custom bean definitions for each key which i can then register using the Key as a qualifier to allow spring to inject all my Property objects.然后我可以为每个键创建自定义 bean 定义,然后我可以使用 Key 作为限定符进行注册,以允许 spring 注入我的所有属性对象。

so first thing was adding the Qualifer annotation to my Key annotation.所以第一件事就是将 Qualifer 注释添加到我的 Key 注释中。

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

next was to create an implementation of the BeanDefinitionRegistryPostProcessor interface, this registers a bean definition with the concrete implementation of the Property interface to inject at runtime, the constructor parameters and the qualifier from the key annotation found by using the reflections to scan packages接下来是创建 BeanDefinitionRegistryPostProcessor 接口的实现,这将注册一个 bean 定义,其中包含要在运行时注入的 Property 接口的具体实现、构造函数参数和通过使用反射扫描包找到的键注释中的限定符

(this was the key to replacing the use of reflections from setting the objects in my bean to just using it to dynamically lookup the key/property and making it available for injection) (这是替换使用反射的关键,从在我的 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();
}

in the end i found this works for me but there may be a better way to create a factory that spring can trigger rather than using the reflections lib as this isn't the fastest最后我发现这对我有用,但可能有更好的方法来创建 spring 可以触发的工厂,而不是使用反射库,因为这不是最快的

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

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