Servlet 教程——检视阅读

参考

Servlet教程——菜鸟——蓝本

Servlet教程——w3cschool

Servlet教程——易百

servlet依赖maven依赖:

    <!--servlet依赖-->
    <dependency>
      <groupId>javax.servlet</groupId>
      <artifactId>javax.servlet-api</artifactId>
      <version>3.0.1</version>
      <scope>provided</scope>
    </dependency>
<!--    <dependency>
      <groupId>javax.servlet.jsp</groupId>
      <artifactId>jsp-api</artifactId>
      <version>2.1</version>
      <scope>provided</scope>
    </dependency>-->

略读

Servlet 为创建基于 web 的应用程序提供了基于组件、独立于平台的方法,可以不受 CGI 程序的性能限制。Servlet 有权限访问所有的 Java API,包括访问企业级数据库的 JDBC API。

Servlet 是什么?

Java Servlet 是运行在 Web 服务器或应用服务器上的程序,它是作为来自 Web 浏览器或其他 HTTP 客户端的请求和 HTTP 服务器上的数据库或应用程序之间的中间层。

使用 Servlet,您可以收集来自网页表单的用户输入,呈现来自数据库或者其他源的记录,还可以动态创建网页。

Java Servlet 通常情况下与使用 CGI(Common Gateway Interface,公共网关接口)实现的程序可以达到异曲同工的效果。但是相比于 CGI,Servlet 有以下几点优势:

  • 性能明显更好。
  • Servlet 在 Web 服务器的地址空间内执行。这样它就没有必要再创建一个单独的进程来处理每个客户端请求。
  • Servlet 是独立于平台的,因为它们是用 Java 编写的。
  • 服务器上的 Java 安全管理器执行了一系列限制,以保护服务器计算机上的资源。因此,Servlet 是可信的。
  • Java 类库的全部功能对 Servlet 来说都是可用的。它可以通过 sockets 和 RMI 机制与 applets、数据库或其他软件进行交互。

Servlet 架构

下图显示了 Servlet 在 Web 应用程序中的位置。

Servlet 生命周期

Servlet 生命周期可被定义为从创建直到毁灭的整个过程。以下是 Servlet 遵循的过程:

  • Servlet 通过调用 init () 方法进行初始化。
  • Servlet 调用 service() 方法来处理客户端的请求。
  • Servlet 通过调用 destroy() 方法终止(结束)。
  • 最后,Servlet 是由 JVM 的垃圾回收器进行垃圾回收的。

Servlet 创建于用户第一次调用对应于该 Servlet 的 URL 时,但是您也可以指定 Servlet 在服务器第一次启动时被加载。

当用户调用一个 Servlet 时,就会创建一个 Servlet 实例(注意,这个实例是单例的,所有的请求都是在这个里面处理,HttpServletRequest才是原型的,每次请求会新建一个实例),每一个用户请求都会产生一个新的线程,适当的时候移交给 doGet 或 doPost 方法。

架构图

下图显示了一个典型的 Servlet 生命周期方案。

  • 第一个到达服务器的 HTTP 请求被委派到 Servlet 容器。
  • Servlet 容器在调用 service() 方法之前加载 Servlet。
  • 然后 Servlet 容器处理由多个线程产生的多个请求,每个线程执行一个单一的 Servlet 实例的 service() 方法。

Servlet 处理表单数据,这些数据会根据不同的情况使用不同的方法自动解析:

  • getParameter():您可以调用 request.getParameter() 方法来获取表单参数的值。
  • getParameterValues():如果参数出现一次以上,则调用该方法,并返回多个值,例如复选框。
  • getParameterNames():如果您想要得到当前请求中的所有参数的完整列表,则调用该方法。

Servlet 简介

Servlet 是什么?

Java Servlet 是运行在 Web 服务器或应用服务器上的程序,它是作为来自 Web 浏览器或其他 HTTP 客户端的请求和 HTTP 服务器上的数据库或应用程序之间的中间层。

使用 Servlet,您可以收集来自网页表单的用户输入,呈现来自数据库或者其他源的记录,还可以动态创建网页。

Java Servlet 通常情况下与使用 CGI(Common Gateway Interface,公共网关接口)实现的程序可以达到异曲同工的效果。但是相比于 CGI,Servlet 有以下几点优势:

  • 性能明显更好。
  • Servlet 在 Web 服务器的地址空间内执行。这样它就没有必要再创建一个单独的进程来处理每个客户端请求。
  • Servlet 是独立于平台的,因为它们是用 Java 编写的。
  • 服务器上的 Java 安全管理器执行了一系列限制,以保护服务器计算机上的资源。因此,Servlet 是可信的。
  • Java 类库的全部功能对 Servlet 来说都是可用的。它可以通过 sockets 和 RMI 机制与 applets、数据库或其他软件进行交互。

Servlet 架构

Servlet 包

Java Servlet 是运行在带有支持 Java Servlet 规范的解释器的 web 服务器上的 Java 类。

Servlet 可以使用 javax.servlet 和 javax.servlet.http 包创建,它是 Java 企业版的标准组成部分,Java 企业版是支持大型开发项目的 Java 类库的扩展版本。

这些类实现 Java Servlet 和 JSP 规范。在写本教程的时候,二者相应的版本分别是 Java Servlet 2.5 和 JSP 2.1。

Java Servlet 就像任何其他的 Java 类一样已经被创建和编译。在您安装 Servlet 包并把它们添加到您的计算机上的 Classpath 类路径中之后,您就可以通过 JDK 的 Java 编译器或任何其他编译器来编译 Servlet。

Servlet 创建的三种方式。

对于一个 Servlet 类,我们日常最常用的方法是继承自 HttpServlet 类,提供了 Http 相关的方法,HttpServlet 扩展了 GenericServlet 类,而 GenericServlet 类又实现了 Servlet 类和 ServletConfig 类。

Servlet 类提供了五个方法,其中三个生命周期方法和两个普通方法 。 一个Servlet只会有一个对象,服务所有的请求(单例)。

public interface Servlet {
    void init(ServletConfig var1) throws ServletException;

    ServletConfig getServletConfig();

    void service(ServletRequest var1, ServletResponse var2) throws ServletException, IOException;

    String getServletInfo();

    void destroy();
}

1、实现 Servlet 接口

//Servlet的生命周期:从Servlet被创建到Servlet被销毁的过程
//一次创建,到处服务
//一个Servlet只会有一个对象,服务所有的请求
/*
 * 1.实例化(使用构造方法创建对象)
 * 2.初始化  执行init方法
 * 3.服务     执行service方法
 * 4.销毁    执行destroy方法
 */
public class ServletDemo1 implements Servlet {

    //public ServletDemo1(){}

     //生命周期方法:当Servlet第一次被创建对象时执行该方法,该方法在整个生命周期中只执行一次
    public void init(ServletConfig arg0) throws ServletException {
                System.out.println("=======init=========");
        }

    //生命周期方法:对客户端响应的方法,该方法会被执行多次,每次请求该servlet都会执行该方法
    public void service(ServletRequest arg0, ServletResponse arg1)
            throws ServletException, IOException {
        System.out.println("hehe");

    }

    //生命周期方法:当Servlet被销毁时执行该方法
    public void destroy() {
        System.out.println("******destroy**********");
    }
//当停止tomcat时也就销毁的servlet。
    public ServletConfig getServletConfig() {

        return null;
    }

    public String getServletInfo() {

        return null;
    }
}

2、继承 GenericServlet 类

它实现了 Servlet 接口,重写 service 的方法,不过这种方法我们极少用。

GenericServlet 是一个抽象类,实现了 Servlet 接口,并且对其中的 init() 和 destroy() 和 service() 提供了默认实现。在 GenericServlet 中,主要完成了以下任务:

  • 将 init() 中的 ServletConfig 赋给一个类级变量,可以由 getServletConfig 获得;

  • 为 Servlet 所有方法提供默认实现;

  • 可以直接调用 ServletConfig 中的方法;

    abstract class GenericServlet implements Servlet,ServletConfig{

     //GenericServlet通过将ServletConfig赋给类级变量
     private trServletConfig servletConfig;
    
     public void init(ServletConfig servletConfig) throws ServletException {
    
        this.servletConfig=servletConfig;
    
        /*自定义init()的原因是:如果子类要初始化必须覆盖父类的init() 而使它无效 这样
         this.servletConfig=servletConfig不起作用 这样就会导致空指针异常 这样如果子类要初始化,
         可以直接覆盖不带参数的init()方法 */
        this.init();
     }
    
     //自定义的init()方法,可以由子类覆盖
     //init()不是生命周期方法
     public void init(){
    
     }
    
     //实现service()空方法,并且声明为抽象方法,强制子类必须实现service()方法
     public abstract void service(ServletRequest request,ServletResponse response)
       throws ServletException,java.io.IOException{
     }
    
     //实现空的destroy方法
     public void destroy(){ }
    

    }

3、继承 HttpServlet 方法

HttpServlet 也是一个抽象类,它进一步继承并封装了 GenericServlet,使得使用更加简单方便,由于是扩展了 Http 的内容,所以还需要使用 HttpServletRequest 和 HttpServletResponse,这两个类分别是 ServletRequest 和 ServletResponse 的子类

HttpServlet 中对原始的 Servlet 中的方法都进行了默认的操作,不需要显式的销毁初始化以及 service(),在 HttpServlet 中,自定义了一个新的 service() 方法,其中通过 getMethod() 方法判断请求的类型,从而调用 doGet() 或者 doPost() 处理 get,post 请求,使用者只需要继承 HttpServlet,然后重写 doPost() 或者 doGet() 方法处理请求即可。

我们一般都使用继承 HttpServlet 的方式来定义一个 servlet。

注意:

service() 是线程安全的。

Servlet 容器处理由多个线程产生的多个请求,每个线程执行一个单一的 Servlet 实例的 service() 方法 。这种多线程是线程安全的,因为我们的service(HttpServletRequest req, HttpServletResponse resp)方法传入的参数

HttpServletRequest req, HttpServletResponse resp都是自己的,虽然只有一个实例,但service方法里面没有调用到类成员变量等共享资源,就不会有线程安全的问题;而在service方法里,虽然只有一个实例,我们假设两个请求同时到该方法中,也是没有问题的,因为局部变量是线程安全的,每个请求调用service方法都会开辟一个栈空间存储局部变量,是不会串位的,这是由于方法直接是线程隔离的,不要担心同时请求导致变量串了。

protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    String method = req.getMethod();
    long lastModified;
    if (method.equals("GET")) {
        lastModified = this.getLastModified(req);
        if (lastModified == -1L) {
            this.doGet(req, resp);
        } else {
            long ifModifiedSince = req.getDateHeader("If-Modified-Since");
            if (ifModifiedSince < lastModified) {
                this.maybeSetLastModified(resp, lastModified);
                this.doGet(req, resp);
            } else {
                resp.setStatus(304);
            }
        }
    } else if (method.equals("HEAD")) {
        lastModified = this.getLastModified(req);
        this.maybeSetLastModified(resp, lastModified);
        this.doHead(req, resp);
    } else if (method.equals("POST")) {
        this.doPost(req, resp);
    } else if (method.equals("PUT")) {
        this.doPut(req, resp);
    } else if (method.equals("DELETE")) {
        this.doDelete(req, resp);
    } else if (method.equals("OPTIONS")) {
        this.doOptions(req, resp);
    } else if (method.equals("TRACE")) {
        this.doTrace(req, resp);
    } else {
        String errMsg = lStrings.getString("http.method_not_implemented");
        Object[] errArgs = new Object[]{method};
        errMsg = MessageFormat.format(errMsg, errArgs);
        resp.sendError(501, errMsg);
    }

}

