SpringBoot 中的 ServletInitializer

本文深入探讨了SpringBoot中的Servlet初始化,从WebApplicationInitializer接口的运作原理到SpringBootServletInitializer的理解,再到如何扩展SpringBootServletInitializer。通过Java SPI机制,我们能在不修改框架的情况下为API提供新的实现。SpringBootServletInitializer在war包部署时,创建IOC容器并启动应用,而ErrorPageFilter处理应用程序的异常,提供自定义错误页面。
摘要由CSDN通过智能技术生成

 

(一)代码部分

项目中使用ServletInitializer 的代码(入口)

7c327bd5a136cbb88156d71e112d1d03.png

image.png

在 SpringBootServletInitializer 中

在 WebApplicationInitializer 中

(二)分析

一、对 WebApplicationInitializer 的理解

现在JavaConfig配置方式在逐步取代xml配置方式。而WebApplicationInitializer可以看做是Web.xml的替代,它是一个接口。通过实现WebApplicationInitializer,在其中可以添加servlet,listener等,在加载Web项目的时候会加载这个接口实现类,从而起到web.xml相同的作用。下面就看一下这个接口的详细内容。

首先打开这个接口,如下:

package org.springframework.web;

import javax.servlet.ServletContext;
import javax.servlet.ServletException;

public interface WebApplicationInitializer {
    void onStartup(ServletContext var1) throws ServletException;
}

只有一个方法,看不出什么头绪。但是,在这个包下有另外一个类,SpringServletContainerInitializer。它的实现如下:

 

e3e5b658659bf0d0219fb96b3cffd55d.png

image.png

1fad495dd8aaa3277bd4c6367cea783f.png

image.png

这个类先判断webAppInitializerClasses这个LinkedList是否为空。如果不为空的话,找到这个LinkedList中不是接口,不是抽象类,并且是WebApplicationInitializer接口实现类的类(isAssignableFrom),将它们保存到list中。

当这个list为空的时候,抛出异常。不为空的话就按照一定的顺序排序,并将它们按照一定的顺序实例化。调用其onStartup方法执行。到这里,就可以解释WebApplicationInitializer实现类的工作过程了。但是,在web项目运行的时候,SpringServletContainerInitializer这个类又是怎样被调用的呢。

public interface ServletContainerInitializer {
    void onStartup(Set<Class<?>> var1, ServletContext var2) throws ServletException;
}

它只有一个接口,ServletContainerInitializer,通过它就可以解释SpringServletContainerInitializer是如何被调用的。

3cdad7577920740269066637f4dfdc55.png

image.png

首先,这个接口是javax.servlet下的。官方的解释是这样的:为了支持可以不使用web.xml。提供了ServletContainerInitializer,它可以通过SPI机制,当启动web容器的时候,会自动到添加的相应jar包下找到META-INF/services下以ServletContainerInitializer的全路径名称命名的文件,它的内容为ServletContainerInitializer实现类的全路径,将它们实例化。既然这样的话,那么SpringServletContainerInitializer作为ServletContainerInitializer的实现类,它的jar包下也应该有相应的文件。打开查看如下:

 

edd96bd5b86d3f22acab2ffa1ea97b3c.png

image.png

双击打卡可以看到SpringServletContainerInitializer作为ServletContainerInitializer的实现类,通过SPI机制,在web容器加载的时候会自动的被调用。(这个类上还有一个注解@HandlesTypes,它的作用是将感兴趣的一些类注入到ServletContainerInitializerde), 而这个类的方法又会扫描找到WebApplicationInitializer的实现类,调用它的onStartup方法,从而起到启动web.xml相同的作用。

 

e41823dd9ff4e4cefa17cca98fc677b5.png

image.png

然后,我们自己通过一个实例来实现相同的功能,通过一样的方式来访问一个servlet。

1、定义接口WebParameter,它就相当于WebApplicationInitializer。内容如下:可以在这里面添加servlet,listener等。
2、定义Servlet。
3、定义MyWebParameter作为WebParameter的实现类,将Servlet添加到上下文,并设置好映射。
4、定义好WebConfig作为ServletContainerInitializer的实现类,它的作用是扫描找到WebParameter的实现类,并调用其方法。

 

5、根据SPI机制,定义一个META-INF/services文件夹,并在其下定义相关文件名称,并将WebConfig的类全名称填入其中。

100a3e9745894ed5759506028d918836.png

image.png


至此,相关内容就完成了,因为我用的maven,通过install将其作为jar包上传到本地仓库。从另外一个web项目调用这个包进行访问。
6、最终结果:

c656c76af8d7e4abec9ba2129c7e60b5.png

image.png

 

25ef9cdbca68a52d538d2c33a6053877.png

image.png

①、这里面会涉及到一些注解,例如: @HandlesTypes ,可以参考文章:Servlet3.0特性详解-笔记 ,里面有详细的解释!!

5f39afd1cdb4c28f518a7defeddfd171.png

image.png


②、SPI机制:参考 Java的SPI机制浅析与简单示例 (或是你们可以搜索其他的文章学习SPI机制)

 

这里先说下SPI的一个概念,SPI英文为Service Provider Interface单从字面可以理解为Service提供者接口,正如从SPI的名字去理解SPI就是Service提供者接口;我对SPI的定义:提供给服务提供厂商与扩展框架功能的开发者使用的接口。

通过Java SPI机制我们就可以在不修改Jar包或框架的时候为Api提供新实现。

二、对 SpringBootServletInitializer 的理解

使用嵌入式Servlet容器:

优点: 简单,便携

缺点: 默认不支持jsp,优化定制比较复杂

使用外置Servlet容器的步骤:

1 必须创建war项目,需要建好web项目的目录结构

2 嵌入式Tomcat依赖scope指定provided

3 编写SpringBootServletInitializer类子类,并重写configure方法

4 启动服务器

jar包和war包启动区别

jar包:执行SpringBootApplication的run方法,启动IOC容器,然后创建嵌入式Servlet容器

war包: 先是启动Servlet服务器,服务器启动Springboot应用(springBootServletInitizer),然后启动IOC容器

Servlet 3.0+规则

1 服务器启动(web应用启动),会创建当前web应用里面所有jar包里面的ServletContainerlnitializer实例

2 ServletContainerInitializer的实现放在jar包的META-INF/services文件夹下
3 还可以使用@HandlesTypes注解,在应用启动的时候加载指定的类。

外部Tomcat流程以及原理

① 启动Tomcat

② 根据上述描述的Servlet3.0+规则,可以在Spring的web模块里面找到有个文件名为javax.servlet.ServletContainerInitializer的文件,而文件的内容为org.springframework.web.SpringServletContainerInitializer,用于加载SpringServletContainerInitializer类

③看看SpringServletContainerInitializer定义
在上面一段长长的注释中可以看到,SpringServletContainerInitializer将@HandlesTypes(WebApplicationInitializer.class)标注的所有WebApplicationInitializer这个类型的类都传入到onStartup方法的Set参数中,并通过反射为这些WebApplicationInitializer类型的类创建实例;

④ 方法最后,每一个WebApplicationInitilizer实现调用自己onstartup方法

⑤ 而WebApplicationInitializer有个抽象实现类SpringBootServletInitializer(记住我们继承了该抽象类),则会调用每一个WebApplicationInitializer实例(包括SpringBootServletInitializer)的onStartup方法:

SpringBootServletInitializer实例执行onStartup方法的时候会通过createRootApplicationContext方法来执行run方法,接下来的过程就同以jar包形式启动的应用的run过程一样了,在内部会创建IOC容器并返回,只是以war包形式的应用在创建IOC容器过程中,不再创建Servlet容器了。

参考:spring boot中servlet启动原理

注:SpringBoot异常处理之ErrorPageFilter

0064315a2a4ea74235f1444fc1955733.png

image.png

ErrorPageFilter是SpringBoot在1.4.0版本提供的一个类,本质上是一个Filter。 它的作用主要有两方面:

  1. 提供应用程序注册ErrorPage的接口,此时它的角色是:ErrorPageRegistry
  2. 处理应用程序异常,根据异常的类型转发到对应的ErrorPage页, 从而不依赖部署的容器错误处理机制

三、了解深入了解SpringBootServletInitializer

熟悉了SpringApplication的原理之后,我们再来了解SpringBootServletInitializer的原理就比较容易了。

SpringBootServletInitializer就是一个org.springframework.web.context.WebApplicationContext,容器启动时会调用其onStartup(ServletContext servletContext)方法,接下来我们就来看一下这个方法:

这里的核心方法就是createRootApplicationContext(servletContext):


说明
SpringBootServletInitializer的执行过程,简单来说就是通过SpringApplicationBuilder构建并封装SpringApplication对象,并最终调用SpringApplication的run方法的过程。


扩展SpringBootServletInitializer

与扩展SpringApplication类似,ApplicationContextInitializerApplicationListener可以基于SpringApplicationBuilder提供的public方法进行扩展

参考: 飘逸峰 Spring Boot学习笔记03--深入了解SpringBoot的启动过程

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值