Spring之Servlet去除web.xml

南楼画角 提交于 2020-11-09 05:59:44

一、概述

Servlet从2.5经历到3.0,SpringMVC也从3.x到了5.x,再到SpringBoot的出现,导致现在Spring环境开发web应用已经跟以前很不一样了。为了弄清楚这之间的变化,为什么发生这个变化?本文从Servlet应用的几个发展阶段来试图回答这个问题。

二、Servlet 2.5

要点

  • 在web.xml中配置servlet、filter、listener组件。

Servlet2.5应用基于web.xml启动,web容器负责加载解析web.xml。Servlet API提供servlet、filter、listener三个组件来帮助我们处理请求,这三个都可以配置在web.xml中。

一个典型的web.xml示例:

<servlet>
    <servlet-name>study</servlet-name>
    <servlet-class>com.guchenbo.study.mvc.servlet25.StudyServlet</servlet-class>
</servlet>
<servlet-mapping>
    <servlet-name>study</servlet-name>
    <url-pattern>/*</url-pattern>
</servlet-mapping>

<filter>
    <filter-name>study</filter-name>
    <filter-class>com.guchenbo.study.mvc.servlet25.StudyFilter</filter-class>
</filter>
<filter-mapping>
    <filter-name>study</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

 

三、SpringMVC + Servlet 2.5

要点

  • web.xml中配置DispatcherServlet
  • HandlerInterceptor接口来替换Filter的工作
  • DelegatingFilterProxy桥接Servlet和Spring IOC

Servlet2.5下集成SpringMVC,只需在web.xml中定义一个DispatcherServlet即可,它会负责统一处理请求。SpringMVC还引入了一个HandlerInterceptor接口来替换Filter的工作,因此基于SpringMVC下,我们只需实现HandlerInterceptor来完成请求的拦截。

当然这不等于我们就不能使用Filter,开发者可以在web.xml中加入自己的Filter,但是如果想把Filter融入Spring的容器中的话,就需要用到DelegatingFilterProxy,这是个代理。开发者只需在web.xml中定义一个DelegatingFilterProxy,它会负责去Spring容器中查找真正的Filter并且委派给他处理。Spring Security就是这么使用的。

SpringMVC下一个典型的web.xml示例:

 <context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>WEB-INF/application.xml</param-value>
</context-param>

<listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

<servlet>
    <servlet-name>study</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
        <param-name>contextConfigLocation</param-name>
        <!--其实默认就是这个地址-->
        <param-value>WEB-INF/study-servlet.xml</param-value>
    </init-param>
</servlet>
<servlet-mapping>
    <servlet-name>study</servlet-name>
    <url-pattern>/*</url-pattern>
</servlet-mapping>

<filter>
    <filter-name>study</filter-name>
    <filter-class>com.guchenbo.study.mvc.servlet25.springmvc.StudyFilter</filter-class>
</filter>
<filter-mapping>
    <filter-name>study</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

<filter>
    <!--filter bean的名字-->
    <filter-name>springStudyFilter</filter-name>
    <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
    <filter-name>springStudyFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

 

四、Servlet 3.0

要点

  • 在ServletContainerInitializer中动态注册servlet、filter、listener

Servlet3.0之后可以去除web.xml启动,这是最主要的区别。

Servlet 2.5下web容器会加载web.xml,从而加载servlet、filter、listener。在Servlet 3.0中由ServletContext#addServlet()、ServletContext#addFilter()、ServletContext#addListener()来实现动态注册。

Servlet API增加了一个ServletContainerInitializer,web容器会在classpath下查找META-INF/services/javax.servlet.ServletContainerInitializer文件,通过SPI执行ServletContainerInitializer#onStartup完成初始化。

根据上面这些技术点,我们在纯Servlet 3.0下开发一个demo,相比web.xml下,需要开发一个ServletContainerInitializer的来注册自己的servlet、filter、listener。

部分代码示例

public class StudyServletContainerInitializer implements ServletContainerInitializer {

    @Override
    public void onStartup(Set<Class<?>> c, ServletContext ctx) throws ServletException {
        ServletRegistration.Dynamic servletRegistration = ctx.addServlet("study", StudyServlet.class);
        servletRegistration.addMapping("/*");

        FilterRegistration.Dynamic filterRegistration = ctx.addFilter("studyFilter", StudyFilter.class);
        // isMatchAfter 优先级 false > true
        filterRegistration.addMappingForUrlPatterns(null, true, "/study");

        FilterRegistration.Dynamic filterRegistration2 = ctx.addFilter("studyFilter2", StudyFilter2.class);
        filterRegistration2.addMappingForUrlPatterns(null, true, "/study");

        ctx.addListener(new StudyServletContextListener());
    }
}

需要定义META-INF/services/javax.servlet.ServletContainerInitializer文件

 

五、SpringMVC + Servlet3.0

要点

  • 在WebApplicationInitializer中动态注册
  • 具体可以继承AbstractAnnotationConfigDispatcherServletInitializer

SpringMVC框架在Servlet 3.0中是怎么使用的?有了Servlet 3.0的知识点之后,我们发现Spring web定义了SpringServletContainerInitializer用于实现ServletContainerInitializer。而SpringServletContainerInitializer中又将工作委托给了WebApplicationInitializer

SpringMVC提供了AbstractAnnotationConfigDispatcherServletInitializer,里面帮我们注册好了DispatcherServlet,开发者需要继承它。讨论在SpringMVC下的启动Servlet 3.0(不用web.xml)应用的方式,这里讨论的情况是不使用web.xml,如果使用web.xml就是跟Servlet 2.5情况相似。
 

六、SpringBoot

我们知道SpringBoot应用可以分为两种模式部署:jar和war,这两种方式有不同的启动流程。分析的前提都是Servelt 3.0下的无web.xml。

6.1、jar包运行

SpringBoot提供了内置的Web容器,提供了以jar包形式运行的web应用,因此它与传统war包方式运行的web应用有很大的不同,它是先启动Spring容器在启动Web容器,传统的war是先启动外部的Web容器,然后由Web容器启动Spring容器

要点

  • Spring容器    -->    Web容器
  • TomcatStarter
  • ServletContextInitializer
  • ServletRegistrationBean、FilterRegistrationBean、ServletListenerRegistrationBean分别用来注册Servlet、Filter、ServletContextListener

jar包运行表示使用了内置的Web容器(通常是tomcat),因此没有走常规Web容器那种加载ServletContainerInitializer逻辑。

SpringBoot下jar包启动是使用main方法启动的,先创建Spring容器:ServletWebServerApplicationContextServletWebServerApplicationContext会创建WebServer(这个就是Web容器),由Web容器负责加载ServletContainerInitializer

值得一提的是这里跟外部Web容器一致也是加载ServletContainerInitializer,只不过不是通过SPI的机制,而是通过代码直接调用(毕竟是在同一个应用中)。

SpringBoot内置的Web容器中(我们以tomcat为例),使用TomcatStarter类实现ServletContainerInitializerTomcatStarter会初始化所有的ServletContextInitializer对象。SpringBoot启动时会把servlet、filter、listener这些web组件封装成ServletContextInitializer对象,完成Web组件的注册。

6.2、war包运行

要点

  • SpringBoot启动类需要继承SpringBootServletInitializer
  • ServletRegistrationBean、FilterRegistrationBean、ServletListenerRegistrationBean用来注册Servlet、Filter、ServletContextListener

war包运行表示使用外部的Web容器,根据Servlet 3.0的知识点和SpringMVC的知识点,SpringBoot也有自己的WebApplicationInitializer,那就是SpringBootServletInitializer来实现我们应用的初始化,里面已经聚合了SpringBoot的启动功能。

Web容器启动之后,SpringBootServletInitializer负责创建Spring容器,然后由Spring容器初始化所有的ServletContextInitializer对象。这也是它和jar包运行不同之处,关键查看源码:

private void createWebServer() {
	WebServer webServer = this.webServer;
	ServletContext servletContext = getServletContext();
	if (webServer == null && servletContext == null) {
		// 1、jar运行会走这里
		ServletWebServerFactory factory = getWebServerFactory();
		this.webServer = factory.getWebServer(getSelfInitializer());
	}
	else if (servletContext != null) {
		// 2、war运行会走这里,servletContext由外部Web容器设置
		try {
			getSelfInitializer().onStartup(servletContext);
		}
		catch (ServletException ex) {
			throw new ApplicationContextException("Cannot initialize servlet context",
					ex);
		}
	}
	initPropertySources();
}

1、jar运行会自己创建内容的Web容器,由TomcatStarter调用ServletContextInitializer初始化

2、war运行不需要直接调用ServletContextInitializer初始化

3、getSelfInitializer()方法负责注册所有的Servlet、Filter、Listener

 

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!