Java打卡:第100天

javaWeb — Servlet

Java EE


Servlet收尾


100天呐🎉,还是蛮感慨的,磕磕碰碰,终于到了这一步了,虽然JSP技术不是那么新了,但是我奉行存在即合理,还是还看看这种动态网页技术

Servlet

昨天最后分享的请求的域属性空间可以携带数据,通过当前ServletRequest的分配器dispatcher将请求req转发给其他的servlet;这些属性是这些Servlet共享的;与Complex的attribute类似;但是Complex的属性是该应用程序的所有的Servlet共享;这里只是分配到的Servlet共享【一个是请求Request的域属性,一个是环境Complex

//TestServlet  

package cfeng;

import java.io.IOException;
import java.util.Enumeration;
import java.util.Map;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;   //使用注解进行动态绑定
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@WebServlet(description = "this is just a test class", urlPatterns = { "/TestServlet" })
public class TestServlet extends HttpServlet {
	
	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		request.setAttribute("author", "Cfeng");
		request.setAttribute("address", "Peking");
		request.getRequestDispatcher("/some").forward(request, response);//将对现路径的请求转发给另外的路径,method不变
		System.out.println(this.getServletName() +" : " + this.getServletContext().getAttribute("school"));
		System.out.println(this.getServletName() +" : " + this.getServletContext().getAttribute("class"));
	}
	
	protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		doGet(request, response);//这里POST提交的时候也会执行GET中的方法
	}

}

//SomeServlet
package cfeng;

import java.io.IOException;

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;

@WebServlet(description = "just for testing", urlPatterns = { "/some" })
public class someServlet extends HttpServlet {

	protected void doGet(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {

		System.out.println(this.getServletName() + ": author = " + request.getAttribute("author"));
		System.out.println(this.getServletName() + ": address = " + request.getAttribute("address"));
		this.getServletContext().setAttribute("school", "Pecking university");
		this.getServletContext().setAttribute("class", "hc2001");
	}

	protected void doPost(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		doGet(request, response);
	}

}

这里客户端进行访问的时候,请求先到达TestServlet;到达后Test的getServletDisptcher获得调配器,使用器forward将请求传递给SomeServlet执行,SomeServle的doPost方法执行完之后,跳转回原来的TestServlet继续执行调配下面的代码

信息: 已完成重新加载名为/Test的上下文
cfeng.someServlet: author = Cfeng
cfeng.someServlet: address = Peking
cfeng.TestServlet : Pecking university
cfeng.TestServlet : hc2001

这里还可以使用getAttributeNames方法

Enumeration<String> attributeNames = request.getAttributeNames();
	while(attributeNames.hasMoreElements()) {
		String name = attributeNames.nextElement();
		System.out.println(name + " == " + request.getAttribute(name));
	}

这样可以看到还输出了原来自带的域属性例如path

javax.servlet.forward.request_uri == /Test/TestServlet
javax.servlet.forward.context_path == /Test
javax.servlet.forward.servlet_path == /TestServlet
javax.servlet.forward.mapping == org.apache.catalina.core.ApplicationMapping$MappingImpl@10667a58
address == Peking
author == Cfeng

除了get和set方法,对于域属性还有remove方法,可以删除域属性

从请求中获取其他信息

之前已经了解到request相当于就是封装的整个请求的内容;包括请求行,请求头,空白行,请求正文;其中请求正文的name可以通过getParameter获取到,其他的内容也有相应的get方法进行获取

getRequestURL

获取请求的URL,就是地址栏中输入的URL;这是StringBuffer类型的

getRequestURI

获取请求的URI, URL去掉协议以及主机 ,统一资源identifier; 是String类型的

getContextPath

获取当前Web项目的根路径,相当于就是创建整个项目的路径,String类型 因为环境对应的就是一个应用程序,所以就可以用comtext代指一个项目

getMethod

获取请求的提交方式;post或者get

getRemoteAddr

remote 遥远的 获取客户端的IP地址; 获取远程的【客户端】的地址

getServlePath

获取注册的url-pattern中匹配的精确的路径的部分

getPathInfo

获取注册的url-pattern中匹配的非精确的路径的部分,也就是对应的通配符的部分

package cfeng;

import java.io.IOException;
import java.util.Enumeration;
import java.util.Map;

import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;   //使用注解进行动态绑定
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@WebServlet(description = "this is just a test class", urlPatterns = { "/TestServlet" })
public class TestServlet extends HttpServlet {
	
	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		request.getRequestDispatcher("/some").forward(request, response);//将对现路径的请求转发给另外的路径,method不变
		//获取请求的请求的URL
		StringBuffer url = request.getRequestURL();
		System.out.println("url = " + url);
		