Servlet 生命周期

Servlet 生命周期可被定义为从创建直到毁灭的整个过程。以下是 Servlet 遵循的过程:

  • Servlet 通过调用 init () 方法进行初始化。
  • Servlet 调用 service() 方法来处理客户端的请求。
  • Servlet 通过调用 destroy() 方法终止(结束)。
  • 最后,Servlet 是由 JVM 的垃圾回收器进行垃圾回收的。

init() 方法

init 方法被设计成只调用一次。它在第一次创建 Servlet 时被调用,在后续每次用户请求时不再调用。

Servlet 创建于用户第一次调用对应于该 Servlet 的 URL 时,但是您也可以指定 Servlet 在服务器第一次启动时被加载。

当用户调用一个 Servlet 时,就会创建一个 Servlet 实例,每一个用户请求都会产生一个新的线程,适当的时候移交给 doGet 或 doPost 方法。init() 方法简单地创建或加载一些数据,这些数据将被用于 Servlet 的整个生命周期。

service() 方法

service() 方法是执行实际任务的主要方法。Servlet 容器(即 Web 服务器)调用 service() 方法来处理来自客户端(浏览器)的请求,并把格式化的响应写回给客户端。

每次服务器接收到一个 Servlet 请求时,服务器会产生一个新的线程并调用服务。service() 方法检查 HTTP 请求类型(GET、POST、PUT、DELETE 等),并在适当的时候调用 doGet、doPost、doPut,doDelete 等方法。

service() 方法由容器调用,service 方法在适当的时候调用 doGet、doPost、doPut、doDelete 等方法。所以,您不用对 service() 方法做任何动作,您只需要根据来自客户端的请求类型来重写 doGet() 或 doPost() 即可。

doGet() 方法

GET 请求来自于一个 URL 的正常请求,或者来自于一个未指定 METHOD 的 HTML 表单,它由 doGet() 方法处理。

doPost() 方法

POST 请求来自于一个特别指定了 METHOD 为 POST 的 HTML 表单,它由 doPost() 方法处理。

destroy() 方法

destroy() 方法只会被调用一次,在 Servlet 生命周期结束时被调用。destroy() 方法可以让您的 Servlet 关闭数据库连接、停止后台线程、把 Cookie 列表或点击计数器写入到磁盘,并执行其他类似的清理活动。

Servlet生命周期架构图

下图显示了一个典型的 Servlet 生命周期方案。

  • 第一个到达服务器的 HTTP 请求被委派到 Servlet 容器。
  • Servlet 容器在调用 service() 方法之前加载 Servlet。
  • 然后 Servlet 容器处理由多个线程产生的多个请求,每个线程执行一个单一的 Servlet 实例的 service() 方法。

Servlet 实例

Servlet 是服务 HTTP 请求并实现 javax.servlet.Servlet 接口的 Java 类。Web 应用程序开发人员通常编写 Servlet 来扩展 javax.servlet.http.HttpServlet,并实现 Servlet 接口的抽象类专门用来处理 HTTP 请求。

报错:路径重名的话tomcat启动不了。

servlet 浏览器访问路径配置有个小问题:

1、java 类里的注解 —— @WebServlet("/HelloServlet") 对应浏览器路径:

http://localhost:8080/TomcatTest/HelloServlet

2、配置文件(web.xml)里对应的浏览器访问路径:

http://localhost:8080/TomcatTest/HelloServlet

这两种配一个就好了,不然路径重名的话反而会让tomcat启动不了。

例如这样就启动不了:

修改 web.xml :

<url-pattern>/HelloServlet</url-pattern>

修改后,web.xml 和 java 类的注解,对应路径都是:

http://localhost:8080/TomcatTest/HelloServlet

导致

命名的 servlet[HelloServlet]和 [com.runoob.test.HelloServlet] 都被映射到 URL 模式 [/ HelloServlet] 这是不允许的。

解决办法:

将注解去掉或者保留注解进入web.xml将映射删除既可以。

示例:

//@WebServlet("/HelloWorld")
public class HelloWorld extends HttpServlet {

    private String message;

    @Override
    public void init() throws ServletException
    {
        // 执行必需的初始化
        message = "Hello World";
    }
    @Override
    public void doGet(HttpServletRequest request,
                      HttpServletResponse response)
            throws ServletException, IOException
    {
        // 设置响应内容类型
        response.setContentType("text/html");

        // 实际的逻辑是在这里
        PrintWriter out = response.getWriter();
        out.println("<h1>" + message.toUpperCase() + "</h1>");
    }
    @Override
    public void destroy()
    {
        // 什么也不做
    }
}

web.xml

<servlet>
  <servlet-name>HelloWorld</servlet-name>
  <servlet-class>com.jsptest.HelloWorld</servlet-class>
</servlet>
<servlet-mapping>
  <servlet-name>HelloWorld</servlet-name>
  <url-pattern>/HelloWorld</url-pattern>
</servlet-mapping>

Servlet 表单数据

很多情况下,需要传递一些信息,从浏览器到 Web 服务器,最终到后台程序。浏览器使用两种方法可将这些信息传递到 Web 服务器,分别为 GET 方法和 POST 方法。

GET 方法

GET 方法向页面请求发送已编码的用户信息。页面和已编码的信息中间用 ? 字符分隔,如下所示:

http://www.test.com/hello?key1=value1&key2=value2

GET 方法是默认的从浏览器向 Web 服务器传递信息的方法,它会产生一个很长的字符串,出现在浏览器的地址栏中。如果您要向服务器传递的是密码或其他的敏感信息,请不要使用 GET 方法。GET 方法有大小限制:请求字符串中最多只能有 1024 个字符。

这些信息使用 QUERY_STRING 头传递,并可以通过 QUERY_STRING 环境变量访问,Servlet 使用 doGet() 方法处理这种类型的请求。

POST 方法

另一个向后台程序传递信息的比较可靠的方法是 POST 方法。POST 方法打包信息的方式与 GET 方法基本相同,但是 POST 方法不是把信息作为 URL 中 ? 字符后的文本字符串进行发送,而是把这些信息作为一个单独的消息。消息以标准输出的形式传到后台程序,您可以解析和使用这些标准输出。Servlet 使用 doPost() 方法处理这种类型的请求。

使用 Servlet 读取表单数据

Servlet 处理表单数据,这些数据会根据不同的情况使用不同的方法自动解析:

  • getParameter():您可以调用 request.getParameter() 方法来获取表单参数的值。
  • getParameterValues():如果参数出现一次以上,则调用该方法,并返回多个值,例如复选框。
  • getParameterNames():如果您想要得到当前请求中的所有参数的完整列表,则调用该方法。

示例:

hellomybatis-servlet.xml 响应字符编码设置为utf-8

<!-- 消息转换器代码,处理请求返回json字符串的中文乱码问题 -->
<mvc:annotation-driven>
    <mvc:message-converters>
        <bean class="org.springframework.http.converter.StringHttpMessageConverter">
            <property name="supportedMediaTypes">
                <list>
                    <value>text/html;charset=UTF-8</value>
                    <!--<value>application/json;charset=UTF-8</value>-->
                </list>
            </property>
        </bean>
    </mvc:message-converters>
</mvc:annotation-driven>

web.xml 请求字符编码过滤为utf-8

<!-- 字符编码过滤器 -->
<filter>
  <filter-name>encodingFilter</filter-name>
  <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
  <init-param>
    <param-name>encoding</param-name>
    <param-value>utf-8</param-value>
  </init-param>
