我只想卷死各位,或者被各位卷死,在此特别感谢黑马程序员的JavaWeb教程


本文摘要:

  • 掌握Request对象的概念与使用
  • 掌握Response对象的概念与使用
  • 能够完成用户登录注册案例的实现
  • 能够完成SqlSessionFactory工具类的抽取

Request和Response的概述

Request是请求对象,Response是响应对象。这两个对象在我们使用Servlet的时候有看到:

1
2
3
4
@Override
public void service(ServletRequest request, ServletResponse response) throws ServletException, IOException {
System.out.println("hello servlet");
}

那么request和response这两个参数的作用是什么?

  • request:获取请求数据

    • 浏览器会发送HTTP请求到后台服务器(Tomcat)
    • HTTP的请求中会包含很多请求数据(请求行+请求头+请求体)
    • 后台服务器(Tomcat)会对HTTP请求的数据进行解析,并把解析解惑存入到一个对象中
    • 所存入的对象即为request对象,我们可以从request对象中获取请求的相关参数
    • 获取到数据后就可以继续后续的业务,比如获取用户名和密码就可以实现登录操作的相关业务
  • response:设置响应数据

    • 业务处理完后,后台就需要给前端返回业务处理的结果,即响应数据
    • 把响应数据封装到response对象中
    • 后台服务器(Tomcat)会解析response对象,按照响应行+响应头+响应体的格式拼接结果
    • 浏览器最终解析结果,把内容展示在浏览器给用户浏览

通过一个案例来初步体验下request和response对象的使用。

写一个表单,请求方式为GET,当我们输入不同的username,并点击提交时,界面上就会出现username,欢迎访问

1
2
3
4
5
6
7
8
9
10
11
12
13
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<form action="/web_demo_war_exploded/demo" method="get">
用户名:<input type="text" name="username">
<input type="submit" value="提交">
</form>
</body>
</html>

启动成功后就可以通过浏览器来访问,并且根据传入参数的不同就可以在页面上展示不同的内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import javax.servlet.*;
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(urlPatterns = "/demo")
public class ServletDemo extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String name = req.getParameter("username");
resp.setHeader("content-type","text/html;charset=utf-8");
resp.getWriter().write("<h1>"+name+",欢迎访问<h1>");
}

@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("post...");
}
}

小结
在这节中,主要认识了一下request对象和reponse对象:

  • request对象是用来封装请求数据的对象
  • response对象是用来封装响应数据的对象

目前我们只知道这两个对象是用来干什么的,那么它们具体是如何实现的,就需要我们继续深入的学习。接下来,就先从Request对象来学习,主要学习下面这些内容:

  • request继承体系
  • request获取请求参数
  • request请求转发

Request对象

Request继承体系

Request的继承体系:
Request的继承体系.png

ServletRequest和HttpServletRequest是继承关系,并且两个都是接口,接口是无法创建对象,那么方法里的HttpServletRequest参数是从哪儿来的呢?

1
2
3
4
5
6
7
8
9
10
11
12
//接口无法创建对象,那么这个参数是哪儿来的呢
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String name = req.getParameter("username");
resp.setHeader("content-type","text/html;charset=utf-8");
resp.getWriter().write("<h1>"+name+",欢迎访问<h1>");
}

@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("post...");
}

这个时候,我们就需要用到Request继承体系中的RequestFacade:

  • 该类实现了HttpServletRequest接口,也间接实现了ServletRequest接口。
  • Servlet类中的service方法、doGet方法或者是doPost方法最终都是由Web服务器(Tomcat)来调用的,所以Tomcat提供了方法参数接口的具体实现类,并完成了对象的创建
  • 要想了解RequestFacade中都提供了哪些方法,我们可以直接查看JavaEE的API文档中关于ServletRequest和HttpServletRequest的接口文档,因为RequestFacade实现了其接口就需要重写接口中的方法

对于上述结论,要想验证,可以编写一个Servlet,在方法中把request对象打印下,就能看到最终的对象是不是RequestFacade,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import javax.servlet.*;
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(urlPatterns = "/demo")
public class ServletDemo extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println(req);
}

@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("post...");
}
}

启动服务器访问页面,控制台输出org.apache.catalina.connector.RequestFacade@36bf693c

小结

  • Request的继承体系为ServletRequest–>HttpServletRequest–>RequestFacade
  • Tomcat需要解析请求数据,封装为request对象,并且创建request对象传递到service方法
  • 使用request对象,可以查阅JavaEE API文档的HttpServletRequest接口中方法说明

Request获取请求数据

HTTP请求数据总共分为三部分内容,分别是请求行、请求头、请求体,对于这三部分内容的数据,分别该如何获取,首先我们先来学习请求行数据如何获取?

获取请求行数据

请求行包含三块内容,分别是请求方式请求资源路径HTTP协议及版本,例如
GET /tomcat_demo_war/index.html?username=suger1201&password=dsaasd HTTP/1.1

对于这三部分内容,request对象都提供了对应的API方法来获取,具体如下:

  • 获取请求方式: GET
1
String getMethod()
  • 获取虚拟目录(项目访问路径): /request-demo
1
String getContextPath()
  • 获取URL(统一资源定位符): http://localhost:8080/request-demo/req1
1
StringBuffer getRequestURL()
  • 获取URI(统一资源标识符): /request-demo/req1
1
String getRequestURI()
  • 获取请求参数(GET方式): username=zhangsan&password=123
1
String getQueryString()

介绍完上述方法后,咱们通过代码把上述方法都使用下:

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
import javax.servlet.*;
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(urlPatterns = "/demo")
public class ServletDemo extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String method = req.getMethod();
System.out.println("请求方式:" + method);
String contextPath = req.getContextPath();
System.out.println("项目访问路径:" + contextPath);
StringBuffer requestURL = req.getRequestURL();
System.out.println("URL:" + requestURL);
String requestURI = req.getRequestURI();
System.out.println("URI:" + requestURI);
String queryString = req.getQueryString();
System.out.println("请求参数:" + queryString);
}

@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("post...");
}
}

这里的请求方式是GET

1
2
3
4
5
6
7
8
9
10
11
12
13
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<form action="/web_demo_war_exploded/demo" method="get">
用户名:<input type="text" name="username">
<input type="submit" value="提交">
</form>
</body>
</html>

启动服务器,并向表单中随便输入一个username,然后点击提交,控制台输出如下
请求方式:GET
项目访问路径:/web_demo_war_exploded
URL:http://localhost:8080/web_demo_war_exploded/demo
URI:/web_demo_war_exploded/demo
请求参数:username=Cyderpunk2077%40gmail.com

获取请求头数据

对于请求头的数据,格式为key: value
所以根据请求头名称获取对应值的方法为

1
String getHeader(String name) 

接下来,在代码中如果想要获取客户端浏览器的版本信息,则可以使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import javax.servlet.*;
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(urlPatterns = "/demo")
public class ServletDemo extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String agent = req.getHeader("user-agent");
System.out.println(agent);
}

@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("post...");
}
}

启动服务器,随便输入数据然后提交,控制台输出如下
Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Safari/537.36

获取请求体数据

浏览器在发送GET请求的时候是没有请求体的,所以需要把请求方式变更为POST

1
2
3
4
5
6
7
8
9
10
11
12
13
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<form action="/web_demo_war_exploded/demo" method="post">
用户名:<input type="text" name="username">
<input type="submit" value="提交">
</form>
</body>
</html>

对于请求体中的数据,Request对象提供了如下两种方式来获取其中的数据,分别是:

  • 获取字节输入流,如果前端发送的是字节数据,比如传递的是文件数据,则使用该方法
1
ServletInputStream getInputStream()
  • 获取字符输入流,如果前端发送的是纯文本数据,则使用该方法
1
BufferedReader getReader()

下面我们在Servlet的doPost方法中获取数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import javax.servlet.*;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedReader;
import java.io.IOException;

@WebServlet(urlPatterns = "/demo")
public class ServletDemo extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("get??");
}

@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//由于获取的是纯文本数据,所以这里用的getReader()
BufferedReader bufferedReader = req.getReader();
String line = bufferedReader.readLine();
System.out.println(line);
}
}

BufferedReader流是通过request对象来获取的,当请求完成后request对象就会被销毁,request对象被销毁后,BufferedReader流就会自动关闭,所以此处就不需要手动关闭流了。

启动服务器,访问 http://localhost:8080/web_demo_war_exploded/index.html ,填入username并提交,在控制台就可以看到前端发送的请求数据了