		//获取请求的请求行的	URI
		String uri = request.getRequestURI();
		System.out.println("uri = " + uri);
		
		//获取web程序的根路径
		String contextPath = request.getContextPath();
		System.out.println("contextPath = " + contextPath);
		
		//获取请求的url-pattern的精确部分
		String servletPath = request.getServletPath();
		System.out.println("servletPath = " + servletPath);
		
		//获取请求的url-pattern的非精确部分,如果都是精确的,那就是null
		String pathInfo = request.getPathInfo();
		System.out.println("pathinfo = " + pathInfo);
		
		//获取客户端ip地址【远程的地址】;remote都代表的是远程,也就是客户端;还可以获取端口post
		String clientIP = request.getRemoteAddr();
		System.out.println("clientIP = " + clientIP);
		System.out.println("clientPort = " + request.getRemotePort());
		System.out.println("clientUser = " + request.getRemoteUser());
	}
	
	protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		doGet(request, response);//这里POST提交的时候也会执行GET中的方法
	}

}

//上面演示了所有的获取的方法
url = http://localhost:8080/Test/TestServlet
uri = /Test/TestServlet                 //去除协议、主机和端口
contextPath = /Test                    //项目的根地址
servletPath = /TestServlet
pathinfo = null               //这里的url-pattern是精确的路径,没有通配的,没有pathInfo
clientIP = 0:0:0:0:0:0:0:1   
clientPort = 59305          //用户的端口
clientUser = null          //这里没有user

请求中文乱码

当使用表单采集信息的时候,如果信息中有中文,会产生乱码的问题【用户名输入张三;servlet获取到的是 user = ???】

这是什么原因导致?

不管是什么字符编码方式,一旦浏览器经过Http协议传输,这些数据都是以字节的方式传给服务器。因为Http协议使用的是TCP传输协议,【TCP协议是面向连接的,可靠的,基于字节流的、端对端的通信协议】,在请求中,字节都是以%开头,以十六进制出现 当用户通过服务器提交一个UTF-8编码格式的两个字符的中文时,浏览器会将两个中文转化为6个字节 , 一个UTF汉字占用三个字节,也就是形成6个类似%8D之类的字节形式,并传送给Tomcat服务器

//可以用watch看一下  输入用户名张三   age 29

user=%E5%BC%A0%E4%B8%89&age=29   可以发现一共有6个字节

Tomcat不知道这些字节是什么编码方式,所以就按照默认的ISO-8859-1的格式进行编码到控制台显示,所以会出现乱码【这种编码不支持汉字】

GET和POST不同

GET的请求的参数出现在请求行中的URI中,而POST的请求参数出现在请求正文中

Tomcat9 中使用GET方式已经解决了中文乱码问题; URL中会显示中文,但是传输基于的是TCP协议,这里的提交的还是字节流

使用setCharacterEncoding解决POST提交乱码

POST的乱码Tomcat解析请求正文的字节使用的是ISO的方式,不支持正文,使用ServletRequest的SetCharacterEncoding将编码方式改成UTF-8就可以解决乱码问题

	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		request.setCharacterEncoding("UTF-8");
		request.getRequestDispatcher("/some").forward(request, response);//将对现路径的请求转发给另外的路径,method不变
		System.out.println("user = " + request.getParameter("user"));
		System.out.println("age = " + request.getParameter("age"));
	}: 已完成重新加载名为/Test的上下文
user = 张三
age = 29

但是这里还没有解决GET的乱码问题,虽然9版本会自动解决,但是低版本不会,那么又要如何?

  • 在Tomcat的server.xml配置文件增加一个URIEncoding

这里对于低版本的Tomcat,就在Tomcat的conf文件夹中的server.xml文件中的连接器connector标签中加上一个URIEncoding = "UTF-8"就可以解决问题

Get和Post都可以; 收集字节码,使用String的编码方式来解析【转、解码】

这是一种万能的解决乱码问题的方案,就是先获取字节码数组,使用String的构造器获得特定格式编码的数据

  • 首先按照特定的编码格式接收字节码
  • new String创建按照特定的格式编码

这就是使用到了String的编码和反编码,在面试题中分享过

protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		request.getRequestDispatcher("/some").forward(request, response);//将对现路径的请求转发给另外的路径,method不变
		//接收到user,其字符编码格式为ISO8859-1
		String user = request.getParameter("user");
		//打散:将user字符串按照原编码方式进行打散  【转码】
		byte[] bytes = user.getBytes("ISO8859-1");
		//组装 : 将bytes字节数组按照指定编码方式进行组装,组装为String 【特定方式解码】
		user = new String(bytes, "UTF-8");
		System.out.println("user = " + user);
}

user = 李十三

user=%E6%9D%8E%E5%8D%81%E4%B8%89&age=29

这里一共有9个字节,当进行转码并解码之后就可以得到正确的中文编码

麻烦的地方在于需要对每一个参数进行转码和解码,如果确定是POST,就直接setCharacterEncoding

HttpServletResponse

服务器收到请求之后,会对每一个请求创建一个Request对象进行封装,同时也有一个Response对象,需要获得客户端的请求的信息就是对Request进行操作,但是如果要向客户端发送数据,那么就需要通过Reponse

向客户端发送数据getWriter

ServletReponse有一个方法为getWriter,可以获取到一个输出流对象PrintWriter,该输出流是专门向客户端浏览器输出字符数据的,标准输出流

响应是要响应到浏览器界面的,所以一个响应就对应一个输出流,输出流输出数据有多种方式;可以通过append或者write方法,但是这里都是不会自动换行的;还有就是print和println;println可以换行

//注意需要重启服务器
//获取输出流对象
		PrintWriter out = response.getWriter();
		out.println("hello! welcome to Cfeng.com!");
		out.append("hello");
		out.write("hello,cfeng");

虽然这里是一个流,之前IO中提到,为了防止资源浪费,经常会及时关闭输出流;但是这里的标准输出流和响应式成对出现的,当响应reponse没有的时候,流就会被服务器自动关闭;并且如果要对流进行操作比如fitter,是不能关闭输入流的。

Response中文乱码 setContentType

这里当向浏览器界面显示中文的时候,如果不处理又会乱码,会变成???, 之前的setCharacterEncoding只能解决Request的乱码问题

使用的方法为setContentType,设置内容格式,其实就是MIME类型

  • MIME类型 : 媒体类型 Multipuporse Internet Mail Extension ; 是一种标准,用来表示各种文件,包括文档、文件、视频、音频、字节流的性质和格式 比如video/mp4 ; image/jpg text/html

所以这里同时就可以设置内容的字符编码方式,就是响应体的编码方式

response.setContentType("text/html;charset=UTF-8");
PrintWriter out = response.getWriter();
out.write("hello,C风官网");

这样输出的就是正常的,这里的MIME格式为text文件中的HTML格式;所以下面输入的其实都是text;也就是输入的都是HTML格式的text;在html分享时,没有标签就会当作普通的文本

那么SerCharacterE你coding怎么回事

setCharacterEncoding 设置的是MIME类型的charset;也就是说这个方法要想起作用,就必须有setContentType方法;所以一半很少采用,就直接使用之前的方法,直接定义好charset就可;如果没有设置,就可以使用这个方法进行设置

response.setContentType("text/html");   //这里没有设置编码类型
response.setCharacterEncoding("UTF-8");      //可以给上面的MIME类型设置字符编码方式

并且这个方法只能在创建输出流之前使用,之后就不起作用了,因为流的编码方式已经确定了

这里的MIME类型,在web引用中进场使用到,type 有text/css text/javascript ……

因为这里的MIME是text/html,所以输出的字符串是直接按照html解析的

response.setContentType("text/html;charset=UTF-8");
PrintWriter out = response.getWriter();
out.write("<font color='red'> 你好,C风<font>");

这里输出的内容就变成红色了,因为解析了字体标签

request请求转发和response重定向

上面已经分享了请求request和响应response;其中请求就是将客户端发送的请求封装为一个对象;可以通过get方法获取到请求对象中的内容;响应对应一个输出流,输出流可以不手动关闭,因为响应的时间短暂,自动关闭,解决乱码问题使用转码解码;response使用的是setContentType;设置MIME类型

客户端来的请求到达一个资源之后,从资源1跳转到资源2的方式有重定向和请求转发

