Servlet之Request和Response的快速上手
阅读提示:
1、Request和Response概述
-
request:获取请求数据
- 浏览器会发送HTTP请求到后台服务器[Tomcat]
- HTTP的请求中会包含很多请求数据[请求行+请求头+请求体]
- 后台服务器[Tomcat]会对HTTP请求中的数据进行解析并把解析结果存入到一个对象中
- 所存入的对象即为request对象,所以我们可以从request对象中获取请求的相关参数
- 获取到数据后就可以继续后续的业务,比如获取用户名和密码就可以实现登录操作的相关业务
-
response:设置响应数据
- 业务处理完后,后台就需要给前端返回业务处理的结果即响应数据
- 把响应数据封装到response对象中
- 后台服务器[Tomcat]会解析response对象,按照[响应行+响应头+响应体]格式拼接结果
- 浏览器最终解析结果,把内容展示在浏览器给用户浏览
-
案例
@WebServlet("/demo") public class ServletDemo extends HttpServlet { @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { //使用request对象 获取请求数据 String name = request.getParameter("name"); //用于获取请求参数,如url?name=zhangsan //使用response对象 设置响应数据 response.setHeader("content-type","text/html;charset=utf-8"); response.getWriter().write("<h1>"+name+",欢迎您!</h1>"); } @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { System.out.println("Post..."); } }
2、Request对象
2.1 Request继承体系
类或接口 | 说明 |
---|---|
ServletRequest | Java提供的请求对象根接口 |
↑ | -- |
HttpServletRequest | Java提供的针对HTTP协议封装的请求对象接口 |
↑ | -- |
RequestFacade | Tomcat定义的实现类 |
2.2 Request获取请求数据
2.2.1 获取请求行
-
请求行示例
GET /request-demo/req1?name=zhangsan&password=123/ HTTP/2
分别为请求方式、请求资源路径和参数、协议和版本号
-
获取方法
// 获取请求方式 GET String getMethod(); // 获取虚拟目录(项目访问路径) /request-demo String getContextPath(); // 获取URL(统一资源定位符)http://localhost:8080/request-demo/req1 StringBuffer getRequestURL(); // 获取URI(统一资源标识符)/request-demo/req1 String getRequestURI(); // 获取请求参数(GET方式)name=zhangsan&password=123 String getQueryString();
2.2.2 获取请求头
-
请求头示例
User-Agent: Mozilla/5.0 Chrome/91.0.4472.106
-
获取方法
// 获取请求头,参数为对应的键 String getHadder(String name);
2.2.3 获取请求体
-
请求体用于POST请求方式
-
获取方法
// 获取字节流,用于二进制数据 ServletInputStream getInputStream(); // 获取字符流,用于纯文本数据 BufferedReader getReader();
2.2.4 获取请求参数的通用方式
-
存在问题
BufferedReader getReader();
获取文本数据需要手动解析 -
改进方法
// 获取所有参数Map集合(Map集合的Value元素采用数据,以应对多选等情况) Map<String,String[]> getParameterMap(); // 根据名称获取参数值(数组) String[] getParameterValues(String name); // 根据名称获取参数值(单个值) String getParameter(String name);
2.3 请求参数中文乱码问题
2.3.1 POST请求解决方案
-
分析原因
-
POST的请求参数是通过
reques
t的getReader()
来获取流中的数据 -
TOMCAT在获取流的时候采用的编码是
ISO-8859-1
-
-
在获取参数前设置使用需要的编码
// 设置字符输入流编码,设置字符集和页面保持一致,不区分大小写 request.setCharacterEncoding("UTF-8");
2.3.2 GET请求解决方案
-
分析原因
- 字符串用过HTTP由浏览器提交到服务器
- 服务器不支持中文,会将字符进行URL编码
- 将URL编码发送到服务器,由服务器进行解码
- TOMCAT服务器对GET请求获取请求参数的方式是
request.getQueryString()
,与POST请求不同 - TOMCAT默认使用
ISO-8859-1
进行URL解码 - TOMCAT中
getParameter(String name);
方法将URL解码采用的编码格式写死,无法修改
-
在获取参数前设置使用需要的编码
// 浏览器采用UTF-8将字符进行RUL编码 // 服务器采用ISO-8859-1进行URL解码,此时采用编码为ISO-8859-1,出现乱码 // 使用ISO-8859-1编码将乱码字符编回二进制码,得到其字节数组 byte[] bytes = username.getBytes(StandardCharsets.ISO_8859_1); // 将字节数组使用UTF-8重新编码 username = new String(bytes, StandardCharsets.UTF_8);
-
备注
Tomcat 8.0以上
解决了GET请求的乱码问题,设置了URL解码的默认方式为UTF-8
-
URL编解码
// 编码 URLEncoder.encode(str,"UTF-8"); // 解码 URLDecoder.decode(s,"ISO-8859-1");
2.4 Request 请求转发
-
请求转发是一种在服务器内部的跳转方式
-
实现请求转发
- 接收资源A请求,执行其对应Servlet方法
- 资源A中要跳转到资源B
- 将请求转发到资源B
- 执行资源BServlet方法
// 在资源A中 req.getRequestDispatcher("资源B路径").forward(req,resp);
-
请求转发资源间共享数据:使用Request对象
将该需要共享的数据(有需要时封装为对象)放在Request域中,可在转发后进行获取
// 以(k, v)存储数据到request域中 void setAttribute(String name,Object o); // 根据key获取value Object getAttribute(String name); // 根据key删除该键值对 void removeAttribute(String name);
-
请求转发的特点
- 浏览器地址栏的路径不发生变化
- 只能转发到当前服务器内部资源
- 一次请求,可以在转发的资源间使用request共享数据
3、Response对象
3.1 Response继承体系
类或接口 | 说明 |
---|---|
ServletResponse | Java提供的响应对象根接口 |
↑ | -- |
HttpServletResponse | Java提供的针对HTTP协议封装的响应对象 |
↑ | -- |
ResponseFacade | Tomcat定义的实现类 |
3.2 Response设置响应数据功能介绍
-
设置响应行
HTTP/2 200 OK
// 设置响应状态码 void setStatus(int sc);
-
设置响应头
Content-Type:text/html
// 设置响应头键值对 void setHeader(String name,String value);
-
设置响应体
HTML代码等,通过字符、字节输出流的方式网浏览器写
// 获取字符输出流 PrintWriter getWriter(); // 获取字节输出流 ServletOutputStream getOutputStream();
3.3 Response请求重定向
-
重定向:一种资源跳转的方式,区别于转发
- 浏览器发送发送请求到服务器,其中资源A接收到请求
- 资源A无法处理,但知道资源B资源可以处理,对浏览器做出响应,告知状态和处理方法
- 响应状态码:
302
- 响应头给出资源位置:
location:xxx
- 响应状态码:
- 浏览器接收到响应状态码
302
- 浏览器重新发送请求到请求头中
location
对应的地址寻找到资源B - 资源B接收请求后,进行处理,返回结果到服务器
-
重定向实现方式
/* // 设置响应状态码和响应头 resp.setStatus(302); resp.setHeader("location","项目虚拟目录(项目访问路径)+资源B的访问路径"); */ // 设置重定向 resp.sendRedirect("虚拟目录(项目访问路径)+资源B的访问路径");
-
重定向的特点(区别于请求转发)
- 浏览器地址栏路径发生变化
- 可以重定向到任意资源位置,不局限于服务器内部
- 两次请求之间,不能在多个资源使用Request域共享数据
3.4 路径问题
-
关于虚拟目录(明确路径被谁使用)
- 浏览器使用:需要加虚拟目录(项目访问路径) -- 如重定向
- 服务器使用:无需虚拟目录 -- 如请求转发
-
举例
- 需要加虚拟目录
<a herf='路径'>
<from action='路径'></from>
resp.sendRedirect("路径")
- 不需要虚拟目录
req.getRequestDispatcher("路径");
- 需要加虚拟目录
-
动态获取虚拟目录
<!-- pom.xml中全局配置 --> <build> <plugins> <plugin> <groupId>org.apache.toncat.maven</groupId> <artifactId>tomcat-maven-plugin</artifactId> <version>2.2</version> <configuration> <path> <!-- 全局虚拟目录 --> </path> </configuration> </plugin> <!-- ... --> </plugins> </build>
// 动态获取虚拟目录 String contextPath = request.getContextPath();
3.5 Response相应字符数据
字符输出流不需要关闭,其由Response对象获取,会随Response对象的回收而回收
-
返回简单字符串
aaa
// 通过Response对象获取字符输出流 PrintWriter writer = response.getWriter(); // 写数据 writer.write("aaa");
-
返回HTML代码,切能被解析
// 通过Response对象获取字符输出流 PrintWriter writer = response.getWriter(); // 设置响应头content-type,让浏览器以HTML格式进行解析 response.setHeader("content-type","text/html"); // 写HTML代码 writer.write("<h1>aaa</h1>");
-
返回中文字符串,设置编码格式为UTF-8(通过Response获取的字符输出流默认编码为
ISO-8859-1
)// 通过Response对象获取字符输出流 PrintWriter writer = response.getWriter(); // 设置响应头content-yupe,使用HTML格式解析,并注明字符集 // response.setHeader("content-type","text/html;cahrset=utf-8"); response.setContentType("text/html;cahrset=utf-8"); // 写HTML代码 writer.write("你好"); // writer.write("<h1>你好</h1>");
3.6 Response相应字节数据
-
返回字节数据使用方法
// 通过Response对象获取字节输出流 ServletOutputStream outputStream = resp.getOutputStream(); // 通过字节输出流写数据 outputStream.write(字节数据);
-
示例
// 读取文件 FileInputStream fis = new FileInputStream("d://demo.jpg"); // 获取response字节输出流 ServletOutputStream os = response.getOutputStream(); // 完成流的copy byte[] buff = new byte[1024]; int len = 0; while ((len = fis.read(buff))!= -1){ os.write(buff, 0, len); } fis.close();
-
使用工具类改进示例
<!-- 在pom.xml中添加配置 --> <dependencies> <!-- ... --> <dependency> <dependency> <groupId>commons-io</groupId> <artifactId>commons-io</artifactId> <version>2.6</version> </dependency> </dependency> </dependencies>
// 读取文件 FileInputStream fis = new FileInputStream("d://demo.jpg"); // 获取response字节输出流 ServletOutputStream os = response.getOutputStream(); // 完成流的copy // byte[] buff = new byte[1024]; // int len = 0; // while ((len = fis.read(buff))!= -1){ // os.write(buff, 0, len); // } IOUtils.copy(fis, os); fis.close();
4、用户注册登录案例
4.1用户登录和注册案例
4.1.1 需求分析
- 登录
- 用户填写用户名密码,提交到LoginServlet
- 在LoginServlet中使用MyBatis查询数据库,验证用户名密码是否正确
- 如果正确响应登录成功,否则响应登录失败
- 注册
- 用户填写用户名密码,提交到RegisterServlet
- 在LoginServlet中使用MyBatis查询数据库,验证用户名是否存在
- 若不存在则向数据库中插入新的用户数据,否则响应注册失败
4.1.2 环境准备
-
依赖导入
<?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"> <modelVersion>4.0.0</modelVersion> <groupId>priv.dandelion</groupId> <artifactId>day09RequestResponse</artifactId> <version>1.0-SNAPSHOT</version> <packaging>war</packaging> <dependencies> <!--mybatis 依赖--> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.5.5</version> </dependency> <!--mysql 驱动--> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.34</version> </dependency> <!--junit 单元测试--> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.11</version> </dependency> <!-- 添加slf4j日志api --> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>1.7.20</version> </dependency> <!-- 添加logback-classic依赖 --> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-classic</artifactId> <version>1.2.3</version> </dependency> <!-- 添加logback-core依赖 --> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-core</artifactId> <version>1.2.3</version> </dependency> <!-- servlet依赖 --> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>3.1.0</version> <scope>provided</scope> </dependency> <!-- io工具类依赖 --> <dependency> <groupId>commons-io</groupId> <artifactId>commons-io</artifactId> <version>2.6</version> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.apache.tomcat.maven</groupId> <artifactId>tomcat7-maven-plugin</artifactId> <version>2.2</version> <!-- <configuration>--> <!--<!– <path><!– 全局虚拟目录 –></path>–>--> <!-- <path>/day09RequestResponse</path>--> <!-- </configuration>--> </plugin> </plugins> </build> </project>
-
页面
注:代码中虚拟目录位置需要进行替换
-
登录
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>login</title> <link href="css/login.css" rel="stylesheet"> </head> <body> <div id="loginDiv"> <form action="/虚拟目录/login" method="post" id="form"> <h1 id="loginMsg">LOGIN IN</h1> <p>Username:<input id="username" name="username" type="text"></p> <p>Password:<input id="password" name="password" type="password"></p> <div id="subDiv"> <input type="submit" class="button" value="login up"> <input type="reset" class="button" value="reset"> <a href="register.html">没有账号?点击注册</a> </div> </form> </div> </body> </html>
-
注册
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>欢迎注册</title> <link href="css/register.css" rel="stylesheet"> </head> <body> <div class="form-div"> <div class="reg-content"> <h1>欢迎注册</h1> <span>已有帐号?</span> <a href="login.html">登录</a> </div> <form id="reg-form" action="/虚拟目录/register" method="post"> <table> <tr> <td>用户名</td> <td class="inputs"> <input name="username" type="text" id="username"> <br> <span id="username_err" class="err_msg" style="display: none">用户名不太受欢迎</span> </td> </tr> <tr> <td>密码</td> <td class="inputs"> <input name="password" type="password" id="password"> <br> <span id="password_err" class="err_msg" style="display: none">密码格式有误</span> </td> </tr> </table> <div class="buttons"> <input value="注 册" type="submit" id="reg_btn"> </div> <br class="clear"> </form> </div> </body> </html>
-
-
数据库和实体类
-
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;
-
实体类
package priv.dandelion.entity; public class User { private Integer id; private String username; private String password; public User(Integer id, String username, String password) { this.id = id; this.username = username; this.password = password; } public User() { } 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 + '\'' + '}'; } }
-
-
MyBatis
-
核心配置文件
注:代码中数据库名称需要进行替换
<?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"> <!-- 采用JDBC的事务管理方式 --> <transactionManager type="JDBC"/> <!-- 数据库连接信息 --> <dataSource type="POOLED"> <property name="driver" value="com.mysql.jdbc.Driver"/> <property name="url" value="jdbc:mysql:///数据库名称?useSSL=false&useUnicode=true&characterEncoding=UTF-8"/> <property name="username" value="root"/> <property name="password" value="123456"/> </dataSource> </environment> <environment id="test"> <transactionManager type="JDBC"/> <!-- 数据库连接信息 --> <dataSource type="POOLED"> <property name="driver" value="com.mysql.jdbc.Driver"/> <property name="url" value="jdbc:mysql:///数据库名称?useSSL=false&useUnicode=true&characterEncoding=UTF-8"/> <property name="username" value="root"/> <property name="password" value="123456"/> </dataSource> </environment> </environments> <!-- 加载SQL映射文件 --> <mappers> <package name="priv.dandelion.mapper"/> </mappers> </configuration>
-
UserMapper.xml映射文件
先完成接口,再由MyBatisX插件生成,或手动再resources目录下创建
<?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="priv.dandelion.mapper.UserMapper"> </mapper>
-
UserMapper接口
public interface UserMapper { // 查询已存在用户数量,防止重复注册 @Select("select * from tb_user where username = #{username} limit 0, 1") public User selectByUsername(@Param("username") String username); // 查询用户信息 @Select("select * from tb_user where username = #{username} and password = #{password} limit 0, 1") public User selectAll(@Param("username") String username, @Param("password") String password); @Insert("insert into tb_user(username, password) VALUES (#{username}, #{password})") public void insertUser(@Param("username") String username, @Param("password") String password); }
-
4.1.3 代码实现
注:实现流程
- 接收用户名和密码
- 解决Request中文乱码
- 使用ISO-8859-1编码将乱码字符编回二进制码,得到其字节数组
- 将字节数组使用UTF-8重新编码
- 调用MyBatis完成查询
- 加载MyBatis核心配置文件,获取SqlSessionFactory
- 获取Session对象,设置自动提交事务
- 获取UserMapper
- 执行SQL
- 处理结果并返回数据
- 设置response编码并获取字符输出流
- 处理结果(注册时需要额外判断用户是否已经存在)
- 释放资源
-
登录Servlet
@WebServlet("/login") public class LoginServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { System.out.println("开始进行登录判断"); // 接收用户名和密码 String username = request.getParameter("username"); String password = request.getParameter("password"); // 解决Request中文乱码 // 使用ISO-8859-1编码将乱码字符编回二进制码,得到其字节数组 byte[] bytes = username.getBytes(StandardCharsets.ISO_8859_1); // 将字节数组使用UTF-8重新编码 username = new String(bytes, StandardCharsets.UTF_8); // 调用MyBatis完成查询 // 加载MyBatis核心配置文件,获取SqlSessionFactory String resource = "mybatis-config.xml"; InputStream inputStream = Resources.getResourceAsStream(resource); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); // 获取Session对象,设置自动提交事务 SqlSession sqlSession = sqlSessionFactory.openSession(true); // 获取UserMapper UserMapper userMapper = sqlSession.getMapper(UserMapper.class); // 执行SQL User user = userMapper.selectAll(username, password); // 设置response编码并获取字符输出流 response.setContentType("text/html;charset=utf-8"); PrintWriter writer = response.getWriter(); // 处理结果 if (user != null) { // 登录成功 writer.write("login success" + user.getUsername() + "欢迎您"); } else { // 登录失败 writer.write("login fail 用户" + user.getUsername() + "不存在"); } // 释放资源 sqlSession.close(); System.out.println("login end"); } @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { this.doGet(request, response); } }
-
注册Servlet
@WebServlet("/register") public class RegisterServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { System.out.println("register start"); // 接收用户名和密码 String username = request.getParameter("username"); String password = request.getParameter("password"); // 解决Request中文乱码 // 使用ISO-8859-1编码将乱码字符编回二进制码,得到其字节数组 byte[] bytes = username.getBytes(StandardCharsets.ISO_8859_1); // 将字节数组使用UTF-8重新编码 username = new String(bytes, StandardCharsets.UTF_8); // 调用MyBatis完成查询 // 加载MyBatis核心配置文件,获取SqlSessionFactory String resource = "mybatis-config.xml"; InputStream inputStream = Resources.getResourceAsStream(resource); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); // 获取Session对象,设置自动提交事务 SqlSession sqlSession = sqlSessionFactory.openSession(true); // 获取UserMapper UserMapper userMapper = sqlSession.getMapper(UserMapper.class); // 执行SQL,处理结果 User user = userMapper.selectByUsername(username); // 设置response编码并获取字符输出流 response.setContentType("text/html;charset=UTF-8"); PrintWriter writer = response.getWriter(); // 查重 if (user == null){ userMapper.insertUser(username, password); // 注册成功 writer.write("register success 成功"); } else { System.out.println(user.getUsername()); // 注册失败 writer.write("用户名已被占用"); } // 释放资源 sqlSession.close(); System.out.println("register end"); } @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { this.doGet(request, response); } }
4.2 SQLSessionFactory工具抽取:改进登录案例
-
案例存在问题
SqlSessionFactory
创建部分存在代码冗余SqlSessionFactory
内置连接池,多次创建资源消耗较大
-
改进方案
- 使用工具类解决代码重复问题
- 使用静态代码块实现仅创建一次
SqlSessionFactory
-
代码实现
-
工具类
public class SqlSessionFactoryUtils { // 提升作用域,用于返回值 private static SqlSessionFactory sqlSessionFactory; // 静态代码块会随着类的加载自动执行且只执行一次 static { String resource = "mybatis-config.xml"; InputStream inputStream = null; try { inputStream = Resources.getResourceAsStream(resource); } catch (IOException e) { e.printStackTrace(); } sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); } // 返回工厂对象 public static SqlSessionFactory getSqlSessionFactory() { return sqlSessionFactory; } }
-
改进后代码(以
LoginServlet
为例)@WebServlet("/login") public class LoginServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { System.out.println("开始进行登录判断"); // 接收用户名和密码 String username = request.getParameter("username"); String password = request.getParameter("password"); // 解决Request中文乱码 // 使用ISO-8859-1编码将乱码字符编回二进制码,得到其字节数组 byte[] bytes = username.getBytes(StandardCharsets.ISO_8859_1); // 将字节数组使用UTF-8重新编码 username = new String(bytes, StandardCharsets.UTF_8); // 调用MyBatis完成查询 // 加载MyBatis核心配置文件,获取SqlSessionFactory,已通过工具类进行改进 SqlSessionFactory sqlSessionFactory = SqlSessionFactoryUtils.getSqlSessionFactory(); // 获取Session对象,设置自动提交事务 SqlSession sqlSession = sqlSessionFactory.openSession(true); // 获取UserMapper UserMapper userMapper = sqlSession.getMapper(UserMapper.class); // 执行SQL User user = userMapper.selectAll(username, password); // 设置response编码并获取字符输出流 response.setContentType("text/html;charset=utf-8"); PrintWriter writer = response.getWriter(); // 处理结果 if (user != null) { // 登录成功 writer.write("login success" + user.getUsername() + "欢迎您"); } else { // 登录失败 writer.write("login fail 用户" + user.getUsername() + "不存在"); } // 释放资源 sqlSession.close(); System.out.println("login end"); } @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { this.doGet(request, response); } }
-