一、概述
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容器:ServletWebServerApplicationContext。ServletWebServerApplicationContext会创建WebServer(这个就是Web容器),由Web容器负责加载ServletContainerInitializer。
值得一提的是这里跟外部Web容器一致也是加载ServletContainerInitializer,只不过不是通过SPI的机制,而是通过代码直接调用(毕竟是在同一个应用中)。
SpringBoot内置的Web容器中(我们以tomcat为例),使用TomcatStarter类实现ServletContainerInitializer。TomcatStarter会初始化所有的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
来源:oschina
链接:https://my.oschina.net/u/2815378/blog/3137591