近期Springboot项目需要在国产Web容器上运行,在打完war包启动后出现Filter中注入的bean为null的错误。
在网上搜了一圈,解决方案如下:
public void init(FilterConfig filterConfig) throws ServletException {
ServletContext servletContext = filterConfig.getServletContext();
WebApplicationContext webApplicationContext = WebApplicationContextUtils.getWebApplicationContext(servletContext);
if (webApplicationContext != null) {
this.ramCacheLoader = (RamCacheLoader)webApplicationContext.getBean(RamCacheLoader.class);
}
}
在Filter的init方法中,通过FilterConfig从容器中获取Serverlet上下文ServerletContex,从而获取Springboot上下文,最后从Springboot上下文对象中获取bean。
解决方案找到了,但是为什么jar包方式可以注入成功,war包方式注入失败的原因还没搞清楚,故做了一番深入的学习,在此记录一下。
我们在创建Springboot工程时,会依赖 spring-boot-starter-web ,从而可以使用Springboot内置Web容器(默认为Tomcat),通过分析Springboot启动流程,SpringApplication.run 会在打印Banner 后创建通过 createApplicationContext() 方法 ApplicationContext 容器(IOC容器),然后通过 refreshContext(context) 方法,刷新容器,此时会获取嵌入式Servlet容器工厂,由容器工厂创建Servlet,也就是说此时是先创建容器,后创建Serverlet,而 Filter 由Serverlet托管,故此时启动实例化 Filter 时可以获取到bean。
而打war包时,我们会在依赖中排除内置tomcat:
<!-- 将原来的内置tomcat依赖scope改为provided-->
<!-- 内置tomcat -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<scope>provided</scope>
</dependency>
并修改 SpringApplication 启动类:
@SpringBootApplication
public class Application extends SpringBootServletInitializer {
public static void main(String[] args) {
SpringApplication.run(ZxblogApplication.class, args);
}
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
return builder.sources(Application.class);
}
}
此时, SpringBootServletInitializer 实例执行onStartup方法的时候会通 createRootApplicationContext 方法来执行run方法,接下来的过程就同以jar包形式启动的应用的run过程一样了,在内部会创建IOC容器并返回,只是以war包形式的应用在创建IOC容器过程中,不再创建Servlet容器了。
因此,相当于Spring并不会托管Serverlet,自然也就不会托管Filter,Filter也就无法通过@AutoWired注解获取到容器中的bean。
那么,文章开头的方式为什么可以获取到bean呢?因为init方法中有一个形参 FilterConfig,通过其 getServletContext() 可以获取 ServletContext 对象的引用,从而获取上下文对象,最后通过上下文获取bean, FilterConfig相当于连接Filter和IOC容器的桥梁。
自己的片面理解,如有错误请指正!