Reputation: 41
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
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
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
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