</filter>
<filter-mapping>
  <filter-name>encodingFilter</filter-name>
  <url-pattern>/*</url-pattern>
</filter-mapping>

//加了@WebServlet 注解就不用在web.xml配置servlet
@WebServlet("/show")
public class ShowServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
      resp.setContentType("text/html;charset=utf-8");
        PrintWriter writer = resp.getWriter();
        String title = "GET 获取称号";
        //不需要转译编码的原因是
        //String name = new String(req.getParameter("name").getBytes("ISO-8859-1"), "UTF-8");
        //String titleName = new String(req.getParameter("title").getBytes("ISO-8859-1"), "UTF-8");
        String docType = "<!DOCTYPE html> \n";
        writer.println(docType +
                "<html>\n" +
                "<head><title>" + title + "</title></head>\n" +
                "<body bgcolor=\"#f0f0f0\">\n" +
                "<h1 align=\"center\">" + title + "</h1>\n" +
                "<ul>\n" +
                "  <li><b>人物</b>:"
                + req.getParameter("name") + "\n" +
                "  <li><b>称号</b>:"
                + req.getParameter("title") + "\n" +
                "</ul>\n" +
                "</body></html>");
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doGet(req,resp);
    }
}

输出:

表单提交get示例:

web.xml

  <servlet>
    <servlet-name>hellomybatis</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
      <param-name>contextConfigLocation</param-name>
      <param-value>
        WEB-INF/hellomybatis-servlet.xml
      </param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
  </servlet>
  <servlet-mapping>
    <servlet-name>hellomybatis</servlet-name>
    <url-pattern>/</url-pattern>
  </servlet-mapping>
  <servlet-mapping>
    <servlet-name>default</servlet-name>
    <url-pattern>*.xml</url-pattern>
  </servlet-mapping>
  <!--如果是*.html则不走代理,走默认请求到html文件上-->
  <servlet-mapping>
    <servlet-name>default</servlet-name>
    <url-pattern>*.html</url-pattern>
  </servlet-mapping>

show.html

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>佣兵天下</title>
</head>
<body>
<form action="/hellomybatis/show" method="GET">
    人物:<input type="text" name="name">
    <br />
    称号:<input type="text" name="title" />
    <input type="submit" value="提交" />
</form>
</body>
</html>

ShowServlet 不变。

使用表单的 POST 方法实例:

只需要把请求改成post,后台对应重写doPost()方法即可。

 <form action="/hellomybatis/show" method="POST">
    人物:<input type="text" name="name">
    <br />
    称号:<input type="text" name="title" />
    <input type="submit" value="提交" />
</form>

Servlet 客户端 HTTP 请求

当浏览器请求网页时,它会向 Web 服务器发送特定信息,这些信息不能被直接读取,因为这些信息是作为 HTTP 请求的头的一部分进行传输的。您可以查看 HTTP 协议 了解更多相关信息。

以下是来自于浏览器端的重要头信息,您可以在 Web 编程中频繁使用:

头信息 描述
Accept 这个头信息指定浏览器或其他客户端可以处理的 MIME 类型。值 image/png 或 image/jpeg 是最常见的两种可能值。
Accept-Charset 这个头信息指定浏览器可以用来显示信息的字符集。例如 ISO-8859-1。
Accept-Encoding 这个头信息指定浏览器知道如何处理的编码类型。值 gzip 或 compress 是最常见的两种可能值。
Accept-Language 这个头信息指定客户端的首选语言,在这种情况下,Servlet 会产生多种语言的结果。例如,en、en-us、ru 等。
Authorization 这个头信息用于客户端在访问受密码保护的网页时识别自己的身份。
Connection 这个头信息指示客户端是否可以处理持久 HTTP 连接。持久连接允许客户端或其他浏览器通过单个请求来检索多个文件。值 Keep-Alive 意味着使用了持续连接。
Content-Length 这个头信息只适用于 POST 请求,并给出 POST 数据的大小(以字节为单位)。
Cookie 这个头信息把之前发送到浏览器的 cookies 返回到服务器。
Host 这个头信息指定原始的 URL 中的主机和端口。
If-Modified-Since 这个头信息表示只有当页面在指定的日期后已更改时,客户端想要的页面。如果没有新的结果可以使用,服务器会发送一个 304 代码,表示 Not Modified 头信息。
If-Unmodified-Since这个头信息是 If-Modified-Since 的对立面,它指定只有当文档早于指定日期时,操作才会成功。
Referer 这个头信息指示所指向的 Web 页的 URL。例如,如果您在网页 1,点击一个链接到网页 2,当浏览器请求网页 2 时,网页 1 的 URL 就会包含在 Referer 头信息中。
User-Agent 这个头信息识别发出请求的浏览器或其他客户端,并可以向不同类型的浏览器返回不同的内容。

读取 HTTP 头的方法

下面的方法可用在 Servlet 程序中读取 HTTP 头。这些方法通过 HttpServletRequest 对象可用。

序号 方法 & 描述
1 Cookie[] getCookies() 返回一个数组,包含客户端发送该请求的所有的 Cookie 对象。
2 Enumeration getAttributeNames() 返回一个枚举,包含提供给该请求可用的属性名称。
3 Enumeration getHeaderNames() 返回一个枚举,包含在该请求中包含的所有的头名。
4 Enumeration getParameterNames() 返回一个 String 对象的枚举,包含在该请求中包含的参数的名称。
5 HttpSession getSession() 返回与该请求关联的当前 session 会话,或者如果请求没有 session 会话,则创建一个。
6 HttpSession getSession(boolean create) 返回与该请求关联的当前 HttpSession,或者如果没有当前会话,且创建是真的,则返回一个新的 session 会话。
7 Locale getLocale() 基于 Accept-Language 头,返回客户端接受内容的首选的区域设置。
8 Object getAttribute(String name) 以对象形式返回已命名属性的值,如果没有给定名称的属性存在,则返回 null。
9 ServletInputStream getInputStream() 使用 ServletInputStream,以二进制数据形式检索请求的主体。
10 String getAuthType() 返回用于保护 Servlet 的身份验证方案的名称,例如,"BASIC" 或 "SSL",如果JSP没有受到保护则返回 null。
11 String getCharacterEncoding() 返回请求主体中使用的字符编码的名称。
12 String getContentType() 返回请求主体的 MIME 类型,如果不知道类型则返回 null。
13 String getContextPath() 返回指示请求上下文的请求 URI 部分。
14 String getHeader(String name) 以字符串形式返回指定的请求头的值。
15 String getMethod() 返回请求的 HTTP 方法的名称,例如,GET、POST 或 PUT。
16 String getParameter(String name) 以字符串形式返回请求参数的值,或者如果参数不存在则返回 null。
17 String getPathInfo() 当请求发出时,返回与客户端发送的 URL 相关的任何额外的路径信息。
18 String getProtocol() 返回请求协议的名称和版本。
19 String getQueryString() 返回包含在路径后的请求 URL 中的查询字符串。
20 String getRemoteAddr() 返回发送请求的客户端的互联网协议(IP)地址。
21 String getRemoteHost() 返回发送请求的客户端的完全限定名称。
22 String getRemoteUser() 如果用户已通过身份验证,则返回发出请求的登录用户,或者如果用户未通过身份验证,则返回 null。
23 String getRequestURI() 从协议名称直到 HTTP 请求的第一行的查询字符串中,返回该请求的 URL 的一部分。
24 String getRequestedSessionId() 返回由客户端指定的 session 会话 ID。
25 String getServletPath() 返回调用 JSP 的请求的 URL 的一部分。
26 String[] getParameterValues(String name) 返回一个字符串对象的数组,包含所有给定的请求参数的值,如果参数不存在则返回 null。
27 boolean isSecure() 返回一个布尔值,指示请求是否使用安全通道,如 HTTPS。
28 int getContentLength() 以字节为单位返回请求主体的长度,并提供输入流,或者如果长度未知则返回 -1。
29 int getIntHeader(String name) 返回指定的请求头的值为一个 int 值。
30 int getServerPort() 返回接收到这个请求的端口号。
31 int getParameterMap() 将参数封装成 Map 类型。

示例:

@WebServlet("/displayHeaderServlet")
public class DisplayHeaderServlet extends HttpServlet {
    private static final long serialVersionUID = 1L;
    // 处理 GET 方法请求的方法
    @Override
    public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
    {
        // 设置响应内容类型
        //如果不设置会导致乱码
        //HTTP Header ???? - ??????
        response.setContentType("text/html;charset=UTF-8");

        PrintWriter out = response.getWriter();
        String title = "HTTP Header 请求响应头";
        String docType =
                "<!DOCTYPE html> \n";
        out.println(docType +
                "<html>\n" +
                "<head><meta charset=\"utf-8\"><title>" + title + "</title></head>\n"+
                "<body bgcolor=\"#f0f0f0\">\n" +
                "<h1 align=\"center\">" + title + "</h1>\n" +
                "<table width=\"100%\" border=\"1\" align=\"center\">\n" +
                "<tr bgcolor=\"#949494\">\n" +
                "<th>Header 名称</th><th>Header 值</th>\n"+
                "</tr>\n");

        Enumeration headerNames = request.getHeaderNames();

        while(headerNames.hasMoreElements()) {
            String paramName = (String)headerNames.nextElement();
            out.print("<tr><td>" + paramName + "</td>\n");
            String paramValue = request.getHeader(paramName);
            out.println("<td> " + paramValue + "</td></tr>\n");
        }
        out.println("</table>\n</body></html>");
    }
    // 处理 POST 方法请求的方法
    @Override
    public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doGet(request, response);
    }
}

输出:

报错:

1、Class com.jsptest.DisplayHeaderServlet is not a Servlet

这是因为我们在给DisplayHeaderServlet加上@WebServlet时却没有继承HttpServlet,导致系统初始化Servlet时因为DisplayHeaderServlet没有初始化init()和destroy()等默认方法无法初始化而报错。

javax.servlet.ServletException: Class com.jsptest.DisplayHeaderServlet is not a Servlet
org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:522)

2、Failed to start component

这是因为@WebServlet("displayHeaderServlet")注解没有加/ 斜杠导致的servlet不能加载。

Caused by: java.lang.IllegalStateException: ContainerBase.addChild: start: org.apache.catalina.LifecycleException: Failed to start component [StandardEngine[Catalina].StandardHost[localhost].StandardContext[/hellomybatis]]
	at org.apache.catalina.core.ContainerBase.addChildInternal(ContainerBase.java:730)

@WebServlet("displayHeaderServlet")
public class DisplayHeaderServlet extends HttpServlet {
    /......
}

//正确写法
@WebServlet("/displayHeaderServlet")
public class DisplayHeaderServlet extends HttpServlet {
    /......
}

Servlet 服务器 HTTP 响应

状态行包括 HTTP 版本(在本例中为 HTTP/1.1)、一个状态码(在本例中为 200)和一个对应于状态码的短消息(在本例中为 OK)。

下表总结了从 Web 服务器端返回到浏览器的最有用的 HTTP 1.1 响应报头,您会在 Web 编程中频繁地使用它们:

头信息 描述
Allow 这个头信息指定服务器支持的请求方法(GET、POST 等)。
Cache-Control 这个头信息指定响应文档在何种情况下可以安全地缓存。可能的值有:public、private 或 no-cache 等。Public 意味着文档是可缓存,Private 意味着文档是单个用户私用文档,且只能存储在私有(非共享)缓存中,no-cache 意味着文档不应被缓存。
Connection 这个头信息指示浏览器是否使用持久 HTTP 连接。值 close 指示浏览器不使用持久 HTTP 连接,值 keep-alive 意味着使用持久连接。
Content-Disposition这个头信息可以让您请求浏览器要求用户以给定名称的文件把响应保存到磁盘。
Content-Encoding 在传输过程中,这个头信息指定页面的编码方式。
Content-Language 这个头信息表示文档编写所使用的语言。例如,en、en-us、ru 等。
Content-Length 这个头信息指示响应中的字节数。只有当浏览器使用持久(keep-alive)HTTP 连接时才需要这些信息。
Content-Type 这个头信息提供了响应文档的 MIME(Multipurpose Internet Mail Extension)类型。
Expires 这个头信息指定内容过期的时间,在这之后内容不再被缓存。
Last-Modified 这个头信息指示文档的最后修改时间。然后,客户端可以缓存文件,并在以后的请求中通过 If-Modified-Since 请求头信息提供一个日期。
Location 这个头信息应被包含在所有的带有状态码的响应中。在 300s 内,这会通知浏览器文档的地址。浏览器会自动重新连接到这个位置,并获取新的文档。
Refresh 这个头信息指定浏览器应该如何尽快请求更新的页面。您可以指定页面刷新的秒数。
Retry-After 这个头信息可以与 503(Service Unavailable 服务不可用)响应配合使用,这会告诉客户端多久就可以重复它的请求。
Set-Cookie 这个头信息指定一个与页面关联的 cookie。

设置 HTTP 响应报头的方法

下面的方法可用于在 Servlet 程序中设置 HTTP 响应报头。这些方法通过 HttpServletResponse 对象可用。

序号 方法 & 描述
1 String encodeRedirectURL(String url) 为 sendRedirect 方法中使用的指定的 URL 进行编码,或者如果编码不是必需的,则返回 URL 未改变。
2 String encodeURL(String url) 对包含 session 会话 ID 的指定 URL 进行编码,或者如果编码不是必需的,则返回 URL 未改变。
3 boolean containsHeader(String name) 返回一个布尔值,指示是否已经设置已命名的响应报头。
4 boolean isCommitted() 返回一个布尔值,指示响应是否已经提交。
5 void addCookie(Cookie cookie) 把指定的 cookie 添加到响应。
6 void addDateHeader(String name, long date) 添加一个带有给定的名称和日期值的响应报头。
7 void addHeader(String name, String value) 添加一个带有给定的名称和值的响应报头。
8 void addIntHeader(String name, int value) 添加一个带有给定的名称和整数值的响应报头。
9 void flushBuffer() 强制任何在缓冲区中的内容被写入到客户端。
10 void reset() 清除缓冲区中存在的任何数据,包括状态码和头。
11 void resetBuffer() 清除响应中基础缓冲区的内容,不清除状态码和头。
12 void sendError(int sc) 使用指定的状态码发送错误响应到客户端,并清除缓冲区。
13 void sendError(int sc, String msg) 使用指定的状态发送错误响应到客户端。
14 void sendRedirect(String location) 使用指定的重定向位置 URL 发送临时重定向响应到客户端。
15 void setBufferSize(int size) 为响应主体设置首选的缓冲区大小。
16 void setCharacterEncoding(String charset) 设置被发送到客户端的响应的字符编码(MIME 字符集)例如,UTF-8。
17 void setContentLength(int len) 设置在 HTTP Servlet 响应中的内容主体的长度,该方法设置 HTTP Content-Length 头。
18 void setContentType(String type) 如果响应还未被提交,设置被发送到客户端的响应的内容类型。
19 void setDateHeader(String name, long date) 设置一个带有给定的名称和日期值的响应报头。
20 void setHeader(String name, String value) 设置一个带有给定的名称和值的响应报头。
21 void setIntHeader(String name, int value) 设置一个带有给定的名称和整数值的响应报头。
22 void setLocale(Locale loc) 如果响应还未被提交,设置响应的区域。
23 void setStatus(int sc) 为该响应设置状态码。

示例:

@WebServlet("/refresh")
public class RefreshServlet extends HttpServlet {
    // 处理 GET 方法请求的方法
    @Override
    public void doGet(HttpServletRequest request,
                      HttpServletResponse response)
            throws ServletException, IOException
    {
        // 设置刷新自动加载时间为 3秒
        response.setIntHeader("Refresh", 3);
        // 设置响应内容类型
        response.setContentType("text/html;charset=UTF-8");
        Cookie cookie = new Cookie("winner","amy");
        response.addCookie(cookie);
        //使用默认时区和语言环境获得一个日历
        Calendar cale = Calendar.getInstance();
        //将Calendar类型转换成Date类型
        Date tasktime=cale.getTime();
        //设置日期输出的格式
        SimpleDateFormat df=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        //格式化输出
        String nowTime = df.format(tasktime);
        PrintWriter out = response.getWriter();
        String title = "自动刷新 Header 页面";
        String docType =
                "<!DOCTYPE html>\n";
        out.println(docType +
                "<html>\n" +
                "<head><title>" + title + "</title></head>\n"+
                "<body bgcolor=\"#f0f0f0\">\n" +
                "<h1 align=\"center\">" + title + "</h1>\n" +
                "<p>当前时间是:" + nowTime + "</p>\n");
    }
    // 处理 POST 方法请求的方法
    @Override
    public void doPost(HttpServletRequest request,
                       HttpServletResponse response)
            throws ServletException, IOException {
        doGet(request, response);
    }
}

输出:

扩展:Cookie是跟着session会话走的,我们请求的链接如果加了Cookie,就是对整个会话加Cookie,后面的请求都会带上这个Cookie。查看Cookie的两种方式。一个从network,一个从application.

Servlet HTTP 状态码

状态行包括 HTTP 版本(在本例中为 HTTP/1.1)、一个状态码(在本例中为 200)和一个对应于状态码的短消息(在本例中为 OK)。

以下是可能从 Web 服务器返回的 HTTP 状态码和相关的信息列表:

代码 消息 描述
100 Continue 只有请求的一部分已经被服务器接收,但只要它没有被拒绝,客户端应继续该请求。
101 Switching Protocols 服务器切换协议。
200 OK 请求成功。
201 Created 该请求是完整的,并创建一个新的资源。
202 Accepted 该请求被接受处理,但是该处理是不完整的。
203 Non-authoritative Information
204 No Content
205 Reset Content
206 Partial Content
300 Multiple Choices 链接列表。用户可以选择一个链接,进入到该位置。最多五个地址。
301 Moved Permanently 所请求的页面已经转移到一个新的 URL。
302 Found 所请求的页面已经临时转移到一个新的 URL。
303 See Other 所请求的页面可以在另一个不同的 URL 下被找到。
304 Not Modified
305 Use Proxy
306 Unused 在以前的版本中使用该代码。现在已不再使用它,但代码仍被保留。
307 Temporary Redirect 所请求的页面已经临时转移到一个新的 URL。
400 Bad Request 服务器不理解请求。
401 Unauthorized 所请求的页面需要用户名和密码。
402 Payment Required 您还不能使用该代码。
403 Forbidden 禁止访问所请求的页面。
404 Not Found 服务器无法找到所请求的页面。.
405 Method Not Allowed 在请求中指定的方法是不允许的。
406 Not Acceptable 服务器只生成一个不被客户端接受的响应。
407 Proxy Authentication Required在请求送达之前,您必须使用代理服务器的验证。
408 Request Timeout 请求需要的时间比服务器能够等待的时间长,超时。
409 Conflict 请求因为冲突无法完成。
410 Gone 所请求的页面不再可用。
411 Length Required "Content-Length" 未定义。服务器无法处理客户端发送的不带 Content-Length 的请求信息。
412 Precondition Failed 请求中给出的先决条件被服务器评估为 false。
413 Request Entity Too Large 服务器不接受该请求,因为请求实体过大。
414 Request-url Too Long 服务器不接受该请求,因为 URL 太长。当您转换一个 "post" 请求为一个带有长的查询信息的 "get" 请求时发生。
415 Unsupported Media Type 服务器不接受该请求,因为媒体类型不被支持。
417 Expectation Failed
500 Internal Server Error 未完成的请求。服务器遇到了一个意外的情况。
501 Not Implemented 未完成的请求。服务器不支持所需的功能。
502 Bad Gateway 未完成的请求。服务器从上游服务器收到无效响应。
503 Service Unavailable 未完成的请求。服务器暂时超载或死机。
504 Gateway Timeout 网关超时。
505 HTTP Version Not Supported 服务器不支持"HTTP协议"版本。

设置 HTTP 状态代码的方法

下面的方法可用于在 Servlet 程序中设置 HTTP 状态码。这些方法通过 HttpServletResponse 对象可用。

序号 方法 & 描述
1 public void setStatus ( int statusCode ) 该方法设置一个任意的状态码。setStatus 方法接受一个 int(状态码)作为参数。如果您的响应包含了一个特殊的状态码和文档,请确保在使用 PrintWriter 实际返回任何内容之前调用 setStatus。
2 public void sendRedirect(String url) 该方法生成一个 302 响应,连同一个带有新文档 URL 的 Location 头。
3 public void sendError(int code, String message) 该方法发送一个状态码(通常为 404),连同一个在 HTML 文档内部自动格式化并发送到客户端的短消息。

示例:

@WebServlet("/error")
public class ErrorServlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.setStatus(403);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
       doGet(req,resp);
    }
}

输出:

Servlet 编写过滤器

Servlet 过滤器可以动态地拦截请求和响应,以变换或使用包含在请求或响应中的信息。

filter对象只会创建一次,init方法也只会执行一次。

可以将一个或多个 Servlet 过滤器附加到一个 Servlet 或一组 Servlet。Servlet 过滤器也可以附加到 JavaServer Pages (JSP) 文件和 HTML 页面。调用 Servlet 前调用所有附加的 Servlet 过滤器。

Servlet 过滤器是可用于 Servlet 编程的 Java 类,可以实现以下目的:

  • 在客户端的请求访问后端资源之前,拦截这些请求。
  • 在服务器的响应发送回客户端之前,处理这些响应。

根据规范建议的各种类型的过滤器:

  • 身份验证过滤器(Authentication Filters)。
  • 数据压缩过滤器(Data compression Filters)。
  • 加密过滤器(Encryption Filters)。
  • 触发资源访问事件过滤器。
  • 图像转换过滤器(Image Conversion Filters)。
  • 日志记录和审核过滤器(Logging and Auditing Filters)。
  • MIME-TYPE 链过滤器(MIME-TYPE Chain Filters)。
  • 标记化过滤器(Tokenizing Filters)。
  • XSL/T 过滤器(XSL/T Filters),转换 XML 内容。

过滤器通过 Web 部署描述符(web.xml)中的 XML 标签来声明,然后映射到您的应用程序的部署描述符中的 Servlet 名称或 URL 模式。

当 Web 容器启动 Web 应用程序时,它会为您在部署描述符中声明的每一个过滤器创建一个实例。

Filter的执行顺序与在web.xml配置文件中的配置顺序一致,一般把Filter配置在所有的Servlet之前。

Servlet 过滤器方法

过滤器是一个实现了 javax.servlet.Filter 接口的 Java 类。javax.servlet.Filter 接口定义了三个方法:

序号 方法 & 描述
1 public void doFilter (ServletRequest, ServletResponse, FilterChain) 该方法完成实际的过滤操作,当客户端请求方法与过滤器设置匹配的URL时,Servlet容器将先调用过滤器的doFilter方法。FilterChain用户访问后续过滤器。
2 public void init(FilterConfig filterConfig) web 应用程序启动时,web 服务器将创建Filter 的实例对象,并调用其init方法,读取web.xml配置,完成对象的初始化功能,从而为后续的用户请求作好拦截的准备工作(filter对象只会创建一次,init方法也只会执行一次)。开发人员通过init方法的参数,可获得代表当前filter配置信息的FilterConfig对象。
3 public void destroy() Servlet容器在销毁过滤器实例前调用该方法,在该方法中释放Servlet过滤器占用的资源。

示例:

public class LogFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        String before = filterConfig.getInitParameter("before");
        // 输出初始化参数
        System.out.println("初始化前:" + before);
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        System.out.println("过滤时执行参数输出:"+ JSON.toJSONString(servletRequest.getParameterMap()));
        // 把请求传回过滤链
        filterChain.doFilter(servletRequest,servletResponse);
    }

    @Override
    public void destroy() {
        System.out.println("关闭 LogFilter");
    }
}

web.xml

<filter>
  <filter-name>LogFilter</filter-name>
  <filter-class>com.filter.LogFilter</filter-class>
  <init-param>
    <param-name>before</param-name>
    <param-value>日志打印</param-value>
  </init-param>
</filter>
<filter-mapping>
  <filter-name>LogFilter</filter-name>
  <!--过滤器适用于所有的 Servlet,因为我们在配置中指定 /* ,url-pattern用于匹配我们想要过滤的请求路径-->
  <url-pattern>/*</url-pattern>
</filter-mapping>

请求:<

输出:

使用多个过滤器

Web 应用程序可以根据特定的目的定义若干个不同的过滤器。假设您定义了两个过滤器 AuthenFilter 和 LogFilter。

项目启动时会初始化filter过滤器,初始化是随机的,不一定以web.xml配置的一样。使用多个过滤器以在web.xml配置的先后顺序执行。

示例:

web.xml

<filter>
  <filter-name>LogFilter</filter-name>
  <filter-class>com.filter.LogFilter</filter-class>
  <init-param>
    <param-name>before</param-name>
    <param-value>日志打印</param-value>
  </init-param>
</filter>
<filter-mapping>
  <filter-name>LogFilter</filter-name>
  <!--过滤器适用于所有的 Servlet,因为我们在配置中指定 /* ,url-pattern用于匹配我们想要过滤的请求路径-->
  <url-pattern>/*</url-pattern>
</filter-mapping>

<filter>
  <filter-name>AuthenFilter</filter-name>
  <filter-class>com.filter.AuthenFilter</filter-class>
  <init-param>
    <param-name>authen</param-name>
    <param-value>权限校验</param-value>
  </init-param>
</filter>
  <filter-mapping>
    <filter-name>AuthenFilter</filter-name>
    <!--过滤器适用于所有的 Servlet,因为我们在配置中指定 /* ,url-pattern用于匹配我们想要过滤的请求路径-->
    <!--<url-pattern>/*</url-pattern>-->
    <!--只适用于@WebServlet("/show")-->
    <url-pattern>/show</url-pattern>
    <!--不能进入-->
    <!--<url-pattern>/showyyy</url-pattern>-->
    <!--也可以用<servlet-name>指定过滤器所拦截的Servlet名称,不过该名字必须配置在web.xml中,不能用注解,否则不能识别-->
    <!--<servlet-name></servlet-name>-->
  </filter-mapping>

public class AuthenFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        String authen = filterConfig.getInitParameter("authen");
        // 输出初始化参数
        System.out.println("初始化前:" + authen);
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        //获取请求信息(测试时可以通过get方式在URL中添加name)
        String name = servletRequest.getParameter("name");

        // 过滤器核心代码逻辑
        System.out.println("过滤器获取请求参数:"+name);
        System.out.println("AuthenFilter 过滤");

        if("amy".equals(name)){
            // 把请求传回过滤链
            filterChain.doFilter(servletRequest, servletResponse);
        }else{
            //设置返回内容类型
            servletResponse.setContentType("text/html;charset=UTF-8");

            //在页面输出响应信息
            PrintWriter out = servletResponse.getWriter();
            out.print("<b>:"+name+"不正确,请求被拦截,不能访问web资源</b>");
            System.out.println("name:"+name+"不正确,请求被拦截,不能访问web资源");
        }
    }

    @Override
    public void destroy() {
        System.out.println("关闭 LogFilter");
    }
}

请求:<

请求:<

输出:

初始化前:权限校验
初始化前:日志打印
过滤时执行参数输出:{"name":["amy"],"title":["屠龙勇士"]}
过滤器获取请求参数:amy
AuthenFilter 过滤
过滤时执行参数输出:{"name":["happ"],"title":["屠龙勇士"]}
过滤器获取请求参数:happ
AuthenFilter 过滤
name:happ不正确,请求被拦截,不能访问web资源

过滤器的应用顺序

web.xml 中的 filter-mapping 元素的顺序决定了 Web 容器应用过滤器到 Servlet 的顺序。若要反转过滤器的顺序,您只需要在 web.xml 文件中反转 filter-mapping 元素即可

web.xml配置各节点说明

<filter>指定一个过滤器。
<filter-name>用于为过滤器指定一个名字,该元素的内容不能为空。
<filter-class>元素用于指定过滤器的完整的限定类名。
<init-param>元素用于为过滤器指定初始化参数,它的子元素<param-name>指定参数的名字,<param-value>指定参数的值。
在过滤器中,可以使用FilterConfig接口对象来访问初始化参数。
<filter-mapping>元素用于设置一个 Filter 所负责拦截的资源。一个Filter拦截的资源可通过两种方式来指定:Servlet 名称和资源访问的请求路径
<filter-name>子元素用于设置filter的注册名称。该值必须是在<filter>元素中声明过的过滤器的名字
<url-pattern>设置 filter 所拦截的请求路径(过滤器关联的URL样式)
<servlet-name>指定过滤器所拦截的Servlet名称。
<dispatcher>指定过滤器所拦截的资源被 Servlet 容器调用的方式,可以是REQUEST,INCLUDE,FORWARD和ERROR之一,默认REQUEST。用户可以设置多个<dispatcher>子元素用来指定 Filter 对资源的多种调用方式进行拦截。
<dispatcher>子元素可以设置的值及其意义
REQUEST:当用户直接访问页面时,Web容器将会调用过滤器。如果目标资源是通过RequestDispatcher的include()或forward()方法访问时,那么该过滤器就不会被调用。
INCLUDE:如果目标资源是通过RequestDispatcher的include()方法访问时,那么该过滤器将被调用。除此之外,该过滤器不会被调用。
FORWARD:如果目标资源是通过RequestDispatcher的forward()方法访问时,那么该过滤器将被调用,除此之外,该过滤器不会被调用。
ERROR:如果目标资源是通过声明式异常处理机制调用时,那么该过滤器将被调用。除此之外,过滤器不会被调用。

Servlet 异常处理

当一个 Servlet 抛出一个异常时,Web 容器在使用了 exception-type 元素的 web.xml 中搜索与抛出异常类型相匹配的配置。

您必须在 web.xml 中使用 error-page 元素来指定对特定异常 或 HTTP 状态码 作出相应的 Servlet 调用。

请求属性 - 错误/异常

以下是错误处理的 Servlet 可以访问的请求属性列表,用来分析错误/异常的性质。

序号 属性 & 描述
1 javax.servlet.error.status_code 该属性给出状态码,状态码可被存储,并在存储为 java.lang.Integer 数据类型后可被分析。
2 javax.servlet.error.exception_type 该属性给出异常类型的信息,异常类型可被存储,并在存储为 java.lang.Class 数据类型后可被分析。
3 javax.servlet.error.message 该属性给出确切错误消息的信息,信息可被存储,并在存储为 java.lang.String 数据类型后可被分析。
4 javax.servlet.error.request_uri 该属性给出有关 URL 调用 Servlet 的信息,信息可被存储,并在存储为 java.lang.String 数据类型后可被分析。
5 javax.servlet.error.exception 该属性给出异常产生的信息,信息可被存储,并在存储为 java.lang.Throwable 数据类型后可被分析。
6 javax.servlet.error.servlet_name 该属性给出 Servlet 的名称,名称可被存储,并在存储为 java.lang.String 数据类型后可被分析。

示例:

web.xml

<!-- error-code 相关的错误页面 -->
<error-page>
  <error-code>404</error-code>
  <location>/errorHandler</location>
</error-page>
<!-- exception-type 相关的错误页面 -->
<error-page>
  <!--<exception-type>javax.servlet.ServletException</exception-type>-->
  <exception-type>java.lang.Throwable</exception-type >
  <location>/errorHandler</location>
</error-page>


@WebServlet("/errorHandler")
public class ErrorHandlerServlet extends HttpServlet {

    // 处理 GET 方法请求的方法
    @Override
    public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
    {
        Throwable throwable = (Throwable)
                request.getAttribute("javax.servlet.error.exception");
        Integer statusCode = (Integer)
                request.getAttribute("javax.servlet.error.status_code");
        String servletName = (String)
                request.getAttribute("javax.servlet.error.servlet_name");
        if (servletName == null){
            servletName = "Unknown";
        }
        String requestUri = (String)
                request.getAttribute("javax.servlet.error.request_uri");
        if (requestUri == null){
            requestUri = "Unknown";
        }
        // 设置响应内容类型
        response.setContentType("text/html;charset=UTF-8");

        PrintWriter out = response.getWriter();
        String title = "异常页面 Error/Exception 信息";

        String docType = "<!DOCTYPE html>\n";
        out.println(docType +
                "<html>\n" +
                "<head><title>" + title + "</title></head>\n" +
                "<body bgcolor=\"#f0f0f0\">\n");
        out.println("<h1>异常信息展示</h1>");
        if (throwable == null && statusCode == null){
            out.println("<h2>错误信息丢失</h2>");
            out.println("请返回 <a href=\"" +
                    response.encodeURL("http://localhost:8080/") +
                    "\">主页</a>。");
        }else if (statusCode != null) {
            out.println("错误代码 : " + statusCode);
        }else{
            out.println("<h2>错误信息</h2>");
            out.println("Servlet Name : " + servletName +
                    "</br></br>");
            out.println("异常类型 : " +
                    throwable.getClass( ).getName( ) +
                    "</br></br>");
            out.println("请求 URI: " + requestUri +
                    "<br><br>");
            out.println("异常信息: " +
                    throwable.getMessage( ));
        }
        out.println("</body>");
        out.println("</html>");
    }
    // 处理 POST 方法请求的方法
    @Override
    public void doPost(HttpServletRequest request,
                       HttpServletResponse response)
            throws ServletException, IOException {
        doGet(request, response);
    }
}

错误请求:

输出:

Servlet Cookie 处理

使用cookie的三个步骤:

  • 服务器脚本向浏览器发送一组 Cookie。例如:姓名、年龄或识别号码等。

  • 浏览器将这些信息存储在本地计算机上,以备将来使用。

  • 当下一次浏览器向 Web 服务器发送任何请求时,浏览器会把这些 Cookie 信息发送到服务器,服务器将使用这些信息来识别用户。

    //Servlet Cookie 处理需要对中文进行编码与解码,方法如下:
    String str = java.net.URLEncoder.encode("中文","UTF-8"); //编码
    String str = java.net.URLDecoder.decode("编码后的字符串","UTF-8"); // 解码

Cookie 剖析

Cookie 通常设置在 HTTP 头信息中.

正如您所看到的,Set-Cookie 头包含了一个名称值对、一个 GMT 日期、一个路径和一个域。名称和值会被 URL 编码。expires 字段是一个指令,告诉浏览器在给定的时间和日期之后"忘记"该 Cookie。

如果浏览器被配置为存储 Cookie,它将会保留此信息直到到期日期。如果用户的浏览器指向任何匹配该 Cookie 的路径和域的页面,它会重新发送 Cookie 到服务器。浏览器的头信息可能如下所示:

Servlet Cookie 方法

以下是在 Servlet 中操作 Cookie 时可使用的有用的方法列表。

序号 方法 & 描述
1 public void setDomain(String pattern) 该方法设置 cookie 适用的域,例如 runoob.com。
2 public String getDomain() 该方法获取 cookie 适用的域,例如 runoob.com。
3 public void setMaxAge(int expiry) 该方法设置 cookie 过期的时间(以秒为单位)。如果不这样设置,cookie 只会在当前 session 会话中持续有效。
4 public int getMaxAge() 该方法返回 cookie 的最大生存周期(以秒为单位),默认情况下,-1 表示 cookie 将持续下去,直到浏览器关闭。
5 public String getName() 该方法返回 cookie 的名称。名称在创建后不能改变。
6 public void setValue(String newValue) 该方法设置与 cookie 关联的值。
7 public String getValue() 该方法获取与 cookie 关联的值。
8 public void setPath(String uri) 该方法设置 cookie 适用的路径。如果您不指定路径,与当前页面相同目录下的(包括子目录下的)所有 URL 都会返回 cookie。
9 public String getPath() 该方法获取 cookie 适用的路径。
10 public void setSecure(boolean flag) 该方法设置布尔值,表示 cookie 是否应该只在加密的(即 SSL)连接上发送。
11 public void setComment(String purpose) 设置cookie的注释。该注释在浏览器向用户呈现 cookie 时非常有用。
12 public String getComment() 获取 cookie 的注释,如果 cookie 没有注释则返回 null。

通过 Servlet 设置 Cookie

通过 Servlet 设置 Cookie 包括三个步骤:

(1) 创建一个 Cookie 对象:您可以调用带有 cookie 名称和 cookie 值的 Cookie 构造函数,cookie 名称和 cookie 值都是字符串。

Cookie cookie = new Cookie("key","value");

请记住,无论是名字还是值,都不应该包含空格或以下任何字符:

[ ] ( ) = , " / ? @ : ;

(2) 设置最大生存周期:您可以使用 setMaxAge 方法来指定 cookie 能够保持有效的时间(以秒为单位)。下面将设置一个最长有效期为 24 小时的 cookie。

cookie.setMaxAge(60*60*24);

(3) 发送 Cookie 到 HTTP 响应头:您可以使用 response.addCookie 来添加 HTTP 响应头中的 Cookie,如下所示:

response.addCookie(cookie);

通过 Servlet 读取 Cookie

要读取 Cookie,您需要通过调用 HttpServletRequest 的 getCookies( ) 方法创建一个 javax.servlet.http.Cookie 对象的数组。然后循环遍历数组,并使用 getName() 和 getValue() 方法来访问每个 cookie 和关联的值。

通过 Servlet 删除 Cookie

删除 Cookie 是非常简单的。如果您想删除一个 cookie,那么您只需要按照以下三个步骤进行:

  • 读取一个现有的 cookie,并把它存储在 Cookie 对象中。
  • 使用 setMaxAge() 方法设置 cookie 的年龄为零,来删除现有的 cookie。
  • 把这个 cookie 添加到响应头。

实例:

添加cookie:

@WebServlet("/show")
public class ShowServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 为名字和姓氏创建 Cookie
        Cookie name = new Cookie("name",
                URLEncoder.encode(req.getParameter("name"), "UTF-8")); // 中文转码
        Cookie title = new Cookie("title",
                req.getParameter("title"));

        // 为两个 Cookie 设置过期日期为 24 小时后
        name.setMaxAge(60*60*24);
        title.setMaxAge(60*60*24);

        // 在响应头中添加两个 Cookie
        resp.addCookie( name );
        resp.addCookie( title );

        resp.setContentType("text/html;charset=utf-8");
        PrintWriter writer = resp.getWriter();
        String top = "GET 获取称号";
        //不需要转译编码的原因是
        //String name = new String(req.getParameter("name").getBytes("ISO-8859-1"), "UTF-8");
        //String titleName = new String(req.getParameter("title").getBytes("ISO-8859-1"), "UTF-8");
        String docType = "<!DOCTYPE html> \n";
        writer.println(docType +
                "<html>\n" +
                "<head><title>" + top + "</title></head>\n" +
                "<body bgcolor=\"#f0f0f0\">\n" +
                "<h1 align=\"center\">" + top + "</h1>\n" +
                "<ul>\n" +
                "  <li><b>人物</b>:"
                + req.getParameter("name") + "\n" +
                "  <li><b>称号</b>:"
                + req.getParameter("title") + "\n" +
                "</ul>\n" +
                "</body></html>");
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doGet(req,resp);
    }
}

@WebServlet("/readCookies")
public class ReadCookiesServlet extends HttpServlet {

    @Override
    public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        Cookie cookie = null;
        Cookie[] cookies = null;
        // 获取与该域相关的 Cookie 的数组
        cookies = request.getCookies();

        // 设置响应内容类型
        response.setContentType("text/html;charset=UTF-8");

        PrintWriter out = response.getWriter();
        String title = "Get Cookie Example";
        String docType = "<!DOCTYPE html>\n";
        out.println(docType +
                "<html>\n" +
                "<head><title>" + title + "</title></head>\n" +
                "<body bgcolor=\"#f0f0f0\">\n");
        if (cookies != null) {
            out.println("<h2>Cookie 名称和值</h2>");
            for (int i = 0; i < cookies.length; i++) {
                cookie = cookies[i];
                if((cookie.getName( )).compareTo("name") == 0 ){
                    cookie.setMaxAge(0);
                    response.addCookie(cookie);
                    out.print("已删除的 cookie:" +
                            cookie.getName( ) + "<br/>");
                }
                out.print("名称:" + cookie.getName() + ",");
                out.print("值:" + URLDecoder.decode(cookie.getValue(), "utf-8") + " <br/>");
            }
        } else {
            out.println(
                    "<h2 class=\"tutheader\">No Cookie founds</h2>");
        }
        out.println("</body>");
        out.println("</html>");
    }


    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doGet(request, response);
    }

}

请求:

<

输出:

Servlet Session 跟踪

HTTP 是一种"无状态"协议,这意味着每次客户端检索网页时,客户端打开一个单独的连接到 Web 服务器,服务器会自动不保留之前客户端请求的任何记录。

但是仍然有以下4种方式来维持 Web 客户端和 Web 服务器之间的 session 会话:

  • Cookies
  • 隐藏的表单字段
  • URL 重写
  • HttpSession 对象

Cookies

一个 Web 服务器可以分配一个唯一的 session 会话 ID 作为每个 Web 客户端的 cookie,对于客户端的后续请求可以使用接收到的 cookie 来识别。

缺点:

这可能不是一个有效的方法,因为很多浏览器不支持 cookie,所以我们建议不要使用这种方式来维持 session 会话。

隐藏的表单字段

一个 Web 服务器可以发送一个隐藏的 HTML 表单字段,以及一个唯一的 session 会话 ID,如下所示:

<input type="hidden" name="sessionid" value="12345">

该条目意味着,当表单被提交时,指定的名称和值会被自动包含在 GET 或 POST 数据中。每次当 Web 浏览器发送回请求时,session_id 值可以用于保持不同的 Web 浏览器的跟踪。

缺点:

这可能是一种保持 session 会话跟踪的有效方式,但是点击常规的超文本链接( 会话标识符被附加为 sessionid=12345,标识符可被 Web 服务器访问以识别客户端。

URL 重写是一种更好的维持 session 会话的方式,它在浏览器不支持 cookie 时能够很好地工作,但是它的缺点是会动态生成每个 URL 来为页面分配一个 session 会话 ID,即使是在很简单的静态 HTML 页面中也会如此。

HttpSession 对象

Servlet 还提供了 HttpSession 接口,该接口提供了一种跨多个页面请求或访问网站时识别用户以及存储有关用户信息的方式。

Servlet 容器使用这个接口来创建一个 HTTP 客户端和 HTTP 服务器之间的 session 会话。会话持续一个指定的时间段,跨多个连接或页面请求。

您会通过调用 HttpServletRequest 的公共方法 getSession() 来获取 HttpSession 对象,如下所示:

HttpSession session = request.getSession();

你需要在向客户端发送任何文档内容之前调用 request.getSession()。下面总结了 HttpSession 对象中可用的几个重要的方法:

序号 方法 & 描述
1 public Object getAttribute(String name) 该方法返回在该 session 会话中具有指定名称的对象,如果没有指定名称的对象,则返回 null。
2 public Enumeration getAttributeNames() 该方法返回 String 对象的枚举,String 对象包含所有绑定到该 session 会话的对象的名称。
3 public long getCreationTime() 该方法返回该 session 会话被创建的时间,自格林尼治标准时间 1970 年 1 月 1 日午夜算起,以毫秒为单位。
4 public String getId() 该方法返回一个包含分配给该 session 会话的唯一标识符的字符串。
5 public long getLastAccessedTime() 该方法返回客户端最后一次发送与该 session 会话相关的请求的时间自格林尼治标准时间 1970 年 1 月 1 日午夜算起,以毫秒为单位。
6 public int getMaxInactiveInterval() 该方法返回 Servlet 容器在客户端访问时保持 session 会话打开的最大时间间隔,以秒为单位。
7 public void invalidate() 该方法指示该 session 会话无效,并解除绑定到它上面的任何对象。
8 public boolean isNew() 如果客户端还不知道该 session 会话,或者如果客户选择不参入该 session 会话,则该方法返回 true。
9 public void removeAttribute(String name) 该方法将从该 session 会话移除指定名称的对象。
10 public void setAttribute(String name, Object value) 该方法使用指定的名称绑定一个对象到该 session 会话。
11 public void setMaxInactiveInterval(int interval) 该方法在 Servlet 容器指示该 session 会话无效之前,指定客户端请求之间的时间,以秒为单位。

删除 Session 会话数据

当您完成了一个用户的 session 会话数据,您有以下几种选择:

  • 移除一个特定的属性:您可以调用 public void removeAttribute(String name) 方法来删除与特定的键相关联的值。

  • 删除整个 session 会话:您可以调用 public void invalidate() 方法来丢弃整个 session 会话。

  • 设置 session 会话过期时间:您可以调用 public void setMaxInactiveInterval(int interval) 方法来单独设置 session 会话超时。

  • 注销用户:如果使用的是支持 servlet 2.4 的服务器,您可以调用 logout 来注销 Web 服务器的客户端,并把属于所有用户的所有 session 会话设置为无效。

  • web.xml 配置:如果您使用的是 Tomcat,除了上述方法,您还可以在 web.xml 文件中配置 session 会话超时,如下所示:

    <session-config>
      <session-timeout>15</session-timeout>
    </session-config>
    

上面实例中的超时时间是以分钟为单位,将覆盖 Tomcat 中默认的 30 分钟超时时间。

在一个 Servlet 中的 getMaxInactiveInterval() 方法会返回 session 会话的超时时间,以秒为单位。所以,如果在 web.xml 中配置 session 会话超时时间为 15 分钟,那么 getMaxInactiveInterval() 会返回 900。

实例:需要用新的tomcat,否则计数没出来。老的tomcat session存储在redis中了。

@WebServlet("/sessionTrack")
public class SessionTrackServlet extends HttpServlet {
    private static final long serialVersionUID = 1L;

    @Override
    public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
    {
        // 如果不存在 session 会话,则创建一个 session 对象
        HttpSession session = request.getSession(true);
        // 获取 session 创建时间
        Date createTime = new Date(session.getCreationTime());
        // 获取该网页的最后一次访问时间
        Date lastAccessTime = new Date(session.getLastAccessedTime());

        //设置日期输出的格式
        SimpleDateFormat df=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

        String title = "Servlet Session 实例 - 菜鸟教程";
        Integer visitCount = new Integer(0);
        String visitCountKey = new String("visitCount");
        String userIDKey = new String("userID");
        String userID = new String("Runoob");
        if(session.getAttribute(visitCountKey) == null) {
            session.setAttribute(visitCountKey, new Integer(0));
        }


        // 检查网页上是否有新的访问者
        if (session.isNew()){
            title = "Servlet Session 实例 - 菜鸟教程";
            session.setAttribute(userIDKey, userID);
        } else {
            visitCount = (Integer)session.getAttribute(visitCountKey);
            visitCount = visitCount + 1;
            userID = (String)session.getAttribute(userIDKey);
        }
        session.setAttribute(visitCountKey,  visitCount);

        // 设置响应内容类型
        response.setContentType("text/html;charset=UTF-8");
        PrintWriter out = response.getWriter();

        String docType = "<!DOCTYPE html>\n";
        out.println(docType +
                "<html>\n" +
                "<head><title>" + title + "</title></head>\n" +
                "<body bgcolor=\"#f0f0f0\">\n" +
                "<h1 align=\"center\">" + title + "</h1>\n" +
                "<h2 align=\"center\">Session 信息</h2>\n" +
                "<table border=\"1\" align=\"center\">\n" +
                "<tr bgcolor=\"#949494\">\n" +
                "  <th>Session 信息</th><th>值</th></tr>\n" +
                "<tr>\n" +
                "  <td>id</td>\n" +
                "  <td>" + session.getId() + "</td></tr>\n" +
                "<tr>\n" +
                "  <td>创建时间</td>\n" +
                "  <td>" +  df.format(createTime) +
                "  </td></tr>\n" +
                "<tr>\n" +
                "  <td>最后访问时间</td>\n" +
                "  <td>" + df.format(lastAccessTime) +
                "  </td></tr>\n" +
                "<tr>\n" +
                "  <td>用户 ID</td>\n" +
                "  <td>" + userID +
                "  </td></tr>\n" +
                "<tr>\n" +
                "  <td>访问统计:</td>\n" +
                "  <td>" + visitCount + "</td></tr>\n" +
                "</table>\n" +
                "</body></html>");
    }
}

Servlet 数据库访问

实例:

@WebServlet("/databaseAccess")
public class DatabaseAccessServlet extends HttpServlet {

    private static final String USER_NAME = "root";

    private static final String PASSWORD = "123456";

    // JDBC 驱动名及数据库 URL
    private static final String JDBC_DRIVER = "com.mysql.jdbc.Driver";
    private static final String DB_URL = "jdbc:mysql://localhost:3306/hello_mybatis";


    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        Connection connection = null;
        PreparedStatement pstmt = null;
        ResultSet rs = null;
        // 设置响应内容类型
        resp.setContentType("text/html;charset=UTF-8");
        PrintWriter out = resp.getWriter();
        String title = "Servlet Mysql Test";
        String docType = "<!DOCTYPE html>\n";
        out.println(docType +
                "<html>\n" +
                "<head><title>" + title + "</title></head>\n" +
                "<body bgcolor=\"#f0f0f0\">\n" +
                "<h1 align=\"center\">" + title + "</h1>\n");
        try {
            Class.forName(JDBC_DRIVER);
            connection = DriverManager.getConnection(DB_URL, USER_NAME, PASSWORD);
            pstmt = connection.prepareStatement("SELECT * FROM t_user WHERE 1=1 AND NAME = ? ");
            if (StringUtils.isNoneEmpty(req.getParameter("name"))){
                pstmt.setString(1, req.getParameter("name"));
            }
            rs = pstmt.executeQuery();
            while (rs.next()) {
                int id = rs.getInt("id");
                String name = rs.getString("name");
                String dept = rs.getString("dept");
                String phone = rs.getString("phone");
                // 输出数据
                out.println("ID: " + id);
                out.println(", 英雄: " + name);
                out.println(", 所属帝国: " + dept);
                out.println(", 联系方式: " + phone);
                out.println("<br />");
            }
            out.println("</body></html>");

        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            try {
                rs.close();
                pstmt.close();
                connection.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
       doGet(req, resp);
    }
}

请求:

输出:

Servlet 文件上传

Servlet 可以与 HTML form 标签一起使用,来允许用户上传文件到服务器。上传的文件可以是文本文件或图像文件或任何文档。

需要引入的 jar 文件:commons-fileupload-1.3.2、commons-io-2.5.jar。

创建一个文件上传表单

下面的 HTML 代码创建了一个文件上传表单。以下几点需要注意:

  • 表单 method 属性应该设置为 POST 方法,不能使用 GET 方法。
  • 表单 enctype 属性应该设置为 multipart/form-data.
  • 表单 action 属性应该设置为在后端服务器上处理文件上传的 Servlet 文件。下面的实例使用了 UploadServlet Servlet 来上传文件。
  • 上传单个文件,您应该使用单个带有属性 type="file" 的 <input .../> 标签。为了允许多个文件上传,请包含多个 name 属性值不同的 input 标签。输入标签具有不同的名称属性的值。浏览器会为每个 input 标签关联一个浏览按钮。

示例:

pom.xml

<!--文件上传依赖,Servlet3.0 已经内置了文件上传这一特性,开发者不再需要将 Commons FileUpload 组件导入到工程中去。-->
    <dependency>
      <groupId>com.asiainfo.crm.apache</groupId>
      <artifactId>commons-fileupload</artifactId>
      <version>1.1</version>
    </dependency>
    <dependency>
      <groupId>commons-io</groupId>
      <artifactId>commons-io</artifactId>
      <version>2.6</version>
    </dependency>

index.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
         pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <title>文件上传实例</title>
</head>
<body>
<h1>文件上传实例</h1>
<form method="post" action="/hellomybatis/uploadServlet" enctype="multipart/form-data">
    选择一个文件:
    <input type="file" name="uploadFile" />
    <br/><br/>
    <input type="submit" value="上传" />
</form>
</body>
</html>

message.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
         pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <title>文件上传结果</title>
</head>
<body>
<center>
    <h2>${message}</h2>
</center>
</body>
</html>

@WebServlet("/uploadServlet")
public class UploadServlet extends HttpServlet {
    private static final long serialVersionUID = 1L;

    // 上传文件存储目录
    private static final String UPLOAD_DIRECTORY = "upload";

    // 上传配置
    private static final int MEMORY_THRESHOLD   = 1024 * 1024 * 3;  // 3MB
    private static final int MAX_FILE_SIZE      = 1024 * 1024 * 40; // 40MB
    private static final int MAX_REQUEST_SIZE   = 1024 * 1024 * 50; // 50MB

    /**
     * 上传数据及保存文件
     */
    @Override
    protected void doPost(HttpServletRequest request,
                          HttpServletResponse response) throws ServletException, IOException {
        // 检测是否为多媒体上传
        if (!ServletFileUpload.isMultipartContent(request)) {
            // 如果不是则停止
            PrintWriter writer = response.getWriter();
            writer.println("Error: 表单必须包含 enctype=multipart/form-data");
            writer.flush();
            return;
        }

        // 配置上传参数
        DiskFileItemFactory factory = new DiskFileItemFactory();
        // 设置内存临界值 - 超过后将产生临时文件并存储于临时目录中
        factory.setSizeThreshold(MEMORY_THRESHOLD);
        // 设置临时存储目录
        factory.setRepository(new File(System.getProperty("java.io.tmpdir")));

        ServletFileUpload upload = new ServletFileUpload(factory);

        // 设置最大文件上传值
        //upload.setFileSizeMax(MAX_FILE_SIZE);

        // 设置最大请求值 (包含文件和表单数据)
        upload.setSizeMax(MAX_REQUEST_SIZE);

        // 中文处理
        upload.setHeaderEncoding("UTF-8");

        // 构造临时路径来存储上传的文件
        // 这个路径相对当前应用的目录
        String uploadPath = request.getServletContext().getRealPath("./") + File.separator + UPLOAD_DIRECTORY;


        // 如果目录不存在则创建
        File uploadDir = new File(uploadPath);
        if (!uploadDir.exists()) {
            uploadDir.mkdir();
        }

        try {
            // 解析请求的内容提取文件数据
            @SuppressWarnings("unchecked")
            List<FileItem> formItems = upload.parseRequest(request);

            if (formItems != null && formItems.size() > 0) {
                // 迭代表单数据
                for (FileItem item : formItems) {
                    // 处理不在表单中的字段
                    if (!item.isFormField()) {
                        String fileName = new File(item.getName()).getName();
                        String filePath = uploadPath + File.separator + fileName;
                        File storeFile = new File(filePath);
                        // 在控制台输出文件的上传路径
                        System.out.println(filePath);
                        // 保存文件到硬盘
                        item.write(storeFile);
                        request.setAttribute("message",
                                "文件上传成功!");
                    }
                }
            }
        } catch (Exception ex) {
            request.setAttribute("message",
                    "错误信息: " + ex.getMessage());
        }
        // 跳转到 message.jsp
        request.getServletContext().getRequestDispatcher("/message.jsp").forward(
                request, response);
    }
}

