• Servlet - Request, Response, Servlet Context


    1. What

    什么是 Servlet ?
    Java Servlet是服务器端技术,通过提供对动态响应和数据持久性的支持来扩展Web服务器的功能。

    javax.servlet和javax.servlet.http包提供用于编写​​我们自己的servlet的接口和类。
    所有servlet必须实现javax.servlet.Servlet接口,该接口定义了servlet生命周期方法。 在实现通用服务时,我们可以扩展Java Servlet API随附的GenericServlet类。 HttpServlet类提供用于处理特定于HTTP的服务的方法,例如doGet()和doPost()。

    什么是 Request ?
    在Servlet API中,定义了一个HttpServletRequest接口,它继承自ServletRequest接口,专门用来封装HTTP请求消息. 由于HTTP请求消息分为请求行、请求头和请求体三部分,因此,在HttpServletRequest接口中定义了获取请求行、请求头和请求消息体的相关方法. Web服务器收到客户端的http请求,会针对每一次请求,分别创建一个用于代表请求的request对象、和代表响应的response对象。

    • 请求方法: GET
    GET /562f25980001b1b106000338.jpg HTTP/1.1
    Host    www.hostname.com
    User-Agent    Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.106 Safari/537.36
    Accept    image/webp,image/*,*/*;q=0.8
    Referer    www.hostname.com
    Accept-Encoding    gzip, deflate, sdch
    Accept-Language    zh-CN,zh;q=0.8
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 请求方法: POST
    POST / HTTP1.1
    Host:www.hostname.com
    User-Agent:Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 2.0.50727; .NET CLR 3.0.04506.648; .NET CLR 3.5.21022)
    Content-Type:application/x-www-form-urlencoded
    Content-Length:40
    Connection: Keep-Alive
    
    name=test&password=password111
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    什么是 Servlet Context ?
    ServletContext是一个全局的储存信息的空间,服务器开始就存在,服务器关闭才释放。由于一个WEB应用中的所有Servlet共享同一个ServletContext对象,因此Servlet对象之间可以通过ServletContext对象来实现通讯。ServletContext对象通常也被称之为context域对象。当web应用关闭、Tomcat关闭或者Web应用reload的时候,ServletContext对象会被销毁。
    javax.servlet.ServletContext接口提供对servlet的Web应用程序参数的访问。 ServletContext是唯一的对象,可用于Web应用程序中的所有Servlet。 当我们希望某些初始化参数可用于Web应用程序中的多个或所有servlet时,我们可以使用ServletContext对象,并使用元素在web.xml中定义参数。 我们可以通过ServletConfig的getServletContext()方法获取ServletContext对象。 Servlet容器还可以提供一组Servlet唯一的上下文对象,并且该上下文对象与主机的URL路径名称空间的特定部分相关联。

    什么是 Response ?
    response是Servlet.service方法的一个参数,类型为javax.servlet.http.HttpServletResponse。在客户端发出每个请求时,服务器都会创建一个response对象,并传入给Servlet.service()方法。response对象是用来对客户端进行响应的,这说明在service()方法中使用response对象可以完成对客户端的响应工作。

    响应格式:

    HTTP/1.1 200 OK
    Bdpagetype: 1
    Bdqid: 0x87b0208600091dd6
    Cache-Control: private
    Connection: keep-alive
    Content-Encoding: gzip
    Content-Type: text/html
    Cxy_all: baidu+47d26b53b8e759d193c0ddcc9480a388
    Date: Sun, 19 Jan 2020 10:51:51 GMT
    Expires: Sun, 19 Jan 2020 10:51:51 GMT
    Server: BWS/1.1
    Set-Cookie: delPer=0; path=/; domain=.baidu.com
    Set-Cookie: BDSVRTM=19; path=/
    Set-Cookie: BD_HOME=0; path=/
    Set-Cookie: H_PS_PSSID=1456_21104_30494_26350_30500; path=/; domain=.baidu.com
    Strict-Transport-Security: max-age=172800
    Traceid: 157943111103799843949777350550919650774
    Vary: Accept-Encoding
    X-Ua-Compatible: IE=Edge,chrome=1
    Transfer-Encoding: chunked
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    HTTP请求中的常用响应头如下:

    • Server:apache tomcat 服务器类型
    • Content-Encoding: gzip 服务器发送数据的压缩格式
    • Content-Length: 80 发送数据的长度
    • Content-Language: zh-cn 发送数据的语言环境
    • Content-Type: text/html; charset=GB2312 可接受数据格式和语言
    • Last-Modified: Tue, 11 Jul 2000 18:23:51 GMT 与请求头的if modified头呼应,主要跟缓存有关
    • Refresh: 1;url=http://www.it315.org 定时刷新
    • Content-Disposition: attachment;filename=aaa.zip 跟下载有关,下载文件名字aaa.zip
    • Transfer-Encoding: chunked 分块编码
    • Set-Cookie:SS=Q0=5Lb_nQ; path=/search
    • ETag: W/“83794-1208174400000” Etag一般和MD5配合使用,验证文件完整性
    • Expires: -1 通知浏览器是否缓存当前资源,如果这个头的值是一个以毫秒为单位的值,就是通知浏览器缓存资源到指定的时间点,如果值是0或-1则是通知浏览器禁止缓存。
    • Cache-Control: no-cache 通知浏览器是否缓存的头,no-cache代表不缓存
    • Pragma: no-cache 通知浏览器是否缓存的头,no-cache代表不缓存
    • Connection: close/Keep-Alive 连接状态
    • Date: Tue, 11 Jul 2000 18:23:51 GMT Date可以用来判断是否来自缓存

    不同 Content-Type 时候,response 返回数据的形式:

    Content-Type : application/json

    Cache-Control: no-cache, no-store, max-age=0, must-revalidate
    Connection: keep-alive
    Content-Type: application/json; charset=UTF-8
    Date: Mon, 19 Sep 2022 06:18:58 GMT
    Expires: 0
    Pragma: no-cache
    Server: nginx/1.16.1
    Transfer-Encoding: chunked
    Vary: Origin
    Vary: Access-Control-Request-Method
    Vary: Access-Control-Request-Headers
    X-Content-Type-Options: nosniff
    X-XSS-Protection: 1; mode=block
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    Content-Type : text/html

    Cache-Control: no-cache, max-age=0, must-revalidate
    Connection: keep-alive
    Content-Type: application/html; charset=UTF-8
    Content-Encoding: gzip
    Date: Mon, 19 Sep 2022 06:18:58 GMT
    Link: ; rel="https://api.w.org/"
    Expires: 0
    Pragma: no-cache
    Server: nginx/1.16.1
    Transfer-Encoding: chunked
    Vary: Origin
    Vary: Access-Control-Request-Method
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    2. Why

    2.1 为什么 J2EE 要有Servlet规范?Servlet解决了什么java 企业服务器的什么问题?

    Servlet 是提供基于协议请求的/响应服务的java类。Servlet是JavaEE规范中的一种,它担当客户端请求与服务器响应的中间层,主要是为了扩展Java作为Web服务的功能,统一定义了对应的接口,比如Servlet接口,HttpRequest接口,HttpResponse接口,Filter接口,然后由具体的服务厂商来实现这些接口功能,比如Tomcat,jetty等。

    简单点说:servlet就是客户端和服务器端的桥梁,通过这个桥梁,才能处理客户端请求与服务器响应。

    大多数时候,使用HTTP协议访问Web应用程序,这就是为什么我们主要扩展HttpServlet类的原因。

    Servlet 生命周期:
    在这里插入图片描述

    有了 Servlet 的存在,开发者可以更好的去关注业务逻辑的实现,而不用太过关心客户端与服务端之间是如何进行通信的。当客户端向服务端发起请求的时候,容器首先会进行判断,解析请求,将请求分发到具体的 Servlet 中,这时,会触发Servlet的init()方法进行初始化,注意:每个Servlet只会被初始化一次,第二次请求时将不会再进行初始化的操作。初始化完成后,service()方法会对请求进行处理,判断请求的方式等一系列操作,将请求具体分发到我们的业务方法中去,最后,经过处理,将处理完成后的信息返回给客户端。

    Servlet 处理 HTTP 请求的流程如下:
    在这里插入图片描述

    1. Servlet 容器接收到来自客户端的 HTTP 请求后,容器会针对该请求分别创建一个 HttpServletRequest 对象和 HttpServletReponse 对象。
    2. 容器将 HttpServletRequest 对象和 HttpServletReponse 对象以参数的形式传入 service() 方法内,并调用该方法。
    3. 在 service() 方法中 Servlet 通过 HttpServletRequest 对象获取客户端信息以及请求的相关信息。
    4. 对 HTTP 请求进行处理。
    5. 请求处理完成后,将响应信息封装到 HttpServletReponse 对象中。
    6. Servlet 容器将响应信息返回给客户端。
    7. 当 Servlet 容器将响应信息返回给客户端后,HttpServletRequest 对象和 HttpServletReponse 对象被销毁。

    2.2 Tomcat 介绍

    通过上面Servlet规范的介绍,其实我们发下我们要实现Servlet规范的话,很重要的就得提供一个服务容器来获取请求,解析封装数据,并调用Servlet实例相关的方法。
    Tomcat是一个容器,用于承载Servlet,那么我们说Tomcat就是一个实现了部分J2EE规范的服务器。

    3. How

    3.1 Request

    3.1.1 获取请求行的信息

    返回值类型方法声明描述
    StringgetMethod()该方法用于获取 HTTP 请求方式(如 GET、POST 等)。
    StringgetRequestURI()该方法用于获取请求行中的资源名称部分,即位于 URL 的主机和端口之后,参数部分之前的部分。
    StringgetQueryString()该方法用于获取请求行中的参数部分,也就是 URL 中“?”以后的所有内容。
    StringgetContextPath()返回当前 Servlet 所在的应用的名字(上下文)。对于默认(ROOT)上下文中的 Servlet,此方法返回空字符串""。
    StringgetServletPath()该方法用于获取 Servlet 所映射的路径。
    StringgetRemoteAddr()该方法用于获取客户端的 IP 地址。
    StringgetRemoteHost()该方法用于获取客户端的完整主机名,如果无法解析出客户机的完整主机名,则该方法将会返回客户端的 IP 地址。

    3.1.2 获取请求头的信息

    浏览器告诉服务器自己的属性,配置的, 以key value存在, 可能一个key对应多个value

    • User-Agent: 浏览器信息
    • Referer:来自哪个网站(防盗链)
    • getHeader(String name);

    3.1.3 获取请求参数

    方法名描述
    String getParameter(String name)获得指定参数名对应的值。如果没有则返回null,如果有多个获得第一个。 例如:username=wang
    String[] getParameterValues(String name)获得指定参数名对应的所有的值。此方法专业为复选框提供的。 例如:hobby=test&hobby=demo
    Map getParameterMap()获得所有的请求参数。key为参数名,value为key对应的所有的值

    请求中乱码问题:
    将客户端与服务端的编码格式统一一下就好了。

    // 设置服务端为 utf-8 解码格式,解决中文乱码问题
    request.setCharacterEncoding("UTF-8"); 
    
    • 1
    • 2

    3.1.4 使用BeanUtils将map中的数据存储到JavaBean对象中

    当一个请求中参数过多时,我们就需要将参数封装到对象中,BeanUtils是Apache Commons组件的成员之一,主要用于简化JavaBean封装数据的操作。

    3.1.5 使用request做请求转发

    1. 请求转发的格式
    request.getRequestDispatcher(url).forward(request, response); //转发
    
    • 1
    1. 请求转发的作用
      当我们需要将 request 对象同时交给两个或者多个 Servlet 程序处理的时候,那么就可以使用请求转发。request 请求转发不会修改浏览器的路径,也不需要浏览器跳转至其他路径,由服务端执行跳转。更加重要的是,request 请求转发还可以访问 WEB-INF 中受保护的资源。

    3.1.6 request对象作为域对象存取数据

    域对象是一个容器,这种容器主要用于Servlet与Servlet/JSP之间的数据传输使用的,域对象限制了数据的访问范围,其值会随着对象的消失而消失。

    Servlet中提供了两个域对象:

    1. request 作用域的值是在一次请求范围内有效(周期短)。
    2. ServletContext是一个全局作用域对象,在整个Web应用内都有效(周期长)

    常用方法:

    方法描述
    Object getAttribute(String name)获取设置的参数数据
    void setAttribute(String name,Object object)设置参数值
    void removeAttribute(String name)移除设置的参数

    3.2 Servlet Context

    3.2.1 ServletContext对象如何得到

    • 通过 GenericServlet 提供的 getServletContext() 方法
    //通过 GenericServlet的getServletContext方法获取ServletContext对象
    ServletContext servletContext = this.getServletContext();
    
    • 1
    • 2
    • 通过 ServletConfig 提供的 getServletContext() 方法
    //通过 ServletConfig的 getServletContext方法获取ServletContext对象
    ServletContext servletContext = this.getServletConfig().getServletContext();
    
    • 1
    • 2
    • 通过 HttpSession 提供的 getServletContext() 方法
    //通过 HttpSession的 getServletContext方法获取ServletContext对象
    ServletContext servletContext = req.getSession().getServletContext();
    
    • 1
    • 2
    • 通过 HttpServletRequest 提供的 getServletContext() 方法
    //通过 HttpServletRequest的 getServletContext方法获取ServletContext对象
    ServletContext servletContext = req.getServletContext();
    
    • 1
    • 2

    3.2.2 获取上下文初始化参数

    1. 设置上下文初始化参数
      通过 web.xml 中的 元素可以为 Web 应用设置一些全局的初始化参数,这些参数被称为上下文初始化参数。与 Servlet 的初始化参数不同,应用中的所有 Servlet 都共享同一个上下文初始化参数。在 Web 应用的整个生命周期中,上下文初始化参数会一直存在,并且可以随时被任意一个 Servlet 访问。
      在 web.xml 文件中配置上下文初始化参数,代码如下所示。
    
    <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
                          http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
        version="4.0" metadata-complete="false">
    
        
        <context-param>
            <param-name>nameparam-name>
            <param-value>testDemoparam-value>
        context-param>
    
        <context-param>
            <param-name>urlparam-name>
            <param-value>www.testAddress.comparam-value>
        context-param>
    
    web-app>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    对以上标签说明如下:

    • 元素用来声明上下文初始化参数,必须在根元素 内使用
    • 子元素表示参数名,参数名在整个 Web 应用中必须是唯一的
    • 子元素表示参数值
    1. 调用接口中方法获取初始化参数
      Servlet 容器启动时,会为容器内每个 Web 应用创建一个 ServletContext 对象,并将 元素中的上下文初始化参数以键值对的形式存入该对象中,因此我们可以通过 ServletContext 的相关方法获取到这些初始化参数。
      用于获取上下文初始化参数的相关方法:
    返回值类型方法描述
    StringgetInitParameter(String name)根据初始化参数名 name,返回对应的初始化参数值。
    EnumerationgetInitParameterNames()返回 Web 应用所有上下文初始化参数名的枚举集合,如果该 Web 应用没有上下文初始化参数,则返回一个空的枚举集合。
    @WebServlet("/ReadContextServlet")
    public class ReadContextServlet extends HttpServlet {
        private static final long serialVersionUID = 1L;
    
        protected void doGet(HttpServletRequest request, HttpServletResponse response)
                throws ServletException, IOException {
            response.setContentType("text/html;charset=UTF-8");
            PrintWriter writer = response.getWriter();
            // 调用httpServlet父类GenericServlet的getServletContext方法获取ServletContext对象
            ServletContext context = super.getServletContext();
            // 返回 context 上下文初始化参数的名称
            Enumeration<String> initParameterNames = context.getInitParameterNames();
            while (initParameterNames.hasMoreElements()) {
                // 获取初始化参数名称
                String initParamName = initParameterNames.nextElement();
                // 获取相应的初始参数的值
                String initParamValue = context.getInitParameter(initParamName);
                // 向页面输出
                writer.write(initParamName + "  :  " + initParamValue + "
    "
    ); } // 关闭流 writer.close(); } protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doGet(request, response); } }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    3.2.3 实现 Servlet 之间的数据通讯

    在 Servlet 中,调用 ServletContext 接口的 setAttribute() 方法可以创建一些属性,这些属性被存放在 ServletContext 对象中。应用中所有 Servlet 都可以对这些属性进行访问和操作,通过它们可以实现应用内不同 Servlet 之间的数据通讯。

    实现数据通讯的相关方法:

    返回值类型方法描述
    voidsetAttribute(String name, Object object)把一个 Java 对象与一个属性名绑定,并将它作为一个属性存放到 ServletContext 中。参数 name 为属性名,参数 object 为属性值。
    voidremoveAttribute(String name)从 ServletContext 中移除属性名为 name 的属性。
    ObjectgetAttribute(String name)根据指定的属性名 name,返回 ServletContext 中对应的属性值。

    ServletContext 属性与上下文初始化参数对比:

    不同点ServletContext 的属性上下文初始化参数
    创建方式ServletContext 的属性通过调用 ServletContext 接口的 setAttribute() 方法创建上下文初始化参数通过 web.xml 使用 元素配置
    可进行的ServletContext 的属性可以通过 ServletContext 接口的方法进行读取、新增、修改、移除等操作上下文初始化参数在容器启动后只能被读取,不能进行新增、修改和移除操作
    生命周期ServletContext 中属性的生命周期从创建开始,到该属性被移除(remove)或者容器关闭结束上下文初始化参数的生命周期,从容器启动开始,到 Web 应用被卸载或容器关闭结束
    作用使用 ServletContext 中的属性可以实现 Servlet 之间的数据通讯使用上下文初始化参数无法实现数据通讯

    3.2.4 读取 Web 应用下的资源文件

    在实际开发中,有时会需要读取 Web 应用中的一些资源文件,如配置文件和日志文件等。为此,ServletContext 接口定义了一些读取 Web 资源的方法 ,如下:

    返回值类型方法方法描述
    SetgetResourcePaths(String path)返回一个 Set 集合,该集合中包含资源目录中的子目录和文件的名称。
    StringgetRealPath(String path)返回资源文件的真实路径(文件的绝对路径)。
    URLgetResource(String path)返回映射到资源文件的 URL 对象。
    InputStreamgetResourceAsStream(String path)返回映射到资源文件的 InputStream 输入流对象。
    • 文件在WebRoot文件夹下,即Web应用的根目录,读取该资源文件

    假设我们Web根目录下有一个配置数据库信息的info.properties文件,里面配置了name和password属性,这时候可以通过ServletContext去读取这个文件

    // 这种方法的默认读取路径就是Web应用的根目录
    InputStream stream = this.getServletContext().getResourceAsStream("info.properties");
    // 创建属性对象
    Properties properties = new Properties();
    properties.load(stream);
    String name = properties.getProperty("name");
    String password = properties.getProperty("password");
    out.println("name="+name+";password="+password);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 如果这个文件放在了src目录下,这时就不能用ServletContext来读取了,必须要使用类加载器去读取。
    // 类加载器的默认读取路径是src根目录
    InputStream stream = MyServlet.class.getClassLoader().getResourceAsStream("dbinfo.properties")
    
    • 1
    • 2

    如果这个文件此时还没有直接在src目录下,而是在src目录下的某个包下,比如在com.gavin包下,此时类加载器要加上包的路径,如下:

    InputStream stream = MyServlet.class.getClassLoader().getResourceAsStream("com/gavin/dbinfo.properties")
    
    • 1

    补充一点,ServletContext只有在读取的文件在web应用的根目录下时,才能获取文件的全路径。比如我们在WebRoot文件夹下有一个images文件夹,images文件夹下有一个Servlet.jpg图片,为了得到这个图片的全路径,如下:

    // 如何读取到一个文件的全路径,这里会得到在Tomcat的全路径
    String path = this.getServletContext().getRealPath("/images/Servlet.jpg");
    
    • 1
    • 2

    3.3 Response

    3.3.1 设置状态码

    response.setStatus(int status); //设置状态码
    
    • 1

    3.3.2 设置响应头

    设置影响头的方法,可以影响到页面的效果,有时候需要和设置状态码的方法配合使用,它常用的方法如下:

    返回值方法描述
    voidsetHeader(String name, String value)用给定名称和值设置响应头。如果已经设置了头,则新值将重写以前的值。containsHeader 方法可用于测试在设置其值之前头是否存在
    voidsetDateHeader(String name, long date)给给定名称和日期值设置响应头,该日期根据距历元时间的毫秒数指定。如果已经设置了头,则新值将重写以前的值
    voidsetIntHeader(String name, int value)此方法的默认行为是对包装的响应对象调用 addIntHeader(String name, int value)。
    voidaddHeader(String name, String value)用给定名称和值添加响应头,此方法允许响应头有多个值
    voidaddDateHeader(String name, long date)用给定名称和日期值添加响应头,该日期根据距历元时间的毫秒数指定,此方法允许响应头有多个值
    voidaddIntHeader(String name, int value)用给定名称和整数值添加响应头。此方法允许响应头有多个值

    3.3.3 设置响应消息

    • ServletOutputStream getOutputStream()
    • PrintWriter getWriter()
    // 使用 getWriter 设置响应消息
    response.getWriter().write("Hello status"); 
    
    • 1
    • 2

    PrintWriter和ServletOutputStream有什么区别?

    PrintWriter是字符流类,而ServletOutputStream是字节流类。 我们可以使用PrintWriter将基于字符的信息(例如,字符数组和String)写入响应,而我们可以使用ServletOutputStream将字节数组数据写入响应。
    我们可以使用ServletResponse getWriter()获取PrintWriter实例,而我们可以使用ServletResponse getOutputStream()方法获取ServletOutputStream对象引用。

    3.3.4 response 常用功能

    response常用功能有请求重定向、控制缓存、页面刷新等

    设置重定向

    //原理实现
    //设置状态码
    response.setStatus(302);
    //设置消息头
    response.setHeader("Location","http://www.baidu.com");
    
    //api实现
    //以上代码等效如下代码
    //response.sendRedirect("http://www.baidu.com");
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    控制缓存

    //设置不使用缓存
    /*public void setDateHeader(String name, long date)
    用给定名称和日期值设置响应头。该日期根据距历元时间的毫秒数指定。如果已经设置了头,则新值将重写以前的值。
    containsHeader 方法可用于测试在设置其值之前头是否存在。*/
    //方式1
    if(response.containsHeader("Expires")){
        response.setDateHeader("Expires",-1);
    }
    //方式2
    response.setHeader("Cache-Control","no-cache");
    //方式3
    response.setHeader("Pragma","no-cache");
    
    Date date=new Date();
    String time = date.toLocaleString();
    //设置使用缓存
    //response.setDateHeader("Expires",System.currentTimeMillis()+1000*60*60*1);//1 hour
    //response.setHeader("Cache-Control","max-age=60");//60 s
    
    response.getWriter().write(""+time+"");
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    页面刷新
    页面刷新可以设置响应头的Refresh,里面一个时间参数单位为秒,一个刷新后页面的url地址,注意使用url,这个可以用在注册后隔几秒跳转到主页这种场景。以下代码就是实现访问ResponseDemo03请求后,等待3秒会刷新进入百度主页的功能。

    package com.boe.response;
    
    /**
     * 定时刷新
     */
    @WebServlet("/ResponseDemo03")
    public class ResponseDemo03 extends HttpServlet {
        protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
            //3 秒
            response.setHeader("Refresh","3;url=http://www.baidu.com");
        }
        protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
            doPost(request, response);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    4. Samples

    4.1 ServletContext 应用

    1. 网站计数器
    2. 网站的在线用户显示
    3. 简单的聊天系统
      只要是涉及到不同用户共享数据,而这些数据量不大,同时不希望写入数据库中,我们就可以考虑使用 ServletContext 实现。

    4.2 Servlet-单例模式,要考虑线程安全的问题

    Servlet 第一次被调用的时候,inti()方法会被调用,然后调用service()方法,从第二次请求开始,就直接调用service()方法。因为 Servlet 是单实例的,然后后面再次请求同一个 Servlet 的时候都不会创建 Servlet 实例,而且web 容器会针对请求创建一个独立的线程,这样多个并发请求会导致多个线程同时调用service() 方法,这样就会存在线程不安全的问题。
    
    • 1

    4.3 如何修复 Servlet 多线程时实例变量引用不当出现的问题:

    示例代码:

    下面的代码,很明显看到status 状态变量是实例变量,这里为了突出效果,这里加了一个延迟。当在并发情况下,多个用户一起访问同一个servlet 的时候,他们对实例变量的修改其实是会影响到别人的,所以在并发时,Servlet 的线程安全问题主要是由于实例变量使用不当而引起。

    public class TestServlet extends HttpServlet {
        
        public boolean status;
    
        @Override
        protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        
            status = true;
            String cmd = req.getParameter("cmd");
            if(cmd.contains("Calculator")){
                status = false;
                try {
                    Thread.sleep(1000);
                }catch (Exception e){
                    
                }
            }
            if(!status){
                return;
            }
            if (cmd.contains("Calculator")) {
                resp.getWriter().write(cmd);
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    4.3.1. 实现 SingleThreadModel 接口

    该接口指定了系统如何处理对同一个Servlet的调用。如果一个Servlet被这个接口指定,那么在这个Servlet中的service方法将不会有两个线程被同时执行,当然也就不存在线程安全的问题。这种方法
    只要继承这个接口就行了,因此将我们上面的代码改为

    public class TestServlet extends HttpServlet implements SingleThreadModel 
    
    • 1

    这样是否就能解决呢?也不是,如果我们将上述状态的定义加上static时:

    public static boolean status;
    
    • 1

    还是可以访问成功的,原因是 SingleThreadModel 不会解决所有的线程安全隐患。会话属性和静态变量仍然可以被多线程的多请求同时访问

    还有一点很重要该接口在Servlet API 2.4中将不推荐使用。

    4.3.2. 避免使用成员变量

    public class TestServlet extends HttpServlet{
    
    //    public  boolean status;
        @Override
        protected void doGet(HttpServletRequest req, HttpServletResponse resp)
                throws ServletException, IOException {
            boolean status = true;
            String cmd = req.getParameter("cmd");
            if (cmd.contains("Calculator")) {
                status = false;
                try {
                    Thread.sleep(1000);
                }catch (Exception e){
    
                }
            }
    
            if (!status) {
                return;
            }
            if (cmd.contains("Calculator")){
                resp.getWriter().write(cmd);
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    4.3.3. 同步对共享数据的操作

    使用synchronized 关键字能保证一次只有一个线程可以访问被保护的区段,因此可以将代码写为

    public class TestServlet extends HttpServlet{
    
        public  boolean status;
        @Override
        protected void doGet(HttpServletRequest req, HttpServletResponse resp)
                throws ServletException, IOException {
            String cmd = req.getParameter("cmd");
            boolean status;
            synchronized(this) {
                status = true;
    
                if (cmd.contains("Calculator")) {
                    status = false;
                    try {
                        Thread.sleep(5000);
                    } catch (Exception e) {
    
                    }
                }
            }
    
            if (!status) {
                return;
            }
            if (cmd.contains("Calculator")){
                resp.getWriter().write(cmd);
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    4.4 forward()-转发跟sendRedirect()-重定向的区别?

    • RequestDispatcher forward()用于将同一请求转发到另一个资源,而ServletResponse sendRedirect()是一个两步过程。 在sendRedirect()中,Web应用程序使用状态码302(重定向)和URL将响应返回给客户端,以发送请求。 发送的请求是一个全新的请求。
    • forward()由容器内部处理,而sednRedirect()由浏览器处理。
    • 访问同一应用程序中的资源时,我们应该使用forward(),因为它比需要额外网络调用的sendRedirect()方法要快。
    • 在forward()中,浏览器不了解实际的处理资源,并且地址栏中的URL保持不变,而在sendRedirect()中,地址栏中的URL更改为转发的资源。
    • forward()不能用于在另一个上下文中调用servlet,在这种情况下,我们只能使用sendRedirect()。
  • 相关阅读:
    C++ Reference: Standard C++ Library reference: Containers: list: list: ~list
    【Java SE】对象的构造及初始化
    vue移动端项目渲染pdf步骤
    PHP转换Excel中日期和时间类型的处理
    【文献整理】基于深度强化学习的知识图谱推理研究
    Flowable动态配置监听器
    机器学习sklearn——day02
    计算机中丢失vcomp140.dll解决方案,可以使用这几个最新方法来修复
    基于FPGA的数据采集系统
    记录一次macos没有sudoers文件问题
  • 原文地址:https://blog.csdn.net/qq_34377273/article/details/126857686