Servlet进阶

    上述能处理/hello这个路径的请求。

    早期的Servlet需要在web.xml中配置映射路径,但最新Servlet版本只需要通过注解就可以完成映射。

    因为浏览器发送请求的时候,还会有请求方法(HTTP Method):即GET、POST、PUT等不同类型的请求。因此,要处理GET请求,我们要覆写doGet()方法:

    1. @WebServlet(urlPatterns = "/hello")
    2. public class HelloServlet extends HttpServlet {
    3. @Override
    4. protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    5. ...
    6. }
    7. }

    类似的,要处理POST请求,就需要覆写doPost()方法。

    如果没有覆写doPost()方法,那么HelloServlet能不能处理POST /hello请求呢?

    我们查看一下HttpServletdoPost()方法就一目了然了:它会直接返回405或400错误。因此,一个Servlet如果映射到/hello,那么所有请求方法都会由这个Servlet处理,至于能不能返回200成功响应,要看有没有覆写对应的请求方法。

    一个Webapp完全可以有多个Servlet,分别映射不同的路径。例如:

    浏览器发出的HTTP请求总是由Web Server先接收,然后,根据Servlet配置的映射,不同的路径转发到不同的Servlet:

    1. ┌──────────>│ HelloServlet
    2. └───────────────┘│
    3. ┌───────┐ ┌──────────┐ /signin ┌───────────────┐
    4. Browser│───>│Dispatcher│─┼──────────>│ SignInServlet ││
    5. └───────┘ └──────────┘ └───────────────┘
    6. └──────────>│ IndexServlet
    7. └───────────────┘│
    8. Web Server

    所以我们在浏览器输入一个也会看到IndexServlet生成的页面。

    HttpServletRequest封装了一个HTTP请求,它实际上是从ServletRequest继承而来。最早设计Servlet时,设计者希望Servlet不仅能处理HTTP,也能处理类似SMTP等其他协议,因此,单独抽出了ServletRequest接口,但实际上除了HTTP外,并没有其他协议会用Servlet处理,所以这是一个过度设计。

    我们通过HttpServletRequest提供的接口方法可以拿到HTTP请求的几乎全部信息,常用的方法有:

    • getMethod():返回请求方法,例如,"GET""POST"
    • getRequestURI():返回请求路径,但不包括请求参数,例如,"/hello"
    • getQueryString():返回请求参数,例如,"name=Bob&a=1&b=2"
    • getParameter(name):返回请求参数,GET请求从URL读取参数,POST请求从Body中读取参数;
    • getContextPath():获取当前Webapp挂载的路径,对于ROOT来说,总是返回空字符串""
    • getCookies():返回请求携带的所有Cookie;
    • getHeader(name):获取指定的Header,对Header名称不区分大小写;
    • getHeaderNames():返回所有Header名称;
    • getInputStream():如果该请求带有HTTP Body,该方法将打开一个输入流用于读取Body;
    • getReader():和getInputStream()类似,但打开的是Reader;
    • getRemoteAddr():返回客户端的IP地址;
    • getScheme():返回协议类型,例如,"http""https"

    此外,HttpServletRequest还有两个方法:setAttribute()getAttribute(),可以给当前对象附加多个Key-Value,相当于把HttpServletRequest当作一个Map<String, Object>使用。

    调用HttpServletRequest的方法时,注意务必阅读接口方法的文档说明,因为有的方法会返回null,例如getQueryString()的文档就写了:

    1. ... This method returns null if the URL does not have a query string...

    HttpServletResponse封装了一个HTTP响应。由于HTTP响应必须先发送Header,再发送Body,所以,操作HttpServletResponse对象时,必须先调用设置Header的方法,最后调用发送Body的方法。

    常用的设置Header的方法有:

    • setStatus(sc):设置响应代码,默认是200
    • setContentType(type):设置Body的类型,例如,"text/html"
    • setCharacterEncoding(charset):设置字符编码,例如,"UTF-8"
    • setHeader(name, value):设置一个Header的值;
    • addCookie(cookie):给响应添加一个Cookie;

    写入响应时,需要通过getOutputStream()获取写入流,或者通过getWriter()获取字符流,二者只能获取其中一个。

    但是,写入完毕后调用flush()却是必须的,因为大部分Web服务器都基于HTTP/1.1协议,会复用TCP连接。如果没有调用flush(),将导致缓冲区的内容无法及时发送到客户端。此外,写入完毕后千万不要调用close(),原因同样是因为会复用TCP连接,如果关闭写入流,将关闭TCP连接,使得Web服务器无法复用此TCP连接。

    写入完毕后对输出流调用flush()而不是close()方法!

    有了HttpServletRequestHttpServletResponse这两个高级接口,我们就不需要直接处理HTTP协议。注意到具体的实现类是由各服务器提供的,而我们编写的Web应用程序只关心接口方法,并不需要关心具体实现的子类。

    一个Servlet类在服务器中只有一个实例,但对于每个HTTP请求,Web服务器会使用多线程执行请求。因此,一个Servlet的doGet()doPost()等处理请求的方法是多线程并发执行的。如果Servlet中定义了字段,要注意多线程并发访问的问题:

    对于每个请求,Web服务器会创建唯一的HttpServletRequestHttpServletResponse实例,因此,HttpServletRequest和实例只有在当前处理线程中有效,它们总是局部变量,不存在多线程共享的问题。

    一个Webapp中的多个Servlet依靠路径映射来处理不同的请求;

    映射为/的Servlet可处理所有“未匹配”的请求;

    如何处理请求取决于Servlet覆写的对应方法;

    Servlet进阶 - 图1