servlet知识体系整理

602 阅读14分钟

概述

servlet是java web开发的核心基础。本文将对servlet的基础知识进行回顾、总结,希望能够查漏补缺,同时也对读者有所裨益。点此下载本文案例源码

学习网站

servlet知识脉络梳理

上面就是servlet知识脉络梳理的思维导图,下面我们来分别总结图中的每个小节。最后再通过一个简单的java web项目来演示下。

servlet基础

什么是servlet

简而言之,servlet就是sun公司制定的一种用来拓展web服务器功能的组件规范。因为很多早期的web服务器,比如apache,iis等,不能处理动态资源,servlet就应运而生了。

如何写一个servlet

  • 写一个java类,实现Servlet接口或者继承HttpServlet类
  • 编译、打包、部署在web容器上(一般使用tomcat)
  • 启动,打开浏览器,输入地址,即可访问

servlet的运行机制

  • 浏览器依据ip和port,建立连接
  • 浏览器创建请求数据包并发送
  • 服务器解析请求数据包,并且将解析到的数据存放到request对象中,同时,创建response对象
  • 服务器创建servlet对象,然后调用该对象的service方法,服务器会将request和response作为参数传给给service方法
  • 服务器从response对象中获取处理结果,然后创建响应数据包并发送
  • 浏览器解析响应数据包,然后依据解析到的数据生成相应的页面

http协议(了解)

  • 是一种网络应用层协议,规定了浏览器与web服务之间的通信规则及数据包结构
  • 数据包结构分为请求数据包和响应数据包,请求数据包分为请求行和实体内容,响应数据包分为状态行和消息头
  • 两种请求方式:get请求和post请求

servlet核心

如何获取请求参数值

  • String request.getParameter(String paramName);
  • String[] request.getParameterValues(String paramName);

中文的处理方式

  • post请求:request.setCharacterEncoding(String charset);
  • get请求:修改server.xml,添加<Connector URIEncoding='utf-8'/>
  • response.setContentType("text/html;charset=utf-8");

转发和重定向

  • 重定向: 服务器通知浏览器重新向某个地址发送请求,服务器一般会发送302状态码
  • 如何重定向:response.sendRedirect(String url); (重定向之前,容器会清空response对象上存放的所有数据)
  • 重定向的特点:重定向的地址是任意的;重定向后,浏览器地址发生改变
  • 转发:一个web组将未完成的处理交给另外一个web组件继续做。最常见的情况:一个servlet获得数据,然后将这些数据转发给一个jsp来展现
  • 如何转发:
    1. 将数据绑定在request对象上:request.setAtribute(String name,Object obj);
    2. 获得转发器:RequestDispatcher rd = request.getRequestDispatcher(String uri);
    3. 转发:rd.forward(request,response);
  • 转发的特点:
    • 转发之后,浏览器地址栏地址不变
    • 转发的地址有限制(要求属于同一个应用)

servlet容器如何处理请求资源路径

举例说明,在浏览器中输入 http://ip:port/day08-2/abc.html

  1. 容器默认为访问的是一个servlet。容器会将资源路径的应用名除掉。得到"/abc.html",然后查找web.xml配置文件,看有没有与之匹配的sevlet。匹配方式有如下三种:
    • 精确匹配
    • 通配符匹配,即使用"*"配置任意的零个或多个字符
    • 后缀匹配,使用"*"开头,后接任意的一个后缀,比如 <url-pattern>*.do</url-pattern>
  2. 如果找不到对应的servlet,容器会查找对应位置的文件,找到则返回文件内容,找不到则返回404
  3. 注意路径问题。不以“/”开头的路径是相对路径,以“/”开头的路径是绝对路径。获取应用名时不要直接硬编码,可以通过request.getContextPath()来获取

如何让一个servlet处理多种请求

  1. 使用后缀匹配。如 <url-pattern>*.do</url-pattern>
  2. 分析请求资源路径,调用对应的分支来处理

servlet生命周期

  • 生命周期:servlet容器如何创建servlet对象,如何对其进行初始化、如何调用其方法处理请求,以及如何销毁该对象的整个过程,即容器如何管理servlet
  • 生命周期的几个阶段:
    1. 实例化
    • 容器调用servlet的构造器,创建servlet实例。容器只会创建一个实例
    • 容器可以在收到请求之后创建servlet实例,或者在容器启动后立即创建(需要配置load-on-startup属性,值越小优先级越高)
    1. 初始化:
    • 创建完servlet实例之后,立即调用该实例的init方法,init方法只会执行一次
    • 初始化参数:配置、读取初始化参数
    1. 就绪(调用)
    • 容器收到请求之后,会调用servlet的service方法来处理
    1. 销毁
    • 容器在删除servlet实例之前,会调用该实例的destroy方法。该方法只会执行一次
  • 相关的接口与类
    • servlet接口:init(ServletConfig config);service(ServletRequest req,ServletResponse res);
    • GenericService抽象类:实现了Servlet接口中的部分方法(init,destroy)
    • HttpServlet抽象类:继承了GenericServlet,实现了service方法

servlet线程安全问题

  • 存在的原因:容器对于某个servlet,只会创建一个实例;容器每收到一个请求,都会启动一个线程处理请求
  • 解决方案:加锁解决,比如使用synchronized来加锁方法或代码块

servlet上下文

  • 定义:容器启动后,会为每个web应用创建唯一的一个符合ServletContext接口要求的对象,该对象被成为Servlet上下文
  • 唯一性:一个web应用只能对应一个Servlet上下文
  • 持久性:只要容器没有被关闭,应用没有被卸载,Servlet上下文就会一直存在
  • 如何获取:HttpSession、GenericService提供了getServletContext方法来获得上下文
  • 作用:request、session、Servlet上下文都提供了绑定数据相关的方法

状态管理

  • 定义:将浏览器与web服务器之间多次交互当作一个整体来处理,并且将多次交互所涉及的数据(即状态)保存下来。保存在浏览器端,叫做cookie,保存在服务器端的,叫做session。

过滤器与监听器

  • 过滤器
    • 定义:servlet规范中定义的一种特殊的组件,用来拦截Servlet容器调用的过程。容器收到请求之后,会先调用过滤器,再调用后续的组件,比如调用Servlet
    • 编写过滤器的步骤:
      1. 写一个java类,实现Fiter接口
      2. 在doFilter方法当中,实现拦截器处理逻辑
      3. 配置过滤器(web.xml)
    • 过滤器的优先级:当有多个过滤器都满足拦截要求时,则容器依据<filer-mapping>配置的先后顺序来执行
    • 初始化参数
      1. 配置初始化参数:使用<init-param>配置
      2. 读取初始化参数值:String illegal = config.getInitParameter("Illegal");
    • 优点:
      1. 可以在不修改原有代码的基础上,为应用添加新的功能
      2. 可以将多个组件相同的功能集中写在一个过滤器中,方便代码的维护
  • 监听器
    • 定义:servlet规范中定义的一种特殊的组件,用于监听servlet容器产生的事件并进行相应的处理
    • 编写监听器的步骤:
      1. 写一个java类,实现监听器接口(比如要监听session对象的创建和销毁,需要实现HttpSessionListener接口)
      2. 在监听器接口方法中,实现监听处理逻辑
      3. 配置监听器(Web.xml)
    • 容器产生的事件:
      • 生命周期相关的事件:比如request、session、servlet上下文的创建、销毁等事件
      • 绑定数据相关的事件:调用了request,session,servlet上下文的setAttribute,removeAttribute方法产生的事件

