Java Web Reinforcement 02 servlet

Java Web Reinforcement 02 servlet

servlet究竟是什么, 当然首先是一个对象, 来自于javax.servlet (与HTTP相关的在 javax.servlet.http 中), 不过servlet的实现类并不是由我们编写的, 而是由容器的提供商进行编写. 在IDEA里如果创建web.xml并且使用tomcat, 在左侧的外部

servlet究竟是什么, 当然首先是一个对象, 来自于javax.servlet (与HTTP相关的在 javax.servlet.http 中), 不过servlet的实现类并不是由我们编写的, 而是由容器的提供商进行编写. 在IDEA里如果创建web.xml并且使用tomcat, 在左侧的外部lib中可以看到Tomcat 9 提供的包, 其中有jsp-api.jar 以及 servlet-api.jar, 其中就有javax.servlet包. 简单的说, Java的Web规范已经规定好了Servlet的所有API, 然后容器厂商来实现容器的时候, 同时实现Servlet类, 使用这个容器的应用程序员, 根据其提供的Servlet来编写具体应用, 这样写出来的类才可被容器正常使用. Servlet的本质, 就是一个向用户提供服务, 或者说实际处理请求和提供响应内容的单元. 自然也就是Java Web的核心, 这一次就来仔细看看Servlet.
  1. Servlet的生命周期
  2. HttpServlet对象
  3. HttpServletRequest
  4. HttpServletResponse
  5. 请求分派与重定向

Servlet的生命周期

从之前已经可以知道, 我们启动一个Web服务, 实际上启动的是容器, 容器再去使用其中的Servlet. 容器如何使用呢, 因为是Java 环境, 本质上是创建对象, 使用对象, 销毁对象. Servlet也是如此, 有如下生命周期:
  1. Web容器加载类文件
  2. 实例化Servlet
  3. 调用init()函数
  4. 调用service()方法
  5. 调用destroy()方法
  6. 销毁(容器不再引用这个servlet)
对于Tomcat来说, 每个请求都会在一个新的线程内执行上述实例化到销毁的全过程, 注意一定是对应请求, 而不是用户, 比如用户连续刷新两次, 是两个请求, 会分别处理, 而不是一个请求. 对于我们来说, 一般会覆盖service()方法中调用的具体处理某种请求的方法, 可能在init()和destroy中做一些必要的工作, 一般不覆盖service()方法.

Servlet接口

我们现在使用的是HTTPServlet, 顾名思义, 就是提供HTTP服务的Servlet. 通过IDEA的diagram类图可以看到这个类的体系如下:
  1. HttpServlet 继承 GenericServlet, 这两个都是抽象类
  2. GenericServlet 实现了 Servlet, ServletConfig, Serializable 三个接口
  3. 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();
    }
    
  4. ServletConfig接口如下:
    public interface ServletConfig {
        String getServletName();
    
        ServletContext getServletContext();
    
        String getInitParameter(String var1);
    
        Enumeration<String> getInitParameterNames();
    }
    
通过上边的体系可以看出, Servlet接口提供了关键的三个生命周期函数, 以及获取ServletConfig对象(每个Servlet都有一个对应的ServletConfig对象,记录了web.xml中针对该servlet的配置信息)的方法, 还有一个获取getServletInfo()方法, 反正一会都打算来尝试一遍. GenericServlet是Servlet接口的扩展, 是一个抽象类, 顾名思义: 通用Servlet, 是一个让Servlet之所以成为Servlet的抽象, 提供了很多方法, 其中的构造器可以看到是一个无参构造器. 然后有几个方法是在ServletConfig的方法基础上套壳, 用来获取配置信息和ServletContext(Web容器上下文). 不过关键的生命周期方法都是留空或者未实现的. HttpServlet是通用Servlet在HTTP处理方面的扩展, 也是一个抽象类, 针对HTTP特化. 其中定义了一批私有的静态变量, 用来标识各种HTTP方法, 也是空参构造器. 对于生命周期函数, 变成了service(HttpServletRequest req, HttpServletResponse resp), 其中的参数是HttpServletRequest和HttpServletResponse类, 一会自然也要去看这两个类. 除此之外还针对每种请求提供了 protect doXXX(HttpServletRequest req, HttpServletResponse resp)方法, 用于继承. 而service()方法其实变成了一个分发器, 通过获取请求的种类交给对应的函数进行处理. 这个类里基本都实现了doXXX默认的处理, 基本上就是判断协议版本然后返回支持或者不支持. 而我们具体的业务Servlet, 就继承HttpServlet, 然后覆盖所需要的doXXX方法即可, 至于service()方法一般不会覆盖. 下边就把这些方法都来试验一下吧:
package com.example.web;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.time.LocalDateTime;