小结

HTTP请求数据中包含了请求行请求头请求体,针对这三部分内容,Request对象都提供了对应的API方法来获取对应的值:

  • 请求行
    • getMethod()获取请求方式
    • getContextPath()获取项目访问路径
    • getRequestURL()获取请求URL
    • getRequestURI()获取请求URI
    • getQueryString()获取GET请求方式的请求参数
  • 请求头
    • getHeader(String name)根据请求头名称获取其对应的值
  • 请求体
    • 注意: 浏览器发送的POST请求才有请求体
    • 如果是纯文本数据:getReader()
    • 如果是字节数据如文件数据:getInputStream()

获取请求参数的通用方式

请求参数获取方式

  • GET方式:
1
String getQueryString()
  • POST方式:
1
BufferedReader getReader();

思考:
GET请求方式和POST请求方式区别主要在于获取请求参数的方式不一样,是否可以提供一种统一获取请求参数的方式,从而统一doGet和doPost方法内的代码?

解决方式一:
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
import javax.servlet.*;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedReader;
import java.io.IOException;

@WebServlet(urlPatterns = "/demo")
public class ServletDemo extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//获取请求方式
String method = req.getMethod();
String param = "";
//根据请求方式来获取请求参数
if ("GET".equals(method)) {
param = req.getQueryString();
} else if ("POST".equals(method)) {
BufferedReader bufferedReader = req.getReader();
param = bufferedReader.readLine();
}
//将请求参数进行打印控制台
System.out.println(param);
}

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

使用request的getMethod()来获取请求方式,根据请求方式的不同分别获取请求参数值,这样就可以解决上述问题,但是以后每个Servlet都需要这样写代码,实现起来比较麻烦,这种方案我们不采用

解决方式二:

request对象已经将上述获取请求参数的方法进行了封装,并且request提供的方法实现的功能更强大,以后只需要调用request提供的方法即可,在request的方法中都实现了哪些操作呢?

  1. 根据不同的请求方式获取请求参数,例如:username=zhangsan&password=asd123&hobby=1&hobby=2
  2. 把获取到的内容进行分割,username=zhangsan&password=asd123&hobby=1 -> username=zhangsan password=asd123 hobby=1 hobby=2 -> username zhangsan password asd123 hobby 1 hobby 2
  3. 把分割后端数据,存入到一个Map集合中,其中Map集合的泛型为<String,String[]>,因为参数的值可能是一个,也可能有多个,所以value的值的类型为String数组。

基于上述理论,request对象为我们提供了如下方法:

  • 获取所有参数Map集合
1
Map<String,String[]> getParameterMap()
  • 根据名称(Key)获取参数值(Value)(数组)
1
String[] getParameterValues(String name)
  • 根据名称(Key)获取参数值(Value)(单个值)
1
String getParameter(String name)

接下来,我们通过案例来把上述的三个方法进行实例演示:

  1. 随便写一个表单,加上一个复选框,爱好可以多选,所以到时候的参数值就不止一个了
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<form action="/web_demo_war_exploded/demo" method="post">
用户名:<input type="text" name="username"><br>
密码:<input type="password" name="password"><br>
爱好:<input type="checkbox" name="hobby" value="1">Apex
<input type="checkbox" name="hobby" value="2">Terraria<br>
<input type="submit" value="提交">
</form>
</body>
</html>
  1. 在Servlet代码中获取页面传递请求的参数值
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
import javax.servlet.*;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Map;

@WebServlet(urlPatterns = "/demo")
public class ServletDemo extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//获取所有参数的Map集合
Map<String, String[]> parameterMap = req.getParameterMap();
//遍历Map
for (String key : parameterMap.keySet()) {
System.out.print(key+":");
String[] values = parameterMap.get(key);
for (String v : values) {
System.out.print(v+" ");
}
System.out.println();
}
}

@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
this.doGet(req, resp);
}
}
/*
获取的结果如下
username:Cyderpunk2077@gmail.com
password:dsaasd
hobby:1 2
*/
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@WebServlet(urlPatterns = "/demo")
public class ServletDemo extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String[] hobbies = req.getParameterValues("hobby");
for (String s : hobbies) {
System.out.println(s);
}
}

