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
  • 请求方法: 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

什么是 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

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

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: <https://www.test.com/wp-json>; 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

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<String,String[]> getParameterMap()获得所有的请求参数。key为参数名,value为key对应的所有的值

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

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

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

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

3.1.5 使用request做请求转发

  1. 请求转发的格式
request.getRequestDispatcher(url).forward(request, response); //转发
  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();
  • 通过 ServletConfig 提供的 getServletContext() 方法
//通过 ServletConfig的 getServletContext方法获取ServletContext对象
ServletContext servletContext = this.getServletConfig().getServletContext();
  • 通过 HttpSession 提供的 getServletContext() 方法
//通过 HttpSession的 getServletContext方法获取ServletContext对象
ServletContext servletContext = req.getSession().getServletContext();
  • 通过 HttpServletRequest 提供的 getServletContext() 方法
//通过 HttpServletRequest的 getServletContext方法获取ServletContext对象
ServletContext servletContext = req.getServletContext();

3.2.2 获取上下文初始化参数

  1. 设置上下文初始化参数
    通过 web.xml 中的 元素可以为 Web 应用设置一些全局的初始化参数,这些参数被称为上下文初始化参数。与 Servlet 的初始化参数不同,应用中的所有 Servlet 都共享同一个上下文初始化参数。在 Web 应用的整个生命周期中,上下文初始化参数会一直存在,并且可以随时被任意一个 Servlet 访问。
    在 web.xml 文件中配置上下文初始化参数,代码如下所示。
<?xml version="1.0" encoding="UTF-8"?>
<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>name</param-name>
        <param-value>testDemo</param-value>
    </context-param>

    <context-param>
        <param-name>url</param-name>
        <param-value>www.testAddress.com</param-value>
    </context-param>

</web-app>

对以上标签说明如下:

  • 元素用来声明上下文初始化参数,必须在根元素 内使用
  • 子元素表示参数名,参数名在整个 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 + "<br/>");
        }
        // 关闭流
        writer.close();
    }

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

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);
  • 如果这个文件放在了src目录下,这时就不能用ServletContext来读取了,必须要使用类加载器去读取。
// 类加载器的默认读取路径是src根目录
InputStream stream = MyServlet.class.getClassLoader().getResourceAsStream("dbinfo.properties")

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

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

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

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

3.3 Response

3.3.1 设置状态码

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

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"); 

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");

控制缓存

//设置不使用缓存
/*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("<font color='blue'>"+time+"</font>");

页面刷新
页面刷新可以设置响应头的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);
    }
}

4. Samples

4.1 ServletContext 应用

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

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

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

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);
        }
    }
}

4.3.1. 实现 SingleThreadModel 接口

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

public class TestServlet extends HttpServlet implements SingleThreadModel 

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

public static boolean status;

还是可以访问成功的,原因是 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);
        }
    }
}

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);
        }
    }
}

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

  • RequestDispatcher forward()用于将同一请求转发到另一个资源,而ServletResponse sendRedirect()是一个两步过程。 在sendRedirect()中,Web应用程序使用状态码302(重定向)和URL将响应返回给客户端,以发送请求。 发送的请求是一个全新的请求。
  • forward()由容器内部处理,而sednRedirect()由浏览器处理。
  • 访问同一应用程序中的资源时,我们应该使用forward(),因为它比需要额外网络调用的sendRedirect()方法要快。
  • 在forward()中,浏览器不了解实际的处理资源,并且地址栏中的URL保持不变,而在sendRedirect()中,地址栏中的URL更改为转发的资源。
  • forward()不能用于在另一个上下文中调用servlet,在这种情况下,我们只能使用sendRedirect()。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

我是王小贱

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值