JSP基础知识总结通俗易懂

4次阅读

共计 13165 个字符,预计需要花费 33 分钟才能阅读完成。

JSP 第一篇:

概述、原理、周期、指令、行为、内置对象、JavaBean

(一) JSP 概述以及简单使用

什么是 JSP?

JSP 全名为 Java Server Pages,java 服务器页面。JSP 是一种基于文本的程序,其特点就是 HTML

和 Java 代码共同存在!

为什么需要 JSP?

JSP 是为了简化 Servlet 的工作出现的替代品,Servlet 输出 HTML 非常困难,JSP 就是替代 Servlet 输出 HTML 的

JSP 还有必要学吗

在 MVC 中,JSP 属于展示层,但是 JSP 却又可以写一定的业务,甚至跑去做数据层的事情,这样开发中就会变得无比混乱,也增加了开发的困难程度,所以将展示层与业务层分开就成为了主流,也就是我们说的前后端分离,但是事无绝对,确实一些比较老的项目仍然在跑 jsp,不管你会不会写,你总得碰到能看懂吧,如果已经接近找工作,确实还是以比较流行的技术学习比较好,但是若作为学生,时间还是比较富裕的,很多本科也必然都会讲,学习一下也是挺好的,况且 JSP 与 Servlet 也是息息相关的,我认为,学它就是为了知道为什么以后会用别的技术代替它(狗头保命),废话有点多了,还是有一点需要的朋友可以简单看一看,希望给你能有一点帮助

(二) JSP 的工作原理

Tomcat 访问任何的资源都是在访问 Servlet!,当然了,JSP 也不例外!JSP 本身就是一种 Servlet。为什么说 JSP 本身就是一种 Servlet 呢?

其实 JSP 在第一次被访问的时候会被编译为 HttpJspPage 类(该类是 HttpServlet 的一个子类)

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title> 简单使用 JSP</title>
</head>
<body>
<%
    String s = "HelloWorld";
    out.println(s);
%>

编译过程是这样子的:

浏览器第一次请求 1.jsp 时,Tomcat 会将 1.jsp 转化成 1_jsp.java 这么一个类,并将该文件编译成 class 文件。编译完毕后再运行 class 文件来响应浏览器的请求。

以后访问 1.jsp 就不再重新编译 jsp 文件了,直接调用 class 文件来响应浏览器。当然了,如果 Tomcat 检测到 JSP 页面改动了的话,会重新编译的。

既然 JSP 是一个 Servlet,那 JSP 页面中的 HTML 排版标签是怎么样被发送到浏览器的?我们来看下上面 1_jsp.java 的源码就知道了。原来就是用 write()出去的罢了。说到底,JSP 就是封装了 Servlet 的 java 程序罢了

out.write("\r\n");
out.write("\r\n");
out.write("<html>\r\n");
out.write("<head>\r\n");
out.write("<title> 简单使用 JSP</title>\r\n");
out.write("</head>\r\n");
out.write("<body>\r\n")

有人可能也会问:JSP 页面的代码服务器是怎么执行的?再看回 1_jsp.java 文件,java 代码就直接在类中的 service()中

String s = "HelloWorld";
out.println(s);

(三) 声明周期

JSP 也是 Servlet,运行时只有一个实例,JSP 初始化和销毁时也会调用 Servlet 的 init()和 destroy()方法。另外,JSP 还有自己初始化和销毁的方法

 public void _jspInit() {_el_expressionfactory = _jspxFactory.getJspApplicationContext(getServletConfig().getServletContext()).getExpressionFactory();
    _jsp_instancemanager = org.apache.jasper.runtime.InstanceManagerFactory.getInstanceManager(getServletConfig());
  }

  public void _jspDestroy() {}

(四) 指令

<%@ page contentType="text/html;charset=UTF-8" language="java" %>

作用:用于配置 JSP 页面,导入资源文件

格式:<%@ 指令名称 属性名 1 = 属性值 1 属性名 2 = 属性值 2 … %>

  • contentType:相当于 response.setContentType()

    • 设置响应体的 mime 类型以及字符集
    • 设置当前 jsp 页面的编码(只能是高级的 IDE 才能生效,如果使用低级工具,则需要设置 pageEncoding 属性设置当前页面的字符集)

      pageEncoding="characterSet | ISO-8859-1"
  • import:导包

    import="{package.class | package.*}, ..."
  • errorPage:当前页面发生异常后,会自动跳转到指定的错误页面

    // 主页面
    <%@ page contentType="text/html;charset=UTF-8" language="java" errorPage="error.jsp" %>
    
    // 错误后转到的页面
    <%@ page contentType="text/html;charset=UTF-8" language="java" isErrorPage="true"   %>
    
    我们发现地址栏是没有变化的,所以属于是服务器跳转。以上的做法是单个页面设置的,如果我会有很多错误(JSP 多的情况下,错误就会多),单个设置太麻烦了!我们可以在 web.xml 文件中全局设置错误页,只要发生了 404 错误或者空指针异常的错误都会跳转到 error.jsp 页面上
    
    <error-page>
        <error-code>404</error-code>
        <location>/error.jsp</location>
    </error-page>
    
    <error-page>
        <exception-type>java.lang.NullPointerException</exception-type>
        <location>/error.jsp</location>
    </error-page>
  • isErrorPage:标识当前也是是否是错误页面

    • true:是,可以使用内置对象 exception
    • false:否。默认值。不可以使用内置对象 exception

(五) 行为

JSP 行为(JSP Actions)是一组 JSP 内置的标签,只书写少量的标记代码就能够使用 JSP 提供丰富的功能,JSP 行为是对常用的 JSP 功能的抽象和封装。

JSP 内置的标签称之为 JSP 行为,是为了能够和 JSTL 标签区分开来。(叫做 JSP 标签也行)

(1) include 行为

上面已经提及到了,include 指令是静态包含,include 行为是动态包含。其实 include 行为就是封装了 request.getRequestDispatcher(String url).include(request,response)

include 行为语法是这样的:

<jsp:include page=""/>

静态包含:<%@ include file="被包含页面"%>
动态包含:<jsp:include page="被包含页面" flush="true">
      <%@ page contentType="text/html;charset=UTF-8" language="java" %>
    <html>
    <head>
        <title> 包含页头和页尾进来 </title>
    </head>
    <body>
        <jsp:include page="head.jsp"/>
        <jsp:include page="foot.jsp"/>
    </body>

jsp 行为包含文件就是先编译被包含的页面,再将页面的结果写入到包含的页面中(1.jsp)

当然了,现在有静态包含和动态包含,使用哪一个更好呢?答案是:动态包含。

动态包含可以向被包含的页面传递参数(用处不大),并且是分别处理包含页面的(将被包含页面编译后得出的结果再写进包含页面)

【如果有相同名称的参数,使用静态包含就会报错!】!

(2) Param 行为

当使用 <jsp:include> 和 <jsp:forward> 行为引入或将请求转发给其它资源时,可以使用 <jsp:param> 行为向这个资源传递参数

(3) forward 行为

在 Servlet 中我们使用 request.getRequestDispatcher(String url).forward(request,response)进行跳转。其实 forward 行为就是对其封装!

我们来看一下 forward 的语法

 <jsp:forward page=""/>
    <%@ page contentType="text/html;charset=UTF-8" language="java" %>
    <html>
    <head>
        <title> 访问 1.jsp 就跳转到 head.jsp</title>
    </head>
    <body>
    
    <jsp:forward page="head.jsp"/>
    
    </body>
    </html>

如果我要传递参数,就要在 forward 行为嵌套 param 行为

在跳转到 head.jsp 时传入参数 username 值为 aaa

<jsp:forward page="head.jsp">
    <jsp:param name="username" value="aaa"/>
 </jsp:forward>
<%
    String ss = request.getParameter("username");
%>
获取到的参数是:<%=ss%>

(4) directive 行为

directive 的中文意思就是指令。该行为就是替代指令 <%@%> 的语法的

· <jsp:directive.include file=""/> 相当于 <%@include file="" %>

· jsp:directive.page/ 相当于 <%@page %>

· jsp:directive.taglib/ 相当于 <%@taglib %>

使用该指令可以让 JSP 页面更加美观!

使用 scriptlet 行为 <jsp:scriptlet> 替代 <%%> 是同样一个道理

(5) javaBean 行为

JSP 还提供了操作 javaBean 对象的行为,暂时记住 JSP 提供了 javaBean 行为来操作简单类即可!后面详细解释:

<jsp:useBean id=""/>
<jsp:setProperty name=""property=""/>
<jsp:getProperty name=""property=""/>

(六) JSP 内置对象(直接使用)

JSP 引擎在调用 JSP 对应的 jspServlet 时,会传递或创建 9 个与 web 开发相关的对象供 jspServlet 使用 。JSP 技术的设计者为便于开发人员在编写 JSP 页面时获得这些 web 对象的引用,特意定义了 9 个相应的变量, 开发人员在 JSP 页面中通过这些变量就可以快速获得这 9 大对象的引用

变量名 真实类型 作用
pageContext PageContext 当前页面共享数据,还可以获取其他八个内置对象
request HttpServletRequest 一次请求访问的多个资源(转发)
session HttpSession 一次会话的多个请求间
application ServletContext 所有用户间共享数据
response HttpServletResponse 响应对象
page Object 当前页面 (Servlet) 的对象 this
out JspWriter 输出对象,数据输出到页面上
config ServletConfig Servlet 的配置对象
exception Throwable 内置对象 exception 是 java.lang.Exception 类的对象

(七) 四种属性范围

到目前为止,我们已经学了 4 种属性范围了。

page【只在一个页面中保存属性,跳转页面无效】

requet【只在一次请求中保存属性,服务器跳转有效,浏览器跳转无效】

session【在一个会话范围中保存属性,无论何种跳转均有效,关闭浏览器后无效】

application【在整个服务器中保存,所有用户都可以使用】

4 个内置对象都支持以下的方法:

  • setAttribute(String name, Object o)
  • getAttribute(String name)
  • removeAttribute(String name)

※ 应用场景

  1. request:如果客户向服务器发请求,产生的数据,用户看完就没用了,像这样的数据就存在 request 域, 像新闻数据,属于用户看完就没用的
  2. session:如果客户向服务器发请求,产生的数据,用户用完了等一会儿还有用,像这样的数据就存在 session 域中,像购物数据,用户需要看到自己购物信息,并且等一会儿,还要用这个购物数据结帐
  3. servletContext:如果客户向服务器发请求,产生的数据,用户用完了,还要给其它用户用,像这样的数据就存在 servletContext 域中,像聊天数据

(八) JavaBean

avaBean 就是一个普通的 java 类,也称之为简单 java 对象 –POJO(Plain Ordinary Java Object),是 Java 程序设计中一种设计模式,是一种基于 Java 平台的软件组件思想

JavaBean 遵循着特定的写法,通常有以下的规则:

有无参的构造函数

成员属性私有化

封装的属性如果需要被外所操作,必须编写 public 类型的 setter、getter 方法

上面的文字看起来好像很高大上,javaBean 其实非常简单,常见的学生类,书籍类就是按照特定写法、规则编写的一个 JavaBean 对象

为什么需要使用 Javabean

使用 javaBean 的好处:封装,重用,可读!

JaveBean 你可以理解为一辆货车,在你的 java 端和 web 页面进行数据传递的载体,你当然可以每个变量单独传递,或者使用集合传递,但是javabean 可以使你的数据更有可读性,方便开发时明确变量的意义,也使其他阅读你代码的人能直接你的意图

如果 bean 类与数据库联合使用,一张表使用 bean 类,可以使你的代码更加简洁高效,易于理解,现在大多数框架都会使用这种机制。

JSP 行为 –JavaBean

JSP 技术提供了三个关于 JavaBean 组件的动作元素,即 JSP 行为(标签),它们分别为:

jsp:useBean【在 JSP 页面中查找 javaBean 对象或者实例化 javaBean 对象】jsp:setProperty【设置 javaBean 的属性】jsp:getProperty【获取 javaBean 的属性】

※ JSP:useBean

<jsp:useBean>

标签用于在指定的域范围内查找指定名称的 JavaBean 对象:

存在则直接返回该 JavaBean 对象的引用。

不存在则实例化一个新的 JavaBean 对象并将它以指定的名称存储到指定的域范围中。

语法:

jsp:useBean id="实例化对象的名称" class="类的全名" scope="保存范围"/>

果 JSP 不支持 <jsp:useBean> 这个行为,我们要使用 Person 类是这样使用的

  <%-- 这里需要导入 Person 类 --%>
    <%@ page import="domain.Person" %>

    <%@ page contentType="text/html;charset=UTF-8" language="java" %>
    <html>
    <head>
        <title></title>
    </head>
    <body>
    
    <%
        //new 出对象
        Person person = new Person();

        person.setName("admin");
        System.out.println(person.getName());
    %>
    </body>

但是我们使用 <jsp:useBean> 就非常整洁,不用导包,不用 new 对象

 <%@ page contentType="text/html;charset=UTF-8" language="java" %>
    <html>
    <head>
        <title></title>
    </head>
    <body>
    
    <jsp:useBean id="person" class="domain.Person" scope="page"/>
    <%
        person.setName("zhongfucheng");
        System.out.println(person.getName());
    %>
    </body>
    </html>

JavaBean 中无参的构造函数改成有参的,会出现异常,这是因为<jsp:useBean>

的内部原理是 new 了一个无参的构造函数

※ JSP:setProperty

<jsp:setProerty name="对象名称" property="属性名" param="参数名" value="值">

四种模式

<jsp:setProperty name="对象名称" property="*"/> 自动匹配
<jsp:setProperty name="对象名称" property="属性名称"/> 指定属性
<jsp:setProperty name="对象名称" property="属性名称" param="参数名称"/> 指定参    数【很少用】<jsp:setProperty name="对象名称" property="属性名称" value="内容"/> 指定内容【很    少用】

当我们没有学习到 <jsp:setProperty> 时,我们获取表单的信息,然后导入到 javaBean 对象中是这样的一种情况:

<jsp:useBean id="person" class="domain.Person" scope="page"/>
<%
    int age = Integer.parseInt(request.getParameter("age"));        
     person.setAge(age);    
     System.out.println(person.getAge());
%>

而我们使用 <jsp:setProperty> 后,代码更少,功能更强大

<jsp:useBean id="person" class="domain.Person" scope="page"/>

<%-- 指定属性名称为 age--%>
<jsp:setProperty name="person" property="age"/>
 <%
        System.out.println(person.getAge());
 %>

代码少很直观的可以看出来,但是强大在什么地方呢?

表单提交过来的数据都是字符串,在我们没有用 <jsp:setProperty> 前,我们存储设置 int 类型或其他非字符串类型的数据是需要强转的!但是 <jsp:setProperty> 不需要我们强转,它内部自动帮我们转换了!

下面再通过自动匹配来感受它的强大

<jsp:useBean id="person" class="domain.Person" scope="page"/>

<%--property 的值设置为 * 就代表自动匹配 --%>
<jsp:setProperty name="person" property="*"/>
<%
    System.out.println(person.getAge());
    System.out.println(person.getName());
%>

为什么 Property 的值可以将表单传递过来的数据封装到 JavaBean 对象中?

JavaBean 属性名要和表单的 name 的名称一致

是通过反射来做的, 调用了内省的方法!

※ JSP:getProperty

<jsp:getProperty name="对象名" property="属性名"/>
<%-- 使用 <jsp:getProperty> 输出 --%>
<jsp:getProperty name="person" property="username"/>
<jsp:getProperty name="person" property="age"/>

JSP 第二篇:

EL 运算符:概述、内置对象、数据回显、自定义函数、EL 函数库

(一) 概述

EL:Expression Language 表达式语言

它的作用就是替换和简化 jsp 页面中 java 代码的编写

EL 表达式支持简单的运算符:加减乘除取摸,逻辑运算符。empty 运算符(判断是否为 null),三目运算符


empty 运算符可以判断对象是否为 null,用作于流程控制!

三目运算符简化了 if 和 else 语句,简化代码书写

<%
    List<Person> list = null;
%>
${list==null?"list 集合为空":"list 集合不为空"}

(二) 内置对象

EL 表达式主要是来对内容的显示,为了显示的方便,EL 表达式提供了 11 个内置对象

pageContext 对应于 JSP 页面中的 pageContext 对象(注意:取的是 pageContext 对象)pageScope 代表 page 域中用于保存属性的 Map 对象

requestScope 代表 request 域中用于保存属性的 Map 对象
 sessionScope 代表 session 域中用于保存属性的 Map 对象

applicationScope 代表 application 域中用于保存属性的 Map 对象

param 表示一个保存了所有请求参数的 Map 对象

paramValues 表示一个保存了所有请求参数的 Map 对象,它对于某个请求参数,返回的是一个 string[]

header 表示一个保存了所有 http 请求头字段的 Map 对象

headerValues 同上,返回 string[]数组。cookie 表示一个保存了所有 cookie 的 Map 对象

initParam 表示一个保存了所有 web 应用初始化参数的 map 对象

(三) 数据回显

<%-- 模拟数据回显场景 --%>
<%
    User user = new User();
    user.setGender("male");

    // 数据回显
    request.setAttribute("user",user);
%>


<input type="radio" name="gender" value="male" ${user.gender=='male'?'checked':''}> 男
<input type="radio" name="gender" value="female" ${user.gender=='female'?'checked':''}> 女

(四) 自定义函数

EL 自定义函数用于扩展 EL 表达式的功能,可以让 EL 表达式完成普通 Java 程序代码所能完成的功能

开发 HTML 转义的 EL 函数

我们有时候想在 JSP 页面中输出 JSP 代码,但是 JSP 引擎会自动把 HTML 代码解析, 输出给浏览器。此时我们就要对 HTML 代码转义。

步骤:

编写一个包含静态方法的类(EL 表达式只能调用静态方法),该方法很常用,Tomcat 都有此方法,可在 webappsexamplesWEB-INFclassesutil 中找到

public static String filter(String message) {if (message == null)
            return (null);

        char content[] = new char[message.length()];
        message.getChars(0, message.length(), content, 0);
        StringBuilder result = new StringBuilder(content.length + 50);
        for (int i = 0; i < content.length; i++) {switch (content[i]) {
            case '<':
                result.append("&lt;");
                break;
            case '>':
                result.append("&gt;");
                break;
            case '&':
                result.append("&amp;");
                break;
            case '"':
                result.append("&quot;");
                break;
            default:
                result.append(content[i]);
            }
      }
    return (result.toString());

在 WEB/INF 下创建 tld(taglib description)文件,在 tld 文件中描述自定义函数

<?xml version="1.0" encoding="ISO-8859-1"?>

<taglib xmlns="http://java.sun.com/xml/ns/javaee"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-jsptaglibrary_2_1.xsd"
        version="2.1">

    <tlib-version>1.0</tlib-version>
    <short-name>myshortname</short-name>
    <uri>/zhongfucheng</uri>

    <!-- 函数的描述 -->
    <function>

        <!-- 函数的名字 -->
        <name>filter</name>

        <!-- 函数位置 -->
        <function-class>utils.HTMLFilter</function-class>

        <!-- 函数的方法声明 -->
        <function-signature>java.lang.String filter(java.lang.String)</function-signature>
    </function>

</taglib>

在 JSP 页面中导入和使用自定义函数,EL 自定义的函数一般前缀为 ”fn”,uri 是 ”/WEB-INF/tld 文件名称 ”

<%@ page language="java" contentType="text/html" pageEncoding="UTF-8" %>
<%@taglib prefix="fn" uri="/WEB-INF/ideal.tld" %>

<html>
<head>
    <title></title>
</head>
<body>

// 完成了 HTML 转义的功能
${fn:filter("<a href='#'> 点这里 </a>")}

</body>
</html>

(五) EL 函数库(fn 方法库)

  • 由于在 JSP 页面中显示数据时,经常需要对显示的字符串进行处理,SUN 公司针对于一些常见处理定义了一套 EL 函数库供开发者使用。
  • 其实 EL 函数库就是 fn 方法库,是 JSTL 标签库中的一个库,也有人称之为 fn 标签库,但是该库长得不像是标签,所以称之为 fn 方法库
  • 既然作为 JSTL 标签库中的一个库,要使用 fn 方法库就需要导入 JSTL 标签!要想使用 JSTL 标签库就要导入 jstl.jar 和 standard.jar 包!
  • 所以,要对 fn 方法库做测试,首先导入开发包(jstl.jar、standard.jar)

在 JSP 页面中指明使用标签库

<%@taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>

JSP 第三篇:

JSTLd 的简单认识、三个常用对象

JSTL 全称为 JSP Standard Tag Library 即 JSP 标准标签库

JSTL 作为最基本的标签库,提供了一系列的 JSP 标签,实现了基本的功能:集合的遍历、数据的输出、字符串的处理、数据的格式化等等!

为什么使用

EL 表达式可以很方便地引用一些 JavaBean 以及其属性但是仍然不够完美,它不能遍历集合,做逻辑的控制。

Scriptlet 的可读性,维护性,重用性都十分差!JSTL 与 HTML 代码十分类似,遵循着 XML 标签语法,使用 JSTL 让 JSP 页面显得整洁,可读性非常好,重用性非常高,可以完成复杂的功能!

在 JSP 中不推荐使用 scriptlet 输出,推荐使用 JSP 标签

使用 JSTL 标签库步骤

  • 导入 jstl 相关 jar 包
  • 引入标签库:taglib 指令:<%@ taglib %>
  • 使用标签

Core 标签库

core 标签库是 JSTL 的核心标签库,实现了最基本的功能:流程控制、迭代输出等操作

core 标签库的前缀一般是 c

常用的三个 JSTL 标签

(一) c:if

属性:

test 必须属性,接受 boolean 表达式

如果表达式为 true,则显示 if 标签体内容,如果为 false,则不显示标签体内容

  • 注意:c:if 标签没有 else 情况,想要 else 情况,则可以在定义一个 c:if 标签
<%-- 如果带过来的名字是 admin,那么可以登陆 --%>
    <c:if test="${param.name=='admin'}">
        用户名:<input type="text" name="username"><br>
        密码:<input type="password" name="password"><br>
        <input type="submit" value="登陆">
    </c:if>
    
    <%-- 如果带过来的名字是 admin888,那么就是注册 --%>
    <c:if test="${param.name=='admin888'}">
        用户名:<input type="text" name="username"><br>
        密码:<input type="password" name="password"><br>
        <input type="submit" value="注册">
    </c:if>

(二) c:choose

它相当于 java 代码的 switch 语句

使用 choose 标签声明,相当于 switch 声明
使用 when 标签做判断,相当于 case
使用 otherwise 标签做其他情况的声明,相当于 default

<c:choose>
        <c:when test="${param.name=='admin'}">
            欢迎管理员
        </c:when>
        <c:when test="${param.name=='user'}">
            欢迎用户
        </c:when>
        <c:otherwise>
            识别不出你是谁
        </c:otherwise>
</c:choose>

(三) c:forEach

forEach 为循环标签,相当于 Java 中的 while 和 for

之前我们在使用 EL 表达式获取到集合的数据,遍历集合都是用 scriptlet 代码循环,现在我们学了 forEach 标签就可以舍弃 scriptlet 代码

向 Session 中设置属性,属性的类型是 List 集合

向 Session 中设置属性,属性的类型是 List 集合

遍历 session 属性中的 List 集合,items:即将要迭代的集合。var:当前迭代到的元素

 <%
    List list = new ArrayList<>();
    list.add("admin");
    list.add("zhangsan");
    list.add("lisi");
    
    session.setAttribute("list", list);
%>

=====================================================
 <c:forEach  var="list" items="${list}" >
    ${list}<br>
</c:forEach>

Map 对象有稍微地不一样保存的不是每个迭代的对象,而是 Map.Entry

 <%
    Map map = new HashMap();
    map.put("1", "tom");
    map.put("2", "jack");
    map.put("3", "jack”);
    
    session.setAttribute("map",map);
%>
    
<c:forEach  var="me" items="${map}" >   
    ${me.key}  ${me.value}<br>
</c:forEach>

特别说明:本篇中 第二 第三篇部分内容转载来自 java3y 所写 jsp 第四篇内容,在作者基础上摘出片段,附上链接

https://juejin.im/post/5a7919…

结尾:

如果内容中有什么不足,或者错误的地方,欢迎大家给我留言提出意见, 蟹蟹大家!^_^

如果能帮到你的话,那就来关注我吧!(系列文章均会在公众号第一时间更新)

在这里的我们素不相识,却都在为了自己的梦而努力 ❤

一个坚持推送原创 Java 技术的公众号:理想二旬不止

正文完
 0