Servlet 说明
首先我们需要了解浏览器访问服务器
我们这里还是以tomcat作为服务器参考对象。
①:Tomcat将http请求文本接收并解析,然后封装成HttpServletRequest类型的request对象,所有的HTTP头数据读可以通过request对象调用对应的方法查询到。
②:Tomcat同时会要响应的信息封装为HttpServletResponse类型的response对象,通过设置response属性就可以控制要输出到浏览器的内容,然后将response交给tomcat,tomcat就会将其变成响应文本的格式发送给浏览器
就那我么部署的项目来说。我们开启服务后。然后我们通过一段链接来访问我们的服务器然后得到响应数据。
http://localhost/login.html
localhost后面还有个80这里省略了。
我们可以根据localhost:80端口号找到tomcat服务器,然后后面的路径分别指定了访问项目路径和访问的内容。
我们在访问tomcat的时候会有一个默认的servlet数据处理。
我们也可以在web.xml查看内容。
我们没有创建Servlet类,里面的一些处理以及默认参数都是按照服务端默认数据进行,我们需要大致了解这些过程。
另外我们需要了解到如果我们在java中运用这个技术,就可以做出动态web开发。
Servlet初步入门尝试
我们运用maven在idea里面添加Servlet依赖坐标
<dependencies> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>3.1.0</version> <scope>provided</scope> </dependency> </dependencies>
然后我们创建一个类,来实现Servlet接口,并重写接口当中所有方法。
package jgd; import javax.servlet.*; import javax.servlet.annotation.WebServlet; import java.io.IOException; @WebServlet("/demo1") public class ServletDemo01 implements Servlet { public void init(ServletConfig servletConfig) throws ServletException { } public ServletConfig getServletConfig() { return null; } public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException { //Servlet被访问的时候service会自动执行 System.out.println("servlet hello world"); } public String getServletInfo() { return null; } public void destroy() { } }
上面的注解说明了访问路径。
启动!
对的什么也没有,我们来看控制台。
我们执行后,service方法被调用了。
Servlet由web服务器创建,Servlet方法由web服务器调用
我们自定义的Servlet,必须实现Servlet接口并复写其方法,而Servlet接口中有service方法
ServletDemo1实现了Servlet接口,所以类中必然会重写service方法供Tomcat Web服务器进行调用
service方法中有ServletRequest和ServletResponse两个参数,ServletRequest封装的是请求数据,ServletResponse封装的是响应数据,后期我们可以通过这两个参数实现前后端的数据交互
Servlet生命周期
在Java中对象的生命周期指一个对象从被创建到被销毁的整个过程。
我们可以将Servlet大致划分为四个阶段
1:加载和实例化,我们的默认情况下,没有自己指定值的话,Servlet在被第一次被访问的时候,会由容器创建Servlet对象。
默认情况,Servlet会在第一次访问被容器创建,但是如果创建Servlet比较耗时的话,那么第一个访问的人等待的时间就比较长,用户的体验就比较差,那么我们能不能把Servlet的创建放到服务器启动的时候来创建,具体如何来配置?
@WebServlet(urlPatterns = “/demo1”,loadOnStartup = 1)
loadOnstartup的取值有两类情况
(1)负整数:第一次访问时创建Servlet对象
(2)0或正整数:服务器启动时创建Servlet对象,数字越小优先级越高
2:初始化,继加载和实例化之后然后Servlet的init()方法会实现初始化对象。初始化完成的工作就是加载配置文件,创建连接等初始化工作。初始化的方法只会执行一次。
3:请求处理,每次请求Servlet都会自动调用service请求进行处理。
4:服务终止,当需要释放内存或者容器关闭时,容器就会调用Servlet实例的==destroy()==方法完成资源的释放。在destroy()方法调用之后,容器会释放这个Servlet实例,该实例随后会被Java的垃圾收集器所回收
测试用例
package jgd; import javax.servlet.*; import javax.servlet.annotation.WebServlet; import java.io.IOException; @WebServlet(urlPatterns = "/demo2", loadOnStartup = 1) public class ServletDemo02 implements Servlet { public void init(ServletConfig servletConfig) throws ServletException { // 初始化方法 // 1:调用时机:默认情况下Servlet被第一次访问的时候调用 // 2:调用次数:只会被调用一次 System.out.println("init"); } public ServletConfig getServletConfig() { return null; } public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException { //每次Servlet被访问的时候service会自动执行 System.out.println("servlet hello world"); } public String getServletInfo() { return null; } /** * 销毁方法 * 2:调用次数:1次 * 1:调用时机:内存或者服务器关闭的时候,Servlet对象会被销毁 */ public void destroy() { System.out.println("destory---"); } }
Servlet方法说明和体系结构
方法说明
在上文中已经说明了三个方法。初始化,服务,以及销毁。
1:初始化方法,只执行一次,默认在Servlet被第一次访问执行。
init(ServletConfig servletConfig)
2:服务方法,每次Servlet被访问的时候会默认访问,主要用于编写处理的访问逻辑.
void service(ServletRequest servletRequest, ServletResponse servletResponse)
3:销毁方法,Servlet被销毁的时候,该方法被调用。在内存释放或者服务器关闭的时候销毁Servlet。需要进行正常的关闭,如果关掉java进程就来不及等到对象被销毁。
destroy()
4:获取Servlet信息,一般的话不怎么用,给它返回空字符串和空就可以
String getServletInfo()
5:获取ServletConfig对象
ServletConfig getServletConfig()
我们研究一下如何获取到这个对象,我们可以这样做。主要是扩大作用范围。看看就明白了。
package jgd; import javax.servlet.*; import javax.servlet.annotation.WebServlet; import java.io.IOException; /* Servlet方法说明演示 */ @WebServlet(urlPatterns = "/demo3", loadOnStartup = 1) public class ServletDemo02 implements Servlet { private ServletConfig servletConfig; public void init(ServletConfig servletConfig) throws ServletException { // 初始化方法 // 1:调用时机:默认情况下Servlet被第一次访问的时候调用 // 2:调用次数:只会被调用一次 System.out.println("init"); //将servletConfig值赋给了局部变量 this.servletConfig=servletConfig; } public ServletConfig getServletConfig() { return servletConfig; } public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException { //每次Servlet被访问的时候service会自动执行 System.out.println("servlet hello world"); } public String getServletInfo() { return null; } /** * 销毁方法 * 2:调用次数:1次 * 1:调用时机:内存或者服务器关闭的时候,Servlet对象会被销毁 */ public void destroy() { System.out.println("destory---"); } }
该对象在init方法的参数中有,而Tomcat Web服务器在创建Servlet对象的时候会调用init方法,必定会传入一个ServletConfig对象,我们只需要将服务器传过来的ServletConfig进行返回即可
体系结构说明
之前写的Servlet类都是来自继承,并且需要重写方法等等。
了解到的体系结构
因为我们将来开发B/S架构的web项目,都是针对HTTP协议,所以我们自定义Servlet,会通过继承HttpServlet
所以我们来操作一下。尝试继承这个类。
package jgd; 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("/demo4") public class ServletDemo4 extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // super.doGet(req, resp); System.out.println("get..."); } @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // super.doPost(req, resp); System.out.println("post..."); } }
这里个方法分别可以代表get和post的处理逻辑。根据不同的请求方式来调用相应的方法以及做出方法提里面的处理逻辑。
在继承HttpServlet时,为什么只重写doGet和doPost
get方法我们就直接在浏览器地址栏访问到项目,如果是post我么就可以去编写一个表单,然后提交到对应web项目地址。
我们现在运行这个项目。直接访问地址。
这里成功输出了get。
然后我们写一个表单,表单的action路径要指定到你的项目路径,也就是Servlet路径。
不妨用之前那个表单
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>欢迎注册</title> <link href="register.css" rel="stylesheet"> </head> <body> <div class="form-div"> <div class="reg-content"> <h1>欢迎注册</h1> <span>已有帐号?</span> <a href="#">登录</a> </div> <form id="reg-form" action="/demo4" 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> <tr> <td>手机号</td> <td class="inputs"><input name="tel" type="text" id="tel"> <br> <span id="tel_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> <script> //1. 验证用户名是否符合规则 //1.1 获取用户名的输入框 var usernameInput = document.getElementById("username"); //1.2 绑定onblur事件 失去焦点 usernameInput.onblur = checkUsername; function checkUsername() { //1.3 获取用户输入的用户名 var username = usernameInput.value.trim(); //1.4 判断用户名是否符合规则:长度 6~12,单词字符组成 var reg = /^\w{6,12}$/; var flag = reg.test(username); //var flag = username.length >= 6 && username.length <= 12; if (flag) { //符合规则 document.getElementById("username_err").style.display = 'none'; } else { //不合符规则 document.getElementById("username_err").style.display = ''; } return flag; } //1. 验证密码是否符合规则 //1.1 获取密码的输入框 var passwordInput = document.getElementById("password"); //1.2 绑定onblur事件 失去焦点 passwordInput.onblur = checkPassword; function checkPassword() { //1.3 获取用户输入的密码 var password = passwordInput.value.trim(); //1.4 判断密码是否符合规则:长度 6~12 var reg = /^\w{6,12}$/; var flag = reg.test(password); //var flag = password.length >= 6 && password.length <= 12; if (flag) { //符合规则 document.getElementById("password_err").style.display = 'none'; } else { //不合符规则 document.getElementById("password_err").style.display = ''; } return flag; } //1. 验证手机号是否符合规则 //1.1 获取手机号的输入框 var telInput = document.getElementById("tel"); //1.2 绑定onblur事件 失去焦点 telInput.onblur = checkTel; function checkTel() { //1.3 获取用户输入的手机号 var tel = telInput.value.trim(); //1.4 判断手机号是否符合规则:长度 11,数字组成,第一位是1 //var flag = tel.length == 11; var reg = /^[1]\d{10}$/; var flag = reg.test(tel); if (flag) { //符合规则 document.getElementById("tel_err").style.display = 'none'; } else { //不合符规则 document.getElementById("tel_err").style.display = ''; } return flag; } //1. 获取表单对象 var regForm = document.getElementById("reg-form"); //2. 绑定onsubmit 事件 regForm.onsubmit = function () { //挨个判断每一个表单项是否都符合要求,如果有一个不合符,则返回false var flag = checkUsername() && checkPassword() && checkTel(); return flag; } </script> </body> </html>
注意:
启动!一定要定位到表单html。
这回我们的post请求就可以得到了。
前端发送GET和POST请求的时候,参数的位置不一致,GET请求参数在请求行中,POST请求参数在请求体中
一些优化封装
为了能处理不同的请求方式,我们得在service方法中进行判断,然后写不同的业务处理,这样能实现,但是每个Servlet类中都将有相似的代码,针对这个问题,有什么可以优化的策略么?
package jgd; import javax.servlet.*; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import java.io.IOException; public class ServletDemo5 implements Servlet { public void init(ServletConfig servletConfig) throws ServletException { } public ServletConfig getServletConfig() { return null; } public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException { // 根据请求方式的不同,进行分别的处理 // 获取请求方式 // 先转换为http协议下的逻辑 HttpServletRequest request = (HttpServletRequest) servletRequest; //获取到方法 String method = request.getMethod(); // 进行判断 if ("GET".equals(method)) { // get方式的处理逻辑 } else if ("POST".equals(method)) { // post方式的处理逻辑 } } public String getServletInfo() { return null; } public void destroy() { } }
比如上面这段代码,我不想每次写的时候都写逻辑判断,这是一段重复的代码。我不想每次写。这里的逻辑处理还不是很多,如果很多很多的话,我们该如何如理?我们每次都需要这样处理的话,我们就应该进行封装调用。
现在我们自己定义一个类
package jgd; import javax.servlet.*; import javax.servlet.http.HttpServletRequest; import java.io.IOException; public class MyHttpServlet implements Servlet { public void init(ServletConfig servletConfig) throws ServletException { } public ServletConfig getServletConfig() { return null; } public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException { // 根据请求方式的不同,进行分别的处理 // 获取请求方式 // 先转换为http协议下的逻辑 HttpServletRequest request = (HttpServletRequest) servletRequest; //获取到方法 String method = request.getMethod(); // 进行判断 if ("GET".equals(method)) { doGet(servletRequest, servletResponse); // get方式的处理逻辑 } else if ("POST".equals(method)) { // post方式的处理逻辑 doPost(servletRequest, servletResponse); } } protected void doPost(ServletRequest servletRequest, ServletResponse servletResponse) { } protected void doGet(ServletRequest servletRequest, ServletResponse servletResponse) { } public String getServletInfo() { return null; } public void destroy() { } }
我们把方法定义好,参数安排好。可以让之后的类继承到这个类。然后实现调用统一的功能。
后面就简化了好多,这也是继承的好处。也是一种功能封装。
package jgd; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.annotation.WebServlet; @WebServlet("/demo6") public class ServletDemo6 extends MyHttpServlet { @Override protected void doGet(ServletRequest servletRequest, ServletResponse servletResponse) { // super.doGet(servletRequest, servletResponse); System.out.println("get..."); } @Override protected void doPost(ServletRequest servletRequest, ServletResponse servletResponse) { // super.doPost(servletRequest, servletResponse); System.out.println("post.."); } }
urlParrern配置
之前我们采用注解的方式配置这个访问路径,只设置了一个访问路径。其实还可以配置多个访问路径。就像这样。
@WebServlet(urlPatterns = {"/demo7","/demo07"})
其实我们这样的配置就是一种路径匹配规则或者说是项目匹配访问规则。
下面我们介绍几种匹配规则
1:精确匹配
我们在注解中这样写的话,将来我们访问的化需要通过这个完整的路径来访问项目。
eg:
@WebServlet("/jgdabc/666")
2:目录匹配
eg:
/@WebServlet(urlPatterns = "/user/*") 目录匹配
将来我们访问的时候首先需要输入user,然后后面可以跟上任何路径。
3:扩展名匹配
@WebServlet("*.do")
将来访问的时候前面可以加上任何路径,但是后面需要加上do的扩展名。
4:任意匹配
//@WebServlet("/") //@WebServlet("/*")
任意匹配不建议使用
当我们的项目中的Servlet配置了 “/”,会覆盖掉tomcat中的DefaultServlet,当其他的url-pattern都匹配不上时都会走这个Servlet
当我们的项目中配置了"/*",意味着匹配任意访问路径
DefaultServlet是用来处理静态资源,如果配置了"/"会把默认的覆盖掉,就会引发请求静态资源的时候没有走默认的而是走了自定义的Servlet类,最终导致静态资源不能被访问
测试用例
package jgd; 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("/jgdabc/666") //可配置多个访问路径,这是精确匹配 //@WebServlet(urlPatterns = {"/demo7","/demo07"}) //@WebServlet(urlPatterns = "/user/*") 目录匹配 //当精确匹配和目录匹配同时都满足的时候,精确匹配优先级别更高 //@WebServlet("*.do") 扩展名匹配 //任意匹配 // /* 的优先级高于/ //@WebServlet("/") //@WebServlet("/*") public class ServletDemo7 extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { System.out.println("demo7get..."); } @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // super.doPost(req, resp); } }
Request 和Response 说明
简单作用说明
从单词字面意思上看,我们明白一个人是请求,一个是响应。
我们写Servlet代码的时候我们会常常看见这两个参数。
我们需要了解这两个参数器到了什么样的作用。
引用黑马的说明
request:获取请求数据
浏览器会发送HTTP请求到后台服务器[Tomcat]
HTTP的请求中会包含很多请求数据[请求行+请求头+请求体]
后台服务器[Tomcat]会对HTTP请求中的数据进行解析并把解析结果存入到一个对象中所存入的对象即为request对象,所以我们可以从request对象中获取请求的相关参数
获取到数据后就可以继续后续的业务,比如获取用户名和密码就可以实现登录操作的相关业务
response:设置响应数据
业务处理完后,后台就需要给前端返回业务处理的结果即响应数据把响应数据封装到response对象中
后台服务器[Tomcat]会解析response对象,按照[响应行+响应头+响应体]格式拼接结果浏览器最终解析结果,把内容展示在浏览器给用户浏览
代码测试案例
package jgd; 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; @WebServlet("/dod/*") public class ServletDemo8 extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // super.doGet(req, resp); // 获取到请求方式 String method = req.getMethod(); System.out.println("请求方式:"+method); // 获取虚拟访问目录(项目访问路径) String contextPath = req.getContextPath(); System.out.println("虚拟目录:"+contextPath); // 获取url StringBuffer requestURL = req.getRequestURL(); System.out.println("url"+requestURL.toString()); // 获取资源标识符 String requestURI = req.getRequestURI(); System.out.println("资源标识符"+requestURI); // 获取请求参数 String queryString = req.getQueryString(); System.out.println("请求参数"+queryString); // 获取浏览器版本的一个请求头 String agent = req.getHeader("user-agent");//获取 System.out.println("浏览器"+agent); } @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // super.doPost(req, resp); // 获取post的2请求体 // 1:获取字符输入流 BufferedReader br = req.getReader(); // 读取数据 String line = br.readLine(); System.out.println(line); // 一次请求完毕后,request对象被销毁后,流会自动关闭 } }
package jgd; 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("/demo9") public class ServletDemo9 extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String name = req.getParameter("name"); resp.setHeader("content-type", "text/html;character=utf-8"); resp.setContentType("text/html;charset=utf-8");//html // resp.setContentType("text/plain; charset=utf-8"); 文本 // charset=UTF-8.如果该方法在getWriter()方法被调用之前调用,那么响应的字符编码将仅从给出的内容类型 中设置。该方法如果在getWriter()方法被调用之后或者在被提交之后调用, // 将不会设置响应的字符编码,在使用http协议的情况中,该方法设 置 Content-type实体报头。 resp.getWriter().write("<h1>" + name + ",欢迎你</h1>"); } @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // super.doPost(req, resp); System.out.println("POST。。。"); } }
需要注意的地方请看代码注释
request对象是用来封装请求数据的对象
response对象是用来封装响应数据的对象
当然这只是我们一个小小的理解
Request的继承体系
然后我们还会发现在不同的Servlet中其中这两个参数的写法也是不一样的。
package jgd; import javax.servlet.*; import javax.servlet.annotation.WebServlet; import java.io.IOException; @WebServlet("/demo1") public class ServletDemo01 implements Servlet { public void init(ServletConfig servletConfig) throws ServletException { } public ServletConfig getServletConfig() { return null; } public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException { //Servlet被访问的时候service会自动执行 System.out.println("servlet hello world"); } public String getServletInfo() { return null; } public void destroy() { } }
package jgd; 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("/jgdabc/666") //可配置多个访问路径,这是精确匹配 //@WebServlet(urlPatterns = {"/demo7","/demo07"}) //@WebServlet(urlPatterns = "/user/*") 目录匹配 //当精确匹配和目录匹配同时都满足的时候,精确匹配优先级别更高 //@WebServlet("*.do") 扩展名匹配 //任意匹配 // /* 的优先级高于/ //@WebServlet("/") //@WebServlet("/*") public class ServletDemo7 extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { System.out.println("demo7get..."); } @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // super.doPost(req, resp); } }
一个是存在于继承Servlet的方法参数体中,一个是HttpServlet的doGet()和doPost()两个方法的参数体中。我们可以明显的发现这两个参数的写法是不一样的。但是其实还是一个实现请求一个实现响应。
现在我们来看Request,想必也是有自己的继承体系。
ServletRequest和HttpServletRequest都是Java提供的。然后它的实现是由Tomcat来完成的。实现类就是RequestFacade。
该类实现了HttpServletRequest接口,也间接实现了ServletRequest接口。
Servlet类中的service方法、doGet方法或者是doPost方法最终都是由Web服务器[Tomcat]
来调用的,所以Tomcat提供了方法参数接口的具体实现类,并完成了对象的创建
验证方法就是你把request打印出来,就可以理解了。
Request获取请求行数据和请求头数据
前面已经给出代码示例了。具体的作用已经在注释中标注。
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // super.doGet(req, resp); // 获取到请求方式 String method = req.getMethod(); System.out.println("请求方式:"+method); // 获取虚拟访问目录(项目访问路径) String contextPath = req.getContextPath(); System.out.println("虚拟目录:"+contextPath); // 获取url StringBuffer requestURL = req.getRequestURL(); System.out.println("url"+requestURL.toString()); // 获取资源标识符 String requestURI = req.getRequestURI(); System.out.println("资源标识符"+requestURI); // 获取请求参数 String queryString = req.getQueryString(); System.out.println("请求参数"+queryString); // 获取浏览器版本的一个请求头 String agent = req.getHeader("user-agent");//获取 System.out.println("浏览器"+agent); }
请求行包含三块内容,分别是请求方式、请求资源路径、HTTP协议及版本
对于请求头的数据,格式为key: value
其实在浏览器当中检查源代码就类似于这样的源代码检查
对比上诉代码我们根据键获取到了值。
在上面的代码示例中我们获取到的是浏览器的数据。
Request获取请求体数据
浏览器在发送GET请求的时候是没有请求体的,所以需要把请求方式变更为POST
我们想要获取到提交的数据。但是我们了解到提交的数据可能是文件,也可能是纯文本数据。于是我们需要考虑到获取数据的方式。
文件当然就需要用到字节流,纯文本需要用到字符流。
获取字节数据的方法
ServletInputStream getInputStream()
获取字符数据的方法
BufferedReader getReader()
我现在就写一个表单作为提交
这是我们之前的表单,我们还用这个表单。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>欢迎注册</title> <link href="register.css" rel="stylesheet"> </head> <body> <div class="form-div"> <div class="reg-content"> <h1>欢迎注册</h1> <span>已有帐号?</span> <a href="#">登录</a> </div> <form id="reg-form" action="/dod/jgdabc" 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> <tr> <td>手机号</td> <td class="inputs"><input name="tel" type="text" id="tel"> <br> <span id="tel_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> <script> //1. 验证用户名是否符合规则 //1.1 获取用户名的输入框 var usernameInput = document.getElementById("username"); //1.2 绑定onblur事件 失去焦点 usernameInput.onblur = checkUsername; function checkUsername() { //1.3 获取用户输入的用户名 var username = usernameInput.value.trim(); //1.4 判断用户名是否符合规则:长度 6~12,单词字符组成 var reg = /^\w{6,12}$/; var flag = reg.test(username); //var flag = username.length >= 6 && username.length <= 12; if (flag) { //符合规则 document.getElementById("username_err").style.display = 'none'; } else { //不合符规则 document.getElementById("username_err").style.display = ''; } return flag; } //1. 验证密码是否符合规则 //1.1 获取密码的输入框 var passwordInput = document.getElementById("password"); //1.2 绑定onblur事件 失去焦点 passwordInput.onblur = checkPassword; function checkPassword() { //1.3 获取用户输入的密码 var password = passwordInput.value.trim(); //1.4 判断密码是否符合规则:长度 6~12 var reg = /^\w{6,12}$/; var flag = reg.test(password); //var flag = password.length >= 6 && password.length <= 12; if (flag) { //符合规则 document.getElementById("password_err").style.display = 'none'; } else { //不合符规则 document.getElementById("password_err").style.display = ''; } return flag; } //1. 验证手机号是否符合规则 //1.1 获取手机号的输入框 var telInput = document.getElementById("tel"); //1.2 绑定onblur事件 失去焦点 telInput.onblur = checkTel; function checkTel() { //1.3 获取用户输入的手机号 var tel = telInput.value.trim(); //1.4 判断手机号是否符合规则:长度 11,数字组成,第一位是1 //var flag = tel.length == 11; var reg = /^[1]\d{10}$/; var flag = reg.test(tel); if (flag) { //符合规则 document.getElementById("tel_err").style.display = 'none'; } else { //不合符规则 document.getElementById("tel_err").style.display = ''; } return flag; } //1. 获取表单对象 var regForm = document.getElementById("reg-form"); //2. 绑定onsubmit 事件 regForm.onsubmit = function () { //挨个判断每一个表单项是否都符合要求,如果有一个不合符,则返回false var flag = checkUsername() && checkPassword() && checkTel(); return flag; } </script> </body> </html>
这是我们的Servlet代码
package jgd; 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; @WebServlet("/dod/*") public class ServletDemo8 extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // super.doGet(req, resp); // 获取到请求方式 String method = req.getMethod(); System.out.println("请求方式:"+method); // 获取虚拟访问目录(项目访问路径) String contextPath = req.getContextPath(); System.out.println("虚拟目录:"+contextPath); // 获取url StringBuffer requestURL = req.getRequestURL(); System.out.println("url"+requestURL.toString()); // 获取资源标识符 String requestURI = req.getRequestURI(); System.out.println("资源标识符"+requestURI); // 获取请求参数 String queryString = req.getQueryString(); System.out.println("请求参数"+queryString); // 获取浏览器版本的一个请求头 String agent = req.getHeader("user-agent");//获取 System.out.println("浏览器"+agent); } @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // super.doPost(req, resp); // 获取post的2请求体 // 1:获取字符输入流 BufferedReader br = req.getReader(); // 读取数据 String line = br.readLine(); System.out.println(line); // 一次请求完毕后,request对象被销毁后,流会自动关闭 } }
启动后项目,定位到页面。
填写信息提交后,就会提交到服务地址。
然后我们看控制台输出。
这些信息我们都获取到了。这就是我们的请求体数据。
BufferedReader流是通过request对象来获取的,当请求完成后request对象就会被销毁, request对象被销毁后,BufferedReader流就会自动关闭,所以此处就不需要手动关闭流了
代码的一些优化
我们主要再来看这个doGet()和doPost()两个方法
这两个方法中的参数体是一样的,参数名只是名字不同。然后处理逻辑可能有一点点差异。
但是我们是否可以提供一种统一获取请求参数的方式,来统一一下doGet和doPost方法体当中的代码。
在这之前我们再来理一下相关的方法
HTTP请求数据中包含了请求行、请求头和请求体,针对这三部分内容,Request对象都提供了对应的
API方法来获取对应的值:
请求行
getMethod()获取请求方式 getContextPath()获取项目访问路径getRequestURL()获取请求URL getRequestURI()获取请求URI getQueryString()获取GET请求方式的请求参数
请求头
getHeader(String name)根据请求头名称获取其对应的值请求体
注意: 浏览器发送的POST请求才有请求体如果是纯文本数据:getReader()
如果是字节数据如文件数据:getInputStream()
我们来演示一下getQueryString();
我们需要在访问地址栏中填入参数。
这是我们用到的代码
package jgd; 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("/demo4") public class ServletDemo4 extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // super.doGet(req, resp); System.out.println("get..."); String queryString = req.getQueryString(); System.out.println(queryString); } @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // super.doPost(req, resp); System.out.println("post..."); } }
这是我们启动项目后访问地址并填入参数的例子。
然后回车看输出。
下面最后一行的字段就是我们这个方法获取到的数据。我们这个叫请求数据。
于是我们归结上面的我们提出两种获取请求数据的方式。
对于请求参数的获取,常用的有以下两种:
GET方式:
String getQueryString()
POST方式:
BufferedReader getReader();
针对不同的请求,我们首先可以这样。
package jgd; 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; @WebServlet("/demo4") public class ServletDemo4 extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // super.doGet(req, resp); System.out.println("get..."); String queryString = req.getQueryString(); System.out.println(queryString); } @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // super.doPost(req, resp); BufferedReader reader = req.getReader(); String s = reader.readLine(); System.out.println(s); } }
重复了,当然你可能说一行不多,但是很有代表性的是,如果是请求方式的不同将来业务逻辑要求我们类似这样做出很多重复代码呢?这样就不是很好。
我们可以这样解决。原因在于方法体参数一样。
package jgd; 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; @WebServlet("/demo4") public class ServletDemo4 extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // super.doGet(req, resp); // System.out.println("get..."); // String queryString = req.getQueryString(); // System.out.println(queryString); String params = " "; String method = req.getMethod(); if ("GET".equals(method)) { params = req.getQueryString(); } else if ("POST".equals(method)) { BufferedReader reader = req.getReader(); params = reader.readLine(); } System.out.println(params); } @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // super.doPost(req, resp); // BufferedReader reader = req.getReader(); // String s = reader.readLine(); // System.out.println(s); this.doGet(req,resp); } }
但是这样的方式还需要写很多代码,我们想要寻求
一种更加简单的方式。
我们来看一些方法
获取所有参数得Map集合
Map<String,String[]> getParameterMap()
这个方法让我们获取的是Map集合类型的数据。对应键值对,键唯一,值可以多,是一个数组。
根据名称获取到值(数组)
String[] getParameterValues(String name)
根据名称获取到单个值
String getParameter(String name)
测试代码
先写一个表单
package jgd; 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("/jgdabc00") public class ServletDemo10 extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // super.doGet(req, resp); // Get请求逻辑 System.out.println("get---"); // 获取所有参数的map集合 Map<String, String[]> map = req.getParameterMap(); String queryString = req.getQueryString(); System.out.println(queryString); for (String key:map.keySet()) { System.out.print(key+":"); String[] values = map.get(key); //获取value for (String value:values) System.out.println(value+" "); } // 根据key获取参数值,数组 String[] hobbies = req.getParameterValues("hobby"); // for(String hobby:hobbies){ // System.out.println(hobby); // } // 根据key获取单个参数值 String password = req.getParameter("password"); System.out.println(password); } // @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { System.out.println("post---"); } }
启动!
访问到这个地址,把表单填写了。
提交以后
这是我们的一个代码举例。
我们刚刚说这是一个统一的获取的请求方式的代码。我们验证一下。我们把这些方法放到doPost里面,然后把表单的提交方式变为post。
package jgd; 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("/jgdabc00") public class ServletDemo10 extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // super.doGet(req, resp); // Get请求逻辑 System.out.println("get---"); // } // @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { System.out.println("post---"); // 获取所有参数的map集合 Map<String, String[]> map = req.getParameterMap(); String queryString = req.getQueryString(); System.out.println(queryString); for (String key:map.keySet()) { System.out.print(key+":"); String[] values = map.get(key); //获取value for (String value:values) System.out.println(value+" "); } // 根据key获取参数值,数组 String[] hobbies = req.getParameterValues("hobby"); // for(String hobby:hobbies){ // System.out.println(hobby); // } // 根据key获取单个参数值 String password = req.getParameter("password"); System.out.println(password); } }
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <form action="/jgdabc00" method="post"> <input type="text" name="username"> <input type="password" name="password"> <input type="checkbox" name="hobby" value="1">游泳 <input type="checkbox" name="hobby" value="2">爬山<br> <input type="submit"> </form> </body> </html>
然后我们再次开启运行一次。看看能不能获取到数据。
在我们提交表单数据后,这里没有参数,代表了我们是按照post方式提交的。然后我们看控制台输出。
可见该段代码可以在两个方法中通用。于是我们写出一种简化的方式。
package jgd; 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("/jgdabc00") public class ServletDemo10 extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // super.doGet(req, resp); // Get请求逻辑 System.out.println("get---"); this.doPost(req,resp); // } // @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { System.out.println("post---"); // 获取所有参数的map集合 Map<String, String[]> map = req.getParameterMap(); String queryString = req.getQueryString(); System.out.println(queryString); for (String key:map.keySet()) { System.out.print(key+":"); String[] values = map.get(key); //获取value for (String value:values) System.out.println(value+" "); } // 根据key获取参数值,数组 String[] hobbies = req.getParameterValues("hobby"); // for(String hobby:hobbies){ // System.out.println(hobby); // } // 根据key获取单个参数值 String password = req.getParameter("password"); System.out.println(password); // 等相同业务代码 } }
Request 请求转发
请求转发是发生在服务器内部的一种资源跳转。
(1) 浏览器发送请求给服务器,服务器中对应的资源A接收到请求
(2)资源A处理完请求后将请求发给资源B
(3) 资源B处理完后将结果响应给浏览器
(4) 请求从资源A到资源B的过程就叫请求转发
资源转发的操作就是我们具体在Servlet代码类中进行操作的。
req.getRequestDispatcher(“资源B路径”).forward(req,resp);
我们先创建一个类
package com.itheima.web.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; /** * 请求转发 */ @WebServlet("/req5") public class RequestDemo5 extends HttpServlet { @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { System.out.println("demo5..."); System.out.println(request); //存储数据 request.setAttribute("msg","hello"); //请求转发 request.getRequestDispatcher("/req6").forward(request,response); } @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { this.doGet(request, response); } }
然后我们创建一个目标转发类
package com.itheima.web.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("/req6") public class RequestDemo6 extends HttpServlet { @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { System.out.println("demo6..."); System.out.println(request); //获取数据 Object msg = request.getAttribute("msg"); System.out.println(msg); } @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { this.doGet(request, response); } }
启动!
先定位到RequestDemo5。
然后看控制台输出
这样的请求转发的特点就是主要发生在服务器内部,浏览器地址栏路径是不会的。这样还不能转发到其他的服务器。这是一个很大的限制。
Response重定向
Request:使用request对象来获取请求数据Response:使用response对象来设置响应数据
HTTP响应数据总共分为三部分内容,分别是响应行、响应头、响应体。
设置响应状态码
void setStatus(int sc);
设置响应头键值对
void setHeader(String name,String value);
获取字符输出流
PrintWriter getWriter();
获取字节输出流
ServletOutputStream getOutputStream()
知道这几个方法后我们完成重定向
主要实现代码
1 resp.setStatus(302); 2 resp.setHeader("location","资源B的访问路径");
完成一次重定向
package com.itheima.web.response; import javax.servlet.*; import javax.servlet.http.*; import javax.servlet.annotation.*; import java.io.IOException; @WebServlet("/resp1") public class ResponseDemo1 extends HttpServlet { @Override 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"); //简化方式完成重定向 //动态获取虚拟目录 String contextPath = request.getContextPath(); // 简化方式完成重定向 response.sendRedirect(contextPath+"/resp2"); } @Override 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 { @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { System.out.println("resp2...."); } @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { this.doGet(request, response); } }
启动!
我们重定向后,地址栏连接也会变化。
相比较转发和重定向,我们发现重定向需要加虚拟目录,而转发是不需要加虚拟目录的。
浏览器使用:需要加虚拟目录(项目访问路径) 服务端使用:不需要加虚拟目录
对于转发来说,因为是在服务端进行的,所以不需要加虚拟目录
对于重定向来说,路径最终是由浏览器来发送请求,就需要添加虚拟目录。
Response响应字符数据
首先我们通过Response对选哪个获取字符输出流。
PrintWriter writer = resp.getWriter();
然后通过字符输出流写数据
writer.write("aaa");
测试代码
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 { @Override 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>"); //细节:流不需要关闭 } @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { this.doGet(request, response); } }
2 //content-type,告诉浏览器返回的数据类型是HTML类型数据,这样浏览器才会解析HTML标签
一次请求响应结束后,response对象就会被销毁掉,所以不要手动关闭流。
Response响应字节数据
响应字节数据首先需要获取到自己输出流
ServletOutputStream outputStream = resp.getOutputStream();
然后通过字节输出流写数据
outputStream.write(字节数据);
现在我们通过自己输出流来将一张图片传到浏览器
准备的一张图片
package com.itheima.web.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.FileInputStream; import java.io.IOException; import java.io.PrintWriter; /** * 响应字节数据:设置字节数据的响应体 */ @WebServlet("/resp4") public class ResponseDemo4 extends HttpServlet { @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { //1. 读取文件 FileInputStream fis = new FileInputStream("E:\\java_project__\\request-demo\\src\\main\\java\\com\\itheima\\web\\response\\be.png"); //2. 获取response字节输出流 ServletOutputStream os = response.getOutputStream(); //3. 完成流的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(); } @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { this.doGet(request, response); } }
==IOUtils.copy(fis,os);==该方法需要我们加入相关的依赖 <dependency> <groupId>commons-io</groupId> <artifactId>commons-io</artifactId> <version>2.6</version> </dependency>
可能存在的乱码的一些解决的办法
乱码的问题主要出现在tomcat8之前,但是目前的插件管理依赖只能到7。
我们还是用一个写的表单,然后写一个Servlet代码
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <form action="/jgdabc00" method="post"> <input type="text" name="username"> <input type="password" name="password"> <input type="checkbox" name="hobby" value="1">游泳 <input type="checkbox" name="hobby" value="2">爬山<br> <input type="submit"> </form> </body> </html>
package jgd; 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("/jgdabc00") public class ServletDemo10 extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // super.doGet(req, resp); // Get请求逻辑 System.out.println("get---"); this.doPost(req,resp); // } // @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { System.out.println("post---"); // 获取所有参数的map集合 Map<String, String[]> map = req.getParameterMap(); String queryString = req.getQueryString(); System.out.println(queryString); for (String key:map.keySet()) { System.out.print(key+":"); String[] values = map.get(key); //获取value for (String value:values) System.out.println(value+" "); } // 根据key获取参数值,数组 String[] hobbies = req.getParameterValues("hobby"); // for(String hobby:hobbies){ // System.out.println(hobby); // } // 根据key获取单个参数值 String password = req.getParameter("password"); System.out.println(password); // 等相同业务代码 } }
启动!
我们填入中文,提交。
这里出现了乱码。
编码与解码的一些说明
post 请求
POST的请求参数是通过request的getReader()来获取流中的数据TOMCAT在获取流的时候采用的编码是ISO-8859-1
ISO-8859-1编码是不支持中文的,所以会出现乱码
解决方案
页面设置的编码格式为UTF-8
把TOMCAT在获取流数据之前的编码设置为UTF-8
通过request.setCharacterEncoding(“UTF-8”)设置编码,UTF-8也可以写成小写
get请求
get获取数据并不是通过流的方式。post是通过流的方式。所以设置处理流的办法是没办法解决乱码问题的。
我们研究一下get请求的原理
(说明该图引用自黑马资料)
说明:
1:浏览器通过http协议发送请求给服务器
2:发送数据的时候一定会进行编码,进行的是url编码
3:在进行URL编码的时候会采用页面 标签指定的UTF-8的方式进行编码, 张三编码后的结果%E5%BC%A0%E4%B8%89
4: 后台服务器(Tomcat)接收到%E5%BC%A0%E4%B8%89 后会默认按照ISO-8859-1 进行URL解码
5:(5) 由于前后编码与解码采用的格式不一样,就会导致后台获取到的数据为乱码。
思考: 如果把req.html 页面的 标签的charset属性改成ISO-8859-1 ,后台不做操作,能解决中文乱码问题么?
答案是否定的,因为ISO-8859-1 本身是不支持中文展示的,所以改了标签的charset属性后,会导致页面上的中文内容都无法正常展示。
url编码
首先转换为二进制
一个汉字占用三个字节,所以张三一共需要6个字节,也就是48位。换算为二进制。
1110 0101 1011 1100 1010 0000 1110 0100 1011 1000 1000 1001
然后转换为16进制
E5BCA0E4B889
其实很好验证
我在浏览器的地址栏的随便一个链接中后面加上参数
然后我再复制完成全部链接,你可以到一个编辑器或者idea里面
取下来
https://cn.bing.com/search?q=jgdabc&form=ANNTH1&refig=%E5%BC%A0%E4%B8%89
你看就是对应上面我们转换出来的16进制数,然后隔两个加一个%号。
这样就完成了我们的url编码。
解码的话就交给对面的服务器,看按照什么解码方式。逻辑是倒着来,但是用到的方式不同的话,就会出现乱码的问题。
String username = "张三"; //1. URL编码 String encode = URLEncoder.encode(username, "utf-8"); System.out.println(encode); //打印:%E5%BC%A0%E4%B8%89 //2. URL解码 //String decode = URLDecoder.decode(encode, "utf-8");//打印:张三 String decode = URLDecoder.decode(encode, "ISO-8859-1");//打印:`å¼ ä¸ ` System.out.println(decode);
这段代码演示了一个编码解码的方式。
浏览器把中文参数按照UTF-8 进行URL编码
Tomcat对获取到的内容进行了ISO-8859-1 的URL解码
在控制台就会出现类上å¼ ä¸□ 的乱码,最后一位是个空格
解决乱码
为什么会出现乱码
一句话说明就是编码和解码的方式不一样。
我们这个按照post提交的,get也一样,也会出现乱码。
解决post乱码
设置字符输入流的编码,设置的字符集要和页面保持一致
10 request.setCharacterEncoding(“UTF-8”);
我们先测试一下能否解决post乱码的问题
package jgd; 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("/jgdabc00") public class ServletDemo10 extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // super.doGet(req, resp); // Get请求逻辑 System.out.println("get---"); this.doPost(req,resp); // } // @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { System.out.println("post---"); // 获取所有参数的map集合 req.setCharacterEncoding("UTF-8"); Map<String, String[]> map = req.getParameterMap(); String queryString = req.getQueryString(); System.out.println(queryString); for (String key:map.keySet()) { System.out.print(key+":"); String[] values = map.get(key); //获取value for (String value:values) System.out.println(value+" "); } // 根据key获取参数值,数组 String[] hobbies = req.getParameterValues("hobby"); // for(String hobby:hobbies){ // System.out.println(hobby); // } // 根据key获取单个参数值 String password = req.getParameter("password"); System.out.println(password); // 等相同业务代码 } }
然后再次填写信息。获取数据。
成功获取到数据。
解决get请求乱码
在进行编码和解码的时候,不管使用的是哪个字符集,他们对应的%E5%BC%A0%E4%B8%89 是一致的
所以我们可以考虑把å¼ ä¸□ 转换成字节,在把字节转换成张三,在转换的过程中是它们的编码一致,就可以解决中文乱码问题。
1.按照ISO-8859-1编码获取乱码å¼ ä¸□ 对应的字节数组
2.按照UTF-8编码获取字节数组对应的字符串
演示代码
String username = "张三"; //1. URL编码 String encode = URLEncoder.encode(username, "utf-8"); System.out.println(encode); //2. URL解码 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 + " "); } //此处打印的是:-27 -68 -96 -28 -72 -119(十进制数字) //4. 将字节数组转为字符串,解码 String s = new String(bytes, "utf-8"); System.out.println(s); //此处打印的是张三
value = new String(value.getBytes(StandardCharsets.ISO_8859_1),StandardCharsets.UTF_8);
完成两件事,首先转换为字节数组,然后解码,其实值转换为字符串。按照utf-8就可以。
来看代码
package jgd; 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("/jgdabc00") public class ServletDemo10 extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // super.doGet(req, resp); // Get请求逻辑 System.out.println("get---"); this.doPost(req, resp); // } // @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // System.out.println("post---"); // 获取所有参数的map集合 // req.setCharacterEncoding("UTF-8"); Map<String, String[]> map = req.getParameterMap(); String queryString = req.getQueryString(); System.out.println(queryString); for (String key : map.keySet()) { System.out.print(key + ":"); String[] values = map.get(key); //获取value for (String value : values) { value = new String(value.getBytes(StandardCharsets.ISO_8859_1), StandardCharsets.UTF_8); System.out.println(value); } // System.out.println(value + " "); } // 根据key获取参数值,数组 String[] hobbies = req.getParameterValues("hobby"); // for(String hobby:hobbies){ // System.out.println(hobby); // } // 根据key获取单个参数值 String password = req.getParameter("password"); password = new String(password.getBytes(StandardCharsets.ISO_8859_1), StandardCharsets.UTF_8); System.out.println(password); System.out.println(password); // 等相同业务代码 } }
一些说明
为什么web中pom的servlet依赖scope为provided
<dependencies> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>3.1.0</version> <scope>provided</scope> </dependency> </dependencies>
provided的作用就是编译的时候起作用,运行的的时候不起作用。
在我们的Tomcat里面也有自带的servlet-api.jar。如果不这样设置的话,在启动的时候会和自带的依赖产生冲突。
使用idea创建servlet
前提是我们装上上面那个项目所需的依赖。然后我们这样创建。
这是创建好的,这是默认的模板,当然我们还可以进行更改。
先打开这里
我们可以在这里修改默认的模板。
但是我习惯自己去写。简单提一下。
完成登录功能
实现逻辑
用户在登录页面输入用户名和密码,提交请求给LoginServlet
在LoginServlet中接收请求和数据[用户名和密码
在LoginServlt中通过Mybatis实现调用UserMapper来根据用户名和密码查询数据库表
将查询的结果封装到User对象中进行返回
在LoginServlet中判断返回的User对象是否为null
如果为nul,说明根据用户名和密码没有查询到用户,则登录失败,返回"登录失败"数据给前端
如果不为null,则说明用户存在并且密码正确,则登录成功,返回"登录成功"数据给前端
完成步骤
1:准备一个静态登录界面
<!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="/request-demo/loginServlet" 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>
css如下
* { margin: 0; padding: 0; } html { height: 100%; width: 100%; overflow: hidden; margin: 0; padding: 0; background: url(../imgs/Desert.jpg) no-repeat 0px 0px; background-repeat: no-repeat; background-size: 100% 100%; -moz-background-size: 100% 100%; } body { display: flex; align-items: center; justify-content: center; height: 100%; } #loginDiv { width: 37%; display: flex; justify-content: center; align-items: center; height: 300px; background-color: rgba(75, 81, 95, 0.3); box-shadow: 7px 7px 17px rgba(52, 56, 66, 0.5); border-radius: 5px; } #name_trip { margin-left: 50px; color: red; } p { margin-top: 30px; margin-left: 20px; color: azure; } input { margin-left: 15px; border-radius: 5px; border-style: hidden; height: 30px; width: 140px; background-color: rgba(216, 191, 216, 0.5); outline: none; color: #f0edf3; padding-left: 10px; } #username{ width: 200px; } #password{ width: 202px; } .button { border-color: cornsilk; background-color: rgba(100, 149, 237, .7); color: aliceblue; border-style: hidden; border-radius: 5px; width: 100px; height: 31px; font-size: 16px; } #subDiv { text-align: center; margin-top: 30px; } #loginMsg{ text-align: center;color: aliceblue; }
图片自己找,我用的还是黑马提供的素材。
实现的页面
注意下路径的问题就好。
2:然后我们做一个数据库的用户表
-- 创建用户表 use mysql; 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; -- 查询 一下
3:写一个java用户的类
package com.itheima.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 + '\'' + '}'; } }
4:在pom中导入mybatis的驱动坐标
<dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.5.5</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.19</version> </dependency>
注意你的mysql 的版本,如果是8的话你就用8的版本驱动。
5:创建UserMapper.xml映射文件,UserMapper接口
package com.itheima.mapper; import com.itheima.pojo.User; import org.apache.ibatis.annotations.Insert; import org.apache.ibatis.annotations.Param; import org.apache.ibatis.annotations.Select; public interface UserMapper { /** * 根据用户名和密码查询用户对象 * @param username * @param password * @return */ @Select("select * from tb_user where username = #{username} and password = #{password}") User select(@Param("username") String username,@Param("password") String password); /** * 根据用户名查询用户对象 * @param username * @return */ @Select("select * from tb_user where username = #{username}") User selectByUsername(String username); /** * 添加用户 * @param user */ @Insert("insert into tb_user values(null,#{username},#{password})") void add(User user); }
<?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="com.itheima.mapper.UserMapper"> </mapper>
6: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> <!--起别名--> <typeAliases> <package name="com.itheima.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:///mysql?serverTimezone=GMT"/> <property name="username" value="root"/> <property name="password" value="123456"/> </dataSource> </environment> </environments> <mappers> <!--扫描mapper--> <package name="com.itheima.mapper"/> </mappers> </configuration>
注意驱动版本的url连接,还有就是时区问题
7:然后就是我们的Servlet代码
package com.itheima.web; import com.itheima.mapper.UserMapper; import com.itheima.pojo.User; import com.itheima.util.SqlSessionFactoryUtils; 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.*; import javax.servlet.http.*; import javax.servlet.annotation.*; import java.io.IOException; import java.io.InputStream; import java.io.PrintWriter; @WebServlet("/loginServlet") public class LoginServlet extends HttpServlet { @Override 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"; InputStream inputStream = Resources.getResourceAsStream(resource); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);*/ SqlSessionFactory sqlSessionFactory = SqlSessionFactoryUtils.getSqlSessionFactory(); //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("登陆失败"); } } @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { this.doGet(request, response); } }
启动!
完成注册功能
首先我们来创建一个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="/request-demo/registerServlet" 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>
css如下
* { margin: 0; padding: 0; list-style-type: none; } .reg-content{ padding: 30px; margin: 3px; } a, img { border: 0; } body { background-image: url("../imgs/reg_bg_min.jpg") ; text-align: center; } table { border-collapse: collapse; border-spacing: 0; } td, th { padding: 0; height: 90px; } .inputs{ vertical-align: top; } .clear { clear: both; } .clear:before, .clear:after { content: ""; display: table; } .clear:after { clear: both; } .form-div { background-color: rgba(255, 255, 255, 0.27); border-radius: 10px; border: 1px solid #aaa; width: 424px; margin-top: 150px; margin-left:1050px; padding: 30px 0 20px 0px; font-size: 16px; box-shadow: inset 0px 0px 10px rgba(255, 255, 255, 0.5), 0px 0px 15px rgba(75, 75, 75, 0.3); text-align: left; } .form-div input[type="text"], .form-div input[type="password"], .form-div input[type="email"] { width: 268px; margin: 10px; line-height: 20px; font-size: 16px; } .form-div input[type="checkbox"] { margin: 20px 0 20px 10px; } .form-div input[type="button"], .form-div input[type="submit"] { margin: 10px 20px 0 0; } .form-div table { margin: 0 auto; text-align: right; color: rgba(64, 64, 64, 1.00); } .form-div table img { vertical-align: middle; margin: 0 0 5px 0; } .footer { color: rgba(64, 64, 64, 1.00); font-size: 12px; margin-top: 30px; } .form-div .buttons { float: right; } input[type="text"], input[type="password"], input[type="email"] { border-radius: 8px; box-shadow: inset 0 2px 5px #eee; padding: 10px; border: 1px solid #D4D4D4; color: #333333; margin-top: 5px; } input[type="text"]:focus, input[type="password"]:focus, input[type="email"]:focus { border: 1px solid #50afeb; outline: none; } input[type="button"], input[type="submit"] { padding: 7px 15px; background-color: #3c6db0; text-align: center; border-radius: 5px; overflow: hidden; min-width: 80px; border: none; color: #FFF; box-shadow: 1px 1px 1px rgba(75, 75, 75, 0.3); } input[type="button"]:hover, input[type="submit"]:hover { background-color: #5a88c8; } input[type="button"]:active, input[type="submit"]:active { background-color: #5a88c8; } .err_msg{ color: red; padding-right: 170px; } #password_err,#tel_err{ padding-right: 195px; } #reg_btn{ margin-right:50px; width: 285px; height: 45px; margin-top:20px; }
接口中的方法我们前面已经都加上了。
查询和添加的方法
@Select("select * from tb_user where username = #{username}") User selectByUsername(String username); /** * 添加用户 * @param user */ @Insert("insert into tb_user values(null,#{username},#{password})") void add(User user);
user类还是之前的,不用再细说。
然后Servlet代码
package com.itheima.web; import com.itheima.mapper.UserMapper; import com.itheima.pojo.User; import com.itheima.util.SqlSessionFactoryUtils; 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.*; import javax.servlet.http.*; import javax.servlet.annotation.*; import java.io.IOException; import java.io.InputStream; @WebServlet("/registerServlet") public class RegisterServlet extends HttpServlet { @Override 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"; InputStream inputStream = Resources.getResourceAsStream(resource); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);*/ SqlSessionFactory sqlSessionFactory = SqlSessionFactoryUtils.getSqlSessionFactory(); //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("用户名已存在"); } } @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { this.doGet(request, response); } }
启动
然后注册查看数据表有没有插入成功。
重要的代码优化说明
我们之前写调用mybatis的时候呀都需要写这样一段代码
String resource = "mybatis-config.xml"; InputStream inputStream = Resources.getResourceAsStream(resource); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream)
写LoginServlet和RegisterServlet的时候我们基本都需要写入这样一段代码。于是想要只调用一次。
于是我们写一个类来完成这样的功能。
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; 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; } }
这就是以上优化后的代码的说明。
重定向登录注册
显然如果要考输入连接进行从一个登录到注册的跳转不是十分合理的。我们要做到当登录显示登录失败的时候或者用户没有注册的时候我们需要提示请注册,然后跳转到注册界面。
那我们要做的就是实现这个功能。
那么主要是从登录界面重定向到注册的html里面。
我们设置这样的几步就可以
在登录失败里面
PrintWriter out = response.getWriter(); // out.println ("<script language=javascript>alert('密码错误');window.location='login.jsp'</script>"); JOptionPane.showMessageDialog(null, "用户不存在,请注册");
然后我们启动一下
点击确定
然后就进入到注册页面,这样就衔接起来了。