我的问题是如何在Web应用程序启动时实现选择性加载spring bean。
背景我们的应用程序基于J2EE和Spring。我们在不同的托管服务器上运行相同的Web应用程序。在其中一些托管服务器上,我们仅运行Web服务,而在其他服务器上,我们还需要运行诸如报告,调度程序等服务。所有这些服务都在spring配置xml文件中配置为spring bean。因此,我们希望在使用Web服务启动服务器时禁用一些未使用的bean。
问题,我试图覆盖customizeContext
中的org.springframework.web.context.ContextLoaderListener
方法,以从上下文中删除那些未使用的bean。 (我知道删除已加载的bean而不是首先停止加载它们不是一个好主意。这是因为我也无法弄清楚如何实现它)。但是,我得到了java.lang.IllegalStateException: BeanFactory not initialized or already closed - call 'refresh' before accessing beans via the ApplicationContext
。
经过一番调查后,我发现BeanFactory
不能在这里使用,但有点卡住,也不知道如何实现此功能。有人可以帮我吗?在启动时停止将bean加载到Spring中,或者在刚启动时从Spring中删除bean对我来说都是有效的。
以下是我的代码,以覆盖方法customizeContext
。
@Override
protected void customizeContext(ServletContext servletContext, ConfigurableWebApplicationContext applicationContext) {
super.customizeContext(servletContext, applicationContext);
ConfigurableListableBeanFactory configurableListableBeanFactory = applicationContext.getBeanFactory();
BeanDefinitionRegistry beanDefinitionRegistry = (BeanDefinitionRegistry) configurableListableBeanFactory;
beanDefinitionRegistry.removeBeanDefinition("testBean");
}
而不是在加载所有Bean之后尝试配置BeanFactory,您应该具有groups的Bean并仅加载与实际运行的服务相关的groups。
传统方法是使中间XML文件包含其他文件的导入,这些文件包含我在上面的groups bean中所称的文件,并在主XML文件中导入正确的文件。从Spring参考手册中摘录:依赖于系统环境变量和包含$ {placeholder}标记的XML语句的组合,这些标记根据环境变量的值解析为正确的配置文件路径
但是现在选择的工具应该是配置文件。您将bean放在与您的服务相对应的不同配置文件中
@Bean
@Profile("production")
public DataSource productionDataSource() throws Exception {
或XML
<beans profile="production">
<jee:jndi-lookup id="dataSource" jndi-name="java:comp/env/jdbc/datasource"/>
</beans>
您只需以编程方式启用相关配置文件:
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.getEnvironment().setActiveProfiles("dev");
或什至作为JVM属性:
-Dspring.profiles.active="profile1,profile2"
参考:Spring参考手册中的Environment Abstraction章。
感谢Serge和其他人的建议。我们目前正在使用3.0.5,因此无法在我们的项目中真正使用3.1配置文件功能。
我们想出了一种方法,可以在方法BeanFactoryPostProcessor
中将ConfigurableWebApplicationContext
添加到customizeContext()
。看来解决了我们的问题。
代码更改为:
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");
}
}
使用Spring Boot(已经在2.1.x上进行了测试,但应该适用于所有版本),非常简单,将此类放置在类路径扫描范围内的某个位置:
@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?.");
}
}
}
如果不使用类路径扫描,则可以将其注入main方法中:
public static void main(String[] args)
{
SpringApplicationBuilder builder = new SpringApplicationBuilder(YourApplication.class);
builder.initializers(context -> context.addBeanFactoryPostProcessor(new ZuulRefreshRoutesListenerKiller()));
builder.run(args);
}