Servlet的体系结构
因为我们每次让一个类继承Servlet就需要重写Servlet里面的全部方法,但是很多时候我们只需要重写里面的service方法就行了,其他方法我们都没有写语句。所以我们可以去找找Servlet的子类。看看有没有一个类实现起来比较方便一点。
我们查看API文档发现他们的继承体现为这样的,Servlet下面有一个GenericServlet类实现了Servlet,HttpServlet又继承了GenericServlet。
Servlet – 接口
|
GenericServlet – 抽象类
|
HttpServlet – 抽象类
所以如果我们只需要重写里面的service方法,可以使用这里的GenericServlet就可以了。你看GenericServlet里面的代码,你会发现,GenericServlet他把Servlet除service方法都用空实现了。只留了一个service抽象方法,这样你要自己的创建的类继承GenericServlet类就只要实现service方法就行了,其他的方法都不用你重写。如果你需要写Servlet的其他方法,比如你想要让你的类在初始化的时候做点事情,你也可以让你创建的类继承自GenericServlet,你只要可以重写init()方法嘛,这样他就产生了覆盖的作用了呀。
例子:
package cn.web.servlet;
import javax.servlet.GenericServlet;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebServlet;
import java.io.IOException;
@WebServlet("/demo2")
public class ServletDemo2 extends GenericServlet {
@Override
public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
System.out.println("demo2.....");
}
}
GenericServlet:将Servlet接口中其他的方法做了默认空实现,只将service()方法作为抽象.
但是虽然用GenericServlet蛮方便的,但是我们实际使用的时候还是用HttpServlet的。
为什么我们以后使用的时候会用HttpServlet呢?
因为,我们将来实现了Servlet的类,一般都是接收浏览器里面表单传过来的数据的,你要对那个数据进行一系列的处理。但是你再处理前你需要判断传到服务器这里的表单信息是用get方法封装信息的还是用post方式来封装表单信息的。用不同的方式传递的数据你去解析的方式不同。所以你自己写的实现Servlet的类就需要自己写语句去判断传递的方式是get还是post,然后再去做你需要做的处理。因为这个GenericServlet只是把Servlet那些你不是很常用的方法给留空了而已,所以你要是自己写类去继承GenericServlet就需要自己去做判断浏览器传递表单的方式是什么,这个步骤其实是很麻烦的,所以你要自己写会很麻烦。所以出现了HttpServlet类,这个类他把判断请求的语句都写好了,然后留了一个要做什么的方法出来,让你自己来写,他的实现方式就是类似下图(下图是简化版的,实际上解析表单的传递方式比这个复杂得多),然后你只要去想做如果是get传递的你需要怎么做,如果是post传递的你需要怎么做就行了。
HttpServlet:对http协议的一种封装,简化操作。
我们使用HttpServlet的步骤如下:
HttpServlet的代码如下图(这里面因为表单传递的方式不只是get和post,所以下面做的判断也不止get和post,但是我们现在只要了解get和post两种表单传递的方式):
Servlet相关配置
idea中用注解配置访问类的资源路径,看下面例子就知道了。
package cn.web.servlet;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
//下面这个表示你用http://localhost:端口号/虚拟路径/d4,http://localhost:端口号/虚拟路径/dd4,http://localhost:端口号/虚拟路径/ddd4都可以访问这个类
//@WebServlet({"/d4","/dd4","/ddd4"})
//下面这个表示可以用两层或者多层资源路径。然后你访问的时候就要用http://localhost:端口号/虚拟路径/user/demo4来访问。这里就演示了两层,其实多层也行。
//@WebServlet("/user/demo4")
//下面这个表示你可以用http://localhost:端口号/虚拟路径/user/任意字符串(空字符串或者“jfkaj/jkl/fs”这种的也行,反正随便填)
//@WebServlet("/user/*")
//下面这个表示你可以用http://localhost:端口号/虚拟路径/任何字符匹配。但是这种带*的访问的优先级会很低,要是你有一个类你设置访问的路径为@WebServlet("/d1"),那么你在浏览器输入http://localhost:端口号/虚拟路径/d1是先匹配那个@WebServlet("/d1")的类的,要是没有找到d1才会匹配这个@WebServlet("/*")
//@WebServlet("/*")
//下面这种表示你可以用http://localhost:端口号/虚拟路径/任意字符串.do来访问这个类(不只.do哦,你写.x、.jlkf、.gg等都行),注意不是@WebServlet("/*.do"),@WebServlet("/*.do")的话idea执行就会出错
@WebServlet("*.do")
public class ServletDemo4 extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("demo4...");
System.out.println(req);
}
}
HTTP:Hyper Text Transfer Protocol 超文本传输协议。
传输协议:客户端和服务器端通信时,发送数据的格式。
请求:客户端给服务端发送的数据消息
响应:服务器端给客户端发送的数据消息
注意:请求里面的请求行中的协议是告诉服务器,你浏览器用网络传到服务器这边的数据是http1.0、http1.1、http2.0还是https的,然后你服务器用正确的格式来解析这信息。你响应行里面也有协议信息,这个协议信息是为了告诉客户端浏览器你要如何解析这个服务器发送过来的信息的,是用http1.0、http1.1、http2.0格式来解析还是https格式来解析。我们现在学的写的代码都是默认用http1.1格式发送请求和响应请求的,即,你写的代码不设置使用什么格式传输,默认就是用http1.1的格式发送请求或响应请求的。而且现在我现在目前学的知识还不知道怎么改变传输格式,即我现在还不知道怎么做到让浏览器发送请求不是用http1.1的,也不知道怎么做到让服务器响应数据的格式不是用http1.1的。
历史版本:
请求消息格式分为四部分:
请求行
请求行的格式:请求方式 请求url 请求协议/版本
比如我们点击浏览器里面某个按钮访问项目文件夹下的login.html文件后,我们看F12,可以看到他的请求行信息是:GET /login.html HTTP/1.1(/login.html是我们访问的资源路径名,即那个地址的虚拟路径后面的那个东西。用get方式可能这个资源路径后面会有参数,/login.html?XX=XX&XX=XX这样的,因为get会把表单提交的数据都在url中显示。)
请求方式:HTTP协议有7中请求方式,常用的有2种,即get和post。
GET:
POST:
请求头:客户端浏览器告诉服务器的一些信息
请求头的格式:请求头名称: 请求头值。比如下面这个例子的Host: localhost、User-Agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:60.0) Gecko/20100101 Firefox/60.0……这些XX:XX的都是请求头。如果某个请求名称的值有多个的话,可以用逗号分隔开来Accept-Encoding: gzip, deflate。
常见的请求头:
Host: localhost表示发送请求的主机是我们本机。
User-Agent:……表示浏览器告诉服务器,我访问你使用的浏览器版本信息,即告诉服务器访问服务器的客户端使用的是什么浏览器,什么版本的浏览器。这个信息还是比较重要的,将来我们在服务器端获取该头的信息,解决浏览器的兼容性问题。因为我们可能用不同的浏览器去访问一个服务器资源,然后你不同浏览器把从服务器得到的信息解析出来的样子可能不同,因为没个浏览器的解析引擎其实不太相同的,所以我们可以在服务器那边把这个User-Agent信息拿出出来,然后你判断一下请求的是什么浏览器,给特定浏览器返回一个特定的资源,这样就让他们即使用不同的解析引擎也能解析得到相似的结果,从而解决兼容问题。
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,/;q=0.8表示告诉服务器我这个浏览器可以接收什么样的响应信息格式。可以看到这个浏览器可以接收到text、html、xhtml、xml、*/*
格式的数据。(*/*
表示什么数据格式都可以)
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2。表示浏览器可以接收的语言格式,可以为zh-CN(中国)、zh-TW(台湾)、zh-HK(香港)、en-US(美国)
Accept-Encoding: gzip, deflate。表示可以接收的压缩格式文件。比如zip格式的。
Referer:http://localhost/login.html。表示告诉服务器,我(当前请求)从哪里来?作用是:防盗链和可以做统计工作。防盗链就是防止别人盗取你的连接。
Connection: keep-alive。表示连接一直都是活着的,即连接可以复用。
Upgrade-Insecure-Requests: 1。这是一个升级的信息。
请求空行
请求空行就是一个换行,作用就是用于分割POST请求的请求头和请求体的。你看下面这个username=zhangsan上面的换行就是请求空行。
请求体(即请求的正文):
get方式传递信息没有请求体,post方式传递信息有请求体。请求体里面放的其实就是一些参数数据,比如传递的表单数据。所以请求体就是封装POST请求消息的请求参数的。
下面是一个完整的请求格式字符串的例子:
POST /login.html HTTP/1.1
Host: localhost
User-Agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:60.0) Gecko/20100101 Firefox/60.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Referer: http://localhost/login.html
Connection: keep-alive
Upgrade-Insecure-Requests: 1
username=zhangsan
request对象和response对象的原理。(request对象对应的是请求消息,response对象对应的就是响应消息)
注意:
request对象继承体系结构:
ServletRequest -- 接口
| 继承
HttpServletRequest -- 接口
| 实现
org.apache.catalina.connector.RequestFacade 类(tomcat包中的类,不是sun公司提供的)
request功能:
获取请求消息数据的功能
获取请求行数据
比如请求行长这样:GET /day14/demo1?name=zhangsan HTTP/1.1
方法:
获取请求方式的方法 :比如获取那个GET。这是HttpServletRequest接口中的方法。
String getMethod()
(重点)获取虚拟目录:比如获取上面的/day14。这是HttpServletRequest接口中的方法。
String getContextPath()
获取Servlet路径:比如获取上面的/demo1。这是HttpServletRequest接口中的方法。
String getServletPath()
获取get方式请求参数:比如获取上面的name=zhangsan。这是HttpServletRequest接口中的方法。
String getQueryString()
(重点)获取请求URI:比如获取上面的/day14/demo1。这两个是HttpServletRequest接口中的方法。
String getRequestURI(): /day14/demo1
StringBuffer getRequestURL() :http://localhost/day14/demo1
什么叫URI,什么叫URL?答:URL是Internet上资源的地址,URL叫统一资源定位符。URI是标识逻辑或物理资源的字符序列,URI叫统一资源标识符。可以说URL是URI(URL是URI的子集),但URI永远不能是URL。
获取协议及版本:比如获取上面的HTTP/1.1。这是ServletRequest接口中的方法。
String getProtocol()
获取客户机的IP地址。这是ServletRequest接口中的方法。
String getRemoteAddr()
演示:
代码如下:
package cn.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;
/**
* @author 刘诗良
* @version 1.00
* @Description
* @Date 2022/11/24 15:22
*/
@WebServlet("/requestDemo1")
public class RequestDemo1 extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
/*
1. 获取请求方式 :GET
* String getMethod()
2. (*)获取虚拟目录:/day
* String getContextPath()
3. 获取Servlet路径: /requestDemo1
* String getServletPath()
4. 获取get方式请求参数:name=zhangsan&age=12
* String getQueryString()
5. (*)获取请求URI:
* String getRequestURI(): /day/requestDemo1
* StringBuffer getRequestURL() :http://localhost/day/requestDemo1
6. 获取协议及版本:HTTP/1.1
* String getProtocol()
7. 获取客户机的IP地址:显示的是IPV6的地址,因为是本机,所以是0:0:0:0:0:0:0:1
* String getRemoteAddr()
*/
//1. 获取请求方式
String method = request.getMethod();
System.out.println(method);
//2.(*)获取虚拟目录
String contextPath = request.getContextPath();
System.out.println(contextPath);
//3. 获取Servlet路径
String servletPath = request.getServletPath();
System.out.println(servletPath);
//4. 获取get方式请求参数
String queryString = request.getQueryString();
System.out.println(queryString);
//5.(*)获取请求URI
String requestURI = request.getRequestURI();
StringBuffer requestURL = request.getRequestURL();
System.out.println(requestURI);
System.out.println(requestURL);
//6. 获取协议及版本
String protocol = request.getProtocol();
System.out.println(protocol);
//7. 获取客户机的IP地址
String remoteAddr = request.getRemoteAddr();
System.out.println(remoteAddr);
}
}
比如我们这浏览器里面这样输入。自己输入url,手动把name数据和age数据写在?后面传过去到服务器,这其实和你写一个表单然后把表单数据传到服务器一样,相当于传了两个键值对过去。表单数据和地址之间用?隔开,多对键值对的表单数据用&隔开。
结果如下:
获取请求头数据
获取请求头的方法(这两个方法都是HttpServletRequest类的方法):
(重要)String getHeader(String name):通过请求头的名称获取请求头的值
Enumeration getHeaderNames():获取所有的请求头名称,然后放在一个容器里面(这个容器是什么我们就不用管了)。这个Enumeration是一个接口,这个接口的使用类似于迭代器的使用,这个接口里面有两个方法:
hasMoreElements():判断后面还有没有更多的元素(类似于Iterator中的hasNext()方法)
nextElement():获取下一个元素(类似于Iterator中的next()方法)
演示1:
package cn.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.util.Enumeration;
@WebServlet("/requestDemo2")
public class RequestDemo2 extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//演示获取请求头数据
//1.获取所有请求头名称
Enumeration<String> headerNames = request.getHeaderNames();//相当于把容器去名为headerNames
//2.遍历
while(headerNames.hasMoreElements()){//判断这个headerNames容器有没有下一个元素,你可以理解为一开始有一个指针,一开始指向的是第一个元素的前面,这个方法相当于是判断指针下一个位置有没有元素。
String name = headerNames.nextElement();//把指针先向后移动一格,然后返回那个指针指向的元素。
String value = request.getHeader(name);//根据名称获取请求头的值。
System.out.println(name+"---"+value);
}
}
}
浏览器访问如下:
结果如下:
演示2(我们会用这个getHeader()方法解决兼容性问题,比如下面这样):
package cn.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("/requestDemo3")
public class RequestDemo3 extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//演示获取请求头数据:user-agent
String agent = request.getHeader("user-agent");//注意这里的请求头是不区分大小写的,你输入USER-AGENT也行
//判断agent的浏览器版本,以解决兼容性问题。这样不同浏览器你给他们返回不一样的数据,使他们返回的结果都和你想要的结果符合你想要的结果,这样就行了。
if(agent.contains("Chrome")){
//谷歌,针对谷歌浏览器的一些操作
System.out.println("谷歌来了...");
}else if(agent.contains("Firefox")){
//火狐,针对火狐浏览器的一系列操作
System.out.println("火狐来了...");
}
}
}
我们先用火狐浏览器访问/requestDemo3资源,然后再用谷歌浏览器访问/requestDemo3资源,结果如下:
获取请求体数据
请求体:只有POST请求方式,才有请求体。请求体中会封装了POST请求的请求参数信息。
获取请求体的步骤:
获取流对象
HttpServletRequest的父接口ServletRequest中有两个方法可以获取到和请求体有关的流对象:
BufferedReader getReader():获取字符输入流,只能操作字符数据。请求体中如果是字符信息的话,就用这个方法获取数据。
ServletInputStream getInputStream():获取字节输入流,可以操作所有类型数据。请求体中如果是字节信息的话,就用这个方法获取数据。这个方法在文件上传知识点后讲解。ServletInputStream 继承了InputStream,所以不要陌生,InputStream的方法他都可以使用。
再从流对象中拿数据
例子
regist的代码如下:
资源代码如下:
其他功能(重要)
其他功能的方法分为3类:
不论get还是post请求方式都可以使用下列方法来获取请求参数。即通用的获取请求参数的方法。
常用方法如下,这几个方法都是ServletRequest类的方法:
使用例子:
package cn.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.util.Enumeration;
import java.util.Map;
import java.util.Set;
@WebServlet("/requestDemo6")
public class RequestDemo6 extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//post 获取请求参数
//根据参数名称获取参数值
String username = request.getParameter("username");//获取了请求体里面键为username的数据.要是请求体里面有多个键为username的键值对,你获取到的就将是第一个键值对的username对应的值。
System.out.println("post");
System.out.println(username);
System.out.println("=================================");
//根据参数名称获取参数值的数组
String[] hobbies = request.getParameterValues("hobby");//获取请求体的键为hobby的数据,因为请求体里面一般都是表单数据,然后表单数据要被传递就要求标签里面有name属性,然后name属性就是传过来的表单数据的键,value属性就表单数据传过来对应的值。一般我们只有把复选框的标签设置为name一样,所以这个方法一般用于获取复选框传到服务器的数据。
for (String hobby : hobbies) {
System.out.println(hobby);//这里看到我们request.getParameterValues("hobby")这个方法能获取name为hobby的复选框中所有被勾选的那些复选框的value。因为对于复选框,只有被勾选的复选框才能被提交表单数据,没有勾选的复选框不能被提交表单数据。
}
System.out.println("=================================");
//获取所有请求的参数名称
Enumeration<String> parameterNames = request.getParameterNames();//这个方法可以或所有请求体里面所有的键,但是一样的键只能存一次。比如你有两个输入框都的name都叫username,那么这个parameterNames里面就存了一个叫username的字符串,不会存两个
while(parameterNames.hasMoreElements()){
String name = parameterNames.nextElement();
System.out.println("name值为:"+name);
String value = request.getParameter(name);
System.out.println("value值为:"+value);
System.out.println("----------------");
}
System.out.println("=================================");
// 获取所有参数的map集合
Map<String, String[]> parameterMap = request.getParameterMap();//这个getParameterMap()方法,能获取所有键和键对应的值。注意:这里的键一样不会重复,比如你有两个表单的name都叫username,那么只会存一个username键,但是这个username对应的String[]可以是多个值,就是比如你有两个输入框的name都叫username,这个getParameterMap()方法就会存一个username这个字符串,然后这个username字符串对应一个字符串数组,字符串数组的第一元素的值是第一个name是username的输入框的value,字符串数组的第二个元素是第二个name是username的输入框的value。
//遍历
Set<String> keyset = parameterMap.keySet();
for (String name : keyset) {
//获取键获取值
String[] values = parameterMap.get(name);
System.out.println(name+"的值为:");
for (String value : values) {
System.out.println(value);
}
System.out.println("-----------------");
}
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//get 获取请求参数
/*
//根据参数名称获取参数值
String username = request.getParameter("username");
System.out.println("get");
System.out.println(username);
*/
//可以看到这个getParameter方法对get方式和对post方式传递的信息都能处理,且里面的代码可以写为一样的,所以我们可以在doGet方法中用这个this.doPost(request,response);语句,让你doGet的时候就去执行doPost的语句,反正两个处理方式都一样的,这样可以减少代码的冗余
this.doPost(request,response);
}
}
中文乱码问题:
get方式:tomcat8以上的版本,你用get传递的表单数据里面带中文,也不会出现乱码。
post方式:但是tomcat8以上的版本你用post方式传递表单,然后表单里面数据是中文,中文会乱码。
解决:在获取参数前,设置request的编码request.setCharacterEncoding(“utf-8”);
例子:
package cn.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("/requestDemo7")
public class RequestDemo7 extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//1.设置流的编码
request.setCharacterEncoding("utf-8");//其实这里不一定是utf-8,主要看你那个传表单数据的到这个资源文件的页面的html文件里面的表示的数据是不是utf-8。要是html的文件里面的meta charset是utf-8则这里也要设置为utf-8。这样post传递中文的表单数据的时候就不会出现乱码。
//获取请求参数username
String username = request.getParameter("username");
System.out.println(username);
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doPost(request,response);
}
}
请求转发:一种在服务器内部的资源跳转方式
请求转发的步骤:
例子:
package cn.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("/requestDemo9")
public class RequestDemo9 extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
System.out.println("demo9999被访问了。。。");
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doPost(request,response);
}
}
package cn.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("/requestDemo8")
public class RequestDemo8 extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
System.out.println("demo8888被访问了。。。");
/*
下面我们来转发到demo9这个Servlet类资源。相当于访问到了demo8这个资源然后跳转到demo9那个资源的service方法里面去执行了。然后你要是demo9这个类也继承了HttpServlet,且重写了doGet和doPost方法,那么是执行demo9这个资源的什么方法呢?答:这个得看你访问这个RequestDemo8这个页面的request对象里面封装的请求方式了,你看这里我们这个RequestDemo8是调用doGet还是doPost方法其实是看他的request对象的,请求对象里面封装的请求方式确定了我们这个RequestDemo8类是去执行doGet方法还是doPost方法的。然后我们这里requestDispatcher.forward(request,response)
把request也传到了demo9里面,用request对象去访问demo9,所以demo9是执行doGet还是doPost方法也是取决于访问RequestDemo8的request对象。要是某个页面用get的方式去访问RequestDemo8,那么这个RequestDemo8去调用demo9这servlet资源也是调用demo9类里面的doGet方法的。注意,我们要是直接输入网址的方式去访问RequestDemo8资源,就是相当于用get请求方式去访问RequestDemo8的。*/
//方式一:
RequestDispatcher requestDispatcher = request.getRequestDispatcher("/requestDemo9");
requestDispatcher.forward(request,response);
//方式二:方式二比方式一少创建一个变量,因为这个RequestDispatcher对象我们一般就用一次,所以我们建议像下面这样写。
request.getRequestDispatcher("/requestDemo9").forward(request,response);
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doPost(request,response);
}
}
请求转发的特点:
共享数据:
先来介绍一下域对象:域对象是一个有作用范围的对象,可以在范围内共享数据。request就是一个域对象。
request域:代表了一次请求的范围。一次请求可以转发多次,你一个请求request对象通过请求转发就可以让这个request对象在两个servlet资源里面使用了,那么这个request域就变大了,原来没转发的时候是一个对象里面使用这一个request对象,这个request域就只有一个servlet资源那么大,然后你请求转发了之后,就是两个request对象共享一个request对象了,就是request域变为了两个servlet资源的范围了。
request对象的方法有下面这些(这些方法都是来自于ServletRequest接口的):
request对象一般用于请求转发的多个资源中共享数据,因为request对象里面可以每次转发的时候携带一些数据到另一个资源里去。
例子:
package cn.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("/requestDemo8")
public class RequestDemo8 extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
System.out.println("demo8888被访问了。。。");
//在转发request对象之前,存储数据到request域中。
request.setAttribute("msg","hello");//setAttribute()这个方法需要一个键一个值,这里我们写的msg就是键(可以随便写,不一定是msg),hello就是键对应的值。
//转发request对象
request.getRequestDispatcher("/requestDemo9").forward(request,response);
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doPost(request,response);
}
}
package cn.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("/requestDemo9")
public class RequestDemo9 extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//获取数据
Object msg = request.getAttribute("msg");
System.out.println(msg);//输出hello,说明数据被request携带着传到了demo9这个资源里了。
System.out.println("demo9999被访问了。。。");
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doPost(request,response);
}
}
获取ServletContext:使用的方法是ServletContext getServletContext(),作用是返回一个Context的对象,这是ServletRequest接口的方法。
使用如下:
package cn.web.request;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@WebServlet("/requestDemo10")
public class RequestDemo10 extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
ServletContext servletContext = request.getServletContext();
System.out.println(servletContext);
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doPost(request, response);
}
}
用户登录案例需求:
登录逻辑梳理:(可以看到我们使用的不止是Servlet类,普通类我们在web中也一样可以用。还有就是下图中的步骤里面第一个步为什么是”设置编码“呢?因为你login.html传过来的表单数据可能里面有中文,要是表单数据传过来是中文,且请求方式用的是post,那么服务器这边拿到的数据就将会是乱码,所以我们一般从浏览器拿到表单数据都是先进行设置编码,然后再去使用浏览器传过来的表单数据的。)
先讲一下web工作目录的结构:
我们一般会把html等资源文件都放在web文件夹的直接目录下。然后要是这个web项目使用的时候需要用到别的架包,我们就在web直接目录下,建一个WEB-INF文件夹,然后在这个WEB-INF文件夹下建一个lib,然后把这个项目需要的架包都放在lib下。我们的java代码都写在src下,一般我们会在src直接目录下先建包,然后把java类写在包里面(Servlet类和其他类都写在这里)。我们配置文件都放在src的直接目录下,因为这样用InputStream is = 类名.getClassLoader().getResourceAsStream("druid.properties");
就可以直接返回一个流指向那个配置文件了,这一点可以看下面的代码例子。
开发步骤:
先构思你这个项目需要怎么做,然后用文字写下你这个项目要完成工作的解决问题的步骤。即,步骤1……,步骤2……,步骤3……,记得要用中文写。
创建项目,然后导入别人做好的html页面(或者你自己提前设计好html,然后导入),你先构思你这个项目需要用到的技术有哪些然后去写好将来肯定会用到的配置文件信息并把这个配置文件放到对应的文件夹中(放那个文件夹看你觉得放哪里比较方便就放哪里),建立WEB-INF和lib然后导入jar包
设计好数据库(你先思考你这个项目解决问题的方案里面需要什么数据(因为第一步已经把解决问题的步骤都想好了嘛,所以你这里已经是一个完整的解决问题的步骤和思路了,所以你可以想到你需要什么样的数据库的表数据),然后去设计数据库的表)。
这个案例我们把数据库的表设计为下面这样:
CREATE DATABASE day14;
USE day14;
CREATE TABLE USER(
id INT PRIMARY KEY AUTO_INCREMENT,
username VARCHAR(32) UNIQUE NOT NULL,
PASSWORD VARCHAR(32) NOT NULL
);
创建domain实体类的包,并创建实体类User。注意:我们设计的时候还是得用三层架构的思想,然后层与层之间的数据传递尽量使用bean类来传递。还有一点就是你servlet类之间的相互跳转用请求转发的方式,但是我们那些普通类之间相互使用其他类的方法和功能就直接和我们之前学的一样,直接new就行了。
创建util包,编写去操作数据库的工具类,JDBCUtils类。注意你写工具类的时候要用到设计模式的单一职责原则,比如你把增删改查某个表的方法写在一个A类里面,增删改查另一个表的方法就应该写在另一个B类里面。且如果你发现这增删改查这两个表的类里面的方法有一些语句或者功能经常出现,你可以把那些语句提取出来,写一个通用一点的方法,放在另一个工具类里面。或者你创建一个抽象类,把这些语句放在抽象类里面,然后A和B类继承这个抽象类,然后A和B去使用那些之前冗余度比较高,但是现在在抽象类里面的语句。
创建dao包,并创建对应的dao类并写好这个dao类可能会用到的访问数据库的方法。这里是创建UserDao类,并提供login方法。
然后我们写好dao的类之后我们就可以建一个Test类,用来测试一下我们写的代码有没有问题了。因为我们要做到问题尽早发现,尽早解决。
编写web.servlet包(一般都写web.servlet表示web的Servlet类的包,然后下面写具体Servlet类),这里是写的具体类是LoginServlet类。
一边写代码一边想是否能对代码进行优化重构。
目录结构如下:
代码案例:
package dao;
import domain.User;
import util.JDBCUtils;
import org.springframework.dao.DataAccessException;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
/**
* 操作数据库中User表的类
*/
public class UserDao {
//声明JDBCTemplate对象共用.这里我们就用template这个对象就行了,这个对象new的时候需要一个数据库连接池对象,我们自己写的工具类里面的JDBCUtils.getDataSource()这个方法就是获取数据库连接池对象的。
private JdbcTemplate template = new JdbcTemplate(JDBCUtils.getDataSource());
/**
* 登录方法
* @param loginUser 只有用户名和密码
* @return user包含用户全部数据,没有查询到,返回null
*/
public User login(User loginUser){
try {
//1.编写sql
String sql = "select * from user where username = ? and password = ?";
//2.调用query方法。queryForObject()这个方法需要的参数是,一个sql字符串,一个是org.springframework.jdbc.core.RowMapper rowMapper的对象,一个是填充sql中的占位符的可变参数。注意,这个方法要是sql查询结果为空,就会抛出异常,所以我们最好try……catch一下。
User user = template.queryForObject(sql,
new BeanPropertyRowMapper<User>(User.class),//这个方法会返回是什么对象看你new BeanPropertyRowMapper(User.class)中的传递参数,这里是传User.class所以返回User对象
loginUser.getUsername(), loginUser.getPassword());
return user;
} catch (DataAccessException e) {
e.printStackTrace();
return null;
}
}
}
package domain;
/**
* 用户的实体类
*/
public class User {
private int id;
private String username;
private String password;
private String gender;
public void setHehe(String gender){
this.gender = gender;
}//这个只是为了测试使用beanUtil的方法,所以这里的方法没有符合bean类的标准
public String getHehe(){
return gender;
}//这个只是为了测试使用beanUtil的方法,所以这里的方法没有符合bean类的标准
public int getId() {
return id;
}
public void setId(int 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 + '\'' +
", gender='" + gender + '\'' +
'}';
}
}
package test;
import domain.User;
import org.apache.commons.beanutils.BeanUtils;
import org.junit.Test;
import java.lang.reflect.InvocationTargetException;
public class BeanUtilsTest {
//这里主要是让我们感受一下setProperty()和getProperty()方法。
@Test
public void test(){
User user = new User();
try {
//setProperty()这个方法需要一个javaBean对象,第二个参数是给他传一个属性名,第三个参数是给他一个属性值。
BeanUtils.setProperty(user,"hehe","male");//这里相当于是去user对象里面去找一个setHehe()方法,然后这个”male“放到setHehe()里做参数,就像是setHehe(”male“)
System.out.println(user);//打印User{id=0, username='null', password='null', gender='male'},所以相当于是自动调用了setHehe(”male“)方法
String gender = BeanUtils.getProperty(user, "hehe");
System.out.println(gender);//打印male,相当于自动匹配这个user对象里的getHehe()方法。
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
}
}
package test;
import dao.UserDao;
import domain.User;
import org.junit.Test;
public class UserDaoTest {
@Test
public void testLogin(){
User loginuser = new User();
loginuser.setUsername("superbaby");
loginuser.setPassword("123");
UserDao dao = new UserDao();
User user = dao.login(loginuser);
System.out.println(user);
}
}
package util;
import com.alibaba.druid.pool.DruidDataSourceFactory;
import javax.sql.DataSource;
import java.io.IOException;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Properties;
/**
* JDBC工具类 使用Durid连接池
*/
public class JDBCUtils {
private static DataSource ds ;
static {
try {
//1.加载配置文件
Properties pro = new Properties();
//使用ClassLoader加载配置文件,获取字节输入流
InputStream is = JDBCUtils.class.getClassLoader().getResourceAsStream("druid.properties");
pro.load(is);
//2.初始化连接池对象
ds = DruidDataSourceFactory.createDataSource(pro);
} catch (IOException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 获取连接池对象
*/
public static DataSource getDataSource(){
return ds;
}
/**
* 获取连接Connection对象
*/
public static Connection getConnection() throws SQLException {
return ds.getConnection();
}
}
package web.servlet;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@WebServlet("/failServlet")
public class FailServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//设置页面的编码
response.setContentType("text/html;charset=utf-8");
//在页面输出信息
response.getWriter().write("登录失败,用户名或密码错误");
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doPost(request,response);
}
}
package web.servlet;
import dao.UserDao;
import domain.User;
import org.apache.commons.beanutils.BeanUtils;
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.lang.reflect.InvocationTargetException;
import java.util.Map;
@WebServlet("/loginServlet")
public class LoginServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//1.设置编码
req.setCharacterEncoding("utf-8");
/*
//2.获取请求参数
String username = req.getParameter("username");
String password = req.getParameter("password");
//3.封装user对象
User loginUser = new User();
loginUser.setUsername(username);
loginUser.setPassword(password);
这种方式可以获取数据然后把数据封装到bean对象里面,但是比较麻烦,你看,如果那个表单里面有多个数据都要封装,你就需要调用很多次req.getParameter();,然后一个个把信息封装到对应的bean对象里面,就很麻烦,所以我们进行了下面的改进。
*/
//下面展示用bean工具类对象来优化代码,来取代上面的2、3两步,解决上面的问题。这个beanutils架包是要自己导入的第三方架包——"commons-beanutils-1.8.0"。
//2.获取所有请求参数
Map<String, String[]> map = req.getParameterMap();
//3.创建User对象
User loginUser = new User();
//3.2使用BeanUtils封装
try {
BeanUtils.populate(loginUser,map);//这个BeanUtils类是org.apache.commons.beanutils架包下的。相当于把这个map集合里面的键对应loginUser对象里面的属性,然后这个键对应的值给set到这个loginUser里面。就是去找那个键(一般是String类型的)的首字母大写,然后前面加一个set地去匹配loginUser对象里面的方法,然后把这个键对应的值用这个方法写进去。如果某一个键对应的值的数组不是长度为1的,就只把那个键对应的值的那个数组的第一个元素写到bean类里面,第一个元素后面的元素值不会被set到那个bean类的对应的属性(对应哪个属性看键是什么)里去。
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
//4.调用UserDao的login方法
UserDao dao = new UserDao();
User user = dao.login(loginUser);
//5.判断user
if(user == null){
//登录失败
req.getRequestDispatcher("/failServlet").forward(req,resp);
}else{
//登录成功
//存储数据
req.setAttribute("user",user);
//转发
req.getRequestDispatcher("/successServlet").forward(req,resp);
}
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
this.doGet(req,resp);
}
}
package web.servlet;
import domain.User;
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("/successServlet")
public class SuccessServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//获取request域中共享的user对象
User user = (User) request.getAttribute("user");
if(user != null){
//设置编码
response.setContentType("text/html;charset=utf-8");
//输出
response.getWriter().write("登录成功!"+user.getUsername()+",欢迎您");
}
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doPost(request,response);
}
}
driverClassName=com.mysql.jdbc.Driver
url=jdbc:mysql:///day14
username=root
password=815924
initialSize=5
maxActive=10
maxWait=3000
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Titletitle>
head>
<body>
<form action="/day14_test/loginServlet" method="post">
用户名:<input type="text" name="username"> <br>
密码:<input type="password" name="password"><br>
<input type="submit" value="登录">
form>
body>
html>
数据库数据为:
效果:
下面来介绍一下BeanUtils工具类。这是第三方架包commons-beanutils-1.8.0里面的工具类。BeanUtils这个工具类,注意是用于简化数据封装,比如上面例子中,我们自己从请求体里面拿数据自己手动封装到对应的bean类里面,然后要是请求体里面有很多表单的数据,然后要封装给很多不同的bean类,那我们自己写就很麻烦。但是我们用这个工具类就能简化封装的操作,给我们省下很多力气。
但是用这个工具类使用时有一些要求的,他要求JavaBean是符合标准的JavaBean类的写法的:
这个工具类有一些方法: