1. 背景
1.1. 背景介紹
在web項目中我們有時會遇到這種需求,在web項目啟動后需要開啟線程去完成一些重要的工作,例如:往數據庫中初始化一些數據,開啟線程,初始化消息隊列等,在這種需求下,如何在web容器啟動后執行這些工作就成為了本文的重點。
1.2. 測試項目搭建
首先我們新建一個web項目來模擬這種需求,這里我們選擇創建一個maven項目

在項目的pom文件中添加以下properties項
<properties>
<!--web-->
<servlet.version>3.1.0</servlet.version>
<!--spring-->
<spring-framework.version>4.3.8.RELEASE</spring-framework.version>
<!--logging-->
<logback.version>1.1.7</logback.version>
<slf4j.version>1.7.5</slf4j.version>
</properties>
添加以下依賴
<dependencies>
<dependency>
<groupId>javax</groupId>
<artifactId>javaee-web-api</artifactId>
<version>8.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>4.3.8.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>4.3.8.RELEASE</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>${servlet.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>${slf4j.version}</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jcl-over-slf4j</artifactId>
<version>${slf4j.version}</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>${logback.version}</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-core</artifactId>
<version>${logback.version}</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-access</artifactId>
<version>${logback.version}</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>3.8.1</version>
<scope>test</scope>
</dependency>
</dependencies>
等待maven構建完成之后我們就可以開始搭建一個用於測試的web項目
常規項目中基本上會搭配spring框架來構建,這里我們也不例外,這里我們使用0配置文件來構建一個web項目
有關spring0配置文件構建項目可以在網上找到很多資料,這里就至簡單的搭建一個,不作詳細解釋
1.首先在項目src目錄下建立包結構如下

2.在config目錄下建立以下兩個文件用於配置本項目

public class WebInitializer implements WebApplicationInitializer {
Logger logger = LoggerFactory.getLogger(WebInitializer.class);
public void onStartup(ServletContext servletContext) throws ServletException {
AnnotationConfigWebApplicationContext ctx = new AnnotationConfigWebApplicationContext();
ctx.register(MyConfig.class);
logger.debug("Boot sequence:開始");
ctx.setServletContext(servletContext);
//ctx.refresh();
ServletRegistration.Dynamic servlet = servletContext.addServlet("dispatcher", new DispatcherServlet(ctx));
servlet.addMapping("/");
servlet.setLoadOnStartup(1);
servlet.setAsyncSupported(true);
}
}
@Configuration
@EnableWebMvc
@ComponentScan("com.hei123")
public class MyConfig extends WebMvcConfigurerAdapter {
}
到這里為止,這個測試項目就已經搭建完成了,接下來介紹幾種常見的解決方案
2. 幾種解決方案
2.1. 基於javaweb的ServletContextListener
1、在listener包下新建類SimpleServletListener實現ServletContextListener接口
public class SimpleServletListener implements ServletContextListener {
Logger logger = LoggerFactory.getLogger(SimpleConsumer.class);
public void contextInitialized(ServletContextEvent sce) {
logger.debug("Boot Sequence:監聽ServletContext的監聽器監聽到ServletContext初始化");
/**
* 在這里寫需要執行的代碼
*/
}
public void contextDestroyed(ServletContextEvent sce) {
}
}
2、在WebInitializer類中的onStartup尾部添加如下代碼
servletContext.addListener(SimpleServletListener.class);
2.2. 基於javaweb的Filter
- 在filter包下新建SimpleFilter類實現Filter接口
public class SimpleFilter implements Filter {
Logger logger = LoggerFactory.getLogger(SimpleConsumer.class);
public void init(FilterConfig filterConfig) throws ServletException {
logger.debug("Boot Sequence:在web初始化配置中配置的Filter初始化");
//在這里寫需要執行的代碼
}
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
}
public void destroy() {
}
}
- 在WebInitializer類中的onStartup尾部添加如下代碼
servletContext.addFilter("SimpleFilter", SimpleFilter.class);
2.3. 基於javaweb的servlet
- 在servlet包下新建SimpleServlet繼承HttpServlet
public class SimpleServlet extends HttpServlet {
Logger logger = LoggerFactory.getLogger(WebInitializer.class);
@Override
public void init() throws ServletException {
logger.debug("Boot Sequence:在web初始化配置中配置的Servlet初始化");
//在這里寫需要執行的代碼
super.init();
}
}
- 在WebInitializer類中的onStartup尾部添加如下代碼
ServletRegistration.Dynamic simpleServlet = servletContext.addServlet("SimpleServlet", new SimpleServlet());
simpleServlet.setLoadOnStartup(2);
//這里設置為2是因為需要先啟動springMVC的dispatcherServlet
2.4. 基於Spring的ApplicationListener
- 在listener包下新建SimpleApplicationListener類實現ApplicationListener<ContextRefreshedEvent>接口來監聽spring容器啟動完成的事件
@Component
public class SimpleApplicationListener implements ApplicationListener<ContextRefreshedEvent> {
Logger logger = LoggerFactory.getLogger(SimpleConsumer.class);
public void onApplicationEvent(ContextRefreshedEvent contextRefreshedEvent) {
logger.debug("Boot Sequence: 監聽spring 容器Context初始化的的方法調用"); //在這里寫需要執行的代碼
}
}
2.5. 基於Spring的PostProcessor
2.5.1 BeanFactoryPostProcessor
在postprocessor包下新建SimpleBeanFactoryPostProcessor類實現BeanFactoryPostProcessor接口
@Component
public class SimpleBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
Logger logger = LoggerFactory.getLogger(WebInitializer.class);
public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {
logger.debug("Boot Sequence:bootFactory的后置處理器執行");
//在這里編寫需要執行的代碼
}
}
2.5.2 BeanPostProcessor
在postprocessor包下新建SimpleBeanPostProcessor類實現BeanPostProcessor接口
@Component
public class SimpleBeanPostProcessor implements BeanPostProcessor {
//注意:此接口中的方法會在初始化每一個Bean時都執行一次
Logger logger = LoggerFactory.getLogger(WebInitializer.class);
public Object postProcessBeforeInitialization(Object o, String s) throws BeansException {
return o;
}
public Object postProcessAfterInitialization(Object o, String s) throws BeansException {
if(o instanceof SimpleConsumer){
logger.debug("Boot Sequence:SimpleConsumer的初始化之后執行");
//在這里編寫需要執行的代碼
}else{
logger.debug("Other Bean:的初始化之后執行");
}
return o;
}
}
2.6. 基於Spring的InitializingBean
- 在initializingbean包下新建SimpleConsumer類實現InitializingBean接口
@Component
public class SimpleConsumer implements InitializingBean {
Logger logger = LoggerFactory.getLogger(SimpleConsumer.class);
public void afterPropertiesSet() throws Exception {
logger.debug("Boot sequence:SimpleConsumer Bean 依賴注入完成");
//在這里編寫需要執行的代碼
}
}
3. 解決方案之間的對比
3.1. 執行順序