public class TestServlet extends HttpServlet {


    //Servlet接口中的除了service()的生命周期方法
    @Override
    public void destroy() {
        System.out.println("Servlet即将被销毁");
        super.destroy();
    }

    @Override
    public void init() throws ServletException {
        System.out.println("Servlet正在init()中");
        super.init();
    }


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

        PrintWriter out = resp.getWriter();

        out.println(LocalDateTime.now().toString());

        //Servlet接口中获取getServletConfig()方法
        ServletConfig servletConfig = getServletConfig();
        //获取了servletConfig对象, 这个对象稍后还会来看, 是配置对象
        System.out.println(servletConfig.getServletName());

        out.println(servletConfig.getServletName());

        //Servlet接口中获取getServletInfo()方法, 其实这个方法由GenericServlet实现
        System.out.println(getServletInfo());

        //GenericServlet中的方法
        //记录日志, 不配置日志插件的话默认会显示在控制台里
        log("gugugu");
        //记录日志并抛出异常, 这里注释掉了
//        log("saner", new RuntimeException());

        //可以看到方法不算多, Generic中的方法主要来自于接口, 新方法只有log, 也是调用ServletContext的log方法
        //这也说明Web容器还需要日志功能.

    }
}

HttpServletRequest

之前已经知道, 容器会创建HttpServletRequest和HttpServletResponse两个对象, 然后交给各个Servlet处理, 处理完之后将请求返回去. 可见这两个对象贯穿Web应用的全部, 而且是Servlet的处理对象, 所以必须知道这两个对象才可以. 先来看HttpServletRequest对象. HttpServletRequest在容器内部, 是一个HTTP请求的封装, 里边携带了按照HTTP协议发过来的请求的全部信息. 在实际中传递给service()方法的两个实现类, 是由容器将对应的请求和响应包装好之后实现的, 这点要理解. 说是HttpServletRequest对象, 其实HttpServletRequest是一个接口, 这个接口继承自ServletRequest接口: ServletRequest接口中的方法有很多, 其中有几个比较重要, HttpServletRequest则是带有很多HTTP特化的内容, 非常重要. 下边就来看一下其中的方法:
    ServletRequest中的方法
  1. void setAttribute(String var1, Object var2);, 非常重要的方法, 给对象设置一个属性(键)和对应的值, 一般可以用来传递简单的模型数据
  2. Object getAttribute(String var1);, 非常重要的方法, 和上一个方法搭配起来使用, 其中获取指定的数据
  3. void removeAttribute(String var1);, 删除指定的键和值, 与前两个方法属于同一个体系
  4. Enumeration<String> getAttributeNames();, 获取被设置上的键名的集合, 与前三个方法属于同一个体系
  5. int getContentLength();, 获取内容的长度
  6. String getContentType();, 获取内容属于什么性质的内容
  7. ServletInputStream getInputStream() throws IOException;, 获取一个字节流, 用于从请求中读取二进制
  8. String getParameter(String var1);, 获取请求体中键对应的值, 注意和前四个方法不同, 是获取请求体中的值, 如果是GET请求, 也可以获取到参数对应的值
  9. Enumeration<String> getParameterNames();, 获取请求体中所有键名,通常用于获取POST的内容, 如果POST也附带请求行参数, 也一并可以获取
  10. String[] getParameterValues(String var1);, 如果请求体中的键对应一系列值, 这个就是将其获取成一个字符串数组.
  11. Map<String, String[]> getParameterMap();, 将请求体中的键值封装成一个Map对象返回
  12. String getProtocol();, 获取请求的协议, 一般是HTTP/1.1
  13. String getScheme();, 这个打印出http, 应该也是协议之类
  14. String getServerName();, 看名字是获取服务端名称
  15. int getServerPort();, 获取服务器所使用的端口, 用tomcat默认就是8080
  16. BufferedReader getReader() throws IOException;, 获取Reader, 用于从请求中获取字符
  17. String getRemoteAddr();, 获取远程地址
  18. String getRemoteHost();, 获取远程访问主机
  19. Locale getLocale();, 获取了当前的地区代码, 我测试出是zh_CN
  20. Enumeration<Locale> getLocales();, 应该也是和国际化有关
  21. boolean isSecure();, 是否安全, 到了HttpServlet中应该是指是否是HTTPS
  22. RequestDispatcher getRequestDispatcher(String var1);, 非常重要的方法, 通过传入字符串获取转发目标, 可以进行转发
  23. int getRemotePort();, 获取用户的端口
  24. String getLocalName();, 主机名称
  25. String getLocalAddr();, 本机地址
  26. int getLocalPort();, 本机端口
  27. ServletContext getServletContext();, 获取Web容器上下文, 通过请求也可以获取Web容器上下文
  28. AsyncContext startAsync() throws IllegalStateException;, 这个好像是异步? 现在不知道
  29. AsyncContext startAsync(ServletRequest var1, ServletResponse var2) throws IllegalStateException;, 也是异步相关吧, 好像是异步处理?
  30. boolean isAsyncStarted();, 也是异步相关吧, 表示异步是否已经开始?
  31. boolean isAsyncSupported();, 这个大概是指是否支持异步?
  32. AsyncContext getAsyncContext();, 异步上下文?
  33. DispatcherType getDispatcherType();, 获取转发器的类型? 这个测试出来是REQUEST, 应该表示转发的是请求?
  34. HttpServletRequest中的方法
  35. String getAuthType(), 获取认证类型, 我测试都是Null
  36. Cookie[] getCookies();, 关键方法, 获取Cookie集合
  37. long getDateHeader(String var1);, 专门获取头部中的时间信息, 然后解析成长整型值, 例如 Date: Thu, 24 Oct 2019 05:14:34 GMT 这样的就可以被这个方法解析
  38. String getHeader(String var1);, 重要, 根据请求头中的键获取对应的值
  39. Enumeration<String> getHeaders(String var1);, 重要, 根据请求头中的键获取对应的多个值
  40. Enumeration<String> getHeaderNames();, 重要, 获取头部信息中的所有键名
  41. int getIntHeader(String var1);, 这个是可以直接将头部中某个可以解析成int的值获取出来
  42. String getMethod();, 获取请求方法,比如是GET还是POST
  43. String getPathInfo();, 实际测试下来, 显示是null
  44. String getPathTranslated(), 实际测试下来, 显示是null
  45. String getContextPath();, 实际测试下来, 显示是空字符串
  46. String getQueryString();, 这个是获取请求后边附带的参数字符串, 如果想自己解析的话也很重要
  47. String getRemoteUser();, 这个一会测试一下
  48. boolean isUserInRole(String var1);, 这里开始和用户相关了, 似乎是HTTPServlet附带的认证功能
  49. Principal getUserPrincipal();, Principal对象, 也和认证相关
  50. String getRequestedSessionId();, 获取SessionID,重要
  51. String getRequestURI();, 获取URI, 是相对地址
  52. StringBuffer getRequestURL();, 获取URL, 是绝对地址
  53. String getServletPath();, 测试下来应该是当前servlet解析的地址, 也就是访问地址
  54. HttpSession getSession(boolean var1);, 获取Session对象, 重要
  55. HttpSession getSession(); , 获取Session对象, 重要
  56. String changeSessionId();, 这个命令测试下来是更改当前的SessionID, 返回新的SessionID
  57. boolean isRequestedSessionIdValid();看名称大概是sessionid是否还有效, 实际测试显示的是true
  58. boolean isRequestedSessionIdFromCookie();看意思是判断session是不是来自cookie, 我用postman和Chrome测试是true, 看来默认策略就是通过Cookie传递SessionID
  59. boolean isRequestedSessionIdFromURL();, 这个测试之后是false, 这几个SessionId相关的估计以后还是要学的
  60. boolean authenticate(HttpServletResponse var1) throws IOException, ServletException;, 这个现在不知道
  61. void login(String var1, String var2) throws ServletException;, 这个现在不知道
  62. void logout() throws ServletException;, 这个现在不知道, 但肯定还是和认证相关
下边就逐个试验一下吧, 除了异步的那几个以及用户验证的,基本都测试到了:
package com.example.web;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.Map;

public class TestServlet extends HttpServlet {


    //Servlet接口中的除了service()的生命周期方法
    @Override
    public void destroy() {
        System.out.println("Servlet即将被销毁");
        super.destroy();
    }

    @Override
    public void init() throws ServletException {
        System.out.println("Servlet正在init()中");
        super.init();
    }

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

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

        resp.setCharacterEncoding("UTF-8");

        resp.setHeader("content-type", "text/html;charset=UTF-8");

        PrintWriter writer = resp.getWriter();

        String target = "saner";

        req.setAttribute("penguin", target);
        req.setAttribute("penguin2", target + "2");

        String result = (String) req.getAttribute("penguin");
        String result2 = (String) req.getAttribute("penguin2");

        writer.println("penguin键的值是: " + result + "<br>");
        writer.println("penguin2键的值是: " + result2 + "<br>");

        Enumeration<String> names = req.getAttributeNames();
        while (names.hasMoreElements()) {
            writer.println(names.nextElement());
        }

        req.removeAttribute("penguin2");

        writer.println("<br>");

        writer.println("删除一个键之后的键名" + "<br>");

        names = req.getAttributeNames();
        while (names.hasMoreElements()) {
            writer.println(names.nextElement());
        }

