Creating beans with BeanFactoryPostProcessor
Solution 1
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:
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);
}
}
}
Solution 2
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.
From the javadoc:
Find all names of beans whose {@code Class} has the supplied {@link Annotation} type, without creating any bean instances yet.
Update: unfortunately this method also seems to instanciate certain (factory) beans, which in my case caused problems with @Resource injection in my processed beans.
Arturo Volpe
Updated on June 08, 2022Comments
-
Arturo Volpe almost 2 years
Spring BeanFactoryPostProcessor problem
I want to create a Spring BeanFactoryPostProcessor that add beans to the current ApplicationContext.
I have a lot of Web-Services definition in my
spring-ws-config.xml
and I want to reduce as much as possible.XML Configuration
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
So, I create a @Configuration class with the following bean definition:
@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:
BeanFactoryPostProcessor
@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.
Workaround
I do a workaround: get all beans by name, get the corresponde class and use #bf.getBeansOfType(Class), (this method dont initialize it!).
My questions:
- This is a valid workaround?
- How I can use the method #getBeansWithAnnotation() and don't initialize the bean?