Request & Response
在之前的博客中,初最初见到Request和Response对象,是在Servlet的Service方法的参数中,之前隐性地介绍过Request的作用是获取请求数据。通过获取的数据来进行进一步的逻辑处理,然后通过对Response来进行数据响应。接下来一起学习下具体知识吧~
Request继承体系
Tomcat需要解析请求数据,封装为request对象,并且创建requestx对象传递到service方法中
Request获取请求数据
请求数据分为3部分:
-
请求行:
// GET/request-demo/req1?username=zhangsan HTTP/1.1 String getMethod() // 获取请求方式:GET String getContextPath():// 获取虚拟目录(项目访问路径 动态获取):/request-demo String Buffer getRequestURL(): // 获取URL(统一资源定位符):http://localhost:8080/request-demo/req1 String getRequestURI(): // 获取URI(统一资源标识符):/request-demo/req1 String getQueryString():// 获取请求参数(GET方式):username:=zhangsan&password-=123
-
请求头:
// User-Agent:Mozilla/5.0 Chrome/91.0.4472.106 浏览器版本信息 String getHeader(String name) //:根据请求头名称,获取值
-
请求体:
// Post情况 // username=superbaby&password=123 ServletInputStream getlnputStream() // :获取字节输入流 BufferedReader getReader() // :获取字符输入流
Request通用方式获取请求参数
GET请求方式和POST请求方式区别主要在于获取请求参数的方式不一样,是否可以提供一种统一获取请求参数的方式,从而统一doGt和doPost方法内的代码?
通过上面的图片可以发现,请求数据可以通过拆分,得到具体的键值对,因此:
// GET 获取请求方式
String getQueryString()
// POST 获取请求方式
BufferedReader getReader()
// 自己去实现的统一获取请求方式
String method = this.getMethod();
//判断
if ("GET".equals(method)){
//GET方式获取请求参数
params = this.getQueryString();
}else if ("POST".equals(method)){
//P0ST方式获取请求参数
BufferedReader reader = this.getReader();
params = reader.readLine();
}
// JAVA EE自带的通用方式
// Map<String,String[]> getParameterMap():获取所有参数Map集合
// String[] getParameterValues(String name):根据名称获取参数值(数组)
// String getParameter(String name):根据名称获取参数值(单个值)
// 使用通用方式获取请求参数后,屏蔽了GET和POST的请求方式代码的不同,则代码可以定义为如下格式:
//此过程中不需要判断是POST 还是 GET,这一步上面的通用方法会自己解决
@WebServlet("/reqDemo3")
public class RequestDemo3 extends HttpServlet {
@override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
}
@override
protected void doPost(HttpServletRequest req,HttpServletResponse resp){
this.doGet(req,resp);
}
// 通用方式在IDEA中可以使用Servlet模板创建Servlet更高效
// 步骤:选择【包】,【右键】选择【New】选择倒数第四个【Servlet】
请求参数中文乱码处理
// 之前介绍过由于POST是通过获取字符流的方式来获取参数的,因此如果POST遇到中文字符乱码的问题,则:
request.setCharacterEncoding("UTF-8");//设置字符输入流的编码
// 而GET方式,由于在参数传递的过程中,首先会通过URL编码,然后到服务器以后再通过URL解码,但解码过程中采用的ISO-8859-1,这个是Tomcat默认的格式,因此通过修改流的方式无法改变,因此要通过:
// 1. 将URL编码的数据转换成字节数据
byte[] bytes = username.getBytes(StandardCharsets.ISO_8859_1);
// 2.将字节数组转为字符串
String s = new String(bytes,StandardCharsets.UTF_8);
// 该方式不仅可以解决GET的乱码问题,也可以解决POST的乱码问题
// 该问题再Tomcat 8.0以后,已将Get请求乱码问题解决,设置默认的编码方式为UTF-8
Request请求转发
一种在服务器内部的资源跳转方式。
-
实现方式:
-
请求转发资源间共享数据:使用Request对象
- void setAttribute(String name,Object o):存储数据到request域中
- Object getAttribute(String name):根据key,获取值
- void removeAttribute(String name):根据key,删除该键值对
//转发之前可以通过上面的方法添加数据 req.setAttribute("msg","name") req.getRequestDispatcher("资源B路径").forward(req,resp); //req:请求对象 resp:响应对象
-
请求转发特点:
- 浏览器地址栏路径不发生变化
- 只能转发到当前服务器的内部资源
- 一次请求,可以在转发的资源间使用request共享数据
Response继承体系
Response设置响应数据功能介绍
响应数据分为3部分
-
响应行
HTTP/1.1 200 OK void setStatus(int sc) //:设置响应状态码
-
响应头
Content-Type:text/html void setHeader(String name,.String value) //:设置响应头键值对
-
响应体
<html><head>head><body></body></html> PrintWriter getWriter() //:获取字符输出流 ServletOutputStream getOutputStream() //:获取字节输出流
Response完成重定向
重定向(Redirect):一种资源跳转方式,访问流程如下:
resp.setStatus(302); //resp 响应对象
resp.setHeader("location”,“资源B的路径"); //resp 响应对象
//简化方式完成重定向
response.sendRedirect("资源B的路径");
重定向特点:
- 浏览器地址栏路径发生变化
- 可以重定向到任意位置的资源(服务器内部、外部均可)
- 两次请求,不能在多个资源使用request共享数据
路径问题
在上面的资源转发和重定向 路径中,时常会遇到需要使用到虚拟路径或不适用虚拟路径的问题,那么什么时候使用,什么时候不使用呢?
这需要明确路径是谁在使用
- 浏览器使用:需要加虚拟目录(项目访问路径)
- 服务端使用:不需要加虚拟目录
Response响应字符数据
-
使用:
-
通过Response对象获取字符输出流
PrintWriter writer = resp.getWriter(); //resp 响应对象
-
写数据
writer.write("aaa"); // 细节:流不需要关闭 // 设置流的编码,同时设置了响应头 response.setcontentType("text/html;charset=utf-8");
-
Response响应字节数据
-
使用:
-
通过Response对象获取字节输出流(字节数据:图片、音频、视频等)
ServletOutputStream outputStream = resp.getOutputStream();
-
写数据
outputStream.write(字节数据);
-
-
举例,向客户端传送一张图片:
-
导入工具commons-io
<!--先引入一个commons-io 依赖--> <dependency> <groupId>commons-io</groupId> <artifactId>commons-io</artifactId> <version>2.6</version> </dependency>
-
实现逻辑代码:
//1.读取文件 FileInputStream fis = new FileInputStream("d://a.jpg"); //2.获取response字节输出流 ServletoutputStream os = response.getOutputStream(); IOUtils.copy(fis,os); fis.close();
-
案例:用户登录
- 流程说明:
- 用户填写用户名密码,提交到LoginServlet
- 在LoginServlet中使用MyBatisi查询数据库,验证用户名密码是否正确
- 如果正确,响应“登录成功”,如果错误,响应“登录失败”
//这里仅给出逻辑代码,对于MyBatis和Maven的配置,以及相关Mapper文件、pojo类、映射文件的接口等代码见前面的文章
//1.接收用户名和密码
String username = request.getParameter(name:"username");
String password = request.getParameter(name:"password");
//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 userMapper = sqlSession.getMapper(UserMapper.class);
//2.4调用方法
User user = userMapper.select(username,password);
//2.5释放资源
sqlSession.close();
//获取字符输出流,并设置content type
response.setcontentType("text/html;charset=utf-8");
PrintWriter writer = response.getWriter();
//3.判断user是否为null
if(user != null){
//登陆成功
writer.write(s:"登陆成功");
}else{
//登陆失败
writer.write(S:"登陆失败");
}
用户注册
- 流程说明
- 用户填写用户名、密码等信息,点击注册按钮,提交到RegisterServlet
- 在RegisterServlet中使用MyBatis保存数据
- 保存前,需要判断用户名是否已经存在:根据用户名查询数据库
//相较于上面,这里需要对数据库进行两次操作,一次是查询,一次是添加,内容见前面博客
//1.接收用户数据
String username = request.getParameter(name:"username");
String password = request.getParameter(name:"password");
/封装用户对象
User user = new User();
user.setUsername(username);
user.setPassword(password);
//2.调用mapper根据用户名查询用户对象
//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 userMapper = sqlSession.getMapper(UserMapper.class);
//2.4调用方法
User u = userMapper.selectByUsername(username);
//3.判断用户对象是否为null
if(u == null){
// 用户名不存在,添加用户
userMapper.add(user);
// 提交事多
sqlSession.commit();
// 释放资源
sqlSession.close();
}else{
//用户名存在,给出提示信息
response.setContentType("text/html;charset=utf-8");
response.getWriter().write("用户名已存在");
// 释放资源
sqlSession.close();
}
案例代码优化
- 问题:
- 代码重复:建立工具类
- SqlSessionFactory工厂只创建一次,不要重复创建,因为里面存在数据库连接池,特别浪费资源:静态代码块
//工具类代码:
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){
e.printstackTrace();
}
public static SqlSessionFactory getSqlSessionFactory(){
return sqlSessionFactory;
}
}
// 然后在上面2.1修改代码,调用静态方法,得到sqlSessionFactory对象即可
// 与sqlSessionFactory不同的是:sqlSession不能仅创建一次,因为多用户情况下,仅创建一次,对于用户体验来说 并不好
如果觉得文章对您有帮助,请帮忙点赞或者收藏,如果在文章中发现什么错误或不准确的地方,欢迎与我交流。