Servlet线程安全问题

Servlet的service方法的调用比较特殊:service方法的执行, 每一次都是在新的线程中.

因为service方法,执行在新线程中, 有可能同时存在多个线程.多线程操作时, 有可能发生线程安全问题

1.静态的同步方法的同步锁 --> 类.class这个对象

2.非静态的同步方法的同步锁 --> this对象

3.同步代码块的同步锁 --> 程序员使用时提供.

代码块有{ }构造代码块,每次调用外面的方法是首先执行它,static{ }类加载时执行它.

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
30
31
32
33
34
35
36
package demo1;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
* Servlet implementation class Servlet1
*/
@WebServlet("/s1.do")
public class Servlet1 extends HttpServlet {
//余票
private int count = 10;
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
synchronized(this) { //让此方法只同时执行一条
if(count>0) {
//有票
System.out.println("有票, 正在出票");
try {
Thread.sleep(1000); //让线程不抢夺时间片
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
count--;
System.out.println("出票完成, 余票:"+count);
}else {
System.out.println("很遗憾 ,没有余票了");
}
}
response.getWriter().append("ok");
}
}

请求的转发

概念:将一个web组件为处理完毕的请求,通过tomcat转交给另一个web组件处理.

步骤:
1.获取请求转发器
RequestDispatcher rd = request.getRequestDispatcher(“转发的地址”);
2.通过转发器发起转发
rd.forward(请求对象,响应对象);

简写步骤:
request.getRequestDispatcher(“转发的地址”).forward(请求对象,响应对象);

原理(tomcat内部执行流程):

1.当浏览器访问服务器中的tomcat时.
2.tomcat将协议中的请求进行封装,封装为HttpServletRequest对象,将响应封装到HttpServletResponse对象中
3.找到对应映射地址的Servlet,(第一次寻找会创建对象),调用对象的service方法,传入请求与响应对象.
4.在serlvet中,我们可以控制将请求转发到另一个地址.
5.tomcat接收到请求转发指令后,将原请求对象和新的响应对象传给转发的新地址.
6.此时旧的响应对象就失效了,无法在进行响应了.由新的地址的响应对象 负责给用户进行响应.
7.新地址准备完毕响应体,发送给浏览器

特点:

1.转发的过程中,只发生了一次请求,多个Servlet之间共享一份请求信息.
2.转发无法跨域实现(无法跨网站进行转发,例如:京东无法转发给淘宝.)
3.无论转发过程中,触发了多少个web组件,对于浏览器而言,它能感知到的只是发起了一次请求,接收到了一次响应.
4.转发过程中,浏览器地址不会发生改变
5.相较于重定向而言,效率高.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package demo2;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@WebServlet("/s2.do")
public class Servlet2 extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
request.setCharacterEncoding("UTF-8");
String username = request.getParameter("username");
String password = request.getParameter("password");
if("admin".equals(username) && "123".equals(password)) {
//登录成功
request.getRequestDispatcher("https://www.baidu.com").forward(request, response);
}else {
//登录失败
request.getRequestDispatcher("s4.do").forward(request, response);
}
}
}

请求的重定向

概念:在进行响应时,告知浏览器新的请求地址,浏览器接到后,自动发起新的请求找指定的地址.

步骤:

response.sendRedirect("重定向的新地址");

原理(执行流程):

1.当浏览器请求某个Servlet时.
2.Servlet对浏览器响应一个302的状态码,以及一个键为location的值,这个值表示新的地址;
3.当浏览器接收到302的状态码以后,会自动寻找lcoation的值,并网页自动跳转到location的值表示地址上 !

特点:

1.重定向会产生新的请求和新的响应.
2.使用重定向,可以跨域实现(可以跨网站操作,例如:京东可以将请求重定向到淘宝)
3.浏览器地址会发生改变,显示的是重定向的地址;
4.相较于请求转发而言,效率较低.

注意:

1.在用户的一次访问过程中,我们可以进行无限次的转发/重定向!但是记住一点:在多次转发/重定向的操作中,一定要有出口!
2.不只是可以将请求转发/重定向到servlet上,任何的web资源都可以接受转发和重定向.
3.当我们的代码进行了转发/重定向时,相当于将响应的操作交给了其他资源.那么在进行转发和重定向的代码后面,就不要再编写响应的操作了.因为无效.
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
package demo3;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@WebServlet("/s22.do")
public class Servlet2 extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
request.setCharacterEncoding("UTF-8");
String username = request.getParameter("username");
String password = request.getParameter("password");
if("admin".equals(username) && "123".equals(password)) {
//登录成功
response.sendRedirect("s33.do");
//response.sendRedirect("https://www.baidu.com");
}else {
//登录失败
response.sendRedirect("s44.do");
}
}
}

