Mingyi Yang
Mingyi Yang

Reputation: 41

How to selective load beans when start spring

My issue is how to implement selectively loading spring beans at the start of an web application.

Background Our application is based on J2EE and Spring. We run the same web application on different managed servers. On some of these managed servers, we only run web service but on others, we also need to run services such as reporting, scheduler and etc. All these services are configured as spring bean in spring configuration xml files. So we'd like to disable some unused beans when start servers with web service.

Problems, I tried to override method of customizeContext in org.springframework.web.context.ContextLoaderListener to remove those unused beans from context. (I know this is not a good idea to remove loaded beans instead of stopping them being loaded at the first place. that's because I couldn't figure out how to implement that as well) However, I got java.lang.IllegalStateException: BeanFactory not initialized or already closed - call 'refresh' before accessing beans via the ApplicationContext.

After some investigation, I get that BeanFactory can't be used here but I'm kind of stuck and don't know how to implement this function. Can anyone help me out of this please? Either stopping loading beans into Spring at start time or removing beans from Spring when it is just started would work for me.

The following is my code to override Method customizeContext.

@Override
protected void customizeContext(ServletContext servletContext, ConfigurableWebApplicationContext applicationContext) {

    super.customizeContext(servletContext, applicationContext);

    ConfigurableListableBeanFactory configurableListableBeanFactory = applicationContext.getBeanFactory();
    BeanDefinitionRegistry beanDefinitionRegistry = (BeanDefinitionRegistry) configurableListableBeanFactory;
    beanDefinitionRegistry.removeBeanDefinition("testBean");
}

Upvotes: 2

Views: 10105

Answers (3)

Mingyi Yang
Mingyi Yang

Reputation: 41

Thanks for the suggestions from Serge and other guys. We are currently using 3.0.5, so can't really use the 3.1 profiles feature in our project.

We figured out a way to do it by adding a BeanFactoryPostProcessor to the ConfigurableWebApplicationContext in the method customizeContext(). It seems solved our problems.

Code changes are:

protected void customizeContext(ServletContext servletContext, ConfigurableWebApplicationContext applicationContext) {
    super.customizeContext(servletContext, applicationContext);
    applicationContext.addBeanFactoryPostProcessor(new BootProcessor());

}

class BootProcessor implements BeanFactoryPostProcessor{

        @Override
        public void postProcessBeanFactory(ConfigurableListableBeanFactory clbf) throws BeansException {
            BeanDefinitionRegistry beanDefinitionRegistry = (BeanDefinitionRegistry) clbf;
            beanDefinitionRegistry.removeBeanDefinition("testerBean");

        }           
    }

Upvotes: 2

jebeaudet
jebeaudet

Reputation: 1603

With Spring Boot (tested with 2.1.x but should work for all versions) it's quite simple, put this class somewhere in the classpath scanning range :

@Component
public class BeanKiller implements BeanFactoryPostProcessor
{    
    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException
    {
        try {
            DefaultListableBeanFactory factory = (DefaultListableBeanFactory) beanFactory;
            factory.removeBeanDefinition("problematicBeanNameHere");
        } catch (NoSuchBeanDefinitionException e) {
            throw new IllegalStateException("Couldn't remove the problematicBeanNameHere, maybe it changed name?.");
        }
    }
}

If you're not using classpath scanning, you can inject it in the main method :

public static void main(String[] args)
{
    SpringApplicationBuilder builder = new SpringApplicationBuilder(YourApplication.class);
    builder.initializers(context -> context.addBeanFactoryPostProcessor(new ZuulRefreshRoutesListenerKiller()));
    builder.run(args);
}

Upvotes: 0

Serge Ballesta
Serge Ballesta

Reputation: 148965

Instead of trying to configure the BeanFactory after loading all the beans, you should have groups of beans and load only the groups related to actual running services.

The legacy method was to have intermediate XML files containing imports for other files containing what I called above groups of beans, and in main XML file, import the correct one. Extract from Spring Reference Manual : relying on a combination of system environment variables and XML statements containing ${placeholder} tokens that resolve to the correct configuration file path depending on the value of an environment variable

But now the tool of choice should be the profiles. You put the beans in different profiles corresponding to your services

@Bean
@Profile("production")
public DataSource productionDataSource() throws Exception {

or in XML

<beans profile="production">
    <jee:jndi-lookup id="dataSource" jndi-name="java:comp/env/jdbc/datasource"/>
</beans>

You have just to enable relevant profiles, either programmaticaly :

AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.getEnvironment().setActiveProfiles("dev");

or even as JVM properties :

-Dspring.profiles.active="profile1,profile2"

References : Chapter Environment Abstraction in Spring Reference Manual.

Upvotes: 5

Related Questions