第二天
第二天
HTTP协议类比TCP/IP协议是需要握手的
Request&Response
Request(请求) & Response(响应)
浏览器向服务器请求,服务器对浏览器做出回应
Request:获取请求数据
Response:设置响应数据
Request
Request 继承体系:
ServletRequest:Java提供的请求对象根接口
|
HttpServletRequest:Java提供的对Http协议封装的请求对象接口
|
RequestFacade:Tomcat定义的实现类
1、Tomcat需要解析数据,封装为request对象,并且创建request对象传递到service方法中
2、使用request对象,查询JavaEE API文档的HttpServletRequest接口
Request 获取请求数据
获取请求数据:
请求数据分为3部分:
1、请求行:
GET/request-demo/req1?username=zhangsan HTTP/1.1 请求方式 + 请求的资源路径和参数 + 协议和版本号 ?前面是URL ?后面是GET的请求参数
常用方法 | 作用 |
---|---|
String getMethod() | 获取请求方式:GET |
String getContextPath() | 获取虚拟目录(项目访问路径):/request-demo |
StringBuffer getRequestURL() | 获取URL(统一资源定位符):http://localhost:8080/request-demo/req1 |
String getRequestURI() | 获取URI(统一资源标识符):/request-demo/req1 |
String getQueryString() | 获取请求参数(GET方式):username=zhangsan&password=123 |
参考代码:
需要使用maven项目,同时配置好Tomcat
添加的pom配置:
<!-- 选择打包方式 --> <packaging>war</packaging> <!-- 配置字符集和JDK --> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <maven.compiler.encoding>UTF-8</maven.compiler.encoding> <java.version>11</java.version> <maven.compiler.source>11</maven.compiler.source> <maven.compiler.target>11</maven.compiler.target> </properties> <build> <plugins> <!-- 配置Tomcat插件 --> <plugin> <groupId>org.apache.tomcat.maven</groupId> <artifactId>tomcat7-maven-plugin</artifactId> <version>2.2</version> <!-- 配置项目访问路径 --> <!--<configuration> <path>/xxx</path> </configuration>--> </plugin> </plugins> </build> <dependencies> <!-- 导入servlet依赖坐标 --> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>3.1.0</version> <!-- 依赖范围:编译,测试有效,运行无效(Tomcat自带,运行环境不用打入war包这样不会冲突) --> <scope>provided</scope> </dependency> </dependencies>
package com.itheima.web; 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("/req1") public class RequestDemo1 extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { //String getMethod()获取请求方式:GET String method = req.getMethod(); System.out.println(method); //GET //String getContextPath()获取虚拟目录(项目访问路径):/request-demo String contextPath = req.getContextPath(); System.out.println(contextPath); //未在pom中设置时默认模块名称 //StringBuffer getRequestURL()获取URL(统一资源定位符):http://localhost:8080/request-demo/req1 StringBuffer url = req.getRequestURL(); System.out.println(url.toString()); //String getRequestURl()获取URl(统一资源标识符):/request-demo/req1 String uri = req.getRequestURI(); System.out.println(uri); //String getQueryString()获取请求参数(GET方式):username=zhangsan&password=123 String queryString = req.getQueryString(); System.out.println(queryString); } @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { } }
2、请求头:
User-Agent: Mozilla/5.0 Chrome/91.0.4472.106 键:值
String getHeader(String name):根据请求头名称,获取值
3、请求体(只有post请求才会有请求体):
username=superbaby&password=123
ServletInputStream getInputStream():获取字节输入流
BufferedReader getReader():获取字符输入流
通用方式获取请求参数:
请求参数获取方式:
GET方式:
String getQueryString();
POST方式:
BufferedReader getReader();
思考:
GET请求方式 和 POST请求方式 区别主要在于获取请求参数的方式不一样,是否可以提供一种统一获取请求参数的方式,从而统一doGet 和 doPost方法内的代码呢?
//伪代码: //获取请求方式 String method = this.getMethod(); //判断 if("GET".equals(method)){ //GET方式获取请求参数 params = this.getQueryString(); }else if("POST".equals(method)){ //POST方式获取请求参数 BufferedReader reader = this.getReader(); params = reader.readLine(); }
Map<String, String[]> //通过Map集合存储参数 //考虑到键相同时,值被覆盖,所以使用String[]存放值,当键相同时可以存放在值所对应的数组后
通用的方法:
Map<String, String[]> getParameterMap():获取所有参数Map集合
String[] getParameterValues(String name):根据名称获取参数值(数组)
String getParameter(String name):根据名称获取参数值(单个值)
参考代码:
方法中的内容一致后,直接在另一个方法中this.xxx
package com.itheima.web; 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("/req2") public class RequestDemo2 extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { //GET请求数据 System.out.println("get..."); //1、获取所有参数的map集合 Map<String, String[]> map = req.getParameterMap(); for(String key: map.keySet()){ //通过keySet()获取键值 //username:zhangsan System.out.print(key + ":"); //获取值 String[] values = map.get(key); //通过get(key)方法获取对应值 for(String value : values){ System.out.print(value + " "); } System.out.println(); } System.out.println("--------------------------------"); //2、获取对应参数值,数组 String[] hobbies = req.getParameterValues("hobby"); for(String hobby : hobbies){ System.out.println(hobby); } System.out.println("--------------------------------"); //3、根据key,获取单个参数值 String username = req.getParameter("username"); String password = req.getParameter("password"); System.out.println(username); System.out.println(password); } @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { //POST请求数据 this.doGet(req, resp); /* System.out.println("post..."); //1、获取所有参数的map集合 Map<String, String[]> map = req.getParameterMap(); for(String key: map.keySet()){ //通过keySet()获取键值 //username:zhangsan System.out.print(key + ":"); //获取值 String[] values = map.get(key); //通过get(key)方法获取对应值 for(String value : values){ System.out.print(value + " "); } System.out.println(); } System.out.println("--------------------------------"); //2、获取对应参数值,数组 String[] hobbies = req.getParameterValues("hobby"); for(String hobby : hobbies){ System.out.println(hobby); } System.out.println("--------------------------------"); //3、根据key,获取单个参数值 String username = req.getParameter("username"); String password = req.getParameter("password"); System.out.println(username); System.out.println(password); */ } }
使用通用方式获取请求参数后,屏蔽了GET和POST的请求方式代码的不同,则代码可以定义为如下格式:
@WebServlet("/req2") public class RequestDemo2 extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { this.doGet(req, resp); }
可以使用Servlet模板创建Servlet更高效,模板可以自定设置
方法直接在对应位置new一下,如果没有看到尝试一下这个设置
请求参数中文乱码
请求参数中如果存在中文数据,则会乱码
解决方案:
POST:设置输入流的编码
request.setCharacterEncoding("UTF-8"); //根据页面设置字符输入流的编码
浏览器将中文转换为URL编码(UTF-8),Tomcat进行解码(ISO-8859-1)
即先将中文进行UTF-8或者ISO-8859-1编码,在此基础上进行URL编码
URL编码(% + 两位16进制数 为一个字节):
1、将字符串按照编码方式转换为二进制
2、每一个字节转为2个16进制数并在前面加上%
UTF-8下一个汉字三个字节
GET乱码分析:
package com.itheima.web; import java.io.UnsupportedEncodingException; import java.net.URLDecoder; import java.net.URLEncoder; //Java提供的URL编码解码工具类模拟编码解码 public class URLDemo { public static void main(String[] args) throws UnsupportedEncodingException { String username = "张三"; //1、URL编码 (浏览器) String encode = URLEncoder.encode(username, "UTF-8"); System.out.println(encode); //2、URL解码 (Tomcat) //String decode = URLDecoder.decode(encode, "UTF-8"); String decode = URLDecoder.decode(encode, "ISO-8859-1"); System.out.println(decode); //3、解决乱码:(我们解决) //将字节数据转换为,字符串(编码) byte[] bytes = decode.getBytes("ISO-8859-1"); /*for(byte b : bytes){ System.out.print(b + " "); }*/ //将字节数组转为字符串(解码) String s = new String(bytes, "UTF-8"); System.out.println(s); } }
关键代码:
//username是Tomcat解码后获取的乱码数据 //先通过对应Tomcat默认字符集将其转化为二进制数组 , 然后通过合适字符集转化为字符串 username = new String(username.getBytes(StandardCharsets.ISO_8859_1), StandardCharsets.UTF_8);
Tomcat 8.0之后,已将GET请求乱码问题解决,设置默认的解码方式为UTF-8(使用的插件只支持到7,可以打个war包去本地的Tomcat中去试试)
Request 请求转发
请求转发(forward):一种在服务器内部的资源跳转方式
实现方式:
req.getRequestDispatcher("资源B路径").forward(req,resp); //传递数据,并没有进行处理,相当于传送带
请求转发资源间共享数据:使用Request对象
(即通过这些方法处理传递的数据,相当于加工工具)
void setAttribute(String name, Object o):存储数据到 request域(作用域,范围的意思)中
Object getAttribute(String name):根据 key,获取值
void removeAttribute(String name):根据 key,删除该键值对
请求转发的特点:
浏览器地址栏路径不发生变化
只能转发到当前服务器的内部资源
一次请求,可以在转发的资源间使用request共享数据
参考代码:
package com.itheima.web; 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; /* 请求转发 */ @WebServlet("/req5") public class RequestDemo5 extends HttpServlet { protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { System.out.println("demo5"); //存储数据: request.setAttribute("msg", "hello"); //请求转发 request.getRequestDispatcher("/req6").forward(request, response); } protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { this.doGet(request, response); } }
package com.itheima.web; 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("/req6") public class RequestDemo6 extends HttpServlet { protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { System.out.println("demo6"); //获取数据: Object msg = request.getAttribute("msg"); System.out.println(msg.toString()); } protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { this.doGet(request, response); } }
Respeonse
继承体系:
ServletResponse:Java提供的请求对象根接口
|
HttpServletResponse:Java提供的对Http协议封装的请求对象接口
|
ResponseFacade:Tomcat定义的实现类
Response 设置响应数据功能介绍
响应数据分为3部分:
1、响应行:
HTTP/1.1 200 OK
void setStatus(int sc):设置响应状态码
2、响应头:
Content-Type:text/html
void setHeader(String name, String value):设置响应头键值对
3、响应体:
<html><head></head><body></body></html>
PrintWriter getWriter():获取字符输出流
ServeltOutputStream getOutputStream():获取字节输出流
Response 完成重定向
重定向(Redirect):一种资源跳转方式
当浏览器向资源A做出请求时,资源A向浏览器响应自己无法处理(响应状态码302),并告诉浏览器(响应头location:xxx)资源B可以解决,这是浏览器收到响应并向资源B做出请求。
实现方式:
resp.setStatus(302); resp.setHeader("location", "资源B的路径");
简化实现方式(只需要输入变化的东内容,固定的状态码和location底层自动输入):
response.sendRedirect("/request-demo/resp2");
重定向特点(和资源转发进行对比):
两者本质区别在于,浏览器是否重新发送请求
浏览器地址栏路径发生变化(浏览器进行了多次请求,资源转发并没有)
可以重定向到任意位置的资源(服务器内部,外部均可,这是因为重定向浏览器会重新请求,这时请求的对象不一定还是服务器)
两次请求,不能在多个资源使用request共享数据(两次是不同的request作用域)
参考代码:
package com.itheima.web.response; 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("/resp1") public class ResponseDemo1 extends HttpServlet { protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { System.out.println("resp1..."); //重定向 /* //1、设置响应状态码302 response.setStatus(302); //2、设置响应头:location response.setHeader("Location", "/request-demo/resp2");*/ //简化方式完成重定向 //虚拟目录可以在 pom中配置,一改代码也要改,所以提供了动态获取 String contextPath = request.getContextPath(); //由于代码的固定性(302,Location),变的只有路径,所以直接提供了一个简化方法,底层进行此操作 response.sendRedirect(contextPath + "/resp2"); // response.sendRedirect("http://www.itcast.cn"); } protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { this.doGet(request, response); } }
package com.itheima.web.response; 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("/resp2") public class ResponseDemo2 extends HttpServlet { protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { System.out.println("resp2..."); } protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { this.doGet(request, response); } }
路径:
先明确路径谁使用?
浏览器使用:需要加上虚拟目录(项目访问路径)
服务端使用:不需要加虚拟目录
练习:
<a href='路径'> //标签a:超链接,浏览器使用需要加虚拟目录 <from action='路径'> //表单:浏览器使用需要加虚拟目录 req.getRequestDispatcher("路径") //服务器内部进行资源跳转,不需要加虚拟目录 resp.sendRedirect("路径") //浏览器使用需要加虚拟目录
Response 响应字符数据
使用:
1、通过Response对象获取字符输出流
PrintWriter writer = resp.getWriter(); //由对象创建的流,随着对象的销毁自动释放资源
2、写数据:
writer.writer("aaa");
写入数据非纯文本时:
//content-Type 表示该响应内容的类型,例如text/html,image/jpeg; //通过方法设置:content-type
写入数据含中文时:
//先设置流的编码 response.setContentType("text/html;charset=UTF-8"); //前面是响应数据类型 ; 后面是编码字符集
参考代码:
package com.itheima.web.response; 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("/resp3") public class ResponseDemo3 extends HttpServlet { protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { //设置流的编码 response.setContentType("text/html;charset=UTF-8"); //1、获取字符输出流 PrintWriter writer = response.getWriter(); //设置content-type 就是告诉浏览器我们写的数据类型,不告诉就默认为存文本 // response.setHeader("content-type", "text/html"); writer.write("你好"); writer.write("<h1>aaa</h1>"); //这个流是response对象获取的,当这个对象被销毁时,会自动释放资源 } protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { this.doGet(request, response); } }
Reponse 响应字节数据
使用:
1、通过Response对象获取字符输出流:
ServeletOutputStream outputStream = resp.getOutputStream();
2、写数据:
outputStream.write(字节数据);
使用IOUtils工具类来简化复制:
1、导入坐标:
<dependency> <groupId>commons-io</groupId> <artifactId>commons-io</artifactId> <version>2.6</version> </dependency>
2、使用:
IOUtils.copy(输入流, 输出流);
案例:用户登录
流程说明:
1、用户填写用户名,密码,提交到LoginServlet
2、在LoginServlet中使用MyBatis查询数据库,验证用户名密码是否正确
3、如果正确,响应”登录成功“,如果错误,响应”登录失败“
需要一个登录页面
需要一个登录的Servlet类来接收数据,做出响应
需要一个连接MyBatis类
准备环境:
1、准备一个静态页面代码到项目的webapp目录下
2、创建db1数据库,创建tb_user表,创建User实体类
3、导入Mybatis坐标,MySQL驱动坐标
4、创建mybatis-config.xml核心配置文件,UserMapper.xml映射文件,UserMapper接口
参考代码:
package com.itheima.web; import com.itheima.mapper.UserMapper; import com.itheima.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 doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { //1、接收用户名和密码 String username = request.getParameter("username"); String password = request.getParameter("password"); //2、调用MyBatis完成查询 //2.1 获取SqlSessionFactory对象 String resource = "mybatis-config.xml"; //位置在resources根目录下 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("登录成功"); }else{ //登录失败 writer.write("登录失败"); } } protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { this.doGet(request, response); } }
案例:用户注册
流程说明:
1、用户填写用户名、密码等信息,点击注册按钮,提交到RegisterServlet
2、在RegisterServlet中使用MyBatis保存数据
3、保存前需要判断用户名是否已经存在:根据用户查询数据库
接收用户数据
调用查询方法,看是否用户名已经存在
判断返回结果,存在返回提示,不存在添加
参考代码:
package com.itheima.web; import com.itheima.mapper.UserMapper; import com.itheima.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; @WebServlet("/registerServlet") public class RegisterServlet extends HttpServlet { protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { //1、接收注册用户名和密码 String username = request.getParameter("username"); String password = request.getParameter("password"); //封装用户对象 User user = new User(); user.setUsername(username); user.setPassword(password); //2、调用mapper,根据用户名查询用户对象 //2.1 获取SqlSessionFactory对象 String resource = "mybatis-config.xml"; //位置在resources根目录下 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(); //2.5 释放资源 sqlSession.close(); } else{ //用户名已存在,提示不能创建 response.setContentType("text/html;charset=UTF-8"); response.getWriter().write("用户名已存在"); } } protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { this.doGet(request, response); } }
代码优化:
创建SqlSessionFactory代码优化
//2.1 获取SqlSessionFactory对象 String resource = "mybatis-config.xml"; //位置在resources根目录下 InputStream inputStream = Resources.getResourceAsStream(resource); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
问题:
1、代码重复:写一个工具类
2、SqlSessionFactory工厂只创建一次,不要重复创建(连接池多个,消耗资源):静态代码块
参考代码:
package com.itheima.util; 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; //获取SqlSessionFactory对象的工具类 public class SqlSessionFactoryUtils { //提升作用域: private static SqlSessionFactory sqlSessionFactory; //静态代码块的代码,会随着类的加载而自动执行,且只会执行一次 //不能抛出异常,使用try...catch static { try { String resource = "mybatis-config.xml"; //位置在resources根目录下 InputStream inputStream = Resources.getResourceAsStream(resource); sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); } catch (IOException e) { e.printStackTrace(); } } public static SqlSessionFactory getSqlSessionFactory(){ return sqlSessionFactory; } }
注意sqlSession不能如此,两者不同。
sqlSessionFactory是一个工厂,为了获取多个sqlSession而创建的,类似数据库连接池,但是用户操作不能并发,修改的同时增加。
sqlSession是用户连接数据库的唯一(针对用户操作)对象,若是增删改查都用一个共有对象处理。。。。。。
本文作者:如此而已~~~
本文链接:https://www.cnblogs.com/fragmentary/p/17094949.html
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步