输出:

Servlet 处理日期

使用 Servlet 的最重要的优势之一是,可以使用核心 Java 中的大多数可用的方法。本章将讲解 Java 提供的 java.util 包中的 Date 类,这个类封装了当前的日期和时间。

   //初始化当前日期和时间的对象。
   Date today = new Date();
   //接受一个参数获得时间对象,该参数等于 1970 年 1 月 1 日午夜以来经过的毫秒数。
   Date today1 = new Date(System.currentTimeMillis());
SimpleDateFormat ft = new SimpleDateFormat ("yyyy.MM.dd  hh:mm:ss E a ");

常用方法:

序号 方法 & 描述
1 boolean after(Date date) 如果调用的 Date 对象中包含的日期在 date 指定的日期之后,则返回 true,否则返回 false。
2 boolean before(Date date) 如果调用的 Date 对象中包含的日期在 date 指定的日期之前,则返回 true,否则返回 false。
3 Object clone( ) 重复调用 Date 对象。
4 int compareTo(Date date) 把调用对象的值与 date 的值进行比较。如果两个值是相等的,则返回 0。如果调用对象在 date 之前,则返回一个负值。如果调用对象在 date 之后,则返回一个正值。
5 int compareTo(Object obj) 如果 obj 是 Date 类,则操作等同于 compareTo(Date)。否则,它会抛出一个 ClassCastException。
6 boolean equals(Object date) 如果调用的 Date 对象中包含的时间和日期与 date 指定的相同,则返回 true,否则返回 false。
7 long getTime( ) 返回 1970 年 1 月 1 日以来经过的毫秒数。
8 int hashCode( ) 为调用对象返回哈希代码。
9 void setTime(long time) 设置 time 指定的时间和日期,这表示从 1970 年 1 月 1 日午夜以来经过的时间(以毫秒为单位)。
10 String toString( ) 转换调用的 Date 对象为一个字符串,并返回结果。