        writer.println("内容的长度是: " + req.getContentLength() + "<br>");
        writer.println("内容的长度是(long): " + req.getContentLengthLong() + "<br>");

        result = req.getParameter("name");

        writer.write("GET请求传参的name=" + result + "<br>");

        names = req.getParameterNames();
        while (names.hasMoreElements()) {
            writer.println(names.nextElement());
        }

        Map<String, String[]> map = req.getParameterMap();
        writer.println(map.keySet() +"<br>");

        writer.println("请求的类型是:" + req.getProtocol()+"<br>");
        writer.println("请求的scheme是:" + req.getScheme()+"<br>");
        writer.println("请求的ServerName是:" + req.getServerName()+"<br>");
        writer.println("请求的Serverport是:" + req.getServerPort()+"<br>");


        BufferedReader in = req.getReader();

        in.lines().forEach(System.out::println);

        writer.println("读出的一行是: " + in.readLine() + "<br>");

        writer.println("请求的RemoteAddr(客户端地址)是:" + req.getRemoteAddr()+"<br>");
        writer.println("请求的RemoteHost(客户端地址)是:" + req.getRemoteHost()+"<br>");
        writer.println("请求的Locale(客户端地址)是:" + req.getLocale()+"<br>");
        writer.println("请求isSecure:" + req.isSecure()+"<br>");
        writer.println("请求的RemotePort:" + req.getRemotePort()+"<br>");
        writer.println("请求的LocalName:" + req.getLocalName()+"<br>");
        writer.println("请求的LocalAddr:" + req.getLocalAddr()+"<br>");
        writer.println("请求的转发器类型:" + req.getDispatcherType()+"<br>");
        writer.println("请求的AuthType:" + req.getAuthType()+"<br>");
        writer.println("请求的Cookie[]:" + Arrays.toString(req.getCookies()) +"<br>");
        writer.println("请求的getDateHeader:" + req.getDateHeader("Date")+"<br>");
        writer.println("请求的Header Date:" + req.getHeader("Date")+"<br>");
        writer.println("请求的Header Postman-Token:" + req.getHeader("Postman-Token")+"<br>");

        Enumeration<String> headers = req.getHeaders("My");
        writer.println("My 头信息对应的值是:");
        while (headers.hasMoreElements()) {
            writer.println(headers.nextElement() + " ");
        }
        writer.println("<br>");

        headers = req.getHeaderNames();
        writer.println("所有头部键名是:");
        while (headers.hasMoreElements()) {
            writer.println(headers.nextElement() + " ");
        }
        writer.println("<br>");
        writer.println("请求的Header Time 解析成int:" + req.getIntHeader("Time")+"<br>");
        writer.println("请求方法是:" + req.getMethod()+"<br>");
        writer.println("请求 PathInfo:" + req.getPathInfo()+"<br>");
        writer.println("请求 PathTranslated:" + req.getPathTranslated()+"<br>");
        writer.println("请求 ContextPath:" + req.getContextPath()+"<br>");
        writer.println("请求 QueryString:" + req.getQueryString()+"<br>");
        writer.println("请求 RemoteUser:" + req.getRemoteUser()+"<br>");
        writer.println("请求 RequestedSessionId:" + req.getRequestedSessionId()+"<br>");
        writer.println("请求 RequestURI:" + req.getRequestURI()+"<br>");
        writer.println("请求 RequestURL:" + req.getRequestURL()+"<br>");
        writer.println("请求 ServletPath:" + req.getServletPath()+"<br>");
        writer.println("请求 Session:" + req.getSession()+"<br>");
        writer.println("请求 changeSessionId:" + req.changeSessionId()+"<br>");
        writer.println("请求 isRequestedSessionIdValid:" + req.isRequestedSessionIdValid()+"<br>");
        writer.println("请求 isRequestedSessionIdFromCookie:" + req.isRequestedSessionIdFromCookie()+"<br>");
        writer.println("请求 isRequestedSessionIdFromURL:" + req.isRequestedSessionIdFromURL()+"<br>");

    }
}
测试下来核心的方法主要是四大内容: 获取请求的基础信息(端口, URL, 方法等), 获取请求头信息, 获取请求行和请求体附带的参数, 操作Attributes. 此外还有一个转发器肯定也会用到.