典型案例

  • 用户管理
  • 登陆(session验证)
    1. 登陆成功之后,在session对象上绑定一些数据。例如:session.setAttribute("user",user);
    2. 当用户访问需要保护的资源时,进行session验证。例如:
        Object obj = session.getAttribute("user");
        if(obj == null){
        //没有登陆
        response.sendRedirect("login.jsp");

servlet3.0新特性

servlet4.0新特性

代码演示

开发环境

  • java: JDK8
  • IDE: Intellij IDEA
  • tomcat: tomcat-9.0.14

使用@WebServlet创建Servlet

@WebServlet(
      name = "annotationServlet", //servlet的名称,没有指定,则默认使用类的全限定名
      //value = "",等价于urlPatterns,不能同时使用
      urlPatterns = "/annotation", //一组servlet的url匹配模式
      loadOnStartup = -1, //servlet的加载顺序,默认为-1
      asyncSupported = false, //是否支持异步操作方式,默认为false
      description = "这是注解版本的servlet",//描述信息
      displayName = "annotationServlet", //servlet的显示名,通常配合工具使用
      initParams = { @WebInitParam(name = "userName", value = "zhangsan"), @WebInitParam(name = "age", value = "18") } //初始化参数
)
public class AnnotationServletDemo extends HttpServlet {
  @Override
  protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
      String userName = this.getInitParameter("userName");
      String age = this.getInitParameter("age");
      PrintWriter writer = resp.getWriter();
      writer.println("hello servlet!!"+userName+","+age);
      writer.flush();
      writer.close();
  }
}

在这里我们使用默认的端口,默认的index.jsp页面,context-path默认与应用名servletDemo相同,因此,我们只需在浏览器输入如下的url,即可访问我们创建的这个servlet:

http://localhost:8080/servletDemo/annotation

这时候,出现如下的结果,说明servlet配置成功了:

hello servlet!!zhangsan,18

使用@WebFilter创建过滤器

@WebFilter(
      filterName = "filterDemo", //过滤器名称,没有指定的话,默认使用全限制类名
      urlPatterns = "/*",  //url匹配
      initParams = {@WebInitParam(name = "className",value = "filer")}, //初始化参数
      dispatcherTypes = DispatcherType.REQUEST //转发器类型,默认REQUEST
      )
public class AnnotationFilterDemo implements Filter {

  FilterConfig filterConfig;

  @Override
  public void init(FilterConfig filterConfig) throws ServletException {
      this.filterConfig = filterConfig;
  }

  @Override
  public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {

      String className = filterConfig.getInitParameter("className");
      System.out.println("go to filer!"+className);
      filterChain.doFilter(servletRequest,servletResponse);
      System.out.println("out of filer!");
  }

}

Servlet3.0对异步处理的支持

由于servlet处理业务逻辑时,一般只创建了一个servlet对象,但是如果同时有多个请求,就可能一个servlet对象同时被分配了多个线程。在servlet2.5中,如果某个逻辑操作超时,则后续的请求都会被阻塞。servlet3.0支持异步处理,即单独开启线程处理业务逻辑。

@WebServlet(urlPatterns = "/demo",asyncSupported = true)
public class AsyncDemoServlet extends HttpServlet {
  @Override
  protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
      resp.setContentType("text/html;charset=UTF-8");
      PrintWriter out = resp.getWriter();
      out.println("进入Servlet的时间:"+new Date()+".");
      out.flush();

      //在子线程中执行业务调用,并由其负责输出响应,主线程退出
      //配置request属性,使其支持异步处理
      req.setAttribute("org.apache.catalina.ASYNC_SUPPORTED", true);
      AsyncContext asyncContext = req.startAsync();
      new Thread(new Executor(asyncContext)).start();

      out.println("结束Servlet的时间:"+new Date()+".");
      out.flush();

  }
}

class Executor implements Runnable {

  private AsyncContext ctx = null;
  public Executor (AsyncContext ctx ){
      this.ctx = ctx;
  }

  @Override
  public void run() {

      try {
          //等待10秒,模拟业务方法的执行
          Thread.sleep(10000);
          PrintWriter out = ctx.getResponse().getWriter();
          out.println("业务处理完毕的时间:"+ new Date()+".");
          out.flush();
          ctx.complete();
      } catch (Exception e) {
          e.printStackTrace();
      }
  }
}

