文章目录
- 02【Http、Request】
- 一、HTTP协议
- 1.1 HTTP协议概述
- 1.1.1 HTTP协议的概念
- 1.1.2 HTTP协议的特点:
- 2.1 HTTP请求的组成
- 2.1.1 请求行
- 2.1.2 请求头
- 2.1.3 请求体
- 二、HttpServletRequest对象
- 2.1 HttpServletRequest对象简介
- 2.2 HttpServletRequest的使用
- 2.2.1 请求行相关的方法
- 2.2.2 请求头相关的方法
- 1)请求头相关方法的使用
- 2)防盗链案例
- 2.2.3 请求参数相关方法
- 2.3 BeanUtils的使用
- 2.3.1 什么是BeanUtils
- 2.3.2 BeanUtils的使用
- 2.4 参数的乱码问题
- 三、请求域有关的方法
- 3.1 作用域介绍
- 3.2 作用域的使用
- 3.2.1 作用域相关的方法
- 3.2.2 作用域操作
- 3.3 RequestDispatcher分发器
- 四、转发与重定向
- 4.1 转发
- 4.1.1 转发的概念
- 4.1.2 转发相关的方法
- 4.1.3 转发的特点
- 4.2 重定向
- 4.2.1 重定向的概念
- 4.2.2 重定向的方法
- 4.2.3 重定向的特点
- 4.3 重定向和转发的区别
- 4.3.1 测试重定向请求域数据丢失
- 4.3.2 转发和重定向后面的代码是否会执行
- 五、案例: 实现登录功能
- 5.1 案例步骤
- 5.2 案例流程图
- 5.3 实现步骤
- 5.3.1 准备登录页面:
- 5.3.2 失败页面:
- 5.3.3 创建User表
- 5.3.4 导入相关jar包
- 5.3.5 创建实体类User
- 5.3.6 配置文件druid.properties:
- 5.3.7 工具类DataSourceUtils:
- 5.3.8 数据层代码
- 5.3.9 业务层代码
- 5.3.10 控制器层代码:
- 5.3.11 SuccessServlet
02【Http、Request】
一、HTTP协议
1.1 HTTP协议概述
1.1.1 HTTP协议的概念
在BS架构中,浏览器通过HTTP协议访问服务器。它是一种应用层协议,运行在TCP传输层协议之上。HTTP作用是指定数据传输格式,全称:Hyper Text Transfer Protocol
超文本传输协议
Tips:HTTP协议的默认端口为80
HTTP是通信双方(客户端、服务器)都需要遵守的协议标准;
当浏览器需要发送数据(请求)到服务器时,浏览器首先会将请求的数据封装成一个合格的HTTP请求报文,然后将其发送给服务器;该报文包含客户端用户所携带的一些普通数据,如用户名、密码等,也包含HTTP请求报文中所固有的一些数据,如请求方式、连接状态等
服务器接收到HTTP请求报文后,将会按照HTTP请求报文的格式进行解析,来提取浏览器发送过来的关键数据,进行业务逻辑处理,当服务器需要响应数据到客户端时,首先会将我们的响应数据封装成一个合格的HTTP响应报文,该报文包含本次响应的一些业务数据,如响应的一个html页面、一个Java对象的值、响应的图片等,也包含HTTP响应报文中所固有的一些数据,如响应状态码、本次响应的数据类型;
1.1.2 HTTP协议的特点:
- 1)HTTP协议组成:由请求报文和响应报文组成
- 2)是一种无状态协议,不记录用户访问状态。同一个用户在同一个浏览器上发送的多次请求,服务器并不能通过HTTP协议来判断是否是同一个用户。
- 3)因为HTTP协议不记录用户的状态信息,所以它的传输效率相对比较高。
2.1 HTTP请求的组成
一个完整的HTTP请求封装了浏览器发送给服务器的数据和信息,我们学习HTTP请求就是学习HTTP协议中到底封装了什么数据发送给服务器;
一个完整的HTTP请求包含请求行、请求头、请求体;
编写测试代码
- LoginServlet:
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;
/**
* @author lscl
* @version 1.0
* @intro:
*/
@WebServlet("/demo01")
public class Demo01Servlet extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.getWriter().write("ok");
}
}
- demo01.html:
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>登录页面</title>
</head>
<body>
<h2>GET方式提交</h2>
<form action="/login" method="get">
用户名:
<input type="text" name="username"><br/>
密 码:
<input type="password" name="pwd"><br/>
<input type="submit" value="登录">
</form>
<hr>
<h2>POST方式提交</h2>
<form action="/login" method="post">
用户名:
<input type="text" name="username"><br/>
密码:
<input type="password" name="password"><br/>
<input type="submit" value="登录">
</form>
</body>
</html>
打开F12,抓包查看请求详情:
2.1.1 请求行
一个完整的请求行包括请求的方式、请求的地址、协议和版本
POST
:请求的方式/login
: 请求的地址HTTP/1.1
协议和版本
- GET和POST请求的区别:
POST方式 | GET方式 | |
---|---|---|
地址栏 | 参数不会在地址栏上显示出来 | 参数会显示出来,以查询字符串的方式发送数据 |
大小 | 理论上没有大小限制,使用流的方式来发送数据。 | 受浏览器限制,通常是1K |
安全性 | POST数据在请求体中发送,安全性要高一些 | 数据在请求行中发送,安全性要低一些 |
缓存 | 不使用缓存 | 如果静态资源在浏览器端已经访问过,下次可能会使用缓存,如果使用缓存状态码是304 |
如果是GET方式请求的服务器,那么数据将会携带在地址栏,并且GET方式的请求没有请求体:
Tips:如果在浏览器上直接输入地址访问的服务器,则使用的是GET方式
2.1.2 请求头
请求头 | 描述 | 示例 |
---|---|---|
Accept | 描述客户端希望接收的响应body 数据类型 | Accept: text/plain, text/html |
Accept-Encoding | 浏览器支持的web服务器返回内容压缩编码类型。 | Accept-Encoding: compress, gzip |
Connection | 表示是否需要持久连接。 | Connection: close |
Cookie | 本次请求携带的Cookie值 | Cookie: user=xiaohui; sex=man; |
Content-Length | 请求的内容长度 | Content-Length: 348 |
Content-Type | 请求的MIME信息(请求的内容的类型) | Content-Type: application/x-www-form-urlencoded |
Host | 请求的服务器的域名和端口号 | Host: localhost:8080 |
Referer | 获取从哪个页面跳转到当前页面来的 得到上一个访问的页面 | Referer: http://localhost:8080/demo01.html |
User-Agent | 请求的用户信息 | User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) |
查看请求头:
Tips:不是每次请求都会显示所有的请求头
2.1.3 请求体
请求体就是本次请求携带的数据(用户自己的数据);
-
GET方法发送数据的时候没有请求体,数据是在地址栏发送,没有请求内容的长度。
-
POST方法发送数据在请求体中发送,有请求内容的长度。
内容类型表示,以键值对的方式发送表单的数据,如:name=xiaohui
关于GET和POST请求的Content-type:
Query String Parameters
:当发起一次GET请求时,参数会以url string的形式进行传递。即?
后的字符串则为其请求参数,并以&
作为分隔符。Form Data
:当发起一次POST请求时,若未指定content-type,则默认content-type为application/x-www-form-urlencoded。即参数会以Form Data的形式进行传递,不会显式出现在请求url中。Request Payload
:当发起一次POST请求时,若content-type为application/json,则参数会以Request Payload的形式进行传递(显然的,数据格式为JSON),不会显式出现在请求url中。
二、HttpServletRequest对象
2.1 HttpServletRequest对象简介
HttpServletRequest是ServletRequest接口的子接口,代表一个请求对象,用来封装所有从浏览器发送给服务器的数据
我们之前介绍HTTP报文时说到,HTTP报文分为请求报文和响应报文,请求报文中包含有请求行、请求头、请求体等数据,HttpServletRequest则是对HTTP请求报文的封装,通过HttpServletRequest对象,可以获取请求报文中的数据,也可以使用该对象设置一些HTTP请求的数据
2.2 HttpServletRequest的使用
2.2.1 请求行相关的方法
- String getMethod():得到请求的方式(GET或POST等)
- String getRequestURI():得到请求的URI
/demo1
只是地址的一部分,Uniform Resource Identifer 统一资源标识符 - StringBuffer getRequestURL():得到请求的URL http://localhost:8080/demo1 Uniform Resource Locator 统一资源定位符
- String getProtocol():得到请求行中的协议和版本 HTTP/1.1
- String getContextPath():得到当前项目的访问地址 /02_Request
- String getRemoteAddr():得到客户端的IP地址 127.0.0.1
- String getServletPath():得到当前Servlet的访问地址 /demo1
【案例】
创建一个Servlet,用于获取请求行中相关信息的方法,并且输出到网页上。
创建项目,并部署到Tomcat中:
访问:http://localhost:8080/02_request/demo01
- 效果:
- 案例代码:
package com.dfbz.request;
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;
import java.io.PrintWriter;
/**
* @author lscl
* @version 1.0
* @intro:
*/
@WebServlet("/demo01")
public class Demo01Servlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//调用doGet方法
this.doGet(req,resp);
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 设置响应的类型
response.setContentType("text/html;charset=utf-8");
PrintWriter out = response.getWriter();
out.print("得到请求的方式:" + request.getMethod() + "<hr>");
out.print("得到请求的URI:" + request.getRequestURI() + "<hr>");
out.print("得到请求的URL:" + request.getRequestURL() + "<hr>");
out.print("得到请求的协议和版本:" + request.getProtocol() + "<hr>");
out.print("得到当前项目的访问地址:" + request.getContextPath() + "<hr>");
out.print("得到客户端的IP地址:" + request.getRemoteAddr() + "<hr>");
out.print("得到当前Servlet的访问地址:" + request.getServletPath() + "<hr>");
}
}
2.2.2 请求头相关的方法
1)请求头相关方法的使用
请求头中是由各种键值对组成,通过键得到值,相关方法如下:
请求方法 | 功能描述 |
---|---|
String getHeader(String headName) | 通过键的名字得到相应的值,返回字符串类型的值 |
Enumeration<String> getHeaderNames() | 得到所有的请求头中的键名字,返回枚举类型。 枚举类型类似于迭代器,可以理解为是一个集合 |
java.util.Enumeration接口中方法 | 说明 |
boolean hasMoreElements() | 判断枚举中是否还有下一个元素,如果有返回true |
E nextElement() | 得到某个元素,并且下移一行 |
【案例】
编写一个Servlet得到所有的请求头信息,并输出所有的请求值信息。
访问:http://localhost:8080/02_request/demo02
- 效果:
- 代码:
package com.dfbz.request;
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;
import java.io.PrintWriter;
import java.util.Enumeration;
/**
* @author lscl
* @version 1.0
* @intro:
*/
@WebServlet("/demo02")
public class Demo02Servlet extends HttpServlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 设置响应类型
response.setContentType("text/html;charset=utf-8");
PrintWriter out = response.getWriter();
out.print("得到host的值:" + request.getHeader("host") + "<hr>");
//得到所有的键(名字)
Enumeration<String> headerNames = request.getHeaderNames();
while(headerNames.hasMoreElements()) { //判断是否还有下一个元素
String name = headerNames.nextElement(); //得到每个元素
out.print("请求头名字:" + name + "==> 请求头值:" + request.getHeader(name) + "<hr>");
}
}
}
2)防盗链案例
【案例】
当我们去访问某个页面的时候,首先会跳转到一个广告页面,看了广告以后才可以访问资源。
原理:通过得到请求头Referer,获取当前页面的从哪里来的。
- 如果为null,表示没有上一个页面。
- 如果不为null,并且是从广告页面过来的,则可以继续访问。
- 否则,表示没有访问广告,跳转到广告页面。
- 页面:adv.html 是广告页面
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>这是广告页面哦!</h1>
<a href="demo03">去资源页面</a>
</body>
</html>
- Demo03Servlet:
package com.dfbz.request;
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;
import java.io.PrintWriter;
/**
* @author lscl
* @version 1.0
* @intro: 这是web资源,必须先访问广告才可以访问
*/
@WebServlet("/demo03")
public class Demo03Servlet extends HttpServlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 设置响应的类型和编码
response.setContentType("text/html;charset=utf-8");
// 获取项目的访问路径
String contextPath = request.getContextPath();
// 获取上一个页面访问地址
String referer = request.getHeader("referer");
// 如果为空,表示直接访问当前的Servlet,没有上一个页面
if (referer == null) {
// 说明还没有看过广告,重定向到广告页面去
response.sendRedirect("http://localhost:8080" + contextPath + "/adv.html");
}
// 有上一个页面,但不是广告页面,盗链
else if (referer != null && !referer.contains("http://localhost:8080" + contextPath + "/adv.html")) {
//重定向到广告页面去
response.sendRedirect("http://localhost:8080" + contextPath + "/adv.html");
}
// 如果有上一个页面,而且是广告页面,可以访问资源
else {
response.sendRedirect("/index.html");
}
}
}
- 资源页面:index.html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>登录页面</title>
</head>
<body>
<h1>web资源....</h1>
</body>
</html>
- 其它的页面:other.html盗链页面
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<a href="demo03">进入网站首页</a>
</body>
</html>
2.2.3 请求参数相关方法
方法名 | 描述 |
---|---|
String getParameter(String name) | 通过参数的名字得到参数值 |
String[] getParameterValues(String name) | 得到所有同名的参数值,一般用复选框。 返回一个字符串数组 |
Enumeration<String> getParameterNames() | 得到所有的参数名字,返回枚举类型 |
Map<String,String[]> getParameterMap() | 得到所有参数名和值,封装成Map对象 键就是参数名字,值就是参数的值 |
【案例】
准备一个表单,将数据提交到后端的servlet,在servlet中获取前端提交的参数;
- 效果:
- 准备register.htm页面:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h2>用户注册</h2>
<form action="demo04" method="post">
用户名: <input type="text" name="name"><hr/>
<!-- 提交的是value值 -->
性别: <input type="radio" name="gender" value="0" checked="checked"/>男
<input type="radio" name="gender" value="1"/>女 <hr/>
城市:
<select name="city">
<option value="ZhaoQing">肇庆</option>
<option value="YangJiang">阳江</option>
<option value="HuiZhou">惠州</option>
</select>
<hr/>
爱好:
<input type="checkbox" name="hobby" value="chang"/>唱
<input type="checkbox" name="hobby" value="tiao"/>跳
<input type="checkbox" name="hobby" value="rap"/>rap
<input type="checkbox" name="hobby" value="lanqiu"/>篮球
<hr/>
<input type="submit" value="注册"/>
</form>
</body>
</html>
- 准备一个servlet来接收前端提交的参数:Demo04Servlet
package com.dfbz.request;
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;
import java.io.PrintWriter;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.Map;
import java.util.Set;
/**
* @author lscl
* @version 1.0
* @intro:
*/
@WebServlet("/demo04")
public class Demo04Servlet extends HttpServlet {
// 处理Post方法
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 解决汉字乱码问题
request.setCharacterEncoding("utf-8"); // tomcat默认采用ISO-8859-1做编码
response.setContentType("text/html;charset=utf-8");
PrintWriter out = response.getWriter();
//得到某个参数值
out.print("得到用户名:" + request.getParameter("name") + "<hr>");
//得到一组同名的值
String[] hobbies = request.getParameterValues("hobby");
out.print("爱好是:" + Arrays.toString(hobbies) + "<hr>");
//得到所有参数的名字
Enumeration<String> names = request.getParameterNames();
while (names.hasMoreElements()) {
//其中的一个名字
String name = names.nextElement();
out.print("参数名:" + name + ", 参数值:" + request.getParameter(name) + "<hr>");
System.out.println("参数名:" + name + ", 参数值:" + request.getParameter(name) + "<hr>");
}
//得到所有的键和值
Map<String, String[]> map = request.getParameterMap();
Set<Map.Entry<String, String[]>> entries = map.entrySet();
//遍历set集合
for (Map.Entry<String, String[]> entry : entries) {
out.print(entry.getKey() + "=" + Arrays.toString(entry.getValue()) + "<hr>");
}
}
}
2.3 BeanUtils的使用
2.3.1 什么是BeanUtils
BeanUtils是Apache Commons组件的成员之一,主要用于简化JavaBean封装数据的操作。
BeanUtils是第三方组织写的工具类,要使用它,得先下载对应的工具jar包。
-
下载地址:http://commons.apache.org/
-
相关的jar包
相关的包 | 说明 |
---|---|
commons‐beanutils‐1.9.3.jar | BeanUtils工具的核心包 |
commons‐logging‐1.2.jar | 用于日志记录功能 |
commons‐collections‐3.2.2.jar | 公共的集合增强的包 |
2.3.2 BeanUtils的使用
BeanUtils常用方法 | 说明 |
---|---|
public static void populate(Object bean, Map<String, ? extends Object> properties) | 作用:将Map中所有的键和值封装到bean的同名的属性中,如果不同的名字会忽略。 参数: bean: 目标对象,要复制到的对象 properties: 源对象,有数据的Map象 |
web项目jar包所在的目录:
【案例】
使用BeanUtils将前端提交的参数封装为一个Java对象;
- 案例效果:
- 准备JavaBean
package com.dfbz.entity;
import java.util.Arrays;
public class Student {
private String name;
private String gender;
private String city;
private String hobby[];
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", gender='" + gender + '\'' +
", city='" + city + '\'' +
", hobby=" + Arrays.toString(hobby) +
'}';
}
public Student() {
}
public Student(String name, String gender, String city, String[] hobby) {
this.name = name;
this.gender = gender;
this.city = city;
this.hobby = hobby;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getGender() {
return gender;
}
public void setGender(String gender) {
this.gender = gender;
}
public String getCity() {
return city;
}
public void setCity(String city) {
this.city = city;
}
public String[] getHobby() {
return hobby;
}
public void setHobby(String[] hobby) {
this.hobby = hobby;
}
}
- 准备一个新的Servlet来接收表单提交的数据:
package com.dfbz.request;
import com.dfbz.entity.Student;
import org.apache.commons.beanutils.BeanUtils;
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;
import java.util.Map;
/**
* @author lscl
* @version 1.0
* @intro:
*/
@WebServlet("/demo05")
public class Demo05Servlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doPost(req,resp);
}
//处理Post方法
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//解决汉字乱码问题
request.setCharacterEncoding("utf-8");
// 设置响应内容的类型
response.setContentType("text/html;charset=utf-8");
Map<String, String[]> map = request.getParameterMap();
// 创建Student对象
Student user = new Student();
//使用BeanUtils工具将表单中所有的数据封装成User对象
try {
BeanUtils.populate(user, map);
} catch (Exception e) {
e.printStackTrace();
}
response.getWriter().println("用户对象:" + user);
}
}
BeanUtils参数封装图解:
BeanUtils可以将Map<String,String[]>类型的数据映射到一个Java对象中,其中Map的key为Java对象属性名,Value为该属性的值;当值为String或String[]时都可以将其映射到Java对象的String或String[]上;
我们现在将Student的bobby改为String类型:
package com.dfbz.entity;
/**
* @author lscl
* @version 1.0
* @intro:
*/
public class Student {
private String name;
private String gender;
private String city;
// 改为String类型而不是String[]类型
private String hobby;
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", gender='" + gender + '\'' +
", city='" + city + '\'' +
", hobby='" + hobby + '\'' +
'}';
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getGender() {
return gender;
}
public void setGender(String gender) {
this.gender = gender;
}
public String getCity() {
return city;
}
public void setCity(String city) {
this.city = city;
}
public String getHobby() {
return hobby;
}
public void setHobby(String hobby) {
this.hobby = hobby;
}
}
默认情况下,只会获取第一个参数;
提交之后的效果:
2.4 参数的乱码问题
Tomcat中默认对使用POST方法提交的参数使用是ISO-8859-1编码,这是一个欧洲码表,不支持汉字。
-
POST方式乱码解决方案:
- 1)方法:
request.setCharacterEncoding("utf-8")
,告诉tomcat,post方法使用utf-8进行解码。
Tips:上面这句话代码位置,必须放在所有得到参数的代码之前。
- 2)必须与页面的编码相同, 如果提交的页面是GBK,则这里也要使用GBK。
- 1)方法:
在Tomcat8以后,GET方法参数是在地址栏发送,那么默认就是UTF-8编码,并且Tomcat解析地址栏中的数据也是以UTF-8解码,所以GET方法汉字没有乱码的问题。
三、请求域有关的方法
3.1 作用域介绍
-
什么是作用域:用于在不同的Servlet之间共享数据区域,这个区域在服务器的内存中。底层的结构是一个Map对象,由键和值组成,键是String,值是Object,作用域中可以存放任意的类型。
-
Servlet中四个作用域:页面域page、请求域request,会话域session,上下文域ServletContext
-
请求域的范围:数据只能在同一个请求中共享,请求结束或其它的请求不能访问其中的数据。
3.2 作用域的使用
3.2.1 作用域相关的方法
request与域有关的方法 | 作用 |
---|---|
void setAttribute(“键”,Object数据) | 向请求域中添加键和值 |
Object getAttribute(“键”) | 从请求域中获取一个值,返回Object类型 |
void removeAttribute(“键”) | 从请求域中删除一个键值对 |
3.2.2 作用域操作
【案例】
OneServlet创建一个键和值,转发到另一个TwoServlet,从TwoServlet中取出键和值,并且输出。使用转发可以将一个Servlet中的请求转到另一个Servlet中
- 案例效果:
- OneServlet:
package com.dfbz.request;
import javax.servlet.RequestDispatcher;
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("/one")
public class OneServlet extends HttpServlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 往请求域对象里面设置值
request.setAttribute("user", "Jack");
// 获取一个转发器,并指定要转发的地址
RequestDispatcher dispatcher = request.getRequestDispatcher("/two");
// 进行转发
dispatcher.forward(request,response);
}
}
- TwoServlet:
package com.dfbz.request;
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;
import java.io.PrintWriter;
@WebServlet("/two")
public class TwoServlet extends HttpServlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 设置响应的格式
response.setContentType("text/html;charset=utf-8");
// 获取打印字符流流(指向的是客户端)
PrintWriter out = response.getWriter();
//从请求域中取出键和值
String user = (String) request.getAttribute("user");
// 向客户端打印字符
out.print("在two中输出user值:" + user + "<br>");
}
}
3.3 RequestDispatcher分发器
在上述案例中,我们使用RequestDispatcher分发器可以当请求进行转发,让当前请求指向一个地址;使用RequestDispatcher也可以将这个请求的结果包含到此次请求中;
forward(ServletRequest request, ServletResponse Response)
:将请求跳转到分发器指定的地方;include(ServletRequest request, ServletResponse Response)
:收集分发器指定的请求的响应结果;
【案例】
定义ArticleServlet:
package com.dfbz.dispatcher;
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;
import java.io.PrintWriter;
@WebServlet("/article")
public class ArticleServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String article = "怒发冲冠,凭栏处、潇潇雨歇。<hr />抬望眼、仰天长啸,壮怀激烈。<hr />三十功名尘与土,八千里路云和月。<hr />莫等闲、白了少年头,空悲切。<hr />靖康耻,犹未雪。<hr />臣子恨,何时灭。<hr />驾长车,踏破贺兰山缺。<hr />壮志饥餐胡虏肉,笑谈渴饮匈奴血。<hr />待从头、收拾旧山河,朝天阙<hr>";
// 设置响应类型以及编码
response.setContentType("text/html;charset=utf8");
// 获取字符打印流
PrintWriter writer = response.getWriter();
writer.println(article);
}
}
- 定义Demo07Servlet:
package com.dfbz.dispatcher;
import javax.servlet.RequestDispatcher;
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;
import java.io.PrintWriter;
@WebServlet("/demo07")
public class Demo07Servlet extends HttpServlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 设置响应类型以及编码
response.setContentType("text/html;charset=utf-8");
// 获取字符打印流
PrintWriter writer = response.getWriter();
writer.println("文章内容如下: <hr />");
// 获取分发器,关联/article请求的内容
RequestDispatcher dispatcher = request.getRequestDispatcher("/article");
dispatcher.include(request, response);
}
}
访问:http://localhost:8080/02_request/demo07
四、转发与重定向
转发与重定向实现的功能是相同的,用于从一个页面跳转到另一个页面。但是从内部细节上有很大的不同;
4.1 转发
4.1.1 转发的概念
- 概念:由服务器进行的页面跳转,因此也叫服务器内部转发;
客户端访问资源/demo01
,服务器发现客户端想要的资源不在/demo01
于是自己将请求转发到/demo02
,这个过程客户端无感知;客户端只请求了一次;
原理图:
4.1.2 转发相关的方法
1)首先获取转发器(分发器)request.getRequestDispatcher(“/转发的地址”)
2)使用转发器的forward方法进行转发
4.1.3 转发的特点
1)转发是服务器内部的行为,因此在转发时,客户端请求的地址不会发生变化,即使服务器内部已经跳转了好几次了,但是客户端访问的地址依旧不变;
2)整个转发过程中,客户端从始至终只发送了一次请求;
3)由于在整个转发过程中,客户端只发送了一次请求,因此请求域的数据不会失效;
4)转发是服务器内部的行为,我们因此我们不需要加上项目名;例如:request.getRequestDispatcher(“/demo01”)
4.2 重定向
4.2.1 重定向的概念
-
概念:客户端根据服务器反馈的信息再次请求服务器;
-
原理图:
客户端首先访问资源/demo01
,服务器发现想要的资源不在demo01这里,于是告诉(sendRedirect)客户端:"你要的资源不在我这里,你去访问/demo02
"吧!于是客户端再次访问资源/demo02
;在重定向中,客户端已经请求了两次服务器;
4.2.2 重定向的方法
- response.sendRedirect(“访问的地址”)
4.2.3 重定向的特点
1)由于客户端重定向是客户端再次请求,因此客户端的地址栏的地址已经发生了变化;
2)客户端请求了两次服务器
3)在整个重定向过程中,客户端发送了两次请求,请求域中的数据丢失;
4)重定向是客户端行为(客户端再次请求),我们需要加上项目名;例如:sendRedirect("request.getContextPath()+/demo01")
Tips:如果需要保留请求域中的数据,使用转发,否则使用重定向。
4.3 重定向和转发的区别
区别 | 转发 | 重定向 |
---|---|---|
根目录 | 采用服务器根目录:/02_request/ | 采用客户端根目录:http://localhost:8080/ |
客户端地址栏 | 不会变化 | 会 |
哪里跳转 | 由服务器进行的跳转 | 由客户端浏览器进行的跳转 |
请求域 | 不会丢失数据 | 会,因为不是同一次请求 |
4.3.1 测试重定向请求域数据丢失
- Demo08Servlet:
往域对象设置值
package com.dfbz.request;
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("/demo08")
public class Demo08Servlet extends HttpServlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 往请求域对象里面设置值
request.setAttribute("user", "Jack");
// 重定向到demo09
response.sendRedirect("/02_request/demo09");
}
}
- Demo09Servlet:
取出域对象的值
package com.dfbz.request;
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("/demo09")
public class Demo09Servlet extends HttpServlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 取出域对象的值
String user = (String) request.getAttribute("user");
// 设置响应的类型和编码
response.setContentType("text/html;charset=utf8");
response.getWriter().println("域对象的值: " + user);
}
}
访问:http://localhost:8080/02_request/dem08
将重定向改为转发:
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 往请求域对象里面设置值
request.setAttribute("user", "Jack");
// 重定向到demo09
// response.sendRedirect("/02_request/demo09");
// 转发到demo09(转发采用服务端的根路径)
request.getRequestDispatcher("/demo09").forward(request,response);
}
}
重启服务器,访问:http://localhost:8080/02_request/demo08
4.3.2 转发和重定向后面的代码是否会执行
修改Demo08Servlet代码,分别测试转发和重定向后面的代码是否会执行:
package com.dfbz.request;
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("/demo08")
public class Demo08Servlet extends HttpServlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 往请求域对象里面设置值
request.setAttribute("user", "Jack");
// 重定向到demo09
// response.sendRedirect("/02_request/demo09");
// 转发到demo09(转发采用服务端的根路径)
request.getRequestDispatcher("/demo09").forward(request,response);
System.out.println("我执行了吗?");
}
}
Tips:不管是转发还是重定向后面的代码依旧会执行!
五、案例: 实现登录功能
5.1 案例步骤
-
用户名和密码正确,将用户成功的信息保存在请求域中,转发到另一个页面,显示用户登录成功
-
用户名和密码错误,重定向到另一个html页面,显示登录失败。
-
使用表示层,业务层,数据访问层的三层架构实现
-
访问数据库Jdbc和连接池Druid
5.2 案例流程图
1)控制器层
- 1)需要接受前端传递的参数
- 2)调用业务逻辑层,做业务处理,数据分析、统计报表、用户行为分析、大数据处理
- 3)根据业务逻辑层反馈的信息来决定跳转到哪个页面
2)业务逻辑层
- 1)接受控制器层传递过来的参数
- 2)调用数据层查询数据库中的结果
- 3)进行判断、业务处理
- 4)将结果返回给控制器层
3)数据层
- 1)将业务逻辑成传递过来的参数变为SQL,查询数据库
5.3 实现步骤
5.3.1 准备登录页面:
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>用户登录</title>
</head>
<body>
<form action="login" method="post">
<table>
<tr>
<td>用户名</td>
<td><input type="text" name="username"/></td>
</tr>
<tr>
<td>密码</td>
<td><input type="password" name="password"/></td>
</tr>
<tr>
<td colspan="2"><input type="submit" value="登录"/></td>
</tr>
</table>
</form>
</body>
</html>
5.3.2 失败页面:
- failure.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>用户名或密码错误!</h1>
</body>
</html>
5.3.3 创建User表
create table `user`(
id int primary key auto_increment,
username varchar(20),
password varchar(32)
);
insert into `user`(username, password) values ('admin','123'),('root','456');
select * from `user`;
5.3.4 导入相关jar包
5.3.5 创建实体类User
package com.dfbz.entity;
public class User {
private int id;
private String username;
private String password;
public User() {
}
public User(int id, String username, String password) {
this.id = id;
this.username = username;
this.password = password;
}
public int getId() {
return id;
}
public void setId(int 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 + ", pas=" + password + "]";
}
}
5.3.6 配置文件druid.properties:
复制到src目录下
driverClassName=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/test
username=root
password=admin
5.3.7 工具类DataSourceUtils:
package com.dfbz.utils;
import com.alibaba.druid.pool.DruidDataSourceFactory;
import javax.sql.DataSource;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Properties;
public class DataSourceUtils {
//创建私有静态数据源成员变量
private static DataSource ds;
//在静态代码块中创建连接池
static {
//创建属性对象
Properties info = new Properties();
try (InputStream in = DataSourceUtils.class.getResourceAsStream("/druid.properties")) {
//从类路径下加载属性文件,得到输入流对象
info.load(in);
//通过工厂类创建一个数据源
ds = DruidDataSourceFactory.createDataSource(info);
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 得到数据源
* @return
*/
public static DataSource getDataSource() {
return ds;
}
/**
* 得到连接对象
* @return
* @throws SQLException
*/
public static Connection getConnection() throws SQLException {
return ds.getConnection();
}
}
5.3.8 数据层代码
数据层负责查询数据库
package com.dfbz.dao;
import com.dfbz.entity.User;
import com.dfbz.utils.DataSourceUtils;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
public class UserDao {
/**
* 根据用户名查询用户
*
* @param username
* @param password
* @return
*/
public User findByUsername(String username,String password) {
// 获取数据库连接
try {
Connection conn = DataSourceUtils.getConnection();
String sql = "select * from user where username=? and password=?";
// 获取PreparedStatement对象
PreparedStatement ps = conn.prepareStatement(sql);
// 给占位符设置值
ps.setString(1, username);
ps.setString(2, password);
ResultSet rs = ps.executeQuery();
// 如果有记录
if (rs.next()) {
// 封装成一个User对象
User user = new User(
rs.getInt("id"),
rs.getString("username"),
rs.getString("password")
);
return user;
}
} catch (SQLException throwables) {
throwables.printStackTrace();
}
return null;
}
}
5.3.9 业务层代码
package com.dfbz.service;
import com.dfbz.dao.UserDao;
import com.dfbz.entity.User;
public class UserService {
private UserDao userDao=new UserDao();
/**
* 调用数据层去数据库查询
* @param username
* @param password
* @return
*/
public User login(String username,String password) {
User user=userDao.findByUsername(username,password);
return user;
}
}
5.3.10 控制器层代码:
package com.dfbz.controller;
import com.dfbz.entity.User;
import com.dfbz.service.UserService;
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("/login")
public class LoginServlet extends HttpServlet {
// 处理User业务类
private UserService userService = new UserService();
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 得到用户名和密码
String username = request.getParameter("username"); //一定要与表单的名字相同
String password = request.getParameter("password");
// 调用业务层登录的方法实现登录
User user = userService.login(username, password);
// 如果user不为null,说明数据库有这一条记录(用户名和密码都正确)
if (user != null){
request.setAttribute("user", user);
// 转发到另一个Servlet
request.getRequestDispatcher("/success").forward(request, response);
} else{
// 用户名或密码错误,登录失败,重定向到失败页面
response.sendRedirect(request.getContextPath() + "/failure.html");
}
}
}
5.3.11 SuccessServlet
package com.dfbz.controller;
import com.dfbz.entity.User;
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;
import java.io.PrintWriter;
@WebServlet("/success")
public class SuccessServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 从请求域中取出用户对象
User user = (User) request.getAttribute("user");
// 设置响应类型和编码
response.setContentType("text/html;charset=utf-8");
PrintWriter out = response.getWriter();
out.print("<h1>欢迎您,登录成功" + user.getUsername() + "</h1>");
}
}