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

  1. 在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() {



    }

}

  1. 在WebInitializer类中的onStartup尾部添加如下代码

servletContext.addFilter("SimpleFilter", SimpleFilter.class);

2.3.       基于javaweb的servlet

  1. 在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();

    }

}

  1. 在WebInitializer类中的onStartup尾部添加如下代码

ServletRegistration.Dynamic simpleServlet =
servletContext.addServlet("SimpleServlet", new SimpleServlet());

simpleServlet.setLoadOnStartup(2);

//这里设置为2是因为需要先启动springMVC的dispatcherServlet

2.4.       基于Spring的ApplicationListener

  1. 在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

  1. 在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别看名字长的差不多,其实里面的内容差距很大,

  1. BeanFactoryPostProcessor是在当Spring容器已经获取到所有的Bean初始化列表,并创建BeanFactory后才调用的后置处理器,此时所有的Bean都还未初始化
  2. 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 如何选择

  1. 如果我们需要在web容器刚初始化就执行程序的话需要采用实现ServletContextListenre的方案来执行
  2. 如果我们需要spring容器中的所有内容都加载完毕的话要采用实现ApplicationContextListener的方案来执行。

总之,我们需要根据自己的实际情况来选择对应的方案来达到最好的效果

4.       总结

本文主要介绍了几种在web容器启动后自动执行代码的解决方案,并对这些解决方案进行了一些大概的分析,对其中的一些细节内容进行了一些解释,详细的解释需要通过观察源码才能对这些内容有更好的理解,不足之处,还请指正。

任何问题请联系hei12138@outlook.com

web容器启动后自动执行程序的几种方式比较的更多相关文章

  1. web容器启动加载WebApplicationContext和初始化DispatcherServlet

    原文地址:http://blog.csdn.net/zghwaicsdn/article/details/51186915 ContextLoaderListener监听器,加载ROOT WebApp ...

  2. spring boot, 容器启动后执行某操作

    常有在spring容器启动后执行某些操作的需求,现做了一个demo的实现,做一下记录,也希望可以给需要的同学提供参考. 1.spring启动后,以新线程执行后续需要的操作,所以执行类实现Runnabl ...

  3. WEB容器启动——web.xml加载详解

    最近在看spring的源码,关于web.xml文件在容器(Tomcat.JBOSS等)启动时加载顺序问题很混乱,通过搜集资料,得出以下的结论: 1.加载顺序与它们在 web.xml 文件中的先后顺序无 ...

  4. web容器启动顺序

    web容器启动顺序: 第一:context-param 第二:Listerer 第三:Filter 第四:servlet

  5. 监听Web容器启动与关闭

    在Servlet API 中有一个 ServletContextListener 接口,它能够监听 ServletContext 对象的生命周期,实际上就是监听 Web 应用的生命周期. 要监听web ...

  6. [Elixir002]节点启动后自动连接其它节点

    问题: 如何指定一个节点在启动后自动连接到别的节点上? 这个我们要使用到sys.config,这是erlang的配置文件,这个文件一般都是$ROOT/releases/Vsn下 1. 首先我们要先启动 ...

  7. Spring源码解析-Web容器启动过程

    Web容器启动过程,主要讲解Servlet和Spring容器结合的内容. 流程图如下: Web容器启动的Root Context是有ContextLoaderListener,一般使用spring,都 ...

  8. springboot启动后自动退出

    有时新建的springboot启动后自动退出运行,如图所示: 此种情况大都数是因为pom文件加入了tomcat的依赖,与springboot内嵌的tomcat冲突导致,所以只需将pom文件中的tomc ...

  9. docker容器启动后添加端口映射

    DOCKER 给运行中的容器添加映射端口 方法1 1.获得容器IP 将container_name 换成实际环境中的容器名 docker inspect `container_name` | grep ...

随机推荐

  1. 团队作业9——展示博客(Bata版本)

    1.团队成员介绍及项目地址 团队的源码仓库地址:https://coding.net/u/app24dian/p/app24dian/git 陈麟凤:(http://www.cnblogs.com/c ...

  2. Swing-BoxLayout用法-入门

    注:本文内容源于http://www.java3z.com/cwbwebhome/article/article20/200016.html?id=4797:细节内容根据笔者理解有修改. BoxLay ...

  3. java--利用exe4j生成.exe的可执行文件

    工具:eclipse,exe4j,jre,这三个都可以直接在官方网站下载,下面所用到的都是最新版的. 前期准备:用eclipse编好需要生成.exe文件的project,另外exe4j需要一个注册码, ...

  4. 201521123019 《Java程序设计》第8周学习总结

    1. 本章学习总结 2. 书面作业 一.List中指定元素的删除(题目4-1) for (int i = list.size()-1; i >=0; i--) {//从最后一个元素开始删除 if ...

  5. 201521123028 《Java程序设计》第5周学习总结

    1. 本周学习总结 2. 书面作业 Q1.代码阅读:Child压缩包内源代码 1.1 com.parent包中Child.java文件能否编译通过?哪句会出现错误?试改正该错误.并分析输出结果. Ch ...

  6. 201521123104《Java程序设计》第4周学习总结

    1. 本周学习总结 1.1 尝试使用思维导图总结有关继承的知识点. 1.2 使用常规方法总结其他上课内容. (1)继承时子类将获得父类的属性与方法,并具有自身特有的属性与方法. (2)使用super还 ...

  7. 201521123056 《Java程序设计》第4周学习总结

    1. 本周学习总结 1.1 尝试使用思维导图总结有关继承的知识点. 1.2 使用常规方法总结其他上课内容. 1.1 instanceof 测试一个对象是否是某个类的实例,即使左边是右边类的子类的实例对 ...

  8. 201521123117 《Java程序设计》第10周学习总结

    1. 本周学习总结 2.2. 书面作业 1.finally 题目4-2 1.1 截图你的提交结果(出现学号) 1.2 4-2中finally中捕获异常需要注意什么? 只要try执行了之后,finall ...

  9. 201521123087《Java程序设计》第10周学习总结

    1. 本周学习总结 2. 书面作业 本次PTA作业题集异常.多线程 finally题目4-21.1 截图你的提交结果(出现学号)1.2 4-2中finally中捕获异常需要注意什么? 用异常改进Arr ...

  10. Play使用

        play框架   打包命令: play war e:/codes/cn.ngmc.frontend -o f:/backup_ngmc/20160614frontend_001Dev; 即:p ...