使用 SimpleDateFormat 格式化日期

SimpleDateFormat 是一个以语言环境敏感的方式来格式化和解析日期的具体类。 SimpleDateFormat 允许您选择任何用户定义的日期时间格式化的模式。(非线程安全类)

简单的日期格式的格式代码

使用事件模式字符串来指定时间格式。在这种模式下,所有的 ASCII 字母被保留为模式字母,这些字母定义如下:

字符 描述 实例
G Era 指示器 AD
y 四位数表示的年 2001
M 一年中的月 July 或 07
d 一月中的第几天 10
h 带有 A.M./P.M. 的小时(1~12)12
H 一天中的第几小时(0~23) 22
m 一小时中的第几分 30
s 一分中的第几秒 55
S 毫秒 234
E 一周中的星期几 Tuesday
D 一年中的第几天 360
F 所在的周是这个月的第几周 2 (second Wed. in July)
w 一年中的第几周 40
W 一月中的第几周 1
a A.M./P.M. 标记 PM
k 一天中的第几小时(1~24) 24
K 带有 A.M./P.M. 的小时(0~11)10
z 时区 Eastern Standard Time
' Escape for text Delimiter
" 单引号 `

Servlet 网页重定向

当文档移动到新的位置,我们需要向客户端发送这个新位置时,我们需要用到网页重定向。当然,也可能是为了负载均衡,或者只是为了简单的随机,这些情况都有可能用到网页重定向。