HttpServletResponse

与HttpServletRequest类似, HttpServletResponse也是一个接口, 然后继承自ServletResponse接口. 这个对象相比响应对象还要重要, 因为承载着向用户返回信息的重任. 从请求中获取信息之后, 剩下的大部分任务都是如何组装这个对象的内容. 由于请求对象的内容很重要, 一般请求对象会先对其进行设置一些响应的头部信息, 比如setContentType()以及其他一些内容, 然后就是获取PrintWriter对象对其中近些 ServletResponse接口是基础的服务响应接口, 其中的方法有:
  1. String getCharacterEncoding();, 获取编码方式, 不设置的话, 获取出来是ISO-8859-1
  2. String getContentType();, 获取MIME内容, 没有设置的话是null
  3. ServletOutputStream getOutputStream() throws IOException;, 获取输出的字节流, 用于写入二进制内容
  4. PrintWriter getWriter() throws IOException;, 这个获取字符流, 用于写入页面内容
  5. void setCharacterEncoding(String var1);, 设置字符编码, 设置的是PrintWriter写字符时候使用的编码, 需要在获取PrintWriter之前调用
  6. void setContentLength(int var1);, 设置内容长度
  7. void setContentLengthLong(long var1);, long类型的设置内容长度
  8. void setContentType(String var1);, 设置ContentType, 和上边的get是一对
  9. void setBufferSize(int var1);, 应该是设置写入时候的缓冲区
  10. int getBufferSize();, 应该是获取写入时候的缓冲区长度, 我试验了一下默认是8192
  11. void flushBuffer() throws IOException;, 这个是刷新缓冲区, 有可能在Writer等方法中已经包含了这个.
  12. void resetBuffer();, 重置缓冲区
  13. boolean isCommitted();, 是否已经提交, 这个自己猜想估计是如果已经提交了, 就无法再更改了. 我测试下来是false
  14. void reset();, 这个是重置输出流, 即清空.
  15. void setLocale(Locale var1);, 设置区域.
  16. Locale getLocale();, 获取区域, 这个在没有先设置的情况下,我测试默认获取了zh_CN.
这其中比较重要的方法就是CharacterEncoding和ContentType相关的方法, 对于HTTP来说, 要将CharacterEncoding设置为"UTF-8, ContentType设置成为正确的MIME类型. 在完成这两个工作之后, 再调用输出流来写入. 在我测试的时候, 如果仅仅只设置 resp.setCharacterEncoding("UTF-8");, 但不去设置resp.setContentType("****"), 则MIME里不会出现charset=utf-8, 如果之后进行了设置, 则ContentType中会追加charset=utf-8 所以一般推荐的顺序, 如果是输出字符流, 就先设置CharacterEncoding, 再设置ContentType, 之后再获取输出流对象进行打印. 关于MIME的设置可以看这里. 然后来看HttpServletResponse中的方法:
  1. void addCookie(Cookie var1);, 添加一个Cookie对象
  2. boolean containsHeader(String var1);, 是否包含指定的头部信息
  3. String encodeURL(String var1);, 重写URL, 使用附带sessionid的参数
  4. String encodeRedirectURL(String var1);, 重写URL, 使用附带sessionid的参数, 然后重定向
  5. void sendError(int var1, String var2) throws IOException;, 待测试
  6. void sendError(int var1) throws IOException;, 待测试
  7. void sendRedirect(String var1) throws IOException;, 重定向方法
  8. void setDateHeader(String var1, long var2);, 设置日期的头部, 属于快捷方法
  9. void addDateHeader(String var1, long var2);, 上一个方法的add版本, 注意和set系列方法的区别
  10. void setHeader(String var1, String var2);, 通用的设置头部键值的方法
  11. void addHeader(String var1, String var2);, 上一个方法的add版本
  12. void setIntHeader(String var1, int var2);, 设置int类型的header的快捷方法
  13. void addIntHeader(String var1, int var2);, 上一个方法的add版本
  14. void setStatus(int var1);, 设置状态码
  15. int getStatus();, 获取状态码
  16. String getHeader(String var1);, 获取某一个头部信息
  17. Collection<String> getHeaders(String var1);, 获取一个包含多个值的头部信息
  18. Collection<String> getHeaderNames();, 获取头部信息的键名集合
