web容器启动后自动执行程序的几种方式比较
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
web容器启动后自动执行程序的几种方式比较的更多相关文章
- web容器启动加载WebApplicationContext和初始化DispatcherServlet
原文地址:http://blog.csdn.net/zghwaicsdn/article/details/51186915 ContextLoaderListener监听器,加载ROOT WebApp ...
- spring boot, 容器启动后执行某操作
常有在spring容器启动后执行某些操作的需求,现做了一个demo的实现,做一下记录,也希望可以给需要的同学提供参考. 1.spring启动后,以新线程执行后续需要的操作,所以执行类实现Runnabl ...
- WEB容器启动——web.xml加载详解
最近在看spring的源码,关于web.xml文件在容器(Tomcat.JBOSS等)启动时加载顺序问题很混乱,通过搜集资料,得出以下的结论: 1.加载顺序与它们在 web.xml 文件中的先后顺序无 ...
- web容器启动顺序
web容器启动顺序: 第一:context-param 第二:Listerer 第三:Filter 第四:servlet
- 监听Web容器启动与关闭
在Servlet API 中有一个 ServletContextListener 接口,它能够监听 ServletContext 对象的生命周期,实际上就是监听 Web 应用的生命周期. 要监听web ...
- [Elixir002]节点启动后自动连接其它节点
问题: 如何指定一个节点在启动后自动连接到别的节点上? 这个我们要使用到sys.config,这是erlang的配置文件,这个文件一般都是$ROOT/releases/Vsn下 1. 首先我们要先启动 ...
- Spring源码解析-Web容器启动过程
Web容器启动过程,主要讲解Servlet和Spring容器结合的内容. 流程图如下: Web容器启动的Root Context是有ContextLoaderListener,一般使用spring,都 ...
- springboot启动后自动退出
有时新建的springboot启动后自动退出运行,如图所示: 此种情况大都数是因为pom文件加入了tomcat的依赖,与springboot内嵌的tomcat冲突导致,所以只需将pom文件中的tomc ...
- docker容器启动后添加端口映射
DOCKER 给运行中的容器添加映射端口 方法1 1.获得容器IP 将container_name 换成实际环境中的容器名 docker inspect `container_name` | grep ...
随机推荐
- 201521123049 《JAVA程序设计》 第8周学习总结
1. 本周学习总结 1.1 以你喜欢的方式(思维导图或其他)归纳总结集合与泛型相关内容. 2. 书面作业 本次作业题集集合 1.List中指定元素的删除(题目4-1) 1.1 实验总结 public ...
- 201521123095 《Java程序设计》第5周学习总结
1. 本周学习总结 1.1 尝试使用思维导图总结有关多态与接口的知识点. 2. 书面作业 1.1 com.parent包中Child.java文件能否编译通过?哪句会出现错误?试改正该错误.并分析输出 ...
- Java补码表和位移运算符
在java中数据都是以二进制的形式保存的. 但是我们看到的数据怎么是10进制的? 因为java展示之前会自动调用toString()方法 这里以4位2进制为例,4位2进制只能表示16个数,即0-15. ...
- 201521123069 《Java程序设计》 第9周学习总结
1. 本章学习总结 1.1 以你喜欢的方式(思维导图或其他)归纳总结异常相关内容. (1)使用try...catch语句捕获异常(try块后可跟一个或多个catch块,注意子类异常要放在父类异常前面, ...
- MarkDown模板
一个例子: 例子开始 1. 本章学习总结 今天主要学习了三个知识点 封装 继承 多态 2. 书面作业 Q1. java HelloWorld命令中,HelloWorld这个参数是什么含义? 今天学了一 ...
- allego 输出报告说明
List of Available Reports Assigned Function Report Lists all assigned functions, sorted by function ...
- MapReduce极简教程
一个有趣的例子 你想数出一摞牌中有多少张黑桃.直观方式是一张一张检查并且数出有多少张是黑桃? MapReduce方法则是: 给在座的所有玩家中分配这摞牌 让每个玩家数自己手中的牌有几张是黑桃,然后 ...
- Cnblogs关于嵌入js和css的一些黑科技
#pong .spoiler{cursor:none;display:inline-block;line-height:1.5;}sup{cursor:help;color:#3BA03B;} Pon ...
- GCD之信号量机制一
在使用NSOperationQueue进行多线程编程时,可通过[queue setMaxConcurrentOperationCount:5]来设置线程池中最多并行的线程数,在GCD中信号量机制也和它 ...
- 【个人笔记】《知了堂》MySQL三种关系:一对一,一对多,多对多。
一对一:比如一个学生对应一个身份证号.学生档案: 一对多:一个班可以有很多学生,但是一个学生只能在一个班: 多对多:一个班可以有很多学生,学生也可以有很多课程: 一对多关系处理: 我们以学生和班级之间 ...