IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 系统运维 -> Servlet、Request、Response、Filter、Cookie、Session -> 正文阅读

[系统运维]Servlet、Request、Response、Filter、Cookie、Session

前言

??MVC 模式是软件工程中的一种软件架构模式,把软件系统分为三个基本部分:模型(Model)、视图(View)和控制器(Controller)。

Controller:对请求进行处理,负责请求转发;
View:界面设计人员进行图形界面设计;
Model: 程序编写程序应用的功能(实现算法等) 、数据库管理。

??MVC不是Java的东西,几乎所有B/S结构的软件都采用了MVC设计模式。
??JavaWeb的三层:

WEB层:包含JSP和Servlet等与WEB相关的内容;
业务层:业务层中不包含JavaWeb API,它只关心业务逻辑;
数据层:封装了对数据库的访问细节。


??Web服务器的作用是接收客户端的请求,给客户端作出响应。对于JavaWeb程序而言,还需要有Servlet容器,Servlet容器的基本功能是把动态资源转换成静态资源。我们需要使用的是Web服务器和Servlet容器,通常这两者会集于一 身。
??一些JavaWeb服务器: Tomcat、Weblogic等。

一、Servlet

??Servlet是JavaWeb的三大组件之一,它的作用是处理请求,服务器会把接收到的请求交给 Servlet 来处理,在 Servlet 中通常需要:接收请求数据、处理请求、完成响应。

1.1 Servlet的生命周期

??生命周期相关的方法:

	//初始化
	void init(ServletConfig)
	//处理请求
	void service(ServletRequest request, ServletResponse response)
	//销毁
	void destroy()

??Servlet的特点:

  1. 单例,一个具体的Servlet类只有一个对象(通过反射来创建对象),当然可能存在多个Servlet类;
  2. 多线程的,所以它的效率是高的,不是线程安全的。

??Servlet类由开发者来写,但对象由服务器来创建,并且由服务器来调用相应的方法。

1.1.1 init

??服务器会在Servlet第一次被访问时创建Servlet,或者是在服务器启动时创建Servlet。如果服务器启动时就创建Servlet,那么还需要在web.xml文件中配置。也就是说默认情况下,Servlet是在第一次被访问时由服务器创建的。
??在 Servlet 被创建后,服务器会马上调用 Servlet 的void init(ServletConfig)方法,并且整个生命周期只调用一次。
??ServletConfig:Servlet的配置信息,即web.xml文件中的<servlet>。一个ServletConfig对象,对应着一个servlet元素的配置信息(servlet-name,servlet-class)。
??ServletConfig中的方法:

??看一个<servlet>的例子:

	<servlet>
		<servlet-name>One</servlet-name>
		<servlet-class>cn.itcast.servlet.OneServlet</servlet-class>
		<init-param>
			<param-name>paramName1</param-name>
			<param-value>paramValue1</param-value>
		</init-param>
		<init-param>
			<param-name>paramName2</param-name>
			<param-value>paramValue2</param-value>
		</init-param>
	</servlet>

??<init-param>代表初始化参数。
??ServletConfig中4个方法的解释:

	//获取Servlet在web.xml文件中的配置名称,即<servlet-name>指定的名称
	String getServletName()
	//用来获取ServletContext对象
	ServletContext getServletContext()
	//用来获取在web.xml中配置的初始化参数,通过参数名来获取参数值
	String getInitParameter(String name)
	//用来获取在web.xml中配置的所有初始化参数名称
	Enumeration getInitParameterNames()

1.1.2 service

??当服务器每次接收到请求时,都会去调用 Servlet 的service()方法来处理请求。服务器接收到一次请求,就会调用service()方法一次。
??ServletRequest和ServletResponse是service()方法的两个参数,代表请求对象和响应对象。开发者可以从ServletRequest对象中获取请求数据,可以使用ServletResponse对象完成响应。
??ServletRequest和ServletResponse的实例由服务器创建,然后传递给service()方法。如果在service()方法中希望使用HTTP相关的功能,那么可以把ServletRequest和ServletResponse强转成HttpServletRequest和HttpServletResponse。
??HttpServletRequest中的方法:

	//获取指定请求参数的值
	String getParameter(String paramName)
	//获取请求方法,例如GET或POST
	String getMethod()
	//获取指定请求头的值
	String getHeader(String name)
	//设置请求体的编码。因为GET请求没有请求体,所以这个方法只对POST请求有效。当调用
	//request.setCharacterEncoding("utf-8")之后,再通过getParameter()方法获取参数
	//值时,那么参数值都已经通过了转码,即转换成了UTF-8编码。所以,这个方法必须在调用
	//getParameter()方法之前调用
	void setCharacterEncoding(String encoding)

??HttpServletResponse中的方法:

	//获取字符响应流,使用该流可以向客户端输出响应信息
	PrintWriter getWriter()
	//获取字节响应流,当需要向客户端响应字节数据时,需要使用这个流,例如要向
	//客户端响应图片
	ServletOutputStream getOutputStream()
	//用来设置字符响应流的编码,例如在调用setCharacterEncoding("utf-8")之后,
	//再response.getWriter()获取字符响应流对象,这时的响应流的编码为utf-8,使
	//用 response.getWriter()输出的中文都会转换成utf-8编码后发送给客户端
	void setCharacterEncoding(String encoding)
	//向客户端添加响应头信息
	void setHeader(String name, String value)
	//是setHeader("content-type", "xxx")的简便写法,即用来添加名为content-type响应头的方法 
	void setContentType(String contentType)
	//向客户端发送状态码,以及错误消息
	void sendError(int code, String errorMsg)

1.1.3 destroy

??在服务器被关闭时,服务器会去销毁Servlet,在销毁Servlet之前服务器会先去调用Servlet的destroy()方法,该方法内可以写一些释放资源之类的代码。
??一个Servlet如果长时间不被使用的话,也会被Tomcat自动销毁。即destroy方法,该方法被调用的两个常见场景:

  • 1、关闭tomcat的时候。
  • 2、server.xml中将reloadable配置为"true" 就表示有任何类发生的更新,web应用会自动重启,当web应用自动重启的时候,destroy()方法就会被调用