重定向请求到另一个网页的最简单的方式是使用 response 对象的 sendRedirect() 方法。也可以通过把 setStatus() 和 setHeader() 方法一起使用来达到同样的效果 。

示例:

@WebServlet("/pageRedirect")
public class PageRedirectServlet extends HttpServlet {

    @Override
    public void doGet(HttpServletRequest request,
                      HttpServletResponse response)
            throws ServletException, IOException
    {
        // 设置响应内容类型
        response.setContentType("text/html;charset=UTF-8");

        // 要重定向的新位置
        //String site = new String("http://www.runoob.com");
         String site = "http://www.runoob.com";
        //方式一:
        //302
        //response.setStatus(HttpServletResponse.SC_MOVED_TEMPORARILY);
        //response.setHeader("Location", site);
        //方式二:
        response.sendRedirect(site);
    }
}

Servlet 点击计数器

网页点击计数器

很多时候,您可能有兴趣知道网站的某个特定页面上的总点击量。使用 Servlet 来计算这些点击量是非常简单的,因为一个 Servlet 的生命周期是由它运行所在的容器控制的。

以下是实现一个简单的基于 Servlet 生命周期的网页点击计数器需要采取的步骤:

  • 在 init() 方法中初始化一个全局变量。
  • 每次调用 doGet() 或 doPost() 方法时,都增加全局变量。
  • 如果需要,您可以使用一个数据库表来存储全局变量的值在 destroy() 中。在下次初始化 Servlet 时,该值可在 init() 方法内被读取。这一步是可选的。
  • 如果您只想对一个 session 会话计数一次页面点击,那么请使用 isNew() 方法来检查该 session 会话是否已点击过相同页面。这一步是可选的。
  • 您可以通过显示全局计数器的值,来在网站上展示页面的总点击量。这一步是可选的。