重启服务,这时候只要在浏览器输入http://localhost:8080/servletDemo/demo,就可以看到页面立刻打印进入servlet和结束servlet的时间,主线程已经退出了。10秒之后,再次打印业务处理完成的时间。

Servlet3.0对文件上传的支持

servlet2.5无法独立完成文件上传的功能,需要这两个jar包的支持: 而servlet3.0已经独立提供了文件上传的支持:

客户端:

  • 表单的提交方式必须是post
  • 必须有一个文件上传组件<input type="file",name="file"/>
  • 必须设置表单的enctype="multipart/form-data"

服务器:

  • 在servlet上添加注解 @MulipartConfig
  • Part getPart(String name) 或者 Collection getParts()

代码演示如下:

@WebServlet(urlPatterns = "/upload")
@MultipartConfig
public class UploadServlet extends HttpServlet {
  @Override
  protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

      //得到上传文件的目录
      String savePath = this.getServletContext().getRealPath("/WEB-INF/upload");
      File file = new File(savePath);

      if(!file.exists() && !file.isDirectory()){
          System.out.println("目录不存在,需要创建");
          file.mkdirs();
      }

      //获取文件
      Part img = req.getPart("file");
      //文件全路径
      String filePath = file.getPath() + File.separator + img.getSubmittedFileName();
      //写入文件
      img.write(filePath);
      //在使用PrintWriter对象打印中文字符之前,先设置utf-8编码,否者会乱码
      resp.setCharacterEncoding("utf-8");
      resp.setContentType("text/html; charset=utf-8");

      PrintWriter writer = resp.getWriter();
      writer.println("File upload:"+filePath);
  }
}

简单的上传页面upload.jsp如下:

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
  <title>$Title$</title>
</head>
<body>
  <h1>Upload File</h1>
  <%--这里的action对应sevlet的url,这里的upload对应着sevlet的 /upload --%>
  <form action="upload" method="post" enctype="multipart/form-data">
    File : <input type="file" name="file"/>
    <input type="submit" value="上传"/>
  </form>
</body>
</html>

重启服务,浏览器输入http://localhost:8080/servletDemo/upload.jsp,即可看到如下页面: 选择文件,点击上传,通过文件上传的servlet处理,页面就会打印文件的上传路径,说明文件上传配置成功了。

Servlet4.0对http2协议的支持

开启http2所需要的软件

  • apache-tomcat-9.0.14-windows-x64.zip
  • tomcat-native-1.2.24-openssl-1.1.1g-win32-bin.zip
  • Win64OpenSSL-1_1_1g.exe
  • 私钥生成命令.txt

开启http2所需要的步骤

  1. 启动startup.bat,测试tomcat是否运行正常
  2. 修改tomcat的server.xml,先注释端口为8080的Connector
	<!--
  <Connector port="8080" protocol="HTTP/1.1"
             connectionTimeout="20000"
             redirectPort="8443" />
        -->
  1. 取消端口为8443的Connector的注释,并且删除证书链的配置,在这里我们并不需要
  <Connector port="8443" protocol="org.apache.coyote.http11.Http11AprProtocol"
             maxThreads="150" SSLEnabled="true" >
      <UpgradeProtocol className="org.apache.coyote.http2.Http2Protocol" />
      <SSLHostConfig>
          <Certificate certificateKeyFile="conf/localhost-rsa-key.pem"
                       certificateFile="conf/localhost-rsa-cert.pem"
                       type="RSA" />
      </SSLHostConfig>
  </Connector>
  1. 上面的certificateKeyFile和certificateFile分为指定私钥和证书的路径,所以我们按照默认的路径,来分别生成私钥和证书,这时候直接重启tomcat会报错或者一闪而过是正常的,因为还没有生成私钥和证书。解压缩tomcat-native,并且执行里面的bin目录下的openssl.exe文件,在打开的窗口输入如下命令