1.2 实现Servlet的相关类

??实现 Servlet 有三种方式:

实现javax.servlet.Servlet接口;
继承javax.servlet.GenericServlet类;
继承 javax.servlet.http.HttpServlet类。

??通常会去继承HttpServlet类来完成自定义Servlet。

1.2.1 GenericServlet

??GenericServlet是Servlet接口的实现类,在GenericServlet源码中,可以看下init相关的方法:

	@Override
	public void init(ServletConfig config) throws ServletException {
		this.config = config;
		this.init();
	}
	
	//这个init 方法是为了拓展init 而设置的,子类可以重写这个无参数的init
	public void init() throws ServletException {}

??在GenericServlet中,定义了一个ServletConfig实例变量,并在init(ServletConfig)方法中把参数ServletConfig赋给了实例变量。然后在该类的很多方法中使用了实例变量config。
??如果子类希望完成一些初始化操作,那么应该去覆盖GenericServlet的提供的init()方法,它是没有参数的init()方法,它会在init(ServletConfig)方法中被调用。
??GenericServlet还实现了ServletConfig接口,所以可以直接调用getInitParameter()、getServletContext()等ServletConfig中的方法。

1.2.2 HttpServlet

??HttpServlet类是GenericServlet的子类,它提供了对HTTP请求的特殊支持,所以通常都会通过继承HttpServlet来完成自定义的Servlet。
??HttpServlet类中提供了service(HttpServletRequest,HttpServletResponse)方法,这个方法是HttpServlet自己的方法,不是从Servlet继承来的。
??在HttpServlet的service(ServletRequest,ServletResponse)方法中,会把 ServletRequest和ServletResponse强转成HttpServletRequest和HttpServletResponse,然后调用service(HttpServletRequest,HttpServletResponse)方法。相关源码:

public abstract class HttpServlet extends GenericServlet {
	protected void service(HttpServletRequest req, HttpServletResponse
resp) throws ServletException, IOException {
		//……
	}
	
	@Override
	public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
		HttpServletRequest request;
		HttpServletResponse response;
		try {
			request = (HttpServletRequest) req;
			response = (HttpServletResponse) res;
		} catch (ClassCastException e) {
			throw new ServletException("non-HTTP request or response");
		}
		service(request, response);
	}
}
  • doGet()和doPost()
    ??在 在 HttpServlet 的 的 service(HttpServletRequest,HttpServletResponse) 方法会去判断当前请求是GET还是POST,如果是GET请求,那么会去调用本类的 doGet()方法,如果是POST请求会去调用doPost()方法,因此在子类中去覆盖doGet()或doPost()方法即可。示例:
public class AServlet extends HttpServlet {
	public void doGet(HttpServletRequest request, HttpServletResponse response)
		throws ServletException, IOException {
		System.out.println("hello doGet()...");
	}

	public void doPost(HttpServletRequest request, HttpServletResponse response)
		throws ServletException, IOException {
		System.out.println("hello doPost()...");
	}
}

1.3 ServletContext

??一个Web应用只有一个ServletContext对象,该对象在Tomcat启动时就创建,在Tomcat关闭时被销毁。
??ServletContext对象的作用是在整个Web应用的动态资源之间共享数据。比如在AServlet中向ServletContext对象中保存一个值,然后在BServlet中就可以获取这个值。
??常见的获取ServletContext的方式:

	//在void init(ServletConfig config)中
	ServletContext context = config.getServletContext();
	//在GenericeServlet中
	ServletContext context = this.getServletContext()

1.3.1 域对象的功能

??JavaWeb四大域对象:PageContext、ServletRequest、HttpSession、ServletContext。
??所有域对象都有存取数据的功能,因为域对象内部有一个Map,用来存储数据。
??ServletContext对象用来操作数据的方法:

	//用来存储一个对象,也可以称之为存储一个域属性。如果多次调用该方法,并且
	//使用相同的 name,那么会覆盖上一次的值
	void setAttribute(String name, Object value)
	//用来获取ServletContext中的数据
	Object getAttribute(String name)
	//用来移除ServletContext中的域属性,如果参数name指定的域属性不存在,那么什么都不做
	void removeAttribute(String name)
	//获取所有域属性的名称
	Enumeration getAttributeNames()

1.3.2 获取初始化参数

??Servlet也可以获取初始化参数,但它是局部的参数,即一个Servlet只能获取自己的初始化参数。
??使用ServletContext可以获取全局(比如在web.xml文件中配置的全局参数)的初始化参数。示例:

	<web-app ...>
		<!--...-->
		<!--为ServletContext 设置的公共初始化参数-->
		<context-param> 
			<param-name>paramName1</param-name>
			<param-value>paramValue1</param-value>
		</context-param>
		<!--...-->
	</web-app>
	ServletContext context = this.getServletContext();
	String value1 = context.getInitParameter("paramName1");

1.4 Servlet的一些特点

1.4.1 线程不安全

??由于Servlet是单例的,当多个用户访问Servlet的时候,服务器会为每个用户创建一个线程。当多个用户并发访问Servlet共享资源的时候就会出现线程安全问题。
??原则:

  • 1、如果一个变量需要多个用户共享,则应当在访问该变量的时候,加同步机制synchronized (对象){}
  • 2、如果一个变量不需要共享,则直接在 doGet() 或者 doPost()定义,这样不会存在线程安全问题。
  • 3、避免使用实例变量
    ??线程安全问题是由实例变量造成的,只要在Servlet里面的任何方法里面都不使用实例变量(尽量都定义局部变量),那么该Servlet就是线程安全的。

1.4.2 Servlet的创建顺序

??默认情况下,服务器会在某个Servlet第一次收到请求时创建它。也可以在web.xml中对Servlet进行配置,使服务器启动时就创建Servlet。示例:

<servlet>
	<servlet-name>hello1</servlet-name>
	<servlet-class>cn.itcast.servlet.Hello1Servlet</servlet-class>
	<load-on-startup>0</load-on-startup>
</servlet>
<servlet-mapping>
	<servlet-name>hello1</servlet-name>
	<url-pattern>/hello1</url-pattern>