在这里,我们假设 Web 容器将无法重新启动。如果是重新启动或 Servlet 被销毁,计数器将被重置。

示例:

@WebServlet("/pageHit")
public class PageHitServlet extends HttpServlet {

    private int hitCount;

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.setContentType("text/html;charset=UTF-8");
        // 增加 hitCount
        hitCount++;
        PrintWriter out = resp.getWriter();
        String title = "总点击量";
        String docType = "<!DOCTYPE html> \n";
        out.println(docType +
                "<html>\n" +
                "<head><title>" + title + "</title></head>\n" +
                "<body bgcolor=\"#f0f0f0\">\n" +
                "<h1 align=\"center\">" + title + "</h1>\n" +
                "<h2 align=\"center\">" + hitCount + "</h2>\n" +
                "</body></html>");
    }

    @Override
    protected void doPut(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doGet(req,resp);
    }

    @Override
    public void init() throws ServletException {
        //初始化
        hitCount = 0;
    }

    @Override
    public void destroy() {
        //可以把 hitCount 的值写入到数据库
    }
}

请求:

输出:

网站点击计数器

很多时候,您可能有兴趣知道整个网站的总点击量。在 Servlet 中,这也是非常简单的,我们可以使用过滤器做到这一点。

以下是实现一个简单的基于过滤器生命周期的网站点击计数器需要采取的步骤:

  • 在过滤器的 init() 方法中初始化一个全局变量。
  • 每次调用 doFilter 方法时,都增加全局变量。
  • 如果需要,您可以在过滤器的 destroy() 中使用一个数据库表来存储全局变量的值。在下次初始化过滤器时,该值可在 init() 方法内被读取, 这一步是可选的。

