JSP九大内置对象(隐式对象)

一、JSP内置对象简介

JSP内置对象是Web容器创建的一组对象,不使用new关键字就可以使用的内置对象。
开发者可以直接使用它们而不用显式声明。JSP隐式对象也被称为预定义变量。

1、JSP内置对象概览

JSP中的9个内置对象将由容器为用户进行实例化,用户直接使用即可。

JSP中的9个内置对象

以上内置对象中常用的是pageContext、request、response、session、application,掌握了这5个,即可进行程序开发。

二、四种属性范围

在JSP中提供了4种属性的保存范围。所谓的属性保存范围,指的就是一个内置对象,可以在多个页面中保存并继续使用。

  • page:只在一个页面中保存属性,跳转之后无效。
  • request:只在一次请求中保存,服务器端跳转后依然有效,超链接跳转无效。
  • session:再一次会话范围中,无论何种跳转都可以使用,但是新开浏览器无法使用。
  • application:在整个服务器上保存,所有用户(会话)都可以使用。如果服务器重启,所有设置属性将全部消失。

以上的四个内置对象都支持下表所示的操作方法:

属性操作方法
1、page属性范围(pageContext)
page属性范围
2、request属性范围
request属性传递

关于request属性范围的理解
request表示客户端的请求。正常情况下,一次请求服务器只会给予一次回应,那么这时如果是服务器端跳转,请求的地址栏没有改变,所以也就相当于回应了一次;而如果地址栏改变了,就相当于是发出了第二次请求,则地一次请求的内容肯定就已经消失了,所以就无法取得。
request属性范围一般在MVC设计模式上应用较多。

3、session属性范围
session属性范围

每一个新的浏览器连接上服务器后就是一个新的session。

4、application属性范围
application属性范围

application范围的属性设置过多会影响服务器性能。

5、深入探究page属性范围

从javax.servlet.jsp.PageContext类中可以发现,有如下一种设置属性的方法:

设置属性的方法

与之前所使用的setAttribute()方法不同,该方法中用一个int的整型变量,此变量可以指定一个属性的保存范围。

4种属性范围常量

由上可以发现,pageContext对象可以设置任意范围的属性。

三、JSP内置对象详解

1、request对象

request内置对象是使用最多的一个对象,每当客户端请求一个JSP页面时,JSP引擎就会制造一个新的request对象来代表这个请求。
其主要作用是接收客户端发送而来的请求信息,如请求的参数、发送的头信息等都属于客户端发来的信息。
request对象提供了一系列方法来获取HTTP头信息,cookies,HTTP方法等等。
注意:当服务器端想得到客户端信息时就会使用request对象来完成。

客户端的请求信息被封装在request对象中,通过他才能了解到客户端需求,然后做出响应。request对象具有请求域,即完成客户端的请求之前,该对象一直有效。

request是javax.servlet.http.HttpServletRequest接口的实例化对象,表示此对象主要是应用在HTTP协议上。
该接口定义如下:

public interface HttpServletRequest extends ServletRequest

从定义上可以发现,HttpServletRequest是ServletRequest接口的子接口。

在Web开发中,交互性是重要的特点,所以request对象在实际开发中使用的较多。

常用的方法如下:

String getParameter(String name);    //返回name指定参数的参数值

String[] getParameterValues(String name);    //返回包含参数name的所有值的属性

void setAttribute(String, Object);    //设置此请求中的属性

Object getAttribute(String name);    //返回指定属性的属性值

String getContentType();    //得到请求提的MIME类型

String getProtocol();    //返回请求用的协议类型及版本号

String getServerName();    //返回请求的服务器主机名

int getServerPort();    //返回服务器接受此请求所用的端口号

String getCharacterEncoding();    //返回字符编码方式

void setCharacterEncoding();    //设置请求的字符编码方式

int getContentLength();    //返回请求体的长度(以字节数)

String getRemoteAddr();    //返回发送此请求的客户端IP地址

String getRealPath();    //返回一虚拟路径的真实路径

String request.getContentPath();    //返回上下文路径(虚拟路径)
request内置对象的常用方法

1.1、乱码解决

在Web开发中使用request接收请求参数是最常见的操作,但是,在进行参数提交时也会存在一些中文的乱码问题。可以直接通过setCharacterEncoding()方法设置一个统一的编码即可。

现阶段的开发中最好每一个JSP页面都写上编码设置。