</servlet-mapping>

<servlet>
	<servlet-name>hello2</servlet-name>
	<servlet-class>cn.itcast.servlet.Hello2Servlet</servlet-class>
	<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
	<servlet-name>hello2</servlet-name>
	<url-pattern>/hello2</url-pattern>
</servlet-mapping>

<servlet>
	<servlet-name>hello3</servlet-name>
	<servlet-class>cn.itcast.servlet.Hello3Servlet</servlet-class>
	<load-on-startup>2</load-on-startup>
</servlet>
<servlet-mapping>
	<servlet-name>hello3</servlet-name>
	<url-pattern>/hello3</url-pattern>
</servlet-mapping>

??在<servlet>元素中配置<load-on-startup>元素,可以让服务器在启动时就创建该 Servlet。其中<load-on-startup>元素的值必须是大于等于0的整数,它表示服务器启动时创建Servlet 的顺序。正数的值越小,启动时加载该servlet的优先级越高。如果为负数,则容器启动时不会加载该servlet,只有该servlet被访问时才会加载。
??上面例子中,根据<load-on-startup>的值可以得知服务器创建Servlet的顺序为Hello1Servlet 、Hello2Servlet 、Hello3Servlet。

1.4.3 路径匹配

??<url-pattern><servlet-mapping>的子元素,用来指定Servlet的访问路径,必须是以“/”开头。
??可以在<servlet-mapping>中给出多个<url-pattern>,示例:

<servlet-mapping>
	<servlet-name>AServlet</servlet-name>
	<url-pattern>/AServlet</url-pattern>
	<url-pattern>/BServlet</url-pattern>
</servlet-mapping>

