基于注释参数的依赖项注入“动态指定的” bean

时间:2019-08-24 21:57:26

标签: spring dynamic dependency-injection factory

我有一个用例,在注入点基于注释参数动态实例化bean(使用某种工厂方法)会非常好。具体来说,我需要能够为创建bean的工厂指定类型参数。

一个非常相关的示例是JSON反序列化器,它需要将其反序列化为的类型。

我设想:

@Inject
@DeserializeQualifier(Car.class)
private Deserializer<Car> _carDeserializer;

@Inject
@DeserializeQualifier(Bus.class)
private Deserializer<Bus> _busDeserializer;

..或简单地,如果可以从泛型类型参数中嗅探类型:

@Inject
private Deserializer<Car> _carDeserializer;

@Inject
private Deserializer<Bus> _busDeserializer;

这里的重点是,我事先不知道项目中需要哪种类型,因为这将是许多项目所包含的通用工具。因此,您可以使用@EnableDeserializer注释@Configuration类,然后可以注入任何类型的反序列化器(制造这些反序列化器的工厂可以处理任何类型,但要创建一个类型,就需要知道反序列化对象的所需类型-普通的泛型不会削减它,因为Java并未使用标准化的泛型。)

因此,我需要能够注入到Spring上下文中,或使用其他任何Spring魔术技巧,使用某种带有类型参数的DeserializerFactory。

基本上,我需要让Spring调用基于以下方法的方法,如第一个示例中的qualifier参数(或该问题的整个DeserializeQualifier-instance),或如第二个示例中的通用类型参数:

DeserializerFactory {
     <T> Deserializer<T> createDeserializer(Class<T> type) { ... }
}

1 个答案:

答案 0 :(得分:1)

您可以创建一个BeanFactoryPostProcessor来设置带有自定义注释的属性。我已经设置了一个小的Spring Boot项目来玩:

// Custom annotation
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface InjectSomeClassHere {
    Class value();
}
// Demo bean
@Component
public class SomeBean {
    @InjectSomeClassHere(String.class)
    private Class someValue;

    public Class getInjectedClass() {
        return someValue;
    }
}

// The BeanFactoryPostProcessor
@Component
public class SomeBeanFactoryPostProcessor implements BeanFactoryPostProcessor {

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        Arrays
                .stream(beanFactory.getBeanDefinitionNames())
                .filter(beanName -> hasAnnotatedField(beanFactory, beanName))
                .forEach(beanName -> {
                    Object bean = beanFactory.getBean(beanName);
                    Stream.of(bean.getClass().getDeclaredFields()).forEach(field -> setFieldValue(bean, field));
                });
    }

    private boolean hasAnnotatedField(ConfigurableListableBeanFactory beanFactory, String beanName) {
        try {
            String className = beanFactory.getBeanDefinition(beanName).getBeanClassName();
            if (className == null) {
                return false;
            }

            return Arrays.stream(Class.forName(className).getDeclaredFields())
                    .anyMatch(field -> field.isAnnotationPresent(InjectSomeClassHere.class));

        } catch (ClassNotFoundException e) {
            // Error handling here
            return false;
        }
    }

    private void setFieldValue(Object filteredBean, Field field) {
        try {
            // Note: field.isAccessible() is deprecated
            if (!field.isAccessible()) {
                field.setAccessible(true);
            }

            // Retrieve the value from the annotation and set the field
            // In your case, you could call `createDeserializer(fieldValue);` and set the field using the return value.
            // Note that you should change the type of `SomeBean#someValue` accordingly.
            Class fieldValue = field.getAnnotation(InjectSomeClassHere.class).value();
            field.set(filteredBean, fieldValue);

        } catch (IllegalAccessException e) {
            // Error handling here
            e.printStackTrace();
        }
    }
}
// A small test to verify the outcome of the BeanFactoryPostProcessor
@RunWith(SpringRunner.class)
@SpringBootTest
public class SomeBeanTests {

    @Autowired
    private SomeBean someBean;

    @Test
    public void getInjectedClass_shouldHaveStringClassInjected() {
        Assert.assertEquals(String.class, someBean.getInjectedClass());
    }
}

请注意,这是一个非常幼稚的实现,需要进行进一步的微调。例如,它会扫描所有弹簧组件中的 all 属性以查看是否存在注释。

祝您的项目好运!