Creating beans using BeanFactoryPostProcessor

Spring BeanFactoryPostProcessor Problem

I want to create a Spring BeanFactoryPostProcessor that will add beans to the current ApplicationContext.

I have many definitions of web services in mine spring-ws-config.xml, and I want to reduce as much as possible.

XML configuration

The configuration looks like this:

<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 an annotation called @WebServiceDefinition and add BeanFactoryPostProcessor to automatically create beans, 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 it doesn’t work !, I wrote a simple test, you can see it here

, IOC , , BeanFactory # getBeansWithAnnotation , .

: beans , # bf.getBeansOfType(), ( !).

:

  • ?
  • #getBeansWithAnnotation() bean?
+4
3

, 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.

, :

@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);
        }
    }

}
+3

- , . Spring 4 ListableBeanFactory:: getBeanNamesForAnnotation, , , .

javadoc:

beans, {@code Class} {@link Annotation} , bean.

: , (factory) beans, @Resource beans.

+1

:

@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();
}

, factory, bean , factory, , .

Another important point to note is that use beanFactory.registerSingleton(name, newWS);means Spring will not invoke its init / destroy methods, will not autowire, etc. While with a bean definition, it should do everything that it usually does.

One more thing. I tried a bit to get properties from application.propertiesconnected to BeanFactoryPostProcessor. Turns out you can autwire org.springframework.core.env.Environment, which is ready at this point in loading and invoking the context:

Integer numberOfBeans = environment.getRequiredProperty("myApp.numberOfBeans", 
                           Integer.class);
0
source

All Articles