??上述配置表示一个Servlet绑定了两个URL,无论访问/AServlet是/BServlet,访问的都是AServlet。
??也可以在<url-pattern>中使用通配符,所谓通配符就是星号“*”,星号可以匹配任何URL前缀或后缀,例如:

	<!--/servlet/a、/servlet/b,都匹配/servlet/*-->
	<url-pattern>/servlet/*<url-patter>
	<!--/abc/def/ghi.do、/a.do,都匹配*.do-->
	<url-pattern>*.do</url-pattern>
	<!--匹配所有URL-->
	<url-pattern>/*<url-pattern>

??配通配符是一种模糊匹配URL的方式,如果存在更具体的<url-pattern>,那么访问路径会去匹配具体的<url-pattern>。例如:

<servlet>
	<servlet-name>hello1</servlet-name>
	<servlet-class>cn.itcast.servlet.Hello1Servlet</servlet-class>
</servlet>
<servlet-mapping>
	<servlet-name>hello1</servlet-name>
	<url-pattern>/servlet/hello1</url-pattern>
</servlet-mapping>

<servlet>
	<servlet-name>hello2</servlet-name>
	<servlet-class>cn.itcast.servlet.Hello2Servlet</servlet-class>
</servlet>
<servlet-mapping>
	<servlet-name>hello2</servlet-name>
	<url-pattern>/servlet/*</url-pattern>
</servlet-mapping>

??当访问http://localhost:8080/hello/servlet/hello1时,因为访问路径即匹配hello1的<url-pattern>,又匹配hello2的<url-pattern>,但因为hello1的<url-pattern>中没有通配符,所以优先匹配,即设置 hello1。

1.4.4 web.xml文件的继承

??在${CATALINA_HOME}\conf\web.xml中的内容,相当于写到了每个项目的web.xml中,它是所有web.xml的父文件。
??每个完整的JavaWeb应用中都需要有web.xml,所有的web.xml文件都有一个共同的父文件,即Tomcat的conf/web.xml。

1.5 Servlet的相关问题

1.5.1 tomcat容器是如何创建servlet类实例

??当容器启动时,会读取在webapps目录下所有的web应用中的web.xml文件。
??然后,对xml文件进行解析,并读取servlet注册信息。
??最后,将每个应用中注册的servlet类都进行加载,并通过 反射的方式实例化。

1.5.2 Servlet的使用流程

??1)Tomcat容器中通过web.xml加载所有的Servlet。
??2)用户在浏览器输入不同的地址,向Tomcat容器请求资源。
??3)Tomcat容器根据地址首先在容器内找到应用对应的项目。
??4)Tomcat容器再根据地址去web.xml找到相应的servlet地址。
??5)Tomcat容器根据找到的servlet地址去web.xml找到相应的Servlet类,并实例化。
??6)Tomcat容器实例化相应的Servlet,首先调用init方法。
??7)Tomcat容器实例化相应的Servlet,首先调用service方法处理用户请求,比如post或者是get。
??8)Servlet处理完成之后,现将数据给Tomcat容器,Tomcat容器再把处理结果给浏览器客户端。
??9)Tomcat容器调用servlet实例的destory方法销毁这个实例。

二、Request

??request是Servlet.service()方法的一个参数,类型为javax.servlet.http.HttpServletRequest。在客户端发出每个请求时,服务器都会创建一个request对象,并把请求数据封装到request中,然后在调用Servlet.service()方法时传递给 service()方法,这说明在service()方法中可以通过request对象来获取请求数据。
??request 的功能可以分为以下几种:

  1. 封装了请求头数据;
  2. 封装了请求正文数据,如果是GET请求,那么就没有正文;
  3. request是一个域对象,可以把它当成Map,来添加获取数据;
  4. request提供了请求转发和请求包含功能。

2.1 Request中的方法

  • 1、域方法
    ??request是域对象。一个请求会创建一个request对象,如果在一个请求中经历了多个Servlet ,那么多个Servlet就可以使用request来共享数据。
    ?? request的域方法:
	//用来存储一个对象,也可以称之为存储一个域属性
	void setAttribute(String name, Object value)
	//用来获取 request 中的数据
	Object getAttribute(String name)
	//用来移除request中的域属性,如果参数name指定的域属性不存在,那么什么都不做
	void removeAttribute(String name)
	//获取所有域属性的名称
	Enumeration getAttributeNames()
  • 2、获取请求头数据
    ??request中,与请求头相关的方法有:
	//获取指定名称的请求头
	String getHeader(String name)
	//返回所有request header的名字
	Enumeration getHeaderNames()
	//获取指定名称请求头的int值
	int getIntHeader(String name)
	//获取指定的头名称的构建Date对象的long值
	long getDateHeader(String name)
  • 3、获取请求相关的其它方法
	//获取请求体的字节数,GET 请求没有请求体,没有请求体返回-1
	int getContentLength()
	//获取请求类型,如果请求是GET,那么这个方法返回null;
	//如果是POST请求,那么默认为application/x-www-form-urlencoded,表示请求体内
	//容使用了URL编码
	String getContentType()
	//返回请求方法,例如:GET/POST
	String getMethod() 
	//返回当前客户端浏览器的 Locale。java.util.Locale表示国家和言语
	Locale getLocale()
	//获取请求编码,如果没有setCharacterEncoding(),那么返回 null,表示使用 ISO-8859-1 编码
	String getCharacterEncoding()
	//设置请求编码,只对请求体有效。对于GET而言,没有请求体,所以此方法只能对POST请求中的参数有效
	void setCharacterEncoding(String code)
	//返回上下文路径(/项目名),例如:/hello
	String getContextPath()
	//返回请求URL中的参数,例如:name=zhangSan
	String getQueryString()
	//返回请求URI路径,例如:/hello/oneServlet
	String getRequestURI()
	//返回请求URL路径,例如:http://localhost/hello/oneServlet,即返回除了参数以外的路径信息
	StringBuffer getRequestURL()
	//返回Servlet路径,例如:/oneServlet
	String getServletPath()
	//返回当前客户端的IP地址
	String getRemoteAddr() 
	//返回当前客户端的主机名,但这个方法的实现还是获取IP地址
	String getRemoteHost()
	//返回请求协议,例如:http
	String getScheme()
	//返回主机名,例如:localhost
	String getServerName()
	//返回服务器端口号,例如:8080
	int getServerPort()

??图示:

??URL=协议名+主机名+端口号+URI
??示例:

	//访问 http://localhost:8080/Request&Response/RequestHeaderServlet?username=sxj&password=sxj
	System.out.println("IP:"+request.getRemoteAddr());
	System.out.println("请求方式:"+request.getMethod());
	System.out.println("User-Agent请求头:"+request.getHeader("User-Agent"));
	System.out.println("协议名:"+request.getScheme());
	System.out.println("主机名:"+request.getServerName());
	System.out.println("端口号:"+request.getServerPort());
	System.out.println("项目:"+request.getContextPath());
	System.out.println("Servlet路径:"+request.getServletPath());
	System.out.println("请求参数:"+request.getQueryString());
	System.out.println("URI:"+request.getRequestURI());
	System.out.println("URL:"+request.getRequestURL());

??结果:

IP:127.0.0.1
请求方式:GET
User-Agent请求头:Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36
(KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36
协议名:http
主机名:localhost
端口号:8080
项目:/Request&Response
Servlet路径:/RequestHeaderServlet
请求参数: username=sxj&password=sxj
URI:/Request&Response/RequestHeaderServlet(RequestURI)
URL:http://localhost:8080/Request&Response/RequestHeaderServlet

2.2 GET和POST

??最为常见的客户端传递参数方式:

  • 1、Get请求
    ??浏览器地址栏直接输入URL和超链接都是Get请求。如果不指定名method,则也是Get请求。
    ??表单提交:可以是GET,也可以是POST,这取决于<form>的method属性值。

??GET和POST请求的简单比较:

GETPOST
请求参数的位置请求参数放在URL地址后面,以?的方式来进行拼接请求参数放在HTTP请求体中
安全性请求参数会在浏览器的地址栏中显示,所以不安全请求参数不会显示浏览器的地址栏,相对安全
参数长度是否有限制请求参数长度限制长度在1K之内请求参数长度没有限制
是否可以设置编码格式没有请求体,无法通过 request.setCharacterEncoding()来设置参数的编码可以设置
用途一般用来获取数据一般用来提交数据
速度

??无论是GET,还是POST请求,都可以使用相同的 API 来获取请求参数。请求参数有一个key一个value的,也有一个key多个value的。示例:

	//通过指定名称获取参数值
	String getParameter(String name)
	//当多个参数名称相同时,可以使用方法来获取
	String[] getParameterValues(String name) 
	//获取所有参数的名字
	Enumeration getParameterNames()
	//获取所有参数封装到Map中,其中key为参数名,value为参数值,因为一个参数名称可能
	//有多个值,所以参数值是String[],而不是String
	Map getParameterMap()

2.3 转发

??在 AServlet 中,把请求转发到BServlet,参数是Servlet路径,示例:

public class AServlet extends HttpServlet {
	public void doGet(HttpServletRequest request, HttpServletResponse response)
		throws ServletException, IOException {
		System.out.println("AServlet");
		RequestDispatcher rd = request.getRequestDispatcher("/BServlet");
		rd.forward(request, response);
	}
}

三、Response

??在客户端发出每个请求时,服务器都会创建一个response对象,并传入给 Servlet.service()方法。response 对象是用来对客户端进行响应的,这说明在service()方法中使用response对象可以完成对客户端的响应工作。
??response对象的功能分为以下四种:设置响应头信息、发送状态码、设置响应正文、重定向。

3.1 响应正文

??response是响应对象,向客户端输出响应正文(响应体)可以使用response的响应流,repsonse一共提供了两个响应流对象:

	//获取字符流
	PrintWriter out = response.getWriter();
	//获取字节流
	ServletOutputStream out = response.getOutputStream();

??如果响应正文内容为字符(html) ,那么使用 response.getWriter() ;如果响应内容是字节(图片等) ,例如下载时,那么可以使用 response.getOutputStream()。
??在一个请求中,不能同时使用这两个流 。即要么你使用repsonse.getWriter(),要么使用response.getOutputStream(),但不能同时使用这两个流 ,不然会出抛出IllegalStateException异常。

  • 字符响应流
    ??使用response.getWriter()时,默认字符编码为ISO-8859-1,如果希望输出给客户端的字符都是使用UTF-8编码,可以使用response.setCharaceterEncoding(“utf-8”)来设置。
    ??但如果仅使用上述方法,客户端浏览器并不知道响应数据是什么编码的。如果希望通知客户端使用UTF-8格式来解读响应数据,那么还是使用response.setContentType("text/html;charset=utf-8")方法比较好。因为这个方法不只会调用response.setCharaceterEncoding(“utf-8”),还会设置content-type响应头,客户端浏览器会使用 content-type 头来解读响应数据。
    ??response.getWriter()是PrintWriter 类型,所以它有缓冲区,缓冲区的默认大小为 8KB。即在响应数据没有输出8KB之前,数据都是存放在缓冲区中,而不会立刻发送到客户端。当 Servlet 执行结束后,服务器才会去刷新流,使缓冲区中的数据发送到客户端。
    ??如果希望响应数据马上发送给客户端:
  1. 向流中写入大于 8KB 的数据;
  2. 调用 response.flushBuffer()方法来手动刷新缓冲区。

3.2 响应头和状态码

??响应头是键值对的形式。可以使用response对象的setHeader()方法来设置响应头。使用该方法设置的响应头最终会发送给客户端浏览器,示例:

	//设置content-type响应头,该头的作用是告诉浏览器响应内容为html类型,编码为utf-8,
	//而且同时会设置response的字符流编码为utf-8
	response.setHeader("content-type", "text/html;charset=utf-8")
	//5秒后自动跳转到指定的URL
	response.setHeader("Refresh","5; URL=http://www.itcast.cn")

??设置状态码的相关方法:

	//等同于调用response.setHeader(“content-type”, “text/html;charset=utf-8”);
	response.setContentType("text/html;charset=utf-8")
	//设置字符响应流的字符编码为utf-8
	response.setCharacterEncoding(“utf-8)
	//设置状态码
	response.setStatus(200) 

3.3 重定向

??重定向是服务器通知浏览器去访问另一个地址,即再发出另一个请求。
??响应码为200,表示响应成功。响应码为302,表示重定向。
??因为重定向是通知浏览器发出第二个请求,所以浏览器需要知道第二个请求的URL,所以完成重定向的第二步是设置Location头,指定第二个请求的URL地址。示例:

public class AServlet extends HttpServlet {
	public void doGet(HttpServletRequest request, HttpServletResponse response)
	    throws ServletException, IOException {
		response.setStatus(302);
		response.setHeader("Location", "http://www.itcast.cn");
	}
}

??sendRedirect是一种简单的重定向方式:

	//通用方式
	response.sendRedirect("http://www.itcast.cn");
	//如果跳转到同一个项目内,可以使用相对路径,即"/项目名/路径",以下代码会
	//重定向到http://localhost:8080/hello/BServlet
	response.sendRedirect("/hello/BServlet");

??重定向的特点:

  1. 重定向是两次请求;
  2. 重定向的URL可以是其他应用,不局限于当前应用;
  3. 重定向的响应头为302,并且必须要有 Location 响应头;
  4. 重定向就不要再使用response.getWriter()或response.getOutputStream() 输出数据 ,不然可能会出现异常。

3.4 转发和重定向的区别

  • 1、实际发生位置不同,地址栏不同
    ??转发是由服务器进行跳转的,在转发的时候,浏览器的地址栏是没有发生变化的,浏览器是不知道该跳转的动作。实现转发只是一次的http请求,一次转发中request和response对象都是同一个。这也解释了,为什么可以使用request作为域对象进行Servlet之间的通讯。
    ??重定向是由浏览器进行跳转的,进行重定向跳转的时候,浏览器的地址会发生变化的。实现重定向的原理是由response的状态码和Location头组合而实现的。这是由浏览器进行的页面跳转实现重定向会发出两个http请求,request域对象是无效的,因为它不是同一个request对象。
  • 2、用法不同
    ?? 给服务器(转发)用的直接从资源名开始写,给浏览器(重定向)用的要把应用名写上
  • 3、能够去往的URL的范围不一样
    ??转发是服务器跳转只能去往当前web应用的资源。
    ??重定向是服务器跳转,可以去往任何的资源。
  • 4、传递数据的类型不同
    ??转发的request对象可以传递各种类型的数据,包括对象
    ??重定向只能传递字符串
  • 5、跳转的时间不同
    ??转发时:执行到跳转语句时就会立刻跳转
    ??重定向:整个页面执行完之后才执行跳转
  • 6、从效率来说
    ??转发:高。
    ??重定向:低。

??转发是带着转发前的请求的参数的。重定向是新的请求

四、Filter

??过滤器是Java Web三大组件之一,它与Servlet很相似。不过过滤器是用来拦截请求的 ,而不是处理请求的。
??当用户请求某个Servlet时,会先执行部署在这个请求上的Filter,如果Filter“ 放行” ,那么会继承执行用户请求的Servlet;如果Filter不 “ 放行”,那么就不会执行用户请求的Servlet。
??Filter简单使用示例:

public class HelloFilter implements Filter {
	public void init(FilterConfig filterConfig) throws ServletException {}
	public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
		System.out.println("filter start...");
		chain.doFilter(request, response);
		System.out.println("filter end...");
	}
	public void destroy() {}
}

??在web.xml文件中配置Filter:

<filter>
	<filter-name>helloFilter</filter-name>
	<filter-class>cn.itcast.filter.HelloFilter</filter-class>
</filter>
<filter-mapping>
	<filter-name>helloFilter</filter-name>
	<url-pattern>/*</url-pattern>
</filter-mapping>

??在经过上面的配置后,在访问项目中的任何一个url时,都会经过HelloFilter。

4.1 过滤器的生命周期

??过滤器也是单例的。

4.1.1 init(FilterConfig)

??在服务器(如Tomcat)启动时建会创建Filter实例,并且每个类型的Filter只创建一个实例 。在创建完Filter实例后,会马上调用init()方法完成初始化工作,这个方法只会被执行一次。
??FilterConfig的功能与ServletConfig相似,与web.xml文件中的配置信息对应。常用方法:

	//获取ServletContext
	ServletContext getServletContext()
	//获取Filter的配置名称,与<filter-name>元素对应
	String getFilterName()
	//获取Filter的初始化配置,与<init-param>元素对应
	String getInitParameter(String name)
	//获取所有初始化参数的名称
	Enumeration getInitParameterNames()

4.1.2 doFilter(ServletRequest req,ServletResponse res,FilterChain chain)

??过滤方法,如果需要“放行”,那么需要调用FilterChain的doFilter(ServletRequest,ServletResponse)方法,如果不调用FilterChain的doFilter() 方法,那么目标资源将无法执行。
??FilterChain的doFilter() 方法的含义:

  1. 如果当前过滤器是最后一个过滤器,那么调用chain.doFilter()方法表示执行目标资源;
  2. 如果不是最后一个过滤器,那么 chain.doFilter() 表示执行下一个过滤器的 doFilter() 方法。

4.1.3 destroy()

??服务器会在创建Filter对象之后,把Filter放到缓存中一直使用,通常不会销毁它,一般会在服务器(如Tomcat)关闭时销毁Filter对象。在销毁Filter对象之前,服务器会调用Filter对象的destory()方法。

4.2 多个过滤器的执行顺序

??一个目标资源可以指定多个过滤器在 ,过滤器的执行顺序是在 web.xml 文件中的部署顺序。示例:

<filter>
	<filter-name>myFilter1</filter-name>
	<filter-class>cn.itcast.filter.MyFilter1</filter-class>
</filter>
<filter-mapping>
	<filter-name>myFilter1</filter-name>
	<url-pattern>/index.jsp</url-pattern>
</filter-mapping>

<filter>
	<filter-name>myFilter2</filter-name>
	<filter-class>cn.itcast.filter.MyFilter2</filter-class>
</filter>
<filter-mapping>
	<filter-name>myFilter2</filter-name>
	<url-pattern>/index.jsp</url-pattern>
</filter-mapping>
public class MyFilter1 extends HttpFilter {
	public void doFilter(HttpServletRequest request, HttpServletResponse response,FilterChain chain) throws IOException, ServletException {
		System.out.println("filter1 start...");
		chain.doFilter(request, response);//放行,执行MyFilter2的doFilter()方法
		System.out.println("filter1 end...");
	}
}

public class MyFilter2 extends HttpFilter {
	public void doFilter(HttpServletRequest request,HttpServletResponse response,FilterChain chain) throws IOException, ServletException {
		System.out.println("filter2 start...");
		chain.doFilter(request, response);//放行,执行目标资源
		System.out.println("filter2 end...");
	}
}

??当有用户访问index.jsp页面时,输出结果:

filter1 start…
filter2 start…
index.jsp
filter2 end…
filter1 end…

4.3 过滤器的应用场景

  • 1、执行目标资源之前做预处理工作
    ??例如设置编码,这种通常都会放行,只是在目标资源执行之前做一些准备工作。几乎是所有的Sevlet中,都需要写request.setCharacterEndoing()可以把它放入到
    一个 Filter 中。
  • 2、实现URL级别的权限认证
    ??例如校验当前用户是否已经登录,或者用户IP是否已经被禁用。
  • 3、在目标资源执行后,做一些后续的特殊处理工作
    ??例如把目标资源输出的数据进行处理。

五、Cookie

5.1 Cookie简介

??网页之间的交互是通过HTTP协议传输数据的,而Http协议是无状态的协议
??无状态的协议是什么意思呢?一旦数据提交完后,浏览器和服务器的连接就会关闭,再次交互的时候需要重新建立新的连接。服务器无法确认用户的信息,于是W3C就提出了:给每一个用户都发一个通行证,无论谁访问的时候都需要携带通行证,这样服务器就可以从通行证上确认用户的信息。通行证就是Cookie。
??Cookie是由服务器创建,然后通过响应发送给客户端的一个键值对。客户端会保存Cookie,并会标注出Cookie的来源(哪个服务器的Cookie) 。当客户端向服务器发出请求时,会把所有这个服务器Cookie包含在请求中发送给服务器,这样服务器就可以识别客户端。

  • 1、Cookie规范
    ?? HTTP的Cookie规范:
  1. Cookie大小上限为4KB;
  2. 一个服务器最多在客户端浏览器上保存20个Cookie;
  3. 一个浏览器最多保存300个Cookie。

??一些浏览器可能对Cookie规范“扩展”了一些,例如每个Cookie的大小为 8KB,最多可保存500个Cookie等。
??不同浏览器之间是不共享 Cookie 的

  • 2、Cookie与HTTP头
    ??Cookie 是通过HTTP请求头和响应头在客户端和服务器端传递的。
    ??在客户端发送给服务器端的请求头,Cookie格式:Cookie: a=A; b=B; c=C 。即多个 Cookie 用分号分开;个 一个 Cookie 对应多个键值对
    ??在服务器端发送给客户端的响应头中,一个Cookie对象一个Set-Cookie,一个键值对:Set-Cookie: a=ASet-Cookie: b=BSet-Cookie: c=C。比如可以用 response.addHeader("Set-Cookie","a=A");发送一个 Cookie。
    ??获取Cookie的方式:
	//HttpServletRequest中获取Cookie
	getCookies()
	//HttpServletResponse中添加Cookie
	addCookie(Cookie cookie)
  • 3、Cookie的覆盖
    ??如果服务器端发送重复的Cookie,那么会覆盖原有的 Cookie,例如客户端的第一个请求服务器端发送的Cookie是Set-Cookie: a=A。第二请求服务器端发送的是Set-Cookie: a=AA,那么客户端只留下一个 Cookie,即a=AA

5.2 Cookie中的属性

  • 1、HttpOnly
    ??如果Cookie中设置了HttpOnly属性,那么通过JS脚本将无法读取到Cookie信息,这样能有效的防止XSS攻击,窃取Cookie内容,这样就增加了Cookie的安全性,即便是这样,也不要将重要信息存入 cookie。
    ??Servlet3.0支持setHttpOnly(boolean httpOnly)
    ??非Servlet3.0的JAVAEE项目也可以通过设置Header进行设置:
    response.setHeader("Set-Cookie", "cookiename=value;Path=/;Domain=domainvalue;Max-Age=seconds;HTTPOnly")
  • 2、max-age
    ??expires/Max-Age 字段为此Cookie超时时间。若设置其值为一个时间,那么当到达此时间后,此Cookie失效。不设置的话默认值是Session,意思是Cookie会和Session一起失效。
    ??当浏览器关闭(不是浏览器标签页,而是整个浏览器) 后,此Cookie失效。
  • 3、secure
    ??设置是否只能通过https来传递此cookie。

5.3 Cookie的生命周期

??所谓生命周期就是Cookie在客户端的有效时间,可以通过setMaxAge(int)来设置Cookie的有效时间。示例:

  • cookie.setMaxAge(-1)
    ??Cookie的maxAge属性的默认值就是-1,表示只在浏览器内存中存活。一旦关闭浏览器窗口,那么Cookie就会消失。
  • cookie.setMaxAge(60*60)
    ??表示Cookie对象可存活1小时 。当生命大于0时,浏览器会把Cookie保存到硬盘上,就算关闭浏览器,就算重启客户端电脑,Cookie也会存活1小时。
  • cookie.setMaxAge(0)
    ??0是一个特殊的值,它表示Cookie被作废。也就是说,如果原来浏览器已经保存了这个Cookie,那么可以通过Cookie的setMaxAge(0) 来删除这个Cookie。无论是在浏览器内存中,还是在客户端硬盘上都会删除这个 Cookie。

5.4 Cookie的路径

??现在有Web应用A,向客户端发送了10个Cookie,这就说明客户端无论访问应用A的哪个Servlet 都会把这10个Cookie包含在请求中!但是也许只有AServlet需要读取请求中的Cookie,而其他Servlet根本就不会获取请求中的Cookie。这说明客户端浏览器有时发送这些Cookie是多余的。
??可以通过设置Cookie的path来指定浏览器,在访问什么样的路径时,包含什么样的Cookie。

  • 1、Cookie路径与请求路径的关系
    ??客户端浏览器保存的3个Cookie的路径示例:

a: /cookietest;
b: /cookietest/servlet;
c: /cookietest/jsp;

??浏览器请求的URL:

A: http://localhost:8080/cookietest/AServlet;
B: http://localhost:8080/cookietest/servlet/BServlet;
C: http://localhost:8080/cookietest/jsp/a.jsp;

??请求A时,会在请求中包含 a;请求B时,会在请求中包含 a、b;请求 C 时,会在请求中包含 a、c.
??请求路径如果包含了Cookie路径,那么会在请求中包含这个Cookie
??1)A请求的URL包含了“/cookietest”,所以会在请求中包含路径为“/cookietest”的 Cookie。
??2)B请求的URL包含了“/cookietest”,以及“/cookietest/servlet”,所以请求中包含路径为“/cookietest”和“/cookietest/servlet”两个Cookie。
??3)C请求的URL包含了“/cookietest”,以及“/cookietest/jsp”,所以请求中包含路径为“/cookietest”和“/cookietest/jsp”两个Cookie。

  • 2、设置Cookie的路径
    ??设置Cookie的路径需要使用setPath()方法,例如:
    cookie.setPath("/cookietest/servlet");
    ??默认路径是访问资源的上一级路径
    ??如果没有设置Cookie的路径,那么Cookie路径的默认值当前访问资源所在路径( 上一级),例如:
  1. 访问http://localhost:8080/cookietest/AServlet时,添加的Cookie默认路径为/cookietest;
  2. 访问 http://localhost:8080/cookietest/servlet/BServlet时,添加的Cookie默认路径为
    /cookietest/servlet;
  3. 访问http://localhost:8080/cookietest/jsp/BServlet时,添加的Cookie默认路径为/cookietest/jsp。

5.5 Cookie的domain

??Cookie的domain属性,可以让网站中二级域共享Cookie 。
??比如一些网址:

http://www.baidu.com
http://zhidao.baidu.com
http://news.baidu.com
http://tieba.baidu.com

??假如希望在这些主机之间共享Cookie(例如在www.baidu.com中响应的Cookie,可以在news.baidu.com请求中包含)。这是主机的问题,即域名的问题。处理这一问题其实很简单,只需要下面两步:

  • 1、设置Cookie的path为"/",示例:cookie.setPath("/")
  • 2、设置Cookie的domain为".baidu.com",示例:cookie.setDomain(".baidu.com")

5.6 Cookie保存中文

??Cookie的name和value都不能使用中文,如果希望在Cookie中使用中文,那么需要先对中文进行URL编码,然后把编码后的字符串放到Cookie中。
??向客户端响应中添加 Cookie:

	String name = URLEncoder.encode("姓名", "UTF-8");
	String value = URLEncoder.encode("张三", "UTF-8");
	Cookie c = new Cookie(name, value);
	c.setMaxAge(3600);
	response.addCookie(c);

??从客户端请求中获取 Cookie:

	response.setContentType("text/html;charset=utf-8");
	Cookie[] cs = request.getCookies();
	if(cs != null) {
		for(Cookie c : cs) {
			String name = URLDecoder.decode(c.getName(), "UTF-8");
			String value = URLDecoder.decode(c.getValue(),"UTF-8");
			String s = name + ": " + value + "<br/>";
			response.getWriter().print(s);
		}
	}

六、Session

6.1 HttpSession概述

??javax.servlet.http.HttpSession 接口表示一个会话,可以把一个会话内需要共享的数据保存到HttSession对象中。
??会话:会话范围是某个用户从首次访问服务器开始,到该用户关闭浏览器结束。
??获取HttpSession对象的方式:

	//如果当前会话已经有了session对象,那么直接返回
	//如果当前会话还不存在会话,那么创建session并返回
	HttpSession request.getSesssion()
	//当参数为true时,与requeset.getSession()相同
	//如果参数为false,那么如果当前会话中存在session则返回,不存在返回null
	HttpSession request.getSession(boolean)

??HttpSession和HttpServletRequest 、ServletContext相似,都是域对象。
??HttpServletRequest:一个请求创建一个request对象,所以在同一个请求中可以共 享request,例如一个请求从AServlet转发到BServlet,那么AServlet和BServlet可以共享request域中的数据。
??ServletContext:一个应用只创建一个ServletContext对象,所以在ServletContext中的数据可以在整个应用中共享,只要不关闭服务器,那么ServletContext 中的数据就可以共享。
??HttpSession:一个会话创建一个HttpSession对象,同一会话中的多个请求中可以共享Session中的数据。

??Session的域方法:

	//用来存储一个对象,也可以称之为存储一个域属性
	void setAttribute(String name, Object value)
	//用来获取session中的数据
	Object getAttribute(String name)
	//用来移除HttpSession中的域属性,如果参数name指定的域属性
	//不存在,那么本方法什么都不做
	void removeAttribute(String name)
	//获取所有域属性的名称
	Enumeration getAttributeNames()

6.2 Session的实现原理

??当首次使用 session 时,服务器端要创建Session,Session是保存在服务器端,而给客户端的Session的id(一个Cookie中保存了sessionId) 。客户端带走的是 sessionId,而数据 是保存在 session 中。
??当客户端再次访问服务器时,在请求中会带上sessionId,而服务器会通过 sessionId找到对应的Session,而无需再创建新的 session。
??如果Servlet中没有创建Session,那么响应头中就不会有sessionid的Cookie。但是所有的JSP页面中,Session可以直接使用,也就是说每个JSP页面都会自动获取Session,无论是否使用,访问JSP页面一定会带回来一个sessionid。

6.3 Session的失效时间

??Session 保存在服务器,而sessionId通过Cookie发送给客户端,但这个Cookie的生命不是-1,即只在浏览器内存中存在,也就是说如果用户关闭了浏览器,那么这个Cookie就丢失了。
??当用户再次打开浏览器访问服务器时,就不会有 sessionId 发送给服务器,那么服务器会认为你没有session,所以服务器会创建一个session,并在响应中把sessionId中的Cookie中发送给客户端。
??当一个 session 长时间没人使用的话,服务器会把session删除。在这个时长在Tomcat中,配置是30分钟,可以在${CATALANA}/conf/web.xml找到这个配置,当然也可以在自己的web.xml中覆盖这个配置:

<session-config>
	<!-- 设置session失效时间,单位是min -->
	<session-timeout>30</session-timeout>
</session-config>

6.4 Session其他常用API

	//获取sessionId,32 位长,16进制字符串 ,使用UUID生成随机字符串
	String getId()
	//获取Session可以的最大不活动时间(秒) ,默认为30分钟。当Session在30分钟
	//内没有使用,那么Tomcat会在Session池中移除这个session
	int getMaxInactiveInterval()
	//设置Session允许的最大不活动时间(秒),如果设置为1 秒,那么只要Session
	//在1秒内不被使用,那么Session就会被移除
	void setMaxInactiveInterval(int interval)
	//返回Session的创建时间,返回值为当前时间的毫秒值
	long getCreationTime()
	//返回Session的最后活动时间,返回值为当前时间的毫秒值
	long getLastAccessedTime()
	//让Session失效.调用这个方法会让Session失效,当Session失效后,客户端再次
	//请求,服务器会给客户端创建一个新的Session,并在响应中给客户端新session的
	//sessionId;(比如退出按钮退出时,让Session失效)
	void invalidate() 
	//查看Session是否为新。当客户端第一次请求时,服务器为客户端创建Session,
	//但这时服务器还没有响应客户端,也就是还没有把sessionId响应给客户端时,
	//这时Session的状态为新。 
	//比如可以用request.getSession().isNew(),判断这个Session是新的还是旧的
	boolean isNew()

6.5 设置过期时间的方式

??一般Web系统都需要控制session自动失效的时间,从而控制用户访问系统超时。设置Session失效有以下三种方式:

  • 1、在主页面或者公共页面中配置
    ??示例:session.setMaxInactiveInterval(3600);,参数单位是秒,即在没有活动1小时后,Session将失效。注意:这里Session设置的时间是根据服务器来计算的,而不是客户端。所以如果是在调试程序,应该是修改服务器端时间来测试,而不是客户端。
  • 2、较通用的设置Session失效时间的方法是在项目的web.xml中设置。
  • 3、直接在应用服务器中设置,例如:若容器是tomcat,可以在tomcat目录下conf/web.xml中设置。

??如果上述三个地方如果都设置了,优先级从高到低:(1)>(2)>(3)。

6.6 Session和Cookie的区别

SessionCookie
存储方式可以存储任何类型的数据只能存储字符串,如果要存储非ASCII字符串还要对其编码
隐私安全存储在服务器,安全性高存储在浏览器,安全性低
有效期保存在服务器中,设置maxInactiveInterval属性值来确定Session的有效期。并且Session依赖于名为JSESSIONID的Cookie,该Cookie默认的maxAge属性为-1。如果关闭了浏览器,该Session虽然没有从服务器中消亡,但也就失效了保存在硬盘中,只需要设置maxAge属性为比较大的正整数,即使关闭浏览器,Cookie还是存在的
对服务器的负担保存在服务器的,每个用户都会产生一个Session,如果是并发访问的用户非常多,是不能使用Session的,Session会消耗大量的内存保存在客户端的,不占用服务器的资源
是否能跨域名只在当前的域名内有效,不可跨域名可以设置domain属性来实现跨域名
大小限制没有大小限制,和服务器的内存大小有关有大小限制,以及浏览器在存Cookie的个数也有限制
  系统运维 最新文章
配置小型公司网络WLAN基本业务(AC通过三层
如何在交付运维过程中建立风险底线意识,提
快速传输大文件,怎么通过网络传大文件给对
从游戏服务端角度分析移动同步(状态同步)
MySQL使用MyCat实现分库分表
如何用DWDM射频光纤技术实现200公里外的站点
国内顺畅下载k8s.gcr.io的镜像
自动化测试appium
ctfshow ssrf
Linux操作系统学习之实用指令(Centos7/8均
上一篇文章      下一篇文章      查看所有文章
加:2022-04-18 18:25:00  更:2022-04-18 18:29:14 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/15 21:01:30-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码