OpenSSL> genrsa -out localhost-rsa-key.pem 2048
Generating RSA private key, 2048 bit long modulus (2 primes)
.................+++++
...................+++++
e is 65537 (0x010001)
OpenSSL>

出现如上类似的提示,说明私钥生成成功,在openssl.exe文件所在的目录下,可以看到localhost-rsa-key.pem文件,也就是刚才生成的私钥文件 5. 接着生成证书

OpenSSL> req -new -x509 -key localhost-rsa-key.pem -out localhost-rsa-cert.pem -days 3650
Can't load ./.rnd into RNG
2516:error:2406F079:random number generator:RAND_load_file:Cannot open file:crypto\rand\randfile.c:98:Filename=./.rnd
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
Country Name (2 letter code) [AU]:
  1. 上面生成证书失败,有可能是以前安装配置过openSSL,配置冲突了,所以这里我重新安装了OpenSSL软件,并且把安装目录下的bin下的cnf里面的openssl.cnf文件拷贝到C:\Program Files\Common Files\SSL目录下。
  2. 重新执行tomcat-native的openssl.exe文件,重新执行上面的两个命令
OpenSSL> genrsa -out localhost-rsa-key.pem 2048
Generating RSA private key, 2048 bit long modulus (2 primes)
.......................+++++
................................................................+++++
e is 65537 (0x010001)
OpenSSL> req -new -x509 -key localhost-rsa-key.pem -out localhost-rsa-cert.pem -days 3650
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.

Country Name (2 letter code) [AU]:
State or Province Name (full name) [Some-State]:
Locality Name (eg, city) []:
Organization Name (eg, company) [Internet Widgits Pty Ltd]:
Organizational Unit Name (eg, section) []:
Common Name (e.g. server FQDN or YOUR name) []:
Email Address []:
OpenSSL>

上面的需要你输入的信息直接回车即可,如上,说明密钥和证书已经生成成功了。在同级目录下,可以看到生成的localhost-rsa-cert.pem和localhost-rsa-key.pem,将它们拷贝到tomcat9的conf目录下。 8. 复制tomcat-native下面的bin目录下的x64下面的2个文件拷贝到jdk1.8的bin目录下 9. 在tomcat9下,点击启动startup.bat,启动完成,打开浏览器,输入https://localhost:8443,测试是否正常访问 10. 打开IDEA,进行相关配置之后,打开浏览器,输入https://localhost:8443/servletDemo/demo,能够正常响应,说明我们的web应用配置ttp2也成功了。

Servlet4.0进行服务器推送

所谓的服务器推送,其实指的是服务器预先将资源发给浏览器进行缓存,从而加快页面的访问速度。

服务器推送步骤

  1. 设置推送资源
PushBuilder pb = request.newPushBuilder();
pb.path("1.jpg");
  1. 进行推送
pb.push();

代码演示

首先,我们修改index.jsp文件,加了一个图片的展示,其中1.jpg这个图片与index.jsp同目录。

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
  <title>$Title$</title>
</head>
<body>
  <img src="1.jpg"/>
</body>
</html>

接着,我们新建一个servlet,推送这个图片

@WebServlet(urlPatterns = "/server")
public class ServerPushServlet extends HttpServlet {

  @Override
  protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
      //获取到PushBuilder对象
      PushBuilder pushBuilder = req.newPushBuilder();
      //指定推送的资源,并且进行推送
      pushBuilder.path("1.jpg").push();
  }
}

我们可以执行这个servlet,先推送图片到浏览器缓存,到访问对应的页面,这时候页面的访问速度就会快很多。

Servlet4.0之HttpServletMapping的使用

详见 servlet4.0社区文档

Servlet4.0之HttpFilter的使用

HttpFilter其实是Filter的实现类,增加了对http协议的支持, 详见 servlet4.0社区文档