小马哥训练营-Java EE单体架构

什么是Servlet

Servlet 是一种基于 Java 技术的 Web 组件,用于生成动态内容,由容器管理。类似于其他 Java 技术组件,Servlet 是平台无关的 Java 类组成,并且由 Java Web 服务器加载执行。通常情况,由 Servlet 容器提供运行时环境。Servlet 容器,有时候也称作为Servlet 引擎,作为Web服务器或应用服务器的一部分。通过请求和响应对话,提供Web客户端与 Servlets 交互的能力。容器管理Servlets实例以及它们的生命周期。
相关技术:
CGI - 基于进程,php, apache httpd CGI
Servlet-基于线程

servlet主要版本

规范版本 发布时间 Java平台 主要更新
Servlet4.0 2017 JavaEE8 支持http/2
Servlet3.1 2013 JavaEE7 非阻塞I/O,http协议更新机制(websoket)
Servlet3.0 2009 JavaEE6 可插拔、简化部署、异步servlet,安全,文件上传
Servlet2.5 2005 JavaEE5 Annotation支持
Servlet2.4 2003 J2EE1.4 web.xml支持xml scheme
Servlet2.3 2001 J2EE1.3 新增filter,事件/监听器,Wrapper
Servlet2.2 1999 J2EE1.2 作为J2ee的一部分,以.war文件作为独立web应用

补充知识:
zip,jar,war,ear 其本质都是zip的拓展,都是压缩文件。war和jar拓展名不一样内容是一样的。

Servlet 核心API

核心组件API 说明 起始版本 SpringFrameWork代表实现
javax.servlet .Servlet 动态内容组件 1.0 DispatcherServlet
javax.servlet.Filter Servlet过滤器 2.3 CharacterEncodingFilter
javax.servlet .ServletContext Servlet 应用上下文
javax.servlet .AsyncContext 异步上下文 3.0 无
javax.servlet.ServletContextListener ServletContext 生命周期监听器 2.3 ContextLoaderListener
javax.servlet.ServletRequestListener ServletRequest 生命周期监听器 2.3 RequestContextListener
javaxservlet.http.HttpSessionListener HttpSession 生命周期监听器 2.3 HttpSessionMutexListener
javax.servlet.AsyncListener 异步上下文监听器 3.0 StandardServletAsyncWebRequest
javax.servlet.ServletContainerInitializer Servlet 容器初始化器 3.0 Springservletcoptelyong

Servlet 组件注册方式

  • 传统web.xml注册方式 (写xml)
  • 注解方式(servlet 3.0 +)注解@WebServlet
  • 编码注册方式(servlet 3.0 +)
    在这里插入图片描述
    注册完成以后返回句柄,可以添加映射:
    在这里插入图片描述

Servlet 生命周期

  • 声明(应用行为)
  • 注册(容器行为 )
  • 初始化: Servlet#initServletConfig)
  • 服务: Servlet#service(ServletRequest, ServletResponse)
  • 销毁: Servlet#destroy()

Filter生命周期

  • 声明(应用行为)
  • 注册(容器行为)
  • 初始化:Filter#init(FilterConfig)
  • 过滤:Filter#doFilter(ServletRequest,ServletResponse,FilterChain)
  • 毁:Filter#destroy()

ServletContext 生命周期

  • 声明(应用行为)
  • 注册(容器行为)
  • 初始化:ServletContextListener#contextInitialized
  • 销毁:ServletContextListener#contextDestroyed

注:
Servlet规范重点章节 2,3,4,5,9,11,12 规范链接
CHAPTER 2 The Servlet Interface
CHAPTER 3 The Request
CHAPTER 4 Servlet Context
CHAPTER 5 The Response
CHAPTER 9 Dispatching Requests(非常重要)
CHAPTER 11Application Lifecycle Events
CHAPTER 12 Mapping Requests to Servlets(非常重要)
还需要看TomcatNIO
并非请求头(header)和请求体(body)都是异步读取的,要看Servlet容器的实现模型。
Tomcat官方文档
在这里插入图片描述

