Creating beans with BeanFactoryPostProcessor

15,076

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.

Share:
15,076
Arturo Volpe
Author by

Arturo Volpe

Updated on June 08, 2022

Comments

  • Arturo Volpe
    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?