对servlet中的两个对象(Request与Response)的理解
Request
在上一篇中我们研究了关于Servlet的作用与相关的配置问题(没有接触的小伙伴可以去接触一下),我们知道在servlet接口中最重要的或者说我们应用最多的就是Servlet方法。
@Override
public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
System.out.println("努力学习的小黄");
}
我们可以发现在重写方法service中有ServletRequest类型的参数,还有ServletResponse类型的参数,它们本质上对应的其实就是前端与后端互动时候的Request与Request的两个过程(即请求与响应)。接下来,我们就主要来看这两个过程,首先是Request。javaee中为针对Servlet为Request提供了一套接口ServletRequest(上面的servise中可以看到),后来javaee又针对Servlet与Web进行互动替Servlet完成了接口的封装并且在ServletRequest的基础上又为它实现了一个子接口HttpServletRequest。
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("doGet");
}
在安装了TomcatWeb服务器之后,内部的Tomcat又在javaee的基础上定义了HttpServletRequest的实现类名字叫做RequestFacade,Tomcat会自动完成对这个的解析,封装为Request对象,我们直接调用即可。
比如下面来说:
http://localhost:8080/MavenWeb_Tomcat_war/ZhangBing?username=%E9%BB%84%E5%85%83%E5%8D%93&password=197953&submit=%E6%8F%90%E4%BA%A4
### request请求内容详情:
getMethon():返回请求的请求方式(get or post)在这个里面是GET,getContextPath():获取虚拟目录/项目访问路径 在这个里面就会返回 /MavenWeb_Tomcat_war (这里没有设置所以项目名就会作为这个虚拟目录名) ,获取统一资源定位符getRequestURL():在这个例子中是http://localhost:8080/MavenWeb_Tomcat_war/ZhangBing ,获取统一资源标识符getRequestURI:/MavenWeb_Tomcat_war/ZhangBing
最后就是获取请求参数: getQueryString():获取请求参数,不过只针对用GET方式来获取。
@WebServlet("/ZhangBing")
public class ServletMaker3 extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String method = req.getMethod();
System.out.println("请求方式:"+method); //GET
String contextPath = req.getContextPath();
System.out.println("虚拟目录:"+contextPath); ///MavenWeb_Tomcat_war
StringBuffer requestURL = req.getRequestURL();
System.out.println("统一资源定位符:"+requestURL); //http://localhost:8080/MavenWeb_Tomcat_war/ZhangBing
String requestURI = req.getRequestURI();
System.out.println("统一资源标识符:"+requestURI); ///MavenWeb_Tomcat_war/ZhangBing
String queryString = req.getQueryString();
System.out.println("获取请求参数:"+queryString); //username=%E5%BC%A0%E9%9B%AA%E5%B3%B0&password=13371237198733&submit=%E6%8F%90%E4%BA%A4
// http://192.168.152.88:8080/MavenWeb_Tomcat_war/ZhangBing?username=%E9%BB%84%E5%85%83%E5%8D%93&password=197953&submit=%E6%8F%90%E4%BA%A4
}
请求头:对于谷歌浏览器来说,都是用默认的名称name = “User-Agent”,作为请求头,运用到的方法就是getHeader(),示范:
String username = req.getHeader("User-Agent");
System.out.println(username);
//输出结果:Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.131 Safari/537.36 SLBrowser/8.0.0.9071 SLBChan/1050
对于请求体来说(只有post)的,对于Post的请求方式,请求体会自动的把请求参数封装在请求体中,这个过程在输出时是借助于字符输入流BufferedReader中的getReade()方法来执行的。但是需要注意在Request执行时需要注意一点,针对不同的内容会输出不同的表达形式,针对要输入的字符串,我们要借助于字符输入流BufferedReader,但是比如我们要是输入图片或者视频音频之类的,这个时候使用字符输入流BufferedReader就会表现出其自身的局限性,针对这类问题,我们就需要借助字节输入流来帮助。
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//Post的执行逻辑
String method = req.getMethod(); //请求方式
String contextPath = req.getContextPath(); //虚拟目录
StringBuffer requestURL = req.getRequestURL(); //统一资源定位符
String requestURI = req.getRequestURI(); //统一资源标识符
String queryString = req.getQueryString(); //获取请求参数
System.out.println("请求方式:"+method);
System.out.println("虚拟目录:"+contextPath);
System.out.println("统一资源定位符:"+requestURL);
System.out.println("统一资源标识符:"+requestURI);
System.out.println("获取请求参数"+queryString); //Post请求中会不存在
BufferedReader reader = req.getReader(); //request调用getReader()方法,来获得把请求参数封装在请求体中的字符输入流
String s = reader.readLine(); //读取字符输入流的一行
System.out.println(s); //输出请求体
}
在前台与后台互动的过程中我们比较关心的就是对于request其中的请求数据,我们先复习一下之前学过的,对于之前来说,获取请求参数的方式因其请求方式的不同而不同,对于GET请求方式来说主要的是getQueryString方法返回请求参数的字符串,对于POST请求参数来说,我们会借助于getReader方法来创建有关请求体的字符输入流,通过读取字符输入流的第一行,来输出请求体内容,但是在我们实际要开发的时候,类似这样的过程太过于繁琐,我们希望于通过一种简单的方法来统一输出GET的请求参数与POST请求参数,如何做,其实在Servlet中已经找到了解决办法。
具体的方法就是应用getParameterMap,getParameter,getParameterValues这三种方法,具体应用是这样的,getParameterMap方法用来返回请求参数的键值对类型,如果一个键对应多个不同的值,改方法会自动的将多个不同的值进行封装,封装成数组的形式,具体范例如下:
System.out.println("参数的具体内容:");
Map<String, String[]> parameterMap = req.getParameterMap();
Set<String> strings = parameterMap.keySet();
for (String key : strings) //第一次遍历
{
System.out.print(key+":");
String[] strings1 = parameterMap.get(key);
for (String i:strings1){ //第二次遍历
System.out.print(i+" ");
}
System.out.println( );
}
```
用getParameter的方法是针对已知的键名来返回相应的键对应的值(注意这个值必须是单个的值不可以是数组)
String username = request.getParameter("username");
System.out.println("username:"+username);
String password = request.getParameter("password");
System.out.println("password:"+password);
String parameter = request.getParameter("Do-submit");
System.out.println("Do-submit:"+parameter);
而专门用来返回键对应的值是数组的也有专门的方法,那就是getParameterValues方法
String[] checkboxes = request.getParameterValues("checkbox");
System.out.print("checkbox: ");
for (String i : checkboxes)
{
System.out.print(i+" ");
}
System.out.println( );
}
到此为止三种方法就全部讲完了,应当注意使用这样的方法时,Tomcat对浏览器的其数据编码解析可能与idea编码不一致,一种比较好的办法就是提前设置一下,让他们的编码统一即可,使用的方法是req.setCharacterEncoding(“UTF-8”)。
请求转发
关于这个请求转发,请求的来源地我们可以看成浏览器,请求发出以后会来到服务器中去,服务器A区可以将该请求数据转发到服务器B区,最终是由服务器B区对浏览器做出响应。那么这个过程就是一个请求转发的过程。我们习惯称之为forword,它的代码比较简单:
request.getRequestDispatcher("/ServletDemo5").forward(request,response); //将请求转发到ServletDemo5中
在从服务区A转到服务区B的这个过程中是通过以request域来存储共享数据的,这一项动作包括1.存储数据到request域中,2.根据key值,获取对应的键值。3.用完了之后需将共享数据域的数据进行清空删除处理。它们分别对应着下面的几种方法
String password = request.getParameter("password");
String[] checkboxes = request.getParameterValues("checkbox");
request.setAttribute("password", password); //将键是password的键值对存储数据到request域中
request.setAttribute("checkbox",checkboxes); //将键是checkbox的键值对存储数据到request域中
request.getRequestDispatcher("/ServletDemo7").forward(request,response); //将请求转发到ServletDemo7中
String username = (String) request.getAttribute("password"); //接收键为password的值
String[] checkbox = (String[]) request.getAttribute("checkbox"); //接收键为checkbox的值
request.removeAttribute(String name); //根据key 删除request域中的键值对
请求的特点:首先对于请求来说,我们浏览器的地址栏处并不会发生改变(比如说我们访问的是服务器A区的资源,A通过请求转发将请求转发到B,在这个过程中浏览器的地址栏上会一直显示着A区资源的路径,而不会跳转到服务器B区资源的地址,这就是请求转发的特点之一),请求转发只能转发到服务器的内部资源,且每次只进行一次,而且在转发的过程中可以在转发的资源间使用request共享域。
Response
我们学习可以用类比的思想,上面已经了解到request是专门用来请求数据的,重点是它会将用户已经输入好的请求有关的重要参数发送到服务器,并且已经了解到了相对应的一系列"get"方法,那么response作为响应,就是用来向浏览器重新设置数据并进行发送的一系列手段,并且应该会出现对应的"set"方法来由后台设置数据已完成响应操作。
同样的java也Servlet提供了相应的Response接口,java提供的请求对象根接口ServletResponse,同时为了接口可以更好的封装Http协议,java在ServletResponse的基础上添加了封装了Http协议的HttpServletResponse接口,它是ServletResponse的一个子接口,而当我们安装了Tomcat服务器后,Tomcat服务器会自动帮我们完成对子接口的实现操作,实现类名叫做ResponseFacade(是由Tomcat定义的)
Response是用来设置响应数据的,数据的响应同样的也分成响应头,响应行,响应体。
响应行中,比较重要的是响应状态码
void setStatus(int sc); //设置响应状态码
响应头与请求头类似都是应用键值对
void setHeader(String name ,String value); //设置响应头的键值对 ,在请求头中是name = "User-Agent";
响应体
响应体实际上是通过流的方式往浏览器是输出(字符输出流,字节输出流) ,当时获得请求体的时候Post我们应用的是字节字符输入流,而类似的我们在设置响应体的时候可以设置一下字节字符输jut出流去设置。getWrite字符输出流,getOutputStream字节输出流,对于文字形式来说,我们通常是应用字符的数据,对于输出图片格式或者音频视频等等,我们通常是应用字节输出流。
Response的重定向,我们类比请求转发,重定向也是一种资源跳转的方式,下面具体演示一下:
1. 浏览器向服务器区A发送请求,但是服务器A区发现自己解决不了这个问题。
1. 服务器A区向浏览器发送一个响应,重点就是说你这个请求我解决不了,你去找服务器B区吧,那个服务器B区是几号楼几号公寓。
1. 浏览器受到响应之后,就会对服务器B区重新发送请求。
服务器A区对浏览器发送响应,发送状态码:302(表示着浏览器提出的请求我是完成不了的,浏览器还是去找其他的服务器资源吧)
同时服务器A区还应该提供给浏览器一个资源B的地址(意思就是让她去找B,B可以解决这个请求)
有关代码:
response.setStatus(302); //状态码
response.setHeader("location","资源B的路径"); //B资源的地址,其实就是设置响应头
简化形式:
response.sendRedirect("资源B的路径");
重定向与请求转发的区别:在经过资源跳转后,请求转发对应的浏览器地址栏不发生变化,而重定向后的浏览器地址栏会发生变化,其实本质原因是因为重定向的时候,浏览器在整个过程中发送了两次请求,而请求转发只发送一次请求。重定向可以重定向到任意资源,但是相比之下,请求转发的转发只能被限制到当前服务器的内部资源,另外重定向并不限制于在服务器内部进行转发,因此在重定向其中的过程中数据并不会储存在request共享域中,而请求转发的过程,其中的数据就会在request共享域中。
一般情况下,给浏览器使用的路径需要虚拟目录,而如果是关于应用服务器的则不需要加上虚拟目录,目前来看,请求转发是不需要加上虚拟目录的(req.getRequestDispatcher(“路径”))。
这里有一个小细节,即对于重定向来说前面我们已经看到了其中的方法,其中需要传递一个参数(带虚拟目录的路径),对于路径的获取,这里我们通常使用request对象中的getContextPath方法与我们的最终路径进行合并处理。具体范式如下:
String contextPath = request.getContextPath();
response.sendRedirect(contextPath+"/ServletTest5");
Response响应字符数据
具体操作如下:
通过Response对象获取到字符输出流:
PrintWriter writer = response.getWriter();
写数据:
writer.write("abc");
这里有一个小细节,对于专业程序员来说我们通常利用响应体发出的是一个html格式的标签,为了让text格式与html格式共存,我们可以提前设置一下响应头
writer.setHeader("content-type","text/html;charset=UTF-8"); //设置响应头,使其能够识别html格式,后面的charset=UTF-8是为了防止出现输出字符流乱码问题。
Response响应字节数据
类比于上式:
通过Response对象获取字节输出流
ServletOutputStream outputStream = resp.getOutputStream();
写数据
outputStream.write(字节数据);
我们知道字节输出流需要有相应的字节数据输入,我们通常的方式是再次写一个字节输入流,传入相关的字节数据后,将字节输入流中的数据复制给字节输出流使其输出。现在的话,有一种更为便利的方式就是首先利用IOUtils工具类,首先导入坐标
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.6</version>
</dependency>
然后使用IOUtils中的copy方法即可完成从字节输入流到字节输出流的复制过程,并且完成输出操作
IOUtils.copy(输入流,输出流);