1.2、接收请求参数

单一的参数都可以使用getParameter()接收,而一组参数要用getParameterValues()接收。
在表单控件中,像文本框(text)、单选按钮(radio)、密码框(password)、隐藏域(hidden)等,一般都会使用getParameter()方法进行接收,因为这些控件在使用时参数的名称都只有一个不会重复;而像复选框(checked),一般参数的名称都是重复的,是一组参数,所以只能使用getParameterValues()接收,如果不小心使用了getParameter()方法,则只会接收第一个选中的内容。

在request内置对象中还有一个灵活的方法就是getParameterNames(),此方法可以返回所有请求参数的名称,但是此方法返回值的类型是Enumeration,所以需要使用hasMoreElements()方法判断是否有内容以及使用nextElement()方法取出内容。

1.3、显示全部的头信息

Java的Web开发使用的是HTTP协议,主要操作就是基于请求和回应,但是在请求和回应的同时也会包含一些其他的信息(如客户端的IP、Cookie、语言等),那么这些信息就称为头信息。

取得头信息的名称,可以再直接通过request内置对象的getHeaderNames()方法;而要想取出每一个头信息的的内容,则需要使用getHeader()方法。

1.4、角色验证

如果现在某些JSP页面需要输入特定的管理员的账号才能访问,那么就需要进行角色验证,而要进行角色验证就必须使用request内置对象中的isUserInRole()方法完成。

2、response对象

当服务器创建request对象时会同时创建用于响应这个客户端的response对象。
response对象的主要作用是对客户端的请求进行回应,将Web服务器处理后的结果发回给客户端。
通过这个对象,开发者们可以添加新的cookies,时间戳,HTTP状态码等等。
response对象属于javax.servlet.http.HttpServletResponse接口的实例。

HttpServletResponse接口的定义如下:

public interface HttpServletResponse extends ServletResponse

常用方法如下:

String getCharacterEncoding();    //  返回响应用的是何种字符编码

void setContentType(String type);    //设置响应的MIME类型

PrintWriter getWriter();    //返回可以向客户端输出字符的一个对象(输出流对象),打印输出提前于内置的out对象。

sendRedirect(java.lang.String location) throws IOException;    //重新定向客户端的请求

void addCookie(Cookie cookie);    //向客户端增加Cookie

void setHeader(String name, String value);    //设置回应的头信息

2.1、设置头信息

客户端在进行请求时会发送许多额外的信息,这些就是头信息。服务器端也可以根据需要向客户端设置头信息,在所有头信息的设置中,定时刷新页面的头信息使用的最多,直接使用setHeader()方法,将头信息名称设置为refresh,同时制定刷新的时间。

设置指定时间跳转到指定页面
response.setHeader("refresh", "3;URL=hello.html");
使用HTML完成定时跳转的功能
<meta http-equiv="refresh" content="3;url=hello.htm">

定时跳转属于客户端跳转

2.2、页面跳转

在JSP中除了可以通过头信息的方式完成跳转外,还可以使用response对象的sendRedirect()方法直接完成页面跳转。

response跳转属于客户端跳转。

请求转发与请求重定向的区别:

  • 请求重定向:客户端行为,response.sendRedirect(),从本质上讲等同于再次请求,前一次的请求对象不会保存,地址栏的URL地址会改变。
  • 请求转发:服务器行为,request.getRequestDispatcher().forward(req, resp);是一次请求,转发后请求对象会保存,地址栏的URL地址不会改变。

客户端跳转和服务器端跳转(<jsp:forward>)有什么区别?
服务器端跳转后地址栏的信息不会有任何的改变,客户端跳转后地址栏会改变,变为跳转之后的页面地址。
在使用request属性范围时,只有服务器端跳转才能够将request属性保存到跳转页;如果客户端跳转,则无法进行属性的传递。
服务器端跳转,在执行到跳转语句时会立刻进行脚砖;使用的是客户端跳转,则是在整个页面执行完后才执行跳转。

由于两种跳转存在这样的差异,所以在代码开发中,尤其在使用了JDBC的操作中,一定要在<jsp:forward>语句执行之前关闭数据库的链接,否则将再也无法关闭。

如果使用了<jsp:forward>,可以通过<jsp:forward>方便的进行参数传递;而如果使用了response.sendRedirect()传递参数,则只能通过地址重写的方式完成。

2.3、操作Cookie