web.xml

<filter>
  <filter-name>SiteHitCountFilter</filter-name>
  <filter-class>com.filter.SiteHitCountFilter</filter-class>
</filter>
<filter-mapping>
  <filter-name>SiteHitCountFilter</filter-name>
  <url-pattern>/*</url-pattern>
</filter-mapping>

public class SiteHitCountFilter implements Filter {

    private int hitCount;

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        //初始化
        hitCount = 0;
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        // 把计数器的值增加 1
        hitCount++;

        // 输出计数器
        System.out.println("网站访问统计:"+ hitCount );

        // 把请求传回到过滤器链
        filterChain.doFilter(servletRequest,servletResponse);
    }

    @Override
    public void destroy() {
        //可以把 hitCount 的值写入到数据库
    }
}

输出:

网站访问统计:8
网站访问统计:9
网站访问统计:10
网站访问统计:11

Servlet 发送电子邮件

//待实践

Servlet 国际化

三个重要术语:

  • 国际化(i18n):这意味着一个网站提供了不同版本的翻译成访问者的语言或国籍的内容。
  • 本地化(l10n):这意味着向网站添加资源,以使其适应特定的地理或文化区域,例如网站翻译成印地文(Hindi)。
  • 区域设置(locale):这是一个特殊的文化或地理区域。它通常指语言符号后跟一个下划线和一个国家符号。例如 "en_US" 表示针对 US 的英语区域设置。

Servlet 可以根据请求者的区域设置拾取相应版本的网站,并根据当地的语言、文化和需求提供相应的网站版本。以下是 request 对象中返回 Locale 对象的方法。

java.util.Locale request.getLocale()

检测区域设置

下面列出了重要的区域设置方法,您可以使用它们来检测请求者的地理位置、语言和区域设置。下面所有的方法都显示了请求者浏览器中设置的国家名称和语言名称。

序号 方法 & 描述
1 String getCountry() 该方法以 2 个大写字母形式的 ISO 3166 格式返回该区域设置的国家/地区代码。
2 String getDisplayCountry() 该方法返回适合向用户显示的区域设置的国家的名称。
3 String getLanguage() 该方法以小写字母形式的 ISO 639 格式返回该区域设置的语言代码。
4 String getDisplayLanguage() 该方法返回适合向用户显示的区域设置的语言的名称。
5 String getISO3Country() 该方法返回该区域设置的国家的三个字母缩写。
6 String getISO3Language() 该方法返回该区域设置的语言的三个字母的缩写。

示例显示某个请求的语言和相关的国家 :

@WebServlet("/locale")
public class GetLocaleServlet extends HttpServlet {

    @Override
    public void doGet(HttpServletRequest request,
                      HttpServletResponse response)
            throws ServletException, IOException
    {
        // 获取客户端的区域设置
        Locale locale = request.getLocale();
        String language = locale.getLanguage();
        String country = locale.getCountry();

        // 设置响应内容类型
        response.setContentType("text/html;charset=UTF-8");
        PrintWriter out = response.getWriter();

        String title = "检测区域设置";
        String docType = "<!DOCTYPE html> \n";
        out.println(docType +
                "<html>\n" +
                "<head><title>" + title + "</title></head>\n" +
                "<body bgcolor=\"#f0f0f0\">\n" +
                "<h1 align=\"center\">" + language + "</h1>\n" +
                "<h2 align=\"center\">" + country + "</h2>\n" +
                "</body></html>");
    }
}

语言设置

Servlet 可以输出以西欧语言(如英语、西班牙语、德语、法语、意大利语、荷兰语等)编写的页面。在这里,为了能正确显示所有的字符,设置 Content-Language 头是非常重要的。

第二点是使用 HTML 实体显示所有的特殊字符,例如,"ñ" 表示 "ñ","¡" 表示 "¡"

 // 设置响应内容类型
    response.setContentType("text/html;charset=UTF-8");
    PrintWriter out = response.getWriter();
    // 设置西班牙语言代码
    response.setHeader("Content-Language", "es");

特定于区域设置的日期

您可以使用 java.text.DateFormat 类及其静态方法 getDateTimeInstance() 来格式化特定于区域设置的日期和时间。

 // 设置响应内容类型
    response.setContentType("text/html;charset=UTF-8");
    PrintWriter out = response.getWriter();
    // 获取客户端的区域设置
    Locale locale = request.getLocale( );
    String date = DateFormat.getDateTimeInstance(
                                  DateFormat.FULL,
                                  DateFormat.SHORT,
                                  locale).format(new Date( ));

特定于区域设置的货币

您可以使用 java.text.NumberFormat 类及其静态方法 getCurrencyInstance() 来格式化数字(比如 long 类型或 double 类型)为特定于区域设置的货币。

  // 设置响应内容类型
    response.setContentType("text/html;charset=UTF-8");
    PrintWriter out = response.getWriter();
    // 获取客户端的区域设置
    Locale locale = request.getLocale( );
    NumberFormat nft = NumberFormat.getCurrencyInstance(locale);
    String formattedCurr = nft.format(1000000);

特定于区域设置的百分比

您可以使用 java.text.NumberFormat 类及其静态方法 getPercentInstance() 来格式化特定于区域设置的百分比。

 // 设置响应内容类型
    response.setContentType("text/html;charset=UTF-8");
    PrintWriter out = response.getWriter();
    // 获取客户端的区域设置
    Locale locale = request.getLocale( );
    NumberFormat nft = NumberFormat.getPercentInstance(locale);
    String formattedPerc = nft.format(0.51);

疑问

Q:Servlet 在 Web 服务器的地址空间内执行。这样它就没有必要再创建一个单独的进程来处理每个客户端请求。这句话怎么理解?

Q:为什么工作中遇到的JSP页面一般不会写java代码,而是html与js或者说jquery整合?

A:让Servlet只负责业务逻辑部分,而不会生成HTML代码;同时JSP中也不会充斥着大量的业务代码,这样能大提高了代码的可读性和可维护性。这是因为我们采用MVC模式,这样一来避免了Servlet通过字符串拼接的方式生成动态HTML内容,这样就容易导致代码维护困难、可读性差的缺点,也避免了JSP在HTML中混入大量、复杂的业务逻辑。

Q:什么是函数式编程?

Q:servlet怎么请求jsp页面?

一种:在webapp下的jsp文件可以通过如这个链接直接访问,为什么把jsp文件放在创建的文件夹下就不行了呢?

扩展阅读

单点登录技术

单点登录的目的是为了能在分布式系统或者集群中对用户的请求校验登录状态。

一般我们用到的是共享session或者唯一token来实现。共享session一般是给网页或者说http请求用,而token一般是给安卓、IOS系统等前端用。

在这里我们说下共享session。一般我们会用单点登录系统或者是在tomcat上配置redis,当用户请求页面的时候,会根据用户在cookie里默认的sessionId到tomcat上配置redis配置的redis上校验是否存在,存在则不同的系统都共有这个session会话。不存在则跳到登录页面登录。理论上单点登录系统也是这样,一般是通过redis来缓存session会话,实现单点登录。

servlet和jsp的区别

ervlet和sp的区别

1、Servlet在Java代码中可以通过HttpServletResponse对象动态输出HTML内容。

2、JSP是在静态HTML内容中嵌入Java代码,然后Java代码在被动态执行后生成HTML内容。

servlet和jsp的各自的特点

1、Servlet虽然能够很好地组织业务逻辑代码,但是在Java源文件中,因为是通过字符串拼接的方式生成动态HTML内容,这样就容易导致代码维护困难、可读性差。

2、JSP虽然规避了Servlet在生成HTML内容方面的劣势,但是在HTML中混入大量、复杂的业务逻辑。

通过MVC双剑合璧

JSP和Servlet都有自身的适用环境,那么有没有什么办法能够让它们发挥各自的优势呢?答案是肯有的,MVC模式就能够完美解决这一问题。

MVC模式,是Model-View-Controller的简称,是软件工程中的一种软件架构模式,分为三个基本部分,分别是:模型(Model)、视图(View)和控制器(Controller):

Controller——负责转发请求,对请求进行处理

View——负责界面显示

Model——业务功能编写(例如算法实现)、数据库设计以及数据存取操作实现

在JSP/Servlet开发的软件系统中,这三个部分的描述如下所示:

1、Web浏览器发送HTTP请求到服务端,然后被Controller(Servlet)获取并进行处理(例如参数解析、请求转发)

2、Controller(Servlet)调用核心业务逻辑——Model部分,获得结果

3、Controller(Servlet)将逻辑处理结果交给View(JSP),动态输出HTML内容

4、动态生成的HTML内容返回到浏览器显示

MVC模式在Web开发中有很大的优势,它完美规避了JSP与Servlet各自的缺点,让Servlet只负责业务逻辑部分,而不会生成HTML代码;同时JSP中也不会充斥着大量的业务代码,这样能大提高了代码的可读性和可维护性。

05-04 14:10