@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
this.doGet(req, resp);
}
}
/*
将两个复选框都勾上,得到结果如下
1
2
*/
1
2
3
4
5
6
7
8
9
10
11
12
13
14
@WebServlet(urlPatterns = "/demo")
public class ServletDemo extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String username = req.getParameter("username");
System.out.println(username);
}

@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
this.doGet(req, resp);
}
}
//输出结果就是你输入的username

如果你在post请求方式下输入了中文username并提交,控制台会输出乱码
解决方案:通过req.setCharacterEncoding("UTF-8");设置编码,UTF-8也可以写成小写

Request请求转发

请求转发(forward):一种在服务器内部的资源跳转方式。

  1. 浏览器发送请求给服务器,服务器中对应的资源A接收到请求
  2. 资源A处理完请求后将请求发给资源B
  3. 资源B处理完后将结果响应给浏览器
  4. 请求从资源A到资源B的过程就叫请求转发

测试步骤

  1. 创建一个RequestDemo1类,接收/req5的请求,在doGet方法中打印这里是RequestDemo1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import javax.servlet.*;
import javax.servlet.http.*;
import javax.servlet.annotation.*;
import java.io.IOException;

@WebServlet("/RequestDemo1")
public class RequestDemo1 extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
System.out.println("这里是RequestDemo1");
}

@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doGet(request, response);
}
}
  1. 创建一个RequestDemo2类,接收/req6的请求,在doGet方法中打印这里是RequestDemo2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import javax.servlet.*;
import javax.servlet.http.*;
import javax.servlet.annotation.*;
import java.io.IOException;

@WebServlet("/RequestDemo2")
public class RequestDemo2 extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
System.out.println("这里是RequestDemo2");
}

@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doGet(request, response);
}
}
  1. 在RequestDemo1的方法中使用req.getRequestDispatcher("/RequestDemo2").forward(req,resp)进行请求转发
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import javax.servlet.*;
import javax.servlet.http.*;
import javax.servlet.annotation.*;
import java.io.IOException;

@WebServlet("/RequestDemo1")
public class RequestDemo1 extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
System.out.println("这里是RequestDemo1");
//加上这行
request.getRequestDispatcher("/RequestDemo2").forward(request, response);
}

@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doGet(request, response);
}
}
  1. 启动测试
    当我们访问RequestDemo1时,控制台会得到如下输出

这里是RequestDemo1
这里是RequestDemo2
说明请求已经转发到了/RequestDemo2

在转发的同时我们还可以传递数据给/RequestDemo2
request对象提供了三个方法:

  • 存储数据到request域[范围,数据是存储在request对象]中
1
void setAttribute(String name,Object o);
  • 根据key获取值
1
Object getAttribute(String name);
  • 根据key删除该键值对
1
void removeAttribute(String name);

接着上个需求来:

  1. 在RequestDemo1的doGet方法中转发请求之前,将数据存入request域对象中.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@WebServlet("/RequestDemo1")
public class RequestDemo1 extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
System.out.println("这里是RequestDemo1");
//存储数据
request.setAttribute("msg","HELLO~");
//请求转发
request.getRequestDispatcher("/RequestDemo2").forward(request, response);
}

@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doGet(request, response);
}
}
  1. 在RequestDemo2的doGet方法从request域对象中获取数据,并将数据打印到控制台.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
@WebServlet("/RequestDemo2")
public class RequestDemo2 extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
System.out.println("这里是RequestDemo2");
Object msg = request.getAttribute("msg");
System.out.println(msg);
}

@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doGet(request, response);
}
}
  1. 启动访问测试,得到的输出结果如下

这里是RequestDemo1
这里是RequestDemo2
HELLO~

此时就可以实现在转发多个资源之间共享数据。

请求转发的特点

  • 浏览器地址栏路径不发生变化
    虽然后台从/这里是RequestDemo1转发到/这里是RequestDemo2,但是浏览器的地址一直是/这里是RequestDemo1,未发生变化
  • 只能转发到当前服务器的内部资源
    不能从一个服务器通过转发访问另一台服务器
  • 一次请求,可以在转发资源间使用request共享数据
    虽然后台从/RequestDemo1转发到/RequestDemo2,但是这个只有一次请求

Response对象

Reponse的继承体系和Request的继承体系也非常相似:
Response的继承体系.png

Response设置响应数据功能介绍