我在測試項目中添加了以上所有的幾種解決方案從日志中可以看到幾種方案的執行順序
| 啟動順序 |
方案名稱 |
解釋 |
| 1 |
基於javaweb的ServletContextListener |
監聽webContext初始化 |
| 2 |
基於javaweb的filter |
WebContext初始化后會先加載定義的過濾器,然后才會加載定義的Servlet,而這里的spring容器也是借助定義的DispatcherServlet來初始化的。 |
| 3 |
基於spring的BeanFactoryPostProcessor |
|
| 4 |
基於spring的InitializingBean |
在SimpleConsumer的屬性注入完成后執行 |
| 5 |
基於spring的BeanPostProcessor |
在SimplerConsumer初始化完成后執行 |
| 6 |
基於spring的ApplicationContextListener |
在spring容器初始化完所有的Bean后執行 |
| 7 |
基於javaweb的servlet |
在配置中我們將其執行順序設置為2,此servlet將會在DispatcherServlet初始化完成后才會去初始化,因此會落在最后 |
3.2. 橫向對比
|
|
Servlet ContextListener |
filter |
BeanFactory PostProcessor |
Initializing Bean |
Bean PostProcessor |
Application ContextListener |
servlet |
| 自動執行 |
√ |
√ |
√ |
√ |
√ |
√ |
√ |
| 引用其他類的變量或方法 |
× |
× |
× |
*不確定 |
*不確定 |
√ |
√ |
| Spring容器是否已進行屬性注入 |
× |
× |
× |
當前Bean所有屬性已注入,且其屬性中引用的其他屬性也已注入 |
當前Bean所有屬性已注入,且其屬性中引用的其他屬性也已注入 |
√ |
√ |
| Web容器完全啟動 |
× |
× |
× |
× |
× |
× |
× |
*指若其他Bean已經初始化完成可引用,未初始化完成的Bean不可引用
3.3. 細節詳解
3.3.1 BeanFactoryPostProcessor與BeanPostProcessor的區別
BeanFactoryPostProcessor與BeanPostProcessor別看名字長的差不多,其實里面的內容差距很大,
- BeanFactoryPostProcessor是在當Spring容器已經獲取到所有的Bean初始化列表,並創建BeanFactory后才調用的后置處理器,此時所有的Bean都還未初始化
- BeanPostProcessor是會在每初始化一個Bean都會調用其中的postProcessAfterInitialization方法,此時可能已經初始化了一些Bean
3.3.2 Initializing中的AfterPropertiesSet與xml配置的init-method以及BeanPostProcessor之間的區別
如果我們通過xml配置文件來配置spring中的Bean的話,其中可以通過init-method配置一個用於初始化方法,那這三者之間有什么區別呢?
執行順序
其實可以在本項目中再加上init-method來驗證執行順序,這里就不再去加了,直接解釋好了,其實僅從名稱上就可以看出執行順序應為
AfterPropertiesSetàinitMethodàbeanPostProcessor
只有當屬性設置完成之后,此Bean才算基本上創建完成,即afterPropertiesSet,
在Bean創建完成之后,可以對此Bean進行一些初始化操作,即init-method
在初始化完成之后,調用Bean的后置處理器來完成一些其他的操作
這一點可以在源碼中查看,這里就不做多的解釋了
3.3.3 web容器的啟動順序
1.在web容器啟動時會所有的webContextListener會收到web容器啟動的通知,並可以執行其中的監聽方法
2.接下來web容器會先去初始化所有的filter過濾器
3.然后web容器會根據servlet的初始化順序去初始化所有的Servlet,在本例中DispacherServlet啟動順序為1最大,
4.DispacherServlet中會去初始化spring容器
5.初始化其他的Servlet
6.web容器啟動完成
3.3.4 如何選擇
- 如果我們需要在web容器剛初始化就執行程序的話需要采用實現ServletContextListenre的方案來執行
- 如果我們需要spring容器中的所有內容都加載完畢的話要采用實現ApplicationContextListener的方案來執行。
總之,我們需要根據自己的實際情況來選擇對應的方案來達到最好的效果
4. 總結
本文主要介紹了幾種在web容器啟動后自動執行代碼的解決方案,並對這些解決方案進行了一些大概的分析,對其中的一些細節內容進行了一些解釋,詳細的解釋需要通過觀察源碼才能對這些內容有更好的理解,不足之處,還請指正。
任何問題請聯系hei12138@outlook.com