关于Tomcat 的一个优化

目前我们都不使用jsp了所以我们可以将jsp的servlet关掉。在tomcat中将此Servlet注释掉。

<servlet>
    <servlet-name>jsp</servlet-name>
    <servlet-class>org.apache.jasper.servlet.JspServlet</servlet-class>
    <init-param>
        <param-name>fork</param-name>
        <param-value>false</param-value>
    </init-param>
    <init-param>
        <param-name>xpoweredBy</param-name>
        <param-value>false</param-value>
    </init-param>
    <load-on-startup>3</load-on-startup>
</servlet>

关于Tomcat处理静态文件,使用了默认的servlet 来处理

因为jspServerlet 只是匹配jsp文件,要匹配静态文件我们需要用DefaultServlet来匹配。各个服务平台往往都是用default来命名,在springboot里静态文件的处理是forward(这个就是转发请求到下一个servlet) 到对应平台的Servlet来处理,从而适配了。所以在springboot 里面将css 和js文件放到static文件下面就可以读取到。
源码:

public class DefaultServletHttpRequestHandler implements HttpRequestHandler, ServletContextAware {
	// 这里定义了不同平台处理静态文件的servlet 名称
    private static final String COMMON_DEFAULT_SERVLET_NAME = "default";
    private static final String GAE_DEFAULT_SERVLET_NAME = "_ah_default";
    private static final String RESIN_DEFAULT_SERVLET_NAME = "resin-file";
    private static final String WEBLOGIC_DEFAULT_SERVLET_NAME = "FileServlet";
    private static final String WEBSPHERE_DEFAULT_SERVLET_NAME = "SimpleFileServlet";
    @Nullable
    private String defaultServletName;
    @Nullable
    private ServletContext servletContext;
    public DefaultServletHttpRequestHandler() {
    }
    public void setDefaultServletName(String defaultServletName) {
        this.defaultServletName = defaultServletName;
    }
    public void setServletContext(ServletContext servletContext) {
        this.servletContext = servletContext;
        if (!StringUtils.hasText(this.defaultServletName)) {
            if (this.servletContext.getNamedDispatcher("default") != null) {
                this.defaultServletName = "default";
            } else if (this.servletContext.getNamedDispatcher("_ah_default") != null) {
                this.defaultServletName = "_ah_default";
            } else if (this.servletContext.getNamedDispatcher("resin-file") != null) {
                this.defaultServletName = "resin-file";
            } else if (this.servletContext.getNamedDispatcher("FileServlet") != null) {
                this.defaultServletName = "FileServlet";
            } else {
                if (this.servletContext.getNamedDispatcher("SimpleFileServlet") == null) {
                    throw new IllegalStateException("Unable to locate the default servlet for serving static content. Please set the 'defaultServletName' property explicitly.");
                }
                this.defaultServletName = "SimpleFileServlet";
            }
        }
    }
    public void handleRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        Assert.state(this.servletContext != null, "No ServletContext set");
        RequestDispatcher rd = this.servletContext.getNamedDispatcher(this.defaultServletName);
        if (rd == null) {
            throw new IllegalStateException("A RequestDispatcher could not be located for the default servlet '" + this.defaultServletName + "'");
        } else {
        	// 分发到对应的servlet 进行处理 因为他们都遵循了同一个规范
            rd.forward(request, response);
        }
    }
}

关于default servlet 处理静态文件的一个配置示例

<!--映射路径-->
<servlet-mapping>
     <servlet-name>default</servlet-name>
     <url-pattern>/</url-pattern>
 </servlet-mapping>
 <servlet-mapping>
     <servlet-name>default</servlet-name>
     <url-pattern>*.css</url-pattern>
 </servlet-mapping>
 <servlet-mapping>
     <servlet-name>default</servlet-name>
     <url-pattern>*.js</url-pattern>
 </servlet-mapping>
      <servlet>
       <servlet-name>default</servlet-name>
       <servlet-class>org.apache.catalina.servlets.DefaultServlet</servlet-class>
       <init-param>
           <param-name>debug</param-name>
           <param-value>0</param-value>
       </init-param>
       <init-param>
           <param-name>listings</param-name>
           <param-value>false</param-value>
       </init-param>
       <load-on-startup>1</load-on-startup>
   </servlet>