HttpServletResponse接口中, 除了上边这些方法, 还通过实例域把所有的HTTP状态码都定义了. 这个HTTP特化的servlet主要添加的功能是操作cookie和头部信息以及状态码. 这里想想还挺有意思, 很重要的contentType之类的设置, 竟然还不是在HTTPServlet中的, 我只能想到一个, 就是在HTTP大为流行之前, EJB的远程调用, 可能就已经使用到了ContentType之类功能, 而HTTP是在其后才大行其道的. 写HTML文件其实我们一般不会直接用HttpServletResponse的PrintWriter, 都会交给JSP. 但是下载文件一般还是用Servlet直接输出的. 输出文件的套路如下:
@Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

        File file = new File("D:\\test3.txt");
        //重置响应内容
        resp.reset();
        //红色部分是文件名, 可以自己定义
        resp.addHeader("Content-Disposition", "attachment;filename=" + "download");
        //设置响应体的字节长度
        resp.addHeader("Content-Length", String.valueOf(file.length()));
        //这里下载了txt文件, 所以要设置如下的MIME, 根据具体文件要设置成不同的MIME类型
        resp.setContentType("text/plain");

        //下边就是从一个输入流读取然后写入到一个输出流的套路
        BufferedOutputStream out = new BufferedOutputStream(resp.getOutputStream());

        InputStream fis = new BufferedInputStream(new FileInputStream(file));

        int read = 0;
        byte[] bytes = new byte[1024];

        while ((read = fis.read(bytes)) != -1) {
            out.write(bytes, 0, read);
        }

        //刷新缓冲区然后关闭流
        out.flush();
        out.close();
    }
这里就不一一测试了, HttpServletResponse的关键方法就在于设置编码,设置MIME, 设置头部, 然后组装响应体. 注意响应里有一个方法叫做sendRedirect, 这个在下边要专门看一下.

请求分派与重定向

由于servlet已经沦为了控制器, 在之前的例子里, 控制器通过模型获取数据, 将数据设置到请求对象上, 然后采取了将请求对象进行转发给jsp处理的方式. 对比HttpServletRequest和HttpServletResponse, 前者有 getRequestDispatcher(String var1); 方法(其实是ServletRequest接口里的), 而后者有 void sendRedirect(String var1) 方法(这个属于HTTP重定向, 因此ServletResponse没这个方法, 是HttpServletResponse的方法). 这里我又想到, 看来请求转发的思想比HTTP出现的要早. 对比一下就发现, 请求可以转发, 而响应的时候可以重定向. 一对比就清晰很多了. 重定向其实就是自己什么也不做, 让浏览器去访问其他的内容. 而请求转发, 转发来转发去, 最后还是要向客户提供服务, 这是最根本的区别. sendRedirect()函数的参数有如下三种方式:
  1. 可以是绝对地址URL, 比如 resp.sendRedirect("http://conyli.cc");, 这样就跳转到绝对地址
  2. 可以是不带斜线的相对URL, 处理就和链接一样, 将当前URL上最后的部分去掉, 然后进行拼接, 比如 resp.sendRedirect("time");
  3. 可以是带斜线的相对URL, 处理就和链接一样, 表示相对Web应用根目录的, 比如 resp.sendRedirect("time");
要注意的是, 不能够在已经打开响应的流对象, 写入之后再调用sendRedirect()方法, 会报错:
resp.getWriter().write("gugugugugu");
//不刷新还没事, 刷新了缓冲区之后就会报错
resp.getWriter().flush();
resp.sendRedirect("/SelectBeer.do");
所以一般为了语义和代码清晰, 需要进行重定向了, 就直接重定向即可. 而请求分派就不同了, 分派的依然是服务器上的其他处理请求的servlet. 请求分派的字符串是一个相对地址, 可以加斜杠也可以不加, 含义与sendRedirect()中的参数是一样的.
LICENSED UNDER CC BY-NC-SA 4.0
Comment