HTTP响应数据总共分为三部分内容,分别是==响应行、响应头、响应体==,对于这三部分内容的数据,respone对象都提供了哪些方法来进行设置?

  1. 响应行
    响应行包含三块内容,分别是 HTTP/1.1[HTTP协议及版本] 200[响应状态码] ok[状态码的描述]
    对于响应头,比较常用的就是设置响应状态码:
1
void setStatus(int sc);
  1. 响应头
    响应头的格式为key:value形式
    设置响应头键值对:
1
void setHeader(String name,String value);
  1. 响应体
    对于响应体,是通过字符、字节输出流的方式往浏览器写,
    获取字符输出流:
1
PrintWriter getWriter();

获取字节输出流

1
ServletOutputStream getOutputStream();

Response请求重定向

  • Response重定向(redirect):一种资源跳转方式。
  1. 浏览器发送请求给服务器,服务器中对应的资源A接收到请求
  2. 资源A现在无法处理该请求,就会给浏览器响应一个302的状态码+location的一个访问资源B的路径(要加上资源B的虚拟目录)
  3. 浏览器接收到响应状态码为302就会重新发送请求到location对应的访问地址去访问资源B
  4. 资源B接收到请求后进行处理并最终给浏览器响应结果,这整个过程就叫重定向
  • 重定向的实现方式
1
2
3
4
response.setStatus(302);
response.setHeader("location","资源B的访问路径");
//或
resposne.sendRedirect("资源B的访问路径");
  • 具体的实现方式
  1. 创建一个ResponseDemo1类,接收/ResponseDemo1的请求,在doGet方法中打印这里是ResponseDemo1
1
2
3
4
5
6
7
8
9
10
11
12
@WebServlet("/ResponseDemo1")
public class ResponseDemo1 extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
System.out.println("这里是ResponseDemo1");
}

@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doGet(request, response);
}
}
  1. 创建一个ResponseDemo2类,接收/ResponseDemo1的请求,在doGet方法中打印这里是ResponseDemo2
1
2
3
4
5
6
7
8
9
10
11
12
@WebServlet("/ResponseDemo2")
public class ResponseDemo2 extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
System.out.println("这里是ResponseDemo2");
}

@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doGet(request, response);
}
}
  1. 在ResponseDemo1的方法中使用response.sendRedirect("/web_demo_war_exploded/RequestDemo2");
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@WebServlet("/ResponseDemo1")
public class ResponseDemo1 extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
System.out.println("这里是ResponseDemo1");
//重定向
response.sendRedirect("/web_demo_war_exploded/RequestDemo2");
}

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

  1. 启动测试,访问/ResponseDemo1,控制台得到如下输出,同时地址栏的地址也变更为/RequestDemo2

这里是RequestDemo1
这里是RequestDemo2

重定向的特点

  • 浏览器地址栏路径发送变化
    当进行重定向访问的时候,由于是由浏览器发送的两次请求,所以地址会发生变化
  • 可以重定向到任何位置的资源(服务内容、外部均可)
    因为第一次响应结果中包含了浏览器下次要跳转的路径,所以这个路径是可以任意位置资源。
  • 两次请求,不能在多个资源使用request共享数据
    因为浏览器发送了两次请求,是两个不同的request对象,就无法通过request对象进行共享数据

介绍完请求重定向请求转发以后,接下来把这两个放在一块对比下:

重定向特点 请求转发特点
浏览器地址栏路径发生变化 浏览器地址栏路径不发生变化
可以重定向到任意位置的资源(服务器内部、外部均可) 只能转发到当前服务器的内部资源
两次请求,不能在多个资源使用request共享数据 一次请求,可以在转发的资源间使用request共享数据

路径问题

  • 问题1:转发的时候路径上没有加虚拟目录web_demo_war_exploded,而重定向加了,那么到底什么时候需要加,什么时候不需要加呢?

  • 其实判断的依据很简单,只需要记住下面的规则即可:

    • 浏览器使用:需要加虚拟目录(项目访问路径)
    • 服务端使用:不需要加虚拟目录

    对于转发来说,因为是在服务端进行的,所以不需要加虚拟目录
    对于重定向来说,路径最终是由浏览器来发送请求,就需要添加虚拟目录。
    掌握了这个规则,接下来就通过一些练习来强化下知识的学习:

1
2
3
4
5
6
7
8
9
10
11
Q:
<a href='路径'>
<form action='路径'>
req.getRequestDispatcher("路径")
resp.sendRedirect("路径")

A:
1.超链接,从浏览器发送,需要加
2.表单,从浏览器发送,需要加
3.转发,是从服务器内部跳转,不需要加
4.重定向,是由浏览器进行跳转,需要加。
  • 问题2:在重定向的代码中,``web_demo_war_exploded`是固定编码的,如果后期通过Tomcat插件配置了项目的访问路径,那么所有需要重定向的地方都需要重新修改,该如何优化?

我们可以去pom.xml配置文件中配置项目的访问地址,然后在代码中动态去获取项目访问的虚拟目录,request对象中提供了getContextPath()方法

配置项目的访问地址,无视掉那个tomcat的插件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<build>
<plugins>
<plugin>
<groupId>org.apache.tomcat.maven</groupId>
<artifactId>tomcat7-maven-plugin</artifactId>
<version>2.2</version>
<configuration>
<path>
/web_demo_war_exploded
</path>
</configuration>
</plugin>
</plugins>
</build>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@WebServlet("/ResponseDemo1")
public class ResponseDemo1 extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
System.out.println("这里是ResponseDemo1");
//获取虚拟目录
String contextPath = request.getContextPath();
//把虚拟目录拼在前面
response.sendRedirect(contextPath + "/RequestDemo2");
}

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

重新启动访问测试,功能依然能够实现,此时就可以动态获取项目访问的虚拟路径,从而降低代码的耦合度。

Response响应字符数据

要想将字符数据写回到浏览器,我们需要两个步骤:

  • 通过Response对象获取字符输出流: PrintWriter writer = resp.getWriter();

  • 通过字符输出流写数据: writer.write(“aaa”);

接下来,我们实现通过些案例把响应字符数据给实际应用下:

  1. 返回一个简单的字符串hello world
1
2
3
4
5
6
7
8
9
10
11
12
13
@WebServlet("/RequestDemo1")
public class RequestDemo1 extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
PrintWriter printWriter = response.getWriter();
printWriter.write("hello world");
}

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

返回一串html字符串,并且能被浏览器解析

  1. 返返回一串html字符串,并且能被浏览器解析,需要注意设置响应数据的编码为utf-8
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@WebServlet("/RequestDemo1")
public class RequestDemo1 extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//设置响应的数据格式及数据的编码
response.setContentType("text/html;charset=utf-8");
PrintWriter printWriter = response.getWriter();
printWriter.write("<h1>你好<h1>");
}

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

Response响应字节数据

要想将字节数据写回到浏览器,我们需要两个步骤:

  • 通过Response对象获取字节输出流:ServletOutputStream outputStream = resp.getOutputStream();

  • 通过字节输出流写数据: outputStream.write(字节数据);

接下来,我们实现通过些案例把响应字符数据给实际应用下:

  1. 返回一个图片文件到浏览器
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
import javax.servlet.*;
import javax.servlet.http.*;
import javax.servlet.annotation.*;
import java.io.FileInputStream;
import java.io.IOException;

@WebServlet("/RequestDemo1")
public class RequestDemo1 extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
FileInputStream fis = new FileInputStream("D:\\background.jpg");
ServletOutputStream os = response.getOutputStream();
byte[] buffer = new byte[1024];
int len = 0;
while ((len = fis.read(buffer)) != -1){
os.write(buffer,0,len);
}
fis.close();
//不需要关闭ServletOutputStream,response会帮我们关闭
}

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

用户注册登录案例

用户登录

需求分析

  1. 用户在登录页面输入用户名和密码,提交请求给LoginServlet
  2. 在LoginServlet中接收请求和数据[用户名和密码]
  3. 在LoginServlt中通过Mybatis实现调用UserMapper来根据用户名和密码查询数据库表
  4. 将查询的结果封装到User对象中进行返回
  5. 在LoginServlet中判断返回的User对象是否为null
  6. 如果为nul,说明根据用户名和密码没有查询到用户,则登录失败,返回"登录失败"数据给前端
  7. 如果不为null,则说明用户存在并且密码正确,则登录成功,返回"登录成功"数据给前端

环境准备

  1. 写一个简单的表单提交
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<!--注意将请求提交给loginServlet-->
<form action="/web_demo_war_exploded/loginServlet" method="post">
用户名:<input type="text" name="username"><br>
密码:<input type="password" name="password"><br>
<input type="submit" value="登录">
</form>
</body>
</html>
  1. 创建db1数据库,创建tb_user表,创建User实体类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
-- 创建数据库
CREATE DATABASE db1;

USE db1;

-- 创建用户表
CREATE TABLE tb_user(
id INT PRIMARY KEY AUTO_INCREMENT,
username VARCHAR(20) UNIQUE,
PASSWORD VARCHAR(32)
);

-- 添加数据
INSERT INTO tb_user(username,PASSWORD) VALUES('zhangsan','123'),('lisi','234');

SELECT * FROM tb_user;

id一定要用Integet类型!!

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
package com.blog.pojo;

public class User {
private Integer id;
private String username;
private String password;

public User() {
}

public User(Integer id, String username, String password) {
this.id = id;
this.username = username;
this.password = password;
}

public Integer getId() {
return id;
}

public void setId(Integer id) {
this.id = id;
}

public String getUsername() {
return username;
}

public void setUsername(String username) {
this.username = username;
}

public String getPassword() {
return password;
}

public void setPassword(String password) {
this.password = password;
}

@Override
public String toString() {
return "User{" +
"id=" + id +
", username='" + username + '\'' +
", password='" + password + '\'' +
'}';
}
}
  1. 导入MyBatis坐标,MySQL驱动坐标
1
2
3
4
5
6
7
8
9
10
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.5</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.34</version>
</dependency>
  1. 创建mybatis-config.xml核心配置文件,UserMapper.xml映射文件,UserMaper接口
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:13306/db1?useSSL=false&amp;useServerPrepStmts=true"/>
<property name="username" value="root"/>
<property name="password" value="PASSWORD."/>
</dataSource>
</environment>
</environments>
<mappers>
<package name="com.blog.mapper"/>
</mappers>
</configuration>
1
2
3
4
5
6
7
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.blog.mapper.UserMapper">

</mapper>
1
2
3
public interface UserMapper {

}

代码实现

  1. 在UserMapper接口中提供一个根据用户名和密码查询用户对象的方法
1
2
3
4
5
6
7
8
9
10
11
12
//由于逻辑比较简单,所以这里用的注解
public interface UserMapper {

/**
* 根据用户名和密码查询是否有此用户
* @param username 用户名
* @param password 密码
* @return 用户
*/
@Select("select * from tb_user where username = #{username} and password = #{password};")
User select(@Param("username") String username, @Param("password") String password);
}
  1. 编写LoginServlet
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
@WebServlet("/loginServlet")
public class LoginServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//设置响应格式和字符编码
response.setContentType("text/html;charset=utf-8");
//1. 接收用户名和密码
String password = request.getParameter("password");
String username = request.getParameter("username");

//2. 调用MyBatis完成查询
//2.1 获取SqlSessionFactory对象
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
//2.2 获取SqlSession对象
SqlSession sqlSession = sqlSessionFactory.openSession();
//2.3 获取mapper
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
//2.4 调用方法
User user = mapper.select(username, password);
//2.5 释放资源
sqlSession.close();
//3. 获取字符输出流,并判断user是否为null
PrintWriter writer = response.getWriter();
if (user != null) {
writer.write("登陆成功");
} else {
writer.write("登陆失败");
}
}

@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doGet(request, response);
}
}
  1. 启动服务器测试
    如果输入用户名或密码错误,则显示登陆失败
    用户名和密码正确,则登录成功

可能遇到的问题:java.lang.NoClassDefFoundError: org/apache/ibatis/io/Resources
去看看Tomcat的lib目录下有没有mybatis的jar包,如果没有则导入一个

至此,一个极其简易的用户登录的功能就完成了

用户注册

需求分析

  1. 用户在注册页面输入用户名和密码,提交请求给RegisterServlet
  2. 在RegisterServlet中接收请求和数据[用户名和密码]
  3. 在RegisterServlet中通过Mybatis实现调用UserMapper来根据用户名查询数据库表
  4. 将查询的结果封装到User对象中进行返回
  5. 在RegisterServlet中判断返回的User对象是否为null
  6. 如果为nul,说明根据用户名可用,则调用UserMapper来实现添加用户
  7. 如果不为null,则说明用户不可以,返回"用户名已存在"数据给前端

代码编写

  1. 编写一个注册页面
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
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>欢迎注册</h1>
<a href="login.html">已有账号?点击登录</a>
<form action="/web_demo_war_exploded/registerServlet" method="post">
<table>
<tr>
<td>用户名:</td>
<td><input type="text" name="username" id="username"></td>
<br>
<span id="username_arr" style="display: none">用户名已被占用</span>
</tr>
<tr>
<td>密码:</td>
<td><input type="password" name="password" id="password"></td>
</tr>
</table>
<input type="submit" value="注册">
</form>
</body>
</html>
  1. 编写UserMapper提供根据用户名查询用户数据方法和添加用户方法
1
2
3
4
5
@Select("select * from tb_user where username = #{username};")
User selectByUserName(@Param("username") String username);

@Insert("insert into tb_user(username, password)VALUES (#{username},#{password});")
void add(User user);
  1. 创建RegisterServlet类
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
@WebServlet("/registerServlet")
public class RegisterServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//设置响应格式和字符编码
response.setContentType("text/html;charset=utf-8");
//获取注册信息
String password = request.getParameter("password");
String username = request.getParameter("username");
//封装用户对象
User user = new User();
user.setUsername(username);
user.setPassword(password);
//获取SqlSessionFactory对象
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
//获取SqlSession对象
SqlSession sqlSession = sqlSessionFactory.openSession();
//获取mapper
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
//调用方法
User tmp = mapper.selectByUserName(username);
//获取字符输出流
PrintWriter writer = response.getWriter();
//判断用户名是否已经存在
if (tmp == null) {
mapper.add(user);
//提交事务
sqlSession.commit();
//释放资源
sqlSession.close();
//提示信息
writer.write("注册成功");
} else {
//提示信息
writer.write("用户名已存在");
}
}

@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doGet(request, response);
}
}
  1. 启动服务器进行测试
    如果测试成功,则在数据库中就能查看到新注册的数据
    如果用户已经存在,则在页面上展示 用户名已存在 的提示信息

SqlSessionFactory工具类抽取

上面两个功能已经实现,但是在写Servlet的时候,因为需要使用Mybatis来完成数据库的操作,所以对于Mybatis的基础操作就出现了些重复代码,如下

1
2
3
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

有了这些重复代码就会造成一些问题:

  • 重复代码不利于后期的维护
  • SqlSessionFactory工厂类进行重复创建
    • 就相当于每次买手机都需要重新创建一个手机生产工厂来给你制造一个手机一样,资源消耗非常大但性能却非常低。所以这么做是不允许的。

那如何来优化呢?

  • 代码重复可以抽取工具类
  • 对指定代码只需要执行一次可以使用静态代码块
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;

import java.io.IOException;
import java.io.InputStream;

public class SqlSessionFactoryUtils {
private static SqlSessionFactory sqlSessionFactory;
static {
try {
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
} catch (IOException e) {
throw new RuntimeException(e);
}
}

public static SqlSessionFactory getSqlSessionFactory(){
return sqlSessionFactory;
}
}

工具类抽取以后,以后在对Mybatis的SqlSession进行操作的时候,就可以直接使用

1
SqlSessionFactory sqlSessionFactory =SqlSessionFactoryUtils.getSqlSessionFactory();

这样就可以很好的解决上面所说的代码重复和重复创建工厂导致性能低的问题了。

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
@WebServlet("/registerServlet")
public class RegisterServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
response.setContentType("text/html;charset=utf-8");
String password = request.getParameter("password");
String username = request.getParameter("username");

User user = new User();
user.setUsername(username);
user.setPassword(password);
// String resource = "mybatis-config.xml";
// InputStream inputStream = Resources.getResourceAsStream(resource);
// SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSessionFactory sqlSessionFactory = SqlSessionFactoryUtils.getSqlSessionFactory();
SqlSession sqlSession = sqlSessionFactory.openSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
User tmp = mapper.selectByUserName(username);
PrintWriter writer = response.getWriter();
if (tmp == null) {
mapper.add(user);
sqlSession.commit();
sqlSession.close();
writer.write("注册成功");
} else {
writer.write("用户名已存在");
}
}

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