开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 23 天,点击查看活动详情
Servlet 规范中对 Servlet 的定义是:
A servlet is a Java™ technology-based Web component, managed by a container, that generates dynamic content.
简单解释下,Servlet 是基于 Java 技术开发的 Web 组件,托管在容器中,用于生成动态内容。 常见的 Servlet 容器有 Tomcat、Jetty 等。 今天,我将介绍下,如何在 Spring Boot 中开发基于 Servlet 的 Web 应用。
01-Servlet 接口
Servlet 规范中定义了 Servlet 接口,主要是它的生命周期函数:
- init,仅被调用一次。如果请求的 Servlet 实例不存在,Servlet 容器会尝试加载对应的 Servlet 实现类,创建它的实例,并调用 init 方法。
- destroy,仅被调用一次。容器通过这个方法,来下线 Servlet 提供的服务。
- service,容器通过这个方法,将请求、响应交给特定的 Servlet 来处理。
在最初阶段,Web 应用对 Servlet 的声明是在 web.xml 中的,例如:
<servlet>
<servlet-name>FormServlet</servlet-name>
<servlet-class>self.samson.example.servlet.FormServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>FormServlet</servlet-name>
<url-pattern>/calculateServlet</url-pattern>
</servlet-mapping>
上述声明的含义是,声明一个 Servlet,并通过 servlet-class 指定它的实现类。 然后,通过 servlet-mapping 将 Servlet 与某个 servlet path 关联起来。 后续对 servlet path 的请求,都会被 Servlet 容器路由到这个 Servlet 的 service 方法中。
在后续的版本中,Servlet 规范支持了程序化配置,即通过注解来定义 Servlet 及其与 servlet path 的映射关系。 例如,与上述 xml 等价的 Java 注解版本如下:
@WebServlet(name = "FormServlet", urlPatterns = "/calculateServlet")
public class FormServlet extends HttpServlet {
}
注:这里需要注意的是,FormServlet 继承了 HttpServlet,并非是直接通过实现 Servlet 接口。 HttpServlet 是 javax.servlet-api-*.jar 中提供的基于 HTTP Servlet 接口实现,内置了许多 API,例如 doGet/doPost 等。 在开发时,如果你要开发的应用是基于 HTTP 协议的,更建议你使用我这种方式,可以避免编写过多的代码。 如果现有实现不能满足你的业务需求是,再考虑直接实现 Servlet 接口。
我们熟知的 Spring MVC 中的 DispatcherServlet 就是一个典型的 Servlet 实现。 只不过,DispatcherServlet 自己又实现了一个称之为前端控制器的模式,可以让我们把业务写在各种 Controller 中。
02-Spring Boot 与 Servlet
当使用 Spring Boot 开发 Servlet 应用时,有一点需要特别注意。
Spring Boot 默认是使用嵌入式 Tomcat 作为应用容器的,它启动时并不会读取我们前面所说的 web.xml。
那它是如何做到 Servlet 加载的呢?主要靠 @ServletComponentScan
注解,即
@ServletComponentScan
@SpringBootApplication
public class Application {
}
Spring 官方文档对这个注解的解释为:
Enables scanning for Servlet components (filters, servlets, and listeners). Scanning is only performed when using an embedded web server.
可以看到,这个注解只有在使用嵌入式 Tomcat 等嵌入式容器时才会生效。
如果部署在独立的 Tomcat 时,他会自动地加载 web.xml,并且也会自动的扫描 @WebServlet
注解。
@ServletComponentScan
底层的实现依赖 org.springframework.boot.web.servlet.ServletComponentRegisteringPostProcessor
。
它是一个 BeanFactoryPostProcessor,它会扫描注解指定的包,查找 @WebServlet 等注解标注的类,作用与 @ComponentScan 类似,只不过查找的注解不同。
注:这里你可能注意到,出了 @WebServlet
注解外,它还会扫描 @WebFilter
和 @WebListener
。
后面两个注解是干什么的呢?
如果你开发过 Servlet 应用,并且使用过 web.xml 配置它,你应该注意到过配置文件中存在下面这种配置行:
<listener>
<listener-class>xxx</listener-class>
</listener>
<filter>
<filter-name>xxx-filter</filter-name>
<filter-class>xxx</filter-class>
</filter>
<filter-mapping>
<filter-name>xxx-filter</filter-name>
<servlet-name>FormServlet</servlet-name>
</filter-mapping>
Filter 和 EventListener 是 Servlet 规范中定义的两类接口。 Filter 会在请求进入 Servlet#serivce 方法之前,对请求、响应进行处理。 EventListener 能够监听 Servlet 容器中的各类事件,并在事件发生时做相应的动作。
出了上述这种方式外,还可以使用 Spring Boot 特有的接口来完成 Servlet 的注册。 方式一,通过 WebApplicationInitializer 接口。 WebApplicationInitializer#onStartup 是该接口定义的唯一一个方法,它会在容器启动时被调用。 具体的调用过程如下:
- Servlet 规范的 API 中定义了一个 SPI 接口,
javax.servlet.ServletContainerInitializer
。 spring-web 实现了这个接口,实现类是SpringServletContainerInitializer
,并且通过注解@HandlesTypes(WebApplicationInitializer.class)
指明了它能处理的类是 WebApplicationInitializer。 - 容器启动时,会通过 SPI 的 loadService 加载到 spring-web 中的实现,并且会将 classpath 所有 WebApplicationInitializer 实现传递给 SpringServletContainerInitializer。
- SpringServletContainerInitializer 会逐个调用 WebApplicationInitializer#onStartup 方法
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
ServletRegistration.Dynamic servlet = servletContext.addServlet("FormServlet", new FormServlet());
servlet.setLoadOnStartup(1);
servlet.addMapping("/calculateServlet");
}
不过,需要注意的是,这种方式在使用嵌入式 Tomcat 时,是不生效的,需要独立的 Tomcat 容器。
方式二,通过 ServletRegistrationBean。
@Bean
public ServletRegistrationBean formServlet() {
ServletRegistrationBean<Servlet> registrationBean = new ServletRegistrationBean<>(new FormServlet(), "/calculateServlet");
registrationBean.setLoadOnStartup(1);
return registrationBean;
}
上述方式与前面介绍的达到的效果是一样的。
03-总结
今天,我介绍了如何在 Spring Boot 中开发基于 Servlet 的应用。 主要内容包括两部分,其一,使用独立部署的 Tomcat 容器时,如何配置应用;其二,使用内嵌的容器,例如 Tomcat Embedded 时,如何使用 Spring Boot 提供的接口配置应用。
希望今天的内容能对你有所帮助。