Java 使用 BeanFactoryPostProcessor 创建 bean
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/19458152/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Creating beans with BeanFactoryPostProcessor
提问by Arturo Volpe
Spring BeanFactoryPostProcessor problem
Spring BeanFactoryPostProcessor 问题
I want to create a Spring BeanFactoryPostProcessor that add beans to the current ApplicationContext.
我想创建一个 Spring BeanFactoryPostProcessor,将 bean 添加到当前的 ApplicationContext。
I have a lot of Web-Services definition in my spring-ws-config.xml
and I want to reduce as much as possible.
我有很多 Web 服务定义,我spring-ws-config.xml
想尽可能地减少。
XML Configuration
XML 配置
The configuration looks like:
配置如下:
<bean id="menu"
class="org.springframework.ws.wsdl.wsdl11.DefaultWsdl11Definition"
lazy-init="true">
<property name="schemaCollection">
<bean
class="org.springframework.xml.xsd.commons.CommonsXsdSchemaCollection">
<property name="inline" value="true" />
<property name="xsds">
<list>
<value>classpath:xsd.xsd</value>
</list>
</property>
</bean>
</property>
<property name="portTypeName" value="portType" />
<property name="serviceName" value="serviceName" />
<property name="locationUri" value="/endpoints" />
</bean>
Java Configuration
Java配置
So, I create a @Configuration class with the following bean definition:
因此,我使用以下 bean 定义创建了一个 @Configuration 类:
@Bean
@Lazy
public DefaultWsdl11Definition webService() throws IOException {
logger.info("Creating Web Service");
DefaultWsdl11Definition toRet = new DefaultWsdl11Definition();
toRet.setPortTypeName("portType");
toRet.setServiceName("serviceName");
CommonsXsdSchemaCollection collection = new CommonsXsdSchemaCollection();
collection.setInline(true);
collection.setXsds(new Resource[] { new ClassPathResource("path1") });
collection.afterPropertiesSet();
toRet.setSchemaCollection(collection);
toRet.setLocationUri("/endpoints");
return toRet;
}
This is much better!, but I want to reduce it more, so I want to create a annotation called @WebServiceDefinition, and add a BeanFactoryPostProcessor to create the beans automatically, so I wrote this:
这好多了!,但我想减少更多,所以我想创建一个名为@WebServiceDefinition的注释,并添加一个BeanFactoryPostProcessor来自动创建bean,所以我写了这个:
BeanFactoryPostProcessor
BeanFactory后处理器
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory bf)
throws BeansException {
Map<String, Object> beans = bf.getBeansWithAnnotation(WebService.class);
for (Entry<String, Object> entry : beans.entrySet()) {
Object bean = entry.getValue();
WebService ws = bean.getClass().getAnnotation(WebService.class);
String name = getName(entry.getKey());
DefaultWsdl11Definition newWS = createWebService(name, ws.xsds());
bf.registerSingleton(name, newWS);
}
}
But, this doesn't works!, I wrote a simple test, you can see it here
但是,这不起作用!,我写了一个简单的测试,你可以在这里看到
I see that the IOC don't work with the classes with the annotations, this is because the method: BeanFactory#getBeansWithAnnotation don't initialize it, mark it as created, and dont inject anything.
我看到 IOC 不适用于带有注释的类,这是因为方法: BeanFactory#getBeansWithAnnotation 不初始化它,将其标记为已创建,并且不注入任何内容。
Workaround
解决方法
I do a workaround: get all beans by name, get the corresponde class and use #bf.getBeansOfType(Class), (this method dont initialize it!).
我做了一个解决方法:按名称获取所有 bean,获取对应的类并使用 #bf.getBeansOfType(Class),(此方法不初始化它!)。
My questions:
我的问题:
- This is a valid workaround?
- How I can use the method #getBeansWithAnnotation() and don't initialize the bean?
- 这是一个有效的解决方法?
- 我如何使用 #getBeansWithAnnotation() 方法而不初始化 bean?
采纳答案by Arturo Volpe
The problem is that the BeanFactoryPostProcessor can't work with instances, and the #getBeansWithAnnotation() returns instances, so, it is not recommended, here the relevant Javadoc:
问题是 BeanFactoryPostProcessor 不能使用实例,并且 #getBeansWithAnnotation() 返回实例,所以,不推荐,这里有相关的Javadoc:
A BeanFactoryPostProcessor may interact with and modify bean definitions, but never bean instances. Doing so may cause premature bean instantiation, violating the container and causing unintended side-effects. If bean instance interaction is required, consider implementing BeanPostProcessor instead.
A BeanFactoryPostProcessor may interact with and modify bean definitions, but never bean instances. Doing so may cause premature bean instantiation, violating the container and causing unintended side-effects. If bean instance interaction is required, consider implementing BeanPostProcessor instead.
So my solution is this:
所以我的解决方案是这样的:
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory bf)
throws BeansException {
String[] beans = bf.getBeanDefinitionNames();
for (String s : beans) {
Class<?> beanType = bf.getType(s);
WebService ws = AnnotationUtils.findAnnotation(beanType,
WebService.class);
if (ws != null) {
String name = getName(s);
DefaultWsdl11Definition newWS = createWebService(name,
ws.xsds());
bf.registerSingleton(name, newWS);
}
}
}
回答by drkicknrush
The above pattern is what I've always used. However, Spring 4 now has the method ListableBeanFactory::getBeanNamesForAnnotation which would seem to offer the same functionality.
上面的模式是我一直使用的。但是,Spring 4 现在具有 ListableBeanFactory::getBeanNamesForAnnotation 方法,它似乎提供了相同的功能。
From the javadoc:
从javadoc:
Find all names of beans whose {@code Class} has the supplied {@link Annotation} type, without creating any bean instances yet.
查找 {@code Class} 具有提供的 {@link Annotation} 类型的 bean 的所有名称,而不创建任何 bean 实例。
Update: unfortunately this method also seems to instanciate certain (factory) beans, which in my case caused problems with @Resource injection in my processed beans.
更新:不幸的是,这种方法似乎也实例化了某些(工厂)bean,在我的情况下,这在我处理的 bean 中导致了 @Resource 注入问题。
回答by Ondrej Burkert
I had some success with following way:
我通过以下方式取得了一些成功:
@Bean
public BeanFactoryPostProcessor beanFactoryPostProcessor() {
return bf -> {
BeanDefinitionRegistry beanFactory = (BeanDefinitionRegistry) bf;
IntStream.range(0, 10).forEach(i ->
beanFactory.registerBeanDefinition(String.format("bean-name-%d", i),
createCustomBeanDefinition()));
};
}
private BeanDefinition createCustomBeanDefinition() {
return BeanDefinitionBuilder.genericBeanDefinition(MyBeanClass.class)
.setFactoryMethodOnBean("create", "myFactory")
.getBeanDefinition();
}
Especially the part about factory helped me because the bean was not so simple to instantiate so I could create a factory, initialize it and then use it to create instances.
特别是关于工厂的部分对我有帮助,因为 bean 的实例化不是那么简单,所以我可以创建一个工厂,初始化它,然后使用它来创建实例。
Another important point to note is that using beanFactory.registerSingleton(name, newWS);
means Spring won't call its init/destroy methods, won't autowire etc. Whereas with bean definition it should do all it normally does.
另一个需要注意的重点是 usingbeanFactory.registerSingleton(name, newWS);
意味着 Spring 不会调用它的 init/destroy 方法,不会自动装配等。而对于 bean 定义,它应该完成它通常所做的一切。
And one more thing. I struggled a bit on getting properties from application.properties
hooked into the BeanFactoryPostProcessor. Turns out you can autowire org.springframework.core.env.Environment
which is ready at that stage of context loading and call:
还有一件事情。我在从application.properties
挂钩到 BeanFactoryPostProcessor 中获取属性方面有点挣扎。事实证明,您可以自动装配org.springframework.core.env.Environment
在上下文加载和调用的那个阶段准备就绪:
Integer numberOfBeans = environment.getRequiredProperty("myApp.numberOfBeans",
Integer.class);