Request&Response的学习
在服务方法中,存在这两个对象:
Request
主要用于获取请求数据,Response
主要用于设置响应数据,它们都是由Web服务器进行创建的。示例:
package com.hhxy.servlet; 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; /** * Request&Response初体验 */ @WebServlet("/ReTest") public class ReTest extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { //1、使用Request对象获取请求数据 String name = req.getParameter("name");//url?name=zhansan //2、使用Response对象设置响应数据 resp.setHeader("content-type","text/html;charset=utf-8");//设置响应头的编码格式 resp.getWriter().write("<h1>"+name+",欢迎您!</h1>");//返回的响应数据 } }
测试:
一、Request学习
1、获取请求数据
1.1 获取请求行数据
请求行包含三块内容,分别是请求方式
、请求资源路径
、HTTP协议及版本
getMethod
获取请求方式:GET
String getMethod()
getContextPath
获取虚拟目录(项目访问路径,就是模块名):/request-demo
String getContextPath()
getRequestURL
获取URL(统一资源定位符):http://localhost:8080/request-demo/req1
StringBuffer getRequestURL()
getRequestURI
获取URI(统一资源标识符):/request-demo/req1
String getRequestURI()
getQueryString
获取请求参数(GET方式):username=zhangsan&password=123
String getQueryString()
示例:
package com.hhxy.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;
/**
* 测试Request对象 获取请求行数据常用的5种方法
*/
@WebServlet("/req1")
public class RequestDemo1 extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 1、String getMethod():获取请求方式: GET
String method = req.getMethod();
System.out.println(method);//GET
// 2、String getContextPath():获取虚拟目录(项目访问路径):/day7_servlet
String contextPath = req.getContextPath();
System.out.println(contextPath);
// 3、StringBuffer getRequestURL(): 获取URL(统一资源定位符):http://localhost:8080/day7_servlet/req1
StringBuffer url = req.getRequestURL();
System.out.println(url.toString());
// 4、String getRequestURI():获取URI(统一资源标识符): /day7_servlet/req1
String uri = req.getRequestURI();
System.out.println(uri);
// 5、String getQueryString():获取请求参数(GET方式): username=zhangsan&password=123
String queryString = req.getQueryString();
System.out.println(queryString);
}
}
1.2 获取请求头数据
对于请求头的数据,格式为key: value
如下:
-
getHeader
根据请求头的名称获取对应值的信息:String getHeader(String name)
示例:
package com.hhxy.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; /** * 测试Request对象获取请求头数据的方法 */ @WebServlet("/req2") public class RequestDemo2 extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String value = req.getHeader("user-agent");//获取请求头: user-agent: 浏览器的版本信息 System.out.println(value); } }
输出:
1.3 获取请求体数据
浏览器在发送GET请求的时候是没有请求体的,所以需要把请求方式变更为POST,请求体中的数据格式如下:
getInputStream
获取字节输入流,如果前端发送的是字节数据,比如传递的是文件数据,则使用该方法:
ServletInputStream getInputStream()
getReader
获取字符输入流,如果前端发送的是纯文本数据,则使用该方法:
BufferedReader getReader()
示例:
编写Servlet:
package com.hhxy.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.BufferedReader;
import java.io.IOException;
/**
* 使用Request对象获取请求体数据
*/
@WebServlet("/req3")
public class RequestDemo3 extends HttpServlet {
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//1、获取Post请求的请求参数
//1.1 获取字符输入流
BufferedReader br = req.getReader();
//1.2 读取数据
String requestData = br.readLine();
System.out.println(requestData);//输出:
}
}
注意:BufferedReader流是通过request对象来获取的,当请求完成后request对象就会被销毁,request对象被销毁后,BufferedReader流就会自动关闭,所以此处就不需要手动关闭流了。
编写Post提交方式的表单:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Test</title>
</head>
<body>
<!--
action:form表单提交的请求地址
method:请求方式,指定为post
-->
<form action="http://localhost:8080/day7_servlet/req3" method="post">
<input type="text" name="username">
<input type="password" name="password">
<input type="submit">
</form>
</body>
</html>
测试:
1.4 获取请求参数
当我们在编码时,由于
GET
方式和POST
方式的请求参数所处位置不同,这就意味着,我们在doGet
方法种和doPost
方法种都需要写一遍处理请求参数的代码,写两遍请求参数的代码很容易出现重复代码,为了去除重复代码,这就意味着我们需要使用一种统一的请求参数方式,从而只需要写一遍代码避免重复代码的产生(程序员就是"懒",我也要成为一名懒"猿"😆)
拓展:
请求参数和请求数据的关系:请求参数是请求数据子集,请求参数只包括用户数据的数据,请求数据包括请求行、请求头、请求体
1.4.1 原始的请求参数方式
-
getQueryString
:用于获取Get请求方式的请求参数String getQueryString()
-
getReader
:用于获取Post请求方式的请求参数BufferedReader getReader()
1.4.2 请求参数的通用方式
在学习请求参数的通用方式之前,我们有必要先了解请求参数在Request对象中的存储原理!
Step1:Request对象会将客户端发送过来的请求参数进行分割
Step2:Request对象会将分割后的参数使用Map集合进行存储
-
getParameterMap
:获取所有请求参数的Map集合Map<String,String[]> getParameterMap()
-
getParameterValues
:获取指定名称的请求参数(数组)String[] getParameterValues(String name)
-
getParameter
:获取指定名称的请求参数(单个值)String getParameter(String name)
示例:
注意:要先启动Servlet,然后提交表单
Servlet:
package com.hhxy.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.util.Map;
/**
* 请求参数的通用方式
*/
@WebServlet("/req4")
public class RequestDemo4 extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("doGet方法被调用了...");
//1、获取所有请求参数
Map<String, String[]> maps = req.getParameterMap();//获取键
for (String key : maps.keySet()) {//遍历所有键
System.out.println(key+":");
//获取值
String[] values = maps.get(key);
for (String value : values) {
System.out.println(value+" ");
}
System.out.println();
}
System.out.println("===============================");
//2、获取单个请求参数(数组)
String[] hobbies = req.getParameterValues("hobby");
for (String hobby : hobbies) {
System.out.println("hobby:"+hobby);
}
System.out.println("================================");
//3、获取单个请求参数(单个值)
String hobby = req.getParameter("hobby");
System.out.println("hobby:"+hobby);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("doPost方法被调用了...");
this.doGet(req,resp);//doPost方法和doGet方法共用一套代码,减少重复代码
}
}
html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Test</title>
</head>
<body>
<!--
action:form表单提交的请求地址
method:请求方式,指定为post
-->
<form action="http://localhost:8080/day7_servlet/req4" method="get">
<input type="text" name="username"><br>
<input type="password" name="password"><br>
<input type="checkbox" name="hobby" value="1"> 游泳
<input type="checkbox" name="hobby" value="2"> 爬山 <br>
<input type="submit">
</form>
</body>
</html>
测试:
1.4.3 解决中文乱码
乱码原因:
编码:浏览器发送的请求数据都是先使用
UTF-8
编码,然后使用URL
进行编码
UTF-8
编码规定编码的符号的占位,汉字是占3个字节(24位二进制数),英文占2个字节;
URL
编码是将一个字节(八位)用两个十六进制数组成,然后再前面加一个%解码:Tomcat先进行
URL
解码,然后再使用ISO-8859-1
进行解码。而在IDEA中所有代码的编码都默认使用
UTF-8
,所以当Servlet程序在接收到来自Tomcat解析后的数据,就直接使用UTF-8
进行了编码,而来自Tomcat解析的数据都是使用ISOISO-8859-1
解码的,就造成了乱码问题w(゚Д゚)w,示意图如下:代码演示:
package com.hhxy.request; import java.io.UnsupportedEncodingException; import java.net.URLDecoder; import java.net.URLEncoder; /** * 模拟请求数据的编码和解码过程 */ public class URLDemo { public static void main(String[] args) throws Exception { String username = "张三";//模拟用户输入数据 /*-------------------第一步:模拟浏览器编码-----------------------*/ //1. URL编码 String encode = URLEncoder.encode(username, "utf-8"); System.out.println(encode);//输出:%E5%BC%A0%E4%B8%89 /*---------------------------------------------------------------*/ /*-------------------第二步:模拟Tomcat解码------------------------*/ //2. URL解码 String decode = URLDecoder.decode(encode, "ISO-8859-1"); //备注:这一步在Tomcat内部是写死的,无法进行外部操作 /*---------------------------------------------------------------*/ /*-----------第三步:Servlet直接默认使用UTF-8进行编码-----------------*/ System.out.println(decode); //此处打印的是对应的乱码数据:å¼ ä /*----------------------------------------------------------------*/ /*------------------------------从第三步下手------------------------*/ //3. 转换为字节数据,使用ISO-8859-1编码 byte[] bytes = decode.getBytes("ISO-8859-1"); for (byte b : bytes) { System.out.print(b + " "); } //此处打印的是:-27 -68 -96 -28 -72 -119 //4. 将字节数组转为字符串,使用UTF-8解码 String s = new String(bytes, "utf-8"); System.out.println(s); //此处打印的是张三 } }
白学警告⚠:在新版Tomcat9,已经默认使用UTF-8编码了,所以新版不存在乱码问题😆,本小节当作了解原理吧😄
-
Post请求方式乱码解决方案:
//在获取请求参数前,添加如下代码: req.setCharacterEncoding("UTF-8");
原因:因为Post底层是使用
BufferedReader getReader()
,以BufferedReader
流的形式的读取来自Tomcat解析的请求参数;Get底层是使用String getQueryString()
,String
无法直接使用该方法设置编码的方案来解决乱码问题,需要手动转换编码。 -
Get/Post请求方式乱码解决方案:
//在获取请求参数前,添加如下代码; result = new String(value.getBytes(StandardCharsets.ISO_8859_1),StandardCharsets.UTF_8);
备注:这就是从底层出发,该方法是通用方法!
其实方式一的底层原理就是方式二,只是Post请求参数是BufferedReader流,能够直接调用封装好的方法罢了,而Get方法是字符串,没有直接封装好的方法使用(谁叫字符串太原始了呢🤭)
示例:
备注:复用1.4.2的测试代码
package com.hhxy.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.nio.charset.StandardCharsets;
import java.util.Map;
/**
* 请求参数的通用方式
*/
@WebServlet("/req4")
public class RequestDemo4 extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("doGet方法被调用了...");
/*---------------解决Post请求方式导致的中文乱码问题-------------------*/
//req.setCharacterEncoding("UTF-8");
//注意:
//1、使用了通用方式后,就不需要这行代码,否则会重复导致乱码
//2、这行代码一定要放在获取请求参数之前!!!
/*--------------------------------------------------------*/
//1、获取所有请求参数
Map<String, String[]> maps = req.getParameterMap();//获取键
for (String key : maps.keySet()) {//遍历所有键
System.out.print(key+":");
//获取值
String[] values = maps.get(key);
for (String value : values) {
System.out.println(value+" ");//原始输出,不进行乱码处理
/*------------解决Get请求方式导致的乱码问题----------------*/
//也能同时解决Post请求方式导致的乱码问题
String result = null;
/*byte[] bytes = value.getBytes(StandardCharsets.ISO_8859_1);
result = new String(bytes,StandardCharsets.UTF_8);*/
result = new String(value.getBytes(StandardCharsets.ISO_8859_1),StandardCharsets.UTF_8);
System.out.println(result);
/*--------------------------------------------------------*/
}
System.out.println();
}
System.out.println("===============================");
//2、获取单个请求参数(数组)
String[] hobbies = req.getParameterValues("hobby");
for (String hobby : hobbies) {
System.out.println("hobby:"+hobby);
}
System.out.println("================================");
//3、获取单个请求参数(单个值)
String hobby = req.getParameter("hobby");
System.out.println("hobby:"+hobby);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("doPost方法被调用了...");
this.doGet(req,resp);
}
}
2、请求转发
请求转发(forward):是一种在服务器内部的资源跳转方式,本质就是利用Request域对象在Tomcat内部各Servlet进行传值,但是只能传递一次。
请求转发的特点:
- 一次请求,数据只能转发一次
- 转发只能发生在当前Web服务器内部
- 发生转发浏览器地址栏不会发生变化,转发是发生在Web服务器内部
2.1 请求转发的实现方式
req.getRequestDispatcher("资源B的访问路径").forward(req,resp);
//只能使用这个语句进行一次转发,多次转发会报错
示例:
2.2 转发常用API
请求转发资源间共享数据,需要使用request对象提供的三个方法:
setAttribute
:存储数据到request对象中
//存储的数据是 key value 形式,vlaue可以是任何类型
void setAttribute(String name,Object o);
getAttribute
根据key获取值
Object getAttribute(String name);
removeAttribute
根据key删除该键值对
void removeAttribute(String name);
示例:
二、Response学习
1、设置响应数据
1.1 设置响应行
响应行主要包括三部分,分别是协议
、状态码
、状态码描述
对于响应头,比较常用的就是设置响应状态码
-
setStatus
:设置状态码void setStatus(int sc)
1.2 设置响应头
对于响应头的数据,格式为key: value
如下:
-
setHeader
:设置响应头键值对:void setHeader(String name,String value)
1.3 设置响应体
响应体是返回给浏览器的数据,主要是html标签:
对于响应体,是通过字符、字节输出流的方式往浏览器写
-
getWriter
:获取字符输出流:PrintWriter getWriter()
-
getOutputStream
:获取字节输出流ServletOutputStream getOutputStream()
2、Response重定向
重定向(Redirect):重定向是一种资源跳转方式,就是通过各种方法将各种网络请求重新定个方向转到其它位置(如:网页重定向、域名的重定向、路由选择的变化也是对数据报文经由路径的一种重定向)
温馨提示:可以去类比一下前面请求转发的资源跳转方式😄
重定向的特点:
- 两次请求,浏览器一次发送两次请求数据
- 可以重定向到任何位置的资源,包括web服务器内部、外部
- 发生重定向浏览器的地址栏会发生变化,地址栏会变成重定向后的资源访问路径
实现方式:
-
方式一:原始写法
response.setStatus(302); resp.setHeader("location","资源B的访问路径");
-
方式二:使用
Sendredirect
方法response.Sendredirect("资源B的访问路径");
备注:这里的资源B的访问路径可以使用 url 路径:http://localhost:8080/day7_servlet/ResponseDemo2
,也可以使用精确匹配:/day7_servlet/ResponseDemo2
示例:
3、虚拟目录之资源路径问题
3.1 虚拟目录概述
-
什么是虚拟目录?
虚拟目录是指通将物理目录进行映射(大白话就是起别名)创建的新目录。
主要作用是进行互联网访问的,URL就是使用虚拟目录的
-
相对物理目录虚拟目录的好处?
- 便于访问、管理。虚拟目录能将分散的文件进行映射,放在分类明确的目录下,便于访问、管理
- 增强服务器的安全性。虚拟目录对用户隐藏了其在服务器上的物理位置,从而增强其安全性
- 提高目录容量。我们在使用物理目录时,想添加其它文件夹中文件,就需要进行CV;而使用虚拟目录,就可以直接通过映射添加到该文件夹中,从而无需创建新的文件或新的目录。
3.2 设置虚拟目录
问题:如何解决什么时候添加虚拟目录这个问题?
【虚拟目录如果不手动设置,默认就是
斜杠+你的模块名
】转发的时候路径上没有加虚拟目录:
/day7_servlet
,而重定向加了,那么到底什么时候需要加,什么时候不需要加呢?
是否添加虚拟目录的判断方法:
1)如果访问路径是给浏览器访问,就需要添加虚拟目录
2)如果访问路径是给Web服务器访问,就不需要添加虚拟目录
常见的情况:
- 超链接
<a href="路径">
,从浏览器发送,需要加虚拟目录 - 表单
<form action="路径">
,从浏览器发送,需要加虚拟目录 - 请求转发,是从服务器内部跳转,不需要加虚拟目录
- 重定向,是由浏览器进行跳转,需要加虚拟目录
虽然我们知道了判断方法,但是每次判断起来还是很麻烦,那么有没有一种更加好的方式不需要进行判断就能直接使用路径呢?程序员的终极目的就是=>化繁为简🐩
-
静态设置
<!--给虚拟目录起别名--> <Path></Path>
我们可以直接在添加Tomcat插件依赖时,通过path标签自定义虚拟目录
示例:
这样就不用在意需不需要加虚拟目录了😃,但是这种方式存在缺陷,当我们的虚拟目录不止一个,且我们只想访问其中一个目录中的Servlet时,这种方式就Out了,因为存在硬编码,每次访问其它的目录都需要进行修改,所以需要一种更加简单的方式!
-
动态获取:
String contextPath = request.getContextPath()
动态获取会根据是谁发送,自动获取虚拟目录。如果是浏览器发送就自动添加虚拟目录,如果是Web服务器就不添加
示例:
4、Response响应字符&字节数据
4.1 响应字符数据
响应字符数据是使用Response对象的getWriter()
方法:
PrintWriter pw = response.getWriter();//获取打印流
pw.write("Hello!");//输出
备注:writer流是随着response对象创建的,当请求完毕,response对象被销毁,writer流也跟着自动关闭
当响应数据是中文或者是html文本时,可以在前面添加以下代码:
//response.setHeader("contenx-type","text/html");//单独设置html文本
response.setContentType("text/html;charset=utf-8");//设置显示html文本,同时设置编码,防止中文乱码
中文乱码原因可以参考1.4.3
4.2 响应字节数据
响应字符数据主要是使用getOutputStream()
方法,用一个简单的案例来演示一下
案例:
当浏览器向Web服务器请求数据时,Servlet将本地的照片用字节输入流读取,读取完毕后,拷贝到ServletOutputStream对象中,然后将响应数据发送给浏览器展示。
如果你对字节流的操作不眼熟了,推荐阅读:
往期回顾:JavaSE基础【回顾+总结】的15节IO流
目录结构:
pom.xml:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<!--项目坐标-->
<artifactId>day7_servlet</artifactId>
<groupId>com.hhxy</groupId>
<version>1.0-SNAPSHOT</version>
<modelVersion>4.0.0</modelVersion>
<packaging>war</packaging>
<!--项目所依赖的JDK版本-->
<properties>
<maven.compiler.source>16</maven.compiler.source>
<maven.compiler.target>16</maven.compiler.target>
</properties>
<!--导入项目的依赖-->
<dependencies>
<!--导入javax.servlet-api-->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<!--
此处为什么需要添加该标签?
provided指的是在编译和测试过程中有效,最后生成的war包时不会加入
因为Tomcat的lib目录中已经有servlet-api这个jar包,如果在生成war包的时候生效就会和Tomcat中的jar包冲突,导致报错
-->
<scope>provided</scope>
</dependency>
<!--导入commons-io-->
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.6</version>
</dependency>
</dependencies>
<build>
<plugins>
<!--添加tomcat7-maven-plugin插件 -->
<plugin>
<groupId>org.apache.tomcat.maven</groupId>
<artifactId>tomcat7-maven-plugin</artifactId>
<version>2.2</version>
<configuration>
<port>8080</port><!--设置端口号-->
<!--<path>/</path>--><!--设置虚拟目录-->
</configuration>
</plugin>
</plugins>
</build>
</project>
Servlet:
package com.hhxy.response;
import org.apache.commons.io.IOUtils;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedInputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
/**
* 测试使用字节流响应数据
*/
@WebServlet("/ResponseDemo4")
public class ResponseDemo4 extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doGet(request, response);
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//1、使用字节输入流读取文件
InputStream is = new FileInputStream("D:\\Test\\1.gif");
//2、获取response字节输出流对象
ServletOutputStream sos = response.getOutputStream();
//3、将输入流对象的数据拷贝给输出流对象
/*方式一:使用字节流按字节数组复制文件*/
/*byte[] buff1 = new byte[1024];
int len = 0;
while(((len = is.read(buff1)) != 1)){
sos.write(buff1,0,len);
}
is.close();*/
//is是自己创建的所以需要手动关,ServletOutputStream是response创建的,不用手动关,后面同理
/*方式二:使用字节缓冲流按字节数组复制文件*/
/*InputStream bis = new BufferedInputStream(is);
byte[] buff2 = new byte[1024];
int len = 0;
while((len = bis.read(buff2)) != -1){
sos.write(buff2,0,len);
}
bis.close();*/
/*方式三:使用Java提供的API*/
/*byte[] buff3 = is.readAllBytes();
sos.write(buff3);*/
/*方式四:使用commons-io工具包提供的方法*/
IOUtils.copy(is,sos);
}
}
测试结果如下:
三、综合案例
5.1 用户登录
任务:编写一个登录的网页
Login.html
,同时编写一个服务器程序LoginServlet
,当用户在登录页面输入用户名和密码时,服务器程序能够判断。要求:
- 项目使用的软件:Maven、Tomcat
- 项目使用的技术:Mybatis(使用Mapper代理、包扫描)
- 项目所需的依赖:
mysql-connector-java
、mybatis
、javax.servlet-api
、``- 项目所需的插件:MavenHelper、tomcat7-maven-plugin、MyBatisX
备注:案例已经上传到Gitee上Github上了,欢迎大家参观我的仓库(●’◡’●)
- 我的Gitee仓库:
- 我的Github仓库:
前期准备:
-
Step1:建立一个数据库,名字为:
servlet
;然后建立一张表,名字为:tb_user
-- 创建用户表 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;
建议直接在IDEA中完成,当然你也可以在Navicat中完成,只是完成后,你仍然需要在IDEA中连接数据库
-
Step2:使用Maven创建一个Web项目。Web项目目录结构如下:
-
Step3:使用Maven导入项目所需要的依赖
pom.xml:
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <!--=============项目信息===================--> <!--Maven版本--> <modelVersion>4.0.0</modelVersion> <!--项目坐标--> <groupId>com.hhxy</groupId> <artifactId>day8_LoginAndRegister</artifactId> <version>1.0-SNAPSHOT</version> <!--项目打包方式--> <packaging>war</packaging> <!--项目所依赖的JDK版本--> <properties> <maven.compiler.source>16</maven.compiler.source> <maven.compiler.target>16</maven.compiler.target> </properties> <!--===================================--> <!--==========导入项目依赖==============--> <!--导入项目所依赖的jar包--> <dependencies> <!--导入MySQL驱动jar包--> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.27</version> </dependency> <!--导入MyBatis依赖--> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.5.5</version> </dependency> <!--导入Servlet依赖--> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>3.1.0</version> <scope>provided</scope> </dependency> </dependencies> <!--导入项目所依赖的插件--> <build> <plugins> <!--导入Tomcat7-maven插件--> <plugin> <groupId>org.apache.tomcat.maven</groupId> <artifactId>tomcat7-maven-plugin</artifactId> <version>2.2</version> <configuration> <port>8080</port><!--设置端口号--> <!--<path>/</path>--><!--设置虚拟目录--> </configuration> </plugin> </plugins> </build> <!--=============================--> </project>
-
Step4:编写实体类(POJO)
User:
package com.hhxy.pojo; public class User { private Integer id; private String username; private String 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 + '\'' + '}'; } }
-
Step5:编写项目所需要的配置文件
1)编写MyBatis核心配置文件
mybatis-config.xml
:<?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> <!--给包起别名,简化书写--> <typeAliases> <package name="com.hhxy.pojo"/> </typeAliases> <!--数据库连接环境配置--> <environments default="development"> <environment id="development"> <transactionManager type="JDBC"/> <dataSource type="POOLED"> <!--数据库的连接信息--> <property name="driver" value="com.mysql.cj.jdbc.Driver"/> <property name="url" value="jdbc:mysql:///servlet?useSSL=false&characterEncoding=utf8&useServerPrepStmts=true"/> <property name="username" value="root"/> <property name="password" value="32345678"/> </dataSource> </environment> </environments> <!--指定SQL映射文件--> <mappers> <!-- 原始写法:<mapper resource="com/hhxy/mapper/UserMapper.xml"/>--> <!--使用包扫描方式--> <package name="com.hhxy.mapper"/> </mappers> </configuration>
2)编写Mapper代理的配置文件
UserMapper.xml
:<?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"> <!--SQL映射语句--> <mapper namespace="com.hhxy.mapper.UserMapper"> <!--statement语句--> </mapper>
正式开始:
-
Step1:编写Mapper接口
UserMapper:
package com.hhxy.mapper; import com.hhxy.pojo.User; import org.apache.ibatis.annotations.Param; import org.apache.ibatis.annotations.Select; public interface UserMapper { /** * 根据用户名和密码查询用户对象 * @param username 账号 * @param password 密码 * @return 返回一个User对象 */ @Select("select * from tb_user where username=#{username} and password=#{password}") User select(@Param("username")String username, @Param("password")String password); }
备注:这里建议如果遇到长一点的SQL语句在SQL映射文件中写,因为文件中只要你的IDEA连接了数据库就能有提示,在注解中写没有提示【≧ ﹏ ≦】
-
Step2:编写Servlet
LoginServlet:
package com.hhxy.servlet; import com.hhxy.mapper.UserMapper; import com.hhxy.pojo.User; import org.apache.ibatis.io.Resources; import org.apache.ibatis.session.SqlSession; import org.apache.ibatis.session.SqlSessionFactory; import org.apache.ibatis.session.SqlSessionFactoryBuilder; 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.InputStream; import java.io.PrintWriter; @WebServlet("/LoginServlet") public class LoginServlet extends HttpServlet { protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { System.out.println("doPost方法被调用了。。。"); this.doGet(request, response); } protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { System.out.println("doGet方法被调用了。。。"); //1、接收请求数据,接收用户输入的用户名和密码 String username = request.getParameter("username"); String password = request.getParameter("password"); //2、使用MyBatis完成查询 //2.1 获取SqlSessionFactory对象 String resource = "./mybatis-config.xml"; InputStream is = Resources.getResourceAsStream(resource); SqlSessionFactory sqlSF = new SqlSessionFactoryBuilder().build(is); //2.2 获取SqlSession对象 SqlSession sqlS = sqlSF.openSession(); //2.3 获取Mapper接口对象 UserMapper userMapper = sqlS.getMapper(UserMapper.class); /*----------------------核心代码(其他的可以CV)----------------------------*/ //2.4 执行SQL语句 User user = userMapper.select(username, password); /*-------------------------------------------------------------------------*/ //2.5 释放资源 sqlS.close(); //3、处理结果,发送响应数据 //3.1 设置响应数据格式 response.setContentType("text/html;charset=utf-8"); //3.2 获取响应数据输出流,用于响应数据 PrintWriter pw = response.getWriter(); //3.2 判断user对象是否为空,确定账号是否存在 if(user == null){ pw.write("登陆失败,请重新登录!"); }else{ pw.write("登陆成功!"); } pw.write("你好丫"); } }
测试:
-
Step1:启动Servlet
-
Step2:启动html页面,模拟用户输入数据
-
Step3:查看结果
5.2 用户注册
任务:编写一个注册页面:
register.html
,同时编写一个服务器程序:RegisterServlet
,当用户访问注册页面并进行注册时,服务器程序会对账号信息进行判断,账号已存在,返回注册失败,账号不存在返回注册成功。接着上面的来写,所以要求和环境前期准备直接省略了🤭
Servlet:
package com.hhxy.servlet;
import com.hhxy.mapper.UserMapper;
import com.hhxy.pojo.User;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
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.InputStream;
import java.io.PrintWriter;
@WebServlet("/RegisterServlet")
public class RegisterServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
System.out.println("doPost方法被调用了");
this.doGet(request, response);
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
System.out.println("doGet方法被调用了");
//1、接收请求数据,接收用户注册的用户名和密码
String username = request.getParameter("username");
String password = request.getParameter("password");
User user = new User();
user.setUsername(username);
user.setPassword(password);
//2、使用MyBatis完成查询
//2.1 获取SqlSessionFactory对象
String resource = "./mybatis-config.xml";
InputStream is = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSF = new SqlSessionFactoryBuilder().build(is);
//2.2 获取SqlSession对象
SqlSession sqlS = sqlSF.openSession();
//2.3 获取Mapper接口对象
UserMapper userMapper = sqlS.getMapper(UserMapper.class);
/*----------------------核心代码(其他的可以CV)----------------------------*/
//2.4 执行SQL语句
User user1 = userMapper.selectByUsername(username);
//3、处理结果,发送响应数据
//3.1 设置响应数据格式
response.setContentType("text/html;charset=utf-8");
//3.2 获取响应数据输出流,用于响应数据
PrintWriter pw = response.getWriter();
//3.3 判断是否注册成功
if (user1 != null) {
pw.write("该用户名已存在,注册失败!");
}else{
userMapper.add(user);
sqlS.commit();
pw.write("注册成功!<br/>");
pw.write("你注册的用户名:username"+username+"<br/>");
pw.write("你注册的密码:"+password);
}
/*-------------------------------------------------------------------------*/
//4、 释放资源
sqlS.close();
}
}
存在一个bug:当我们使用
pw.write()
方法输出字符串对象时,且字符串的值含有中文就会导致乱码,charset=utf-8
直接解决直接输出中文不乱码,示意图:charset只是将数据的响应编码改成urf-8,而响应数据本身就出现了乱码
相当于一条河上游源头有人下毒,下游再怎么发解药也于事无补
解决方法:
Mapper接口:
package com.hhxy.mapper;
import com.hhxy.pojo.User;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
public interface UserMapper {
/*登录的SQL语句*/
/**
* 根据用户名和密码查询用户对象
* @param username 账号
* @param password 密码
* @return 返回一个User对象
*/
@Select("select * from tb_user where username=#{username} and password=#{password}")
User select(@Param("username")String username, @Param("password")String password);
/*注册的SQL语句*/
/**
* 根据用户输入的账号查询用户是否存在
*
* @param s
* @param username
* @return
*/
@Select("select * from tb_user where username=#{username}")
User selectByUsername(@Param("username") String username);
/**
*
* @param user
*/
// @Insert("insert into tb_user values(#{username},#{password})")
//id字段自增长无需设置。长一点的SQL语句推荐导SQL映射文件中去写,有提示
void add(User user);
}
SQL映射文件:
<?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">
<!--SQL映射语句-->
<mapper namespace="com.hhxy.mapper.UserMapper">
<!--statement语句-->
<insert id="add">
insert into tb_user(username, password) value(#{username},#{password});
</insert>
</mapper>
5.3. SqlSessionFactory工具类抽取
上面的用户登录和用户注册代码虽然能达到要求,但是仍存在不足:每次启动一个新的Servlet都会创建一个新的
SqlSessionFactory
对象,很浪费内存,所以我们可以使用设计模式中的单例模式进行对代码进行优化(准确来讲应该是单例模式中的饿汉单例模式)。往期回顾:JavaSE常用关键字详解
具体步骤如下:将重复代码封装成一个工具类,然后将创建对象的代码放在静态代码块中,同时工具类提供一个get方法,用于其它类获取SqlSessionFactory
对象。这样就能做到多次获取并使用SqlSessionFactory
对象,对象只被创建一次,从而大大减少内存的消耗
工具类:
package com.hhxy.utils;
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 sqlSFUtils {
private static SqlSessionFactory sqlSF;
static{
String resource = "./mybatis-config.xml";
InputStream is = null;
try {
is = Resources.getResourceAsStream(resource);
} catch (IOException e) {
e.printStackTrace();
}
sqlSF = new SqlSessionFactoryBuilder().build(is);
//System.out.println("我被执行了。。。");
}
public static SqlSessionFactory sqlSF(){
return sqlSF;
}
}
Servlet每次像创建SqlSessionFactory对象就只需要调用工具类的静态方法即可:
测试:
启动一个连续访问两个Servlet,静态代码块只被执行了一次
拓展:
是不是所有获取相同类型对象都可以进行工具类抽取?
首先答案肯定是不可以的!只有当每次创建的对象它的所有数据都是一样时,才可以进行抽取,变成公用对象。
SqlSessionFactory
对象的创建可以被抽取,主要原因是所有的SqlSessionFactory
对象的目的都是用来获取数据库连接对象的,加载配置文件的,每个SqlSessionFactory
对象的数据都是一样的。而有些对象虽然一个类中多个方法中会用到,但是不会进行抽取,比如SqlSession
对象的获取,他是数据库连接对象,在登录中它存储的LoginServlet和数据库的连接信息,在注册中他存储的是RegisterServlet和数据库的连接信息,如果公用会造成数据混乱,产生严重的bug!
四、ServletConfig&ServletContext
1、ServletConfig
ServeltConfig对象,会在Servlet初始化阶段被Tomcat创建,该对象存储了Servlet相关的初始化配置信息,即获取we.xml文件中<init-param>标签中的信息
ServletConfig对象常用API:
方法 | 作用 |
---|---|
String getInitParameter(String name) | 根据指定的参数名获取参数值 |
Enumeration getInitParameterNames() | 返回一个Enumeration对象,该对象包含所有参数名称列表 |
ServletContext getServletContext() | 返回一个当前Web应用的ServletContext对象 |
String getServletName() | 返回Servlet的访问名1 |
示例:
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">
<servlet>
<!--给ServletConfigTestServlet起别名-->
<servlet-name>ServletConfigTestServlet</servlet-name>
<servlet-class>com.hhxy.servlet.ServletConfigTestServlet</servlet-class>
<!--初始化配置-->
<init-param>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
<init-param>
<param-name>张三</param-name>
<param-value>123</param-value>
</init-param>
</servlet>
<!--Context参数配置,也属于初始化配置-->
<context-param>
<param-name>username</param-name>
<param-value>ls</param-value>
</context-param>
<context-param>
<param-name>password</param-name>
<param-value>123</param-value>
</context-param>
<!--设置Servlet访问路径-->
<servlet-mapping>
<servlet-name>ServletConfigTestServlet</servlet-name>
<url-pattern>/servletConfigTestServlet</url-pattern>
</servlet-mapping>
</web-app>
Servlet:
注意事项:
- 只能访问</init-param>标签中的<param-name>标签的值
- 必须使用配置文件设置Servlet访问路径,如果使用注解设置Servlet访问路径,获取的参数值是null
package com.hhxy.servlet;
import javax.servlet.ServletConfig;
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 ghp
* @date 2022/9/8
*/
//@WebServlet("/servletConfigTestServlet")
public class ServletConfigTestServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doGet(request, response);
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//1、获取ServletConfig对象
ServletConfig config = this.getServletConfig();
//2、获取参数encoding对应的值,同时打印测试
String param = config.getInitParameter("encoding");
System.out.println(param);//输出:UTF-8
String name = config.getInitParameter("张三");
System.out.println(name);//输出:123
String servletConfigTestServlet = config.getInitParameter("ServletConfigTestServlet");
System.out.println(servletConfigTestServlet);//输出:null
String username = config.getInitParameter("username");
System.out.println(username);//输出:null
}
}
2、ServletContext
ServletContext官方叫servlet上下文,是一个域对象,实现了HttpSession。在Tomcat启动时,Tomcat会为每一个部署在其上的Servlet创建一个唯一的ServletCentext对象,该对象不仅封装了当前Web应用的所有信息,而且还实现了多个Servlet之间数据的共享(因为它是一个域对象)
推荐阅读:
Java中四大域对象
ServletContext常用API:
方法 | 作用 |
---|---|
String getInitParameter(String name) | 根据指定的参数名获取参数值 |
Enumeration<String> getInitParameterNames() | 获取Enumberation对象,该对象包含所有参数名称列表 |
示例:
注意事项:
- 只能访问<context-param>标签中的<param-name>标签的值
- 必须使用配置文件设置Servlet访问路径,如果使用注解设置Servlet访问路径,获取的参数值是null
package com.hhxy.servlet;
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;
import java.util.Enumeration;
/**
* @author ghp
* @date 2022/9/8
*/
@WebServlet("/servletContextTestServlet")
public class ServletContextTestServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doGet(request, response);
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//1、获取ServletContext对象
ServletContext context = this.getServletContext();
//2、获取所有初始化配置的参数列表
Enumeration<String> params = context.getInitParameterNames();
//3、遍历参数列表,同时打印各参数对应的值
while(params.hasMoreElements()){
String name = params.nextElement();
String value = context.getInitParameter(name);
System.out.println(name+"="+value);
}
}
}
如果是使用注解设置Servlet访问路径,则是
@WebServlet()
中的值;如果是使用配置文件设置Servlet访问路径,则是<Servlet-name>
中的值 ↩︎