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. 201521123006 《java程序设计》 第8周学习总结

    1. 本周学习总结 1.1 以你喜欢的方式(思维导图或其他)归纳总结集合与泛型相关内容. 1.2 选做:收集你认为有用的代码片段 class ArrayAlg { public static < ...

  2. 201521123004 《Java程序设计》第1周学习总结

    1. 本章学习总结 (1)安装各种软件(jdk,eclipse,git(安装不了)) 注册账号(博客,网易邮箱(QQ邮箱不能用)码云) 创建项目(码云,Java) (2)了解JAVA语言的发展史(su ...

  3. ASCII中关于大小写字母间隔为32的思考

    一直没有搞清楚为什么在ASCII中要把大小写字母的间隔设置为32,今天才发现这样设置的精妙之处:方便了程序对大小写字母进行转换.请看: ================= 十进制        32 ...

  4. 201521123114 《Java程序设计》第11周学习总结

    1. 本周学习总结 1.1 以你喜欢的方式(思维导图或其他)归纳总结多线程相关内容. 2. 书面作业 Q1. 互斥访问与同步访问 完成题集4-4(互斥访问)与4-5(同步访问) 1.1 除了使用syn ...

  5. 在腾讯云上搭建WordPress博客

    笔者一直很羡慕那些搭建了个人博客的大牛,在最近工作之余也尝试着搭建了自己的博客,历时1周,这篇文章就将踩过的坑记录下来,先看下成果,链接在此 1- 购买腾讯云主机 腾讯云官网,我选了79元/月的最便宜 ...

  6. virtualbox修改主机名

    virtualbox修改主机名 /etc/hostname /etc/hosts

  7. GCD之死锁

    GCD相当好用,但用不好就会死锁,始终要记着这样一句秘籍: 不要在串行队列放dispatch_sync.dispatch_apply 下面看几个例子 1 2 3 4 5 6 7 8 9 10 11 1 ...

  8. 深入理解计算机系统chapter3

    栈在处理过程调用中起到至关重要的作用,栈向下增长,栈顶元素的地址是所有栈中元素最小的.栈指针%esp保存着栈顶元素的地址 控制: 重点: 基于条件数据传送的代码比基于条件控制转移(预测错误惩罚比较高) ...

  9. vue学习心得

    前言 使用vue框架有一段时间了,这里总结一下心得,主要为新人提供学习vue一些经验方法和项目中一些解决思路. 文中谨代表个人观点,如有错误,欢迎指正. 环境搭建 假设你已经通读vue官方文档(文档都 ...

  10. Gradient Boost 算法流程分析

    我们在很多Gradient Boost相关的论文及分析文章中都可以看到下面的公式: 但是,对这个公式的理解,我一直也是一知半解,最近,终于下决心对其进行了深入理解. 步骤1:可以看作优化目标的损失函数 ...