HttpServletRequest类的常用操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
1.获取客户端的ip地址:
String ip = request.getRemoteAddr();

2.获取客户端访问的路径地址:
String url = reuqest.getRequestURI();

3.获取tomcat运行的ip地址
String ip = request.getServerName();

4.获取tomcat运行的端口号
String port = request.getServerPort();

5.获取请求方式
String method = request.getMethod();

6.获取get请求的?后的参数列表
String params = request.getQueryString();

7.设置请求的内容编码格式(常用于请求键值)
request.setCharacterEncoding("UTF-8");

8.获取请求地址中的键值
request.getParameter()

HttpServletResponse类的常用操作

1
2
3
4
5
6
7
8
1.设置响应的内容类型(网页) 以及 编码格式(UTF-8)
response.setContentType("text/html;charset=utf-8");

2.设置响应的内容编码格式 (常用于响应JSON数据)
response.setCharacterEncoding("UTF-8");

3.响应错误码给客户端
response.sendError(int status,String msg);
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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
package demo4;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
* Servlet implementation class Servlet5
*/
@WebServlet("/s5.do")
public class Servlet5 extends HttpServlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
request.setCharacterEncoding("UTF-8");

response.setContentType("text/html;charset=utf-8");

int port = request.getServerPort();
response.getWriter().append("tomcat运行的端口号是:"+port);

String method = request.getMethod();
response.getWriter().append("请求的方式是:"+method);

String params = request.getQueryString();
response.getWriter().append("获取get请求的?后的参数列表:"+method);

String ip = request.getRemoteAddr();
response.getWriter().append("你的ip地址是:"+ip);

String url = request.getRequestURI();
response.getWriter().append("你的url地址是:"+url);

if(request.getParameter("heiheihei")!=null) {
//表示请求地址中 包含一个键值对, 键为heiheihei
response.getWriter().append("<h1>网站主页</h1>");
}else {
response.sendError(404,"资源不存在, 哈哈哈");
}
}

/**
* @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response)
*/
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// TODO Auto-generated method stub
doGet(request, response);
}
}

ServletContext对象(Servlet上下文)

每一个Servlet都是一个独立的网页地址,它们之间无法进行通信以及交流

例如:我们想统计网站的访问次数,每一个servlet只能统计自己被访问的次数,无法汇总.

Servlet上下文对象,就是用于Servlet之间信息共享的,是多个servlet之间通信的桥梁

Servelt上下文对象,在项目的运行过程中,只有一个.每一个Servlet获取的上下文对象, 都是同一份.

Servlet上下文对象,就像一个Map集合,可以存储n个键值对信息!

格式:
ServletContext context = getServletContext();

  • 上下文对象常用方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    1.存储数据
    context.setAttribute(String name,Object value);

    2.获取数据
    Object value = context.getAttribute(String name);

    3.删除数据
    context.removeAttribute(String name);

    4.获取项目运行时的文件夹 绝对路径.
    tring path = context.getRealPath("/");
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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
package demo6;

import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
* Servlet implementation class Count1
*/
@WebServlet("/c1.do")
public class Count1 extends HttpServlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//1. 先设置响应的编码格式以及内容类型
response.setContentType("text/html;charset=utf-8");
//2. 得到上下文对象
ServletContext context = getServletContext();
//3. 从上下文对象中, 获取一个键为count的数据值
Integer i = (Integer) context.getAttribute("count");
if(i==null) {
//从未存储过
i=0;
}
//4. 将数量进行加一
i++;
//5. 将计算后的新数量, 存储到上下文中.
context.setAttribute("count", i);
//根据上下文对象, 得到项目运行的文件夹路径
String path = context.getRealPath("/");

response.getWriter().append("path:"+path);
response.getWriter().append("c1: 你是项目的第"+i+"位访问者");
}

/**
* @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response)
*/
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// TODO Auto-generated method stub
doGet(request, response);
}
}