请求有一个方法为getServletDisptcher可以获得一个servlet调配器,通过该对象的forward方法可以完成请求转发功能; 还可以通过Response的SendRedirect,完成重定向功能 【redirect 使改变方向 重定向】

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GRE5a4Dh-1640098027115)(https://tse1-mm.cn.bing.net/th/id/R-C.3deb487d5d8c3857284f900303ff7345?rik=SpgUSmXynjbZvw&riu=http%3a%2f%2fwww.monkey1024.com%2fwp-content%2fuploads%2f2018%2f03%2f%e8%bd%ac%e5%8f%91%e4%b8%8e%e9%87%8d%e5%ae%9a%e5%90%91.png&ehk=18dbhmA4pkGl7qfcC%2f%2fkrzh3sfLJB38rtOZo6g%2fzw5k%3d&risl=&pid=ImgRaw&r=0)]

可以直观看出来,两者还是有很大的区别,请求转发只有一次请求,一次响应【反馈】,服务器内转发;

重定向n次请求和n次响应;无状态协议,请求是不相同的,所以数据不能共享,就是域属性后面的资源不能访问;服务器外转发; 所以这里还是比转发麻烦的,但是后面的请求发送是服务器自动发送的,用户感知不到;用户的感知还是一次请求,一次响应;但是内部的执行过程不一样

//请求转发,只有一次请求,所以都是相同的
//这里请求转发因为只有一次请求,所以数据共享,特别是域属性,和请求中的内容,也就是request是相同的,parameter相同;通过forward方法就可以发现是将当前servlet的request和response给了下一个资源
public class TestServlet extends HttpServlet {
	
	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		String user = request.getParameter("user");
		//解决乱码问题
		user = new String(user.getBytes("ISO8859-1"),"UTF-8");      //经典问题,以字面量在堆中新创建一个对象
		System.out.println("user = " + user);
		
//		response.setContentType("text/html;charset=UTF-8");
		response.getWriter().append("你好,java学习者");
		
		request.getRequestDispatcher("/some").forward(request, response); //相当于给到下一个的request和response都是这个
		
	}
	
	protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		doGet(request, response);//这里POST提交的时候也会执行GET中的方法
	}

}
//上面是TestServlet,就是最开始跳转的界面


@WebServlet(description = "just for testing", urlPatterns = { "/some" })
public class someServlet extends HttpServlet {

	protected void doGet(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		response.getWriter().append("<br>你好,Cfeng!");
	}

	protected void doPost(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		doGet(request, response);
	}

}
//这里的some是请求转发的目标资源

经过反复的尝试可以发现

  • response是只有一个,在Test中设置了MIME格式,在some中就没有乱码;如果注释掉就会乱码; 并且test的输出流printwriter不起作用,起作用的是后面的some 一个response只有一个wirter

重定向是resonse的sendRedirect方法

//重定向其实就是新的请求了,相当于以一个新的请求触发Servlet的Service服务
//所以最开始的parameter就没有了
response.sendRedirect("some"); //重定向,服务器自动进行外部跳转【新的请求】

System.out.println("user = ” + request.getParameter("user"));
                   
                   
user = null;   //这里 就是因为重定向的请求不一样了,数据不共享

重定向中填入地址不需要加上/;而请求转发则要加上/

请求转发的响应是最开始的servlet的response,地址栏为第一个的URL; 但是重定向多次请求,多次响应,最后发出来的是最后一个servlet的response,也就是最后一个的URL 因为请求转发不断将第一个资源的response和request传递给下一个资源 ;

如何解决重定向数据传输问题

重定向的方法中加的是项目后面的部分;不需要加上/;其实重定向后面的请求的触发方式都是GET,因为POST就两种,一种是HTML表单,一种是AJAX;所以如何携带数据; 这里就是GET的携带数据就是在URL中直接显示的,那么也是属于资源的一部分

那就是直接在资源中使用?name=value&name=value…… 这样就可以携带数据了;

response.sendRedirect("some?user=" + user);   //这就是get方式提交参数

request.setCharacterEncoding("UTF-8");
System.out.println("user = " + request.getParameter("user"));

这里就输出了
user = 张三

重定向乱码问题 URLEncoder

重定向数据传输中文乱码http://localhost:8080/Test/some?user=?? 这里就可以看出出现了乱码,使用new String()没有用处

之前说过,HTTP进行数据传输的时候,是基于TCP协议的,TCP是使用的字节流进行传输的【也就是byte[],而不是String】,这里上面使用的是UTF-8编码的字符串;这里传输的必须是URL的字节流,不是getbytes打散的;要使用工具类URLEncoder进行编码成字节流,再使用URLDecoder转码;

转码和解码要成对使用,但是在后面的资源位置解码之后,得到的不是UTF-8类型的String;而是一个ISO8859-1类型的,这里还要再使用new String进行再转码和解码

感觉有点长,先这样吧,剩下的明天再发📕

在这里插入图片描述

Logo

CSDN联合极客时间,共同打造面向开发者的精品内容学习社区,助力成长!

更多推荐