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 ...
随机推荐
- SNS团队Beta阶段第六次站立会议(2017.5.27)
1.立会照片 2.每个人的工作 成员 今天已完成的工作 明天计划完成的工作 罗于婕 发音图标的改进 对界面各部分的图标进行完善.美化 龚晓婷 对于历史记录功能的测试 对于历史记录功能进一步完善 林仕庄 ...
- 201521123082 《Java程序设计》第12周学习总结
201521123082 <Java程序设计>第12周学习总结 标签(空格分隔): java 1. 本周学习总结 1.1 以你喜欢的方式(思维导图或其他)归纳总结多流与文件相关内容. An ...
- C语言程序设计课程设计自查表格
课程设计自查表格 序号 项目 完成与否(完成打勾) 1 格式是否符合标准(缩进是否规范) 2 是否模块化设计(使用函数分解系统功能) 3 函数名否易懂(不得使用f1(int a1,int a2)这样的 ...
- 201521123093 java 第二周学习总结
201521123093 <java程序设计> 第二周学习总结 一.第二周学习总结 答:(1)关于进一步使用码云管理代码,本周才真正学会了如何将Eclipse里的代码上传到码云中,并且能够 ...
- [BT5]信息收集1-2 Dnsmap
0.工具介绍 dnsmap is mainly meant to be used by pentesters during the information gathering/enumeration ...
- 201521123081《java程序设计》 第11周学习总结
1. 本周学习总结 1.1 以你喜欢的方式(思维导图或其他)归纳总结多线程相关内容. 参考资料:XMind ============================================== ...
- 201521123050 《Java程序设计》第13周学习总结
1. 本周学习总结 2. 书面作业 1. 网络基础 1.1 比较ping www.baidu.com与ping cec.jmu.edu.cn,分析返回结果有何不同?为什么会有这样的不同? 对比可以看出 ...
- Java swing: 实现ActionListener监听器的三种途径
Swing是目前Java中不可缺少的窗口工具组,是用户建立图形化用户界面(GUI)程序的 强大工具.Java Swing组件自动产生各种事件来响应用户行为.如当用户点击按钮或选择菜单项目时,Swing ...
- mysql查询文章的评论数量
作为小白的我,这个问题弄了半天才解决,特此记录下. 两张表:文章表和评论表 文章表(article):id 评论表(comment):id,c_aid 要求:查询出所有文章及评论数量然后降序显示(没有 ...
- Jquery第一篇【介绍Jquery、回顾JavaScript代码、JS对象与JQ对象的区别】
什么是Jquery? Jquey就是一款跨主流浏览器的JavaScript库,简化JavaScript对HTML操作 就是封装了JavaScript,能够简化我们写代码的一个JavaScript库 为 ...