注解:
关于forward 的时候会不会经过过滤链,这需要在过滤链进行配置。关于执行顺序,先定义mapping的filter优先。

<filter-mapping>
    <filter-name>CharsetEncodingFilter</filter-name>
    <url-pattern>/*</url-pattern>
    <!-- 配置 各种操作的时候也经过过滤器-->
    <dispatcher>REQUEST</dispatcher>
    <dispatcher>FORWARD</dispatcher>
    <dispatcher>INCLUDE</dispatcher>
    <dispatcher>ERROR</dispatcher>
</filter-mapping>

课堂上的补充知识点:
SPI:spi加载机制

关于打包

使用tomcat打包插件将项目打包为可执行的jar文件

<plugin>
  <groupId>org.apache.tomcat.maven</groupId>
  <artifactId>tomcat7-maven-plugin</artifactId>
  <version>2.1</version>
  <executions>
    <execution>
      <id>tomcat-run</id>
      <goals>
        <!-- 最终打包成可执行的jar包 -->
        <goal>exec-war-only</goal>
      </goals>
      <phase>package</phase>
      <configuration>
        <!-- ServletContext 路径 -->
        <path>/</path>
        <enableNaming>true</enableNaming>
        <tomcatConfigurationFilesDirectory>
          src/main/webapp/META-INF/conf/
        </tomcatConfigurationFilesDirectory>
      </configuration>
    </execution>
  </executions>
</plugin>

顺便也补充一个web.xml,关于映射路径规则可以详情见12章,同时补充/ 与/星号的区别,/星号如果配置了就会匹配所有的路径,所有的请求都会走这个Servlet, 所以/星号一般是配在过滤器上。/与/*的区别

<?xml version="1.0" encoding="GBK"?>
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
         http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
         metadata-complete="true" version="3.0">
  <servlet>
    <servlet-name>FrontControllerServlet</servlet-name>
    <servlet-class>FrontControllerServlet</servlet-class>
    <load-on-startup>1</load-on-startup>
  </servlet>
  <!--处理请求的Servlet 在SpringMvc里面是DispatchServlet-->
  <servlet-mapping>
    <servlet-name>FrontControllerServlet</servlet-name>
    <url-pattern>/</url-pattern>
  </servlet-mapping>
  <display-name>Archetype Created Web Application</display-name>
  <filter>
    <filter-name>CharsetEncodingFilter</filter-name>
    <filter-class>filter.CharsetEncodingFilter</filter-class>
    <init-param>
      <!-- Filter 配置 = FilterConfig -->
      <param-name>encoding</param-name>
      <param-value>UTF-8</param-value>
    </init-param>
  </filter>

  <filter-mapping>
    <filter-name>CharsetEncodingFilter</filter-name>
    <url-pattern>/*</url-pattern>
    <dispatcher>REQUEST</dispatcher>
    <dispatcher>FORWARD</dispatcher>
    <dispatcher>INCLUDE</dispatcher>
    <dispatcher>ERROR</dispatcher>
  </filter-mapping>
  <servlet>
    <servlet-name>default</servlet-name>
    <!--处理静态文件的servlet-->
    <servlet-class>org.apache.catalina.servlets.DefaultServlet</servlet-class>
    <init-param>
      <param-name>debug</param-name>
      <param-value>0</param-value>
    </init-param>
    <init-param>
      <param-name>listings</param-name>
      <param-value>false</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
  </servlet>
  <servlet-mapping>
    <servlet-name>default</servlet-name>
    <url-pattern>*.css</url-pattern>
  </servlet-mapping>

  <servlet-mapping>
    <servlet-name>default</servlet-name>
    <url-pattern>*.js</url-pattern>
  </servlet-mapping>
</web-app>