Cookie是浏览器所提供的一种技术,能够让服务器端将一些只需保存在客户端或者客户端进行处理的数据,放在客户端本身使用的计算机中,不需通过网络的传输,因而提高了网页处理的效率,而且也能够减少服务器端的负载。但是由于Cookie是服务器端保存在客户端的信息,所以其安全性也是很差的。

使用Cookie保存信息可以减少客户端的部分操作。(比如记住密码的功能)

在JSP总能专门提供了javax.servlet.http.Cookie的操作类,

Cookie定义的常用方法

所有的Cookie是由服务器端设置到客户端上去的,所以要向客户端增加Cookie,必须使用response对象的以下方法:

设置Cookie

如果想要获取向客户端设置的Cookie,可以通过request对象完成,使用request的以下方法:

取得Cookie

客户端每次向服务器端发送请求时都会将之前设置Cookie随着头信息一起发送到服务器上,所以这时使用request对象的getCookies()方法就可以取出全部设置的Cookie。

实际上,之前设置的Cookie并没有真正的保存在客户端上,在重新启动浏览器后,之前设置的Cookie就全部不存在了,则再使用getCookies()方法取得的就是null。如果想要真正的将Cookie保存在客户端上,就必须设置Cookie 的保存时间,使用setMaxAge()方法即可。
虽然Cookie中可以保存信息,但是并不能无限制的保存,一般一个客户端最多只能保存300个Cookie,所以数据量太大时将无法使用Cookie。

3、session对象

3.1.、什么是session

session表示客户端与服务器的一次会话。Web中的session指定是用户在浏览某个网站时,从进入网站到浏览器关闭所经过的这段时间,也就是用户浏览这个网站所花费的时间。从上述定义中可以看到,session实际上是一个时间上的概念。
在服务器的内存中保存着不同用户的session。

session对象在第一个JSP页面被装载时自动创建,完成会话期管理。
从一个客户打开浏览器并连接到服务器开始,到客户关闭浏览器离开这个浏览器结束,称此为一个会话。
当一个客户访问一个服务器时,可能会在服务器的几个页面之间切换,服务器应当通过某种办法知道这是一个客户,就需要session对象。

实际上在开发中session对象最主要的用处就是完成用户的登录(login)、注销(logout)等常见功能,每一个session对象都表示不同的访问用户。
session对象是javax.servlet.http.HttpSession接口的实例化对象,所以session只能应用在HTTP协议中。

session对象的常用方法如下:

long getCreationTime();    //返回session创建时间

public String getId();    //返回session创建时JSP引擎为它设的唯一ID号

public Object setAttribute(String name, Object value);    //使用指定名称将对象绑定到此会话

public Object getAttribute(String name);    //返回与此会话中的指定名称绑定在一起的对象,如果没有对象绑定在该名称下,则返回null

String[] getValueNames();    //返回一个包含此session的所有可用属性的数组

int getMaxInactiveInterval();    //返回两次请求间隔多长时间此session被取消(单位秒)

public long getLastAccessedTime();    //取得session的最后一次操作时间

public boolean isNew();    //判断是否是新的session(新用户)

public void invalidate();    //让session失效

在HttpSession接口中最重要的部分还是属性操作,主要是可以完成用户登录的合法性的验证。

3.2、session的生命周期

创建:当客户端第一次访问某个JSP或者Servlet的时候,服务器会为当前会话创建一个sessionID,每次客户端向服务端发送请求时,都会讲此session携带过去,服务端会对此sessionID进行校验。
活动:某次会话当中通过超链接打开的新页面属于同一次会话。只要当前会话页面没有全部关闭,重新打开的新的浏览器窗口访问统一项目资源时属于同一次会话。除非本次会话的所有页面都关闭后再重新访问某个JSP或者Servlet将会创建新的会话。

注意:原有会话还存在,只是这个旧的sessionID任然存在于服务端,只不过再也没有客户端会携带它然后交予服务端校验。

销毁:session的销毁只有三种方式。
(1)调用了session.invalidate()方法。
(2)session过期(超时)
(3)服务器重新启动

  • Tomcat的默认session超时时间为30分钟
  • 设置session超时有两种方式:
    1、session.setMaxInactiveInterval(time); //单位秒
    2、在web.xml配置
<session-config>
        //单位是分钟
        <session-timeout> 10 </session-timeout>
</session-config>

3.3、取得session ID

当一个用户连接到服务器后,服务器会自动为此session分配一个不会重复的Session Id,服务器依靠这些不同的session id 来区分每一个不同的用户,在Web中可以使用HttpSession接口中的getId()方法取得这些编号。

在使用session时必须注意一点,对于每一个已经连接到服务器上的用户,如果重新启动服务器,则这些用户再次发出请求实际上表示的都是一个新连接的用户,服务器会为每个用户重新分配新的session id。

3.4、登录及注销

在各系中几乎都存在用户登录验证及注销的功能,此功能完可以使用session实现。
具体思路是:当用户登录成功后,设置一个session范围的属性,然后在其他需要验证的界面中判断是否存在此session范围的属性,如果存在,则表示已经是正常登陆过的合法用户;如果不存在,则给出提示,并跳转会登录页提示用户重新登录,用户登陆后可以进行注销的操作。

完成该功能,如下要求:

程序列表

3.5、判断新用户

在session对象中可以使用inNew()方法判断一个用户是否是第一次访问页面。

3.6、取得用户的操作时间

在session对象中,可以通过getCreationTime()方法取得一个session的创建时间,也可以使用getLastAccessedTime()方法取得一个session的最后一次操作时间。

关于getCreationTime()方法
当用户第一次连接到服务器上时,服务器就会自动保留一个session的创建时间。

4、application对象

这个对象在JSP页面的整个生命周期中都代表着这个JSP页面。这个对象在JSP页面初始化时被创建,随着jspDestroy()方法的调用而被移除。
通过向application中添加属性,则所有组成您web应用的JSP文件都能访问到这些属性。

application对象实现了用户间数据的共享,可存放全局变量。
application对象开始于服务器的启动,结束予服务器的关闭。
在用户的前后连接或不同用户之间的连接中,可以对application对象的同一属性进行操作。
在任何地方对application对象属性的操作,都将影响到其他用户对此的访问。
服务器的启动和关闭决定了application对象的生命。
application对象是javax.servlet.ServletContext接口的实例化对象。

application对象常用方法如下:

public void setAttribute(String name, Object value);    //使用指定名称将对象绑定到此对话

public Object getAttribute(String name);    //返回与此会话中的指定名称绑定在一起的对象,如果没有对象绑定在该名称下,则返回null

Enumeration getAttributeName();    //返回所有可用属性名的枚举

String setServerInfo();    //返回JSP(Servlet)引擎名及版本号

String getRealPath(String path);    //得到虚拟路径对应的绝对路径

public String getContextPath();    //取得当前的虚拟路径名称

在application中最重要的部分也是属性操作。

4.1、取得虚拟目录对应的绝对路径

在Toncat中配置虚拟目录:

 <Context path="/mldn" docBase="D:\mldnwebdome" />

以上表示的是在浏览器中使用“”/mldn“”访问 "D:\mldnwebdome"目录,这时如果希望在Web开发中取得docBase对应的真实路径,就需要使用application对象中的getRealPath()方法来完成。

对于application对象而言,在Web中也可以使用getServletContext()方法代替。在开发中尽量使用该方法。

5、Web安全性及config对象

5.1、Web安全性

在Web目录中必须存在一个WEB_INF文件夹。在JavaEES的标准中,Web目录中的WEB_INF是必须存在的,而且此文件夹的安全性是最高的,在各个程序的开发中,基本上都将一些配置信息保存在此文件夹中。
在定义WEB_INF目录时一定要注意大小写的问题,这里的字母都必须是大写。

5.2、config对象

config对象是在一个Servlet初始化时,JSP引擎向它传递信息用的,此信息包括Servlet初始化时所用到的参数(通过属性名和属性值)以及服务器的有关信息(通过传递一个ServletContext对象)。
config是对象是javax.servlet.ServletConfig接口的实例化对象,主要功能是取得一些初始化的配置信息。

Config对象的常用方法如下:

ServletContext getServletContext();    //返回含有服务器相关信息的ServletContext对象

String getInitParameter(String name);    //返回初始化参数的值

Enumeration getInitParameterNames(String name);    //返回Servlet初始化所需所有参数的枚举

所有初始化参数一定要在web.xml中配置,即如果一个JSP文件想要通过初始化参数取得一些信息,则一定要在web.xml中完成映射。

6、out对象

out是javax.servlet.jsp.JspWriter类的实例化对象,主要功能就是完成页面的输出操作,使用println()或print()方法输出,但是从实际的开发来看,直接使用out对象的几率较小,一般使用表达式完成输出的操作。
用来在response对象中写入内容。
最初的JspWriter类对象根据页面是否有缓存来进行不同的实例化操作。可以在page指令中使用buffered='false'属性来轻松关闭缓存。
JspWriter类包含了大部分java.io.PrintWriter类中的方法。不过,JspWriter新增了一些专为处理缓存而设计的方法。还有就是,JspWriter类会抛出IOExceptions异常,而PrintWriter不会。

常用方法如下:

public void println();   //向客户端打印字符串

public void clear();   //清除缓存区内容,如果在flush之后调用会抛出异常。

public void clearBuffer();    //清除缓存区的内容,如果在flush之后调用不会抛出异常

public void flush();      //将缓存区内容输出到客户端

public int getBufferSize();      //返回缓存区的大小以字节数的大小,如不设缓存区则为0

public int getRemaining();    //返回缓存区还剩余多少可用

public boolean isAutoFlush();    //返回缓存区满时,是自动清空还是抛出异常

public void close();    //关闭输出流
7、paget对象

page对象就是指向当前JSP页面本身,有点像类中的this指针,它是java.lang.Object类的实例。

8、pageContext对象

pageContext对象提供了对JSP页面内所有的对象及名字空间的访问。
pageContext对象可以访问到本页所在的session,也可以取本页面所在的application的某一属性值。
pageContext对象相当于页面中所有功能的集大成者。
pageContext对象的本类名也叫pageContext。
pageContext对象是javax.servlet.jsp.PageContext类的实例,主要表示一个JSP页面的上下文。
这个对象主要用来访问页面信息,同时过滤掉大部分实现细节。
这个对象存储了request对象和response对象的引用。application对象,config对象,session对象,out对象可以通过访问这个对象的属性来导出。
pageContext对象也包含了传给JSP页面的指令信息,包括缓存信息,ErrorPage URL,页面scope等。
PageContext类定义了一些字段,包括PAGE_SCOPE,REQUEST_SCOPE,SESSION_SCOPE, APPLICATION_SCOPE。它也提供了40余种方法,有一半继承自javax.servlet.jsp.JspContext 类。
其中一个重要的方法就是removeArribute(),它可接受一个或两个参数。比如,pageContext.removeArribute("attrName")移除四个scope中相关属性。
但是下面这种方法只移除特定scope中的相关属性:

pageContext.removeAttribute("attrName", PAGE_SCOPE);

pageContext对象的常用方法:

JspWriter getOut();    //返回当前客户端响应被使用的JspWriter流(out)

HttpSession getSession();    //返回当前页中的HttpSession对象(session)

Object getPage();    //返回当前页的Object对象(page)

ServletRequest getRequest();    //返回当前页的ServletRequest对象(request)

ServletResponse getResponse();    //返回当前页的ServletResponse对象(response)

ServletConfig getServletConfig();    //返回当前页的ServletConfig对象(config)

void setAttribute(String name, Object attribute);    //设置属性及属性值

Object getAttribute(String name, int scope);    //在指定范围内取属性的值

int getAttributeScope(String name);    //返回某属性的作用范围

void forward(String relativeUrlPath);    //使当前页面跳转到另一页面

void include(String relativeUrlPath);    //在当前位置包含另一文件
8、Exception对象

exception对象是一个异常对象,当一个页面在运行过程中发生了异常,就产生这个对象。如果一个JSP页面要应用此对象,就必须把isErrorPage设置为true,否则无法编译。
exception对象是java.lang.Throwable的对象。

exception对象的常用方法如下:

String getMessage();    //返回描述异常的信息

String toString();    //返回关于异常的简短描述信息

void printStackTrace();    //显示异常及其栈轨迹

Throwable FIllInStackTrace();    //重写异常的执行栈轨迹

不难发现,request、response、config、application、<jsp:include>、<jsp:forward>等操作实际上都可以在pageContext对象中完成。

四、实战开发

使用JSP+JDBC完成一个用户的登录程序,登录成功后可以使用session进行用户的登录验证,用户根据需要也可以直接进行系统的退出操作。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,185评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,445评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,684评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,564评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,681评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,874评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,025评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,761评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,217评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,545评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,694评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,351评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,988评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,778评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,007评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,427评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,580评论 2 349

推荐阅读更多精彩内容