JAVAWEB开发之JSTL标签库的使用、 自定义EL函数、自定义标签(带属性的、带标签体的)...
程序员文章站
2022-06-01 23:29:52
...
JSTL
JSTL简介:
JSTL的全称:JSP Standard Tag Library,JSP标准标签库
JSTL的作用:
提供给Java Web开发人员一个标准通用的标签函数库
和EL来取代传统直接在页面上嵌入Java程序(Scripting)的做法,以提高程序可读性、维护性和方便性
JSTL的版本:
JSTL的主要版本是1.0、1.1和1.2(区别不大)
1.0版本EL表达式还没有纳入官方规范
1.1和1.2版本EL表达式已经纳入了官方规范
JSTL1.1 下载相应的jar包
JSTL的下载
JSTL主要由Apache组织的Jakarta Project实现
http://tomcat.apache.org/taglibs/standard/
容器必须支持Servlet2.4且JSP2.0以上的版本
JavaEE1.4
JSTL导入jar包
解压缩后将lib中的jstl.jar、standard.jar 复制到WEB应用程序的WEB-INF\lib下
JSTL标签库
- 核心标签库(core)---c(重点)
- XML(x:操作xml的标签库)
- SQL(sql标签库)
- FMT(fmt:国际化标签库)
- JSTL函数(EL函数)el
JSTL快速入门
导入jar包(jstl.jar和standard.jar)
其中jstl.jar是编译后的Java类文件,standard.jar定义的是标准接口
新建JSP的页面
在页面中引入核心标签库
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
JSTL标签库关于导入版本的问题
http://java.sun.com/jsp/core 1.1或者1.2版本(使用该版本)
http://java.sun.com/jstl/core 1.0版本(不支持EL表达式)
<c:out>标签
输出常量或者域范围中的变量(value属性,使用EL表达式)
输出默认值(default属性)
默认HTML标签不转义(escapeXml)
属性名 | 是否支持EL | 属性类型 | 属性描述 |
value | true | Object | 指定要输出的内容 |
escapeXml | true | Boolean | 指定是否将>、<、&、'、" 等 特殊字符进行HTML编码转换 后再进行输出。默认值是true。 |
default | true | Object | 指定如果value属性的值为null时所输出的默认值 |
<!-- c:out 输出数据到浏览器 -->
<c:out value="Hello c out "></c:out>
Hello c out
<!-- 输出一个变量 -->
<c:set var="m" value="10" scope="page"/>
<c:out value="${m}"></c:out>
${m }
<!-- 转义HTML 默认转义,通过设置escapeXml 为false 不进行转义-->
<c:out value="<a href='xxx'>link</a>" />
${fn:escapeXml("<a href='xxx'>link</a>") }
<!-- 允许输出默认值 ,如果city不存在,输出北京-->
<c:out value="${city}" default="北京"></c:out>
${empty city?"北京":city }
在WebRoot下新建jstl文件夹,在文件夹下新建out.jsp
实例代码如下:<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
<h4>传统方式</h4>
<%= "Hello" %>
<%
int a = 10;
request.setAttribute("name", "xy");
%>
<%= a %>
<h4>JSTL的方式 </h4>
<c:out value="Hello"></c:out>
<c:out value="${name }"></c:out>
<!-- "" -->
<c:out value="${ city }" default="北京"></c:out>
<c:out value="<a href='#'>超链接</a>" escapeXml="false"/>
<c:out value="<a href='#'>超链接2</a>" escapeXml="true"/>
</body>
</html>
<c:set>标签
- 向4个域中存入值。(var value scope属性)
- 设置Web域中的java.util.Map 类型的属性对象或JavaBean类型的属性对象的属性(target property value属性)
属性名 | 是否支持EL | 属性类型 | 属性描述 |
value | true | Object | 用于指定属性 |
var | false | String | 用于指定要设置的Web域属性的名称 |
scope | false | String | 用于指定属性所在的Web域 |
target | true | Object | 用于指定要设置属性的对象,这个对象必须是 JavaBean对象或java.util.Map对象 |
property | true | String | 用于指定当前要为对象设置的属性名称 |
在WebRoot/jstl下新建set.jsp
代码如下:
<%@page import="cn.itcast.vo.User"%>
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
<h4>传统方式</h4>
<%
pageContext.setAttribute("name", "10", pageContext.REQUEST_SCOPE);
%>
<%
User user = new User();
user.setUsername("美美");
user.setPassword("123");
request.setAttribute("user", user);
%>
${ user.username }
<h4>JSTL方式</h4>
<c:set var="i" value="10" scope="request" ></c:set>
${ i }
<c:set target="${ user }" property="username" value="小凤"></c:set>
${ user.username }
</body>
</html>
<c:remove>标签
<c:remoive>标签用于删除各种Web域中的属性
其语法格式如下:
<c:remove var="varName" [scope="{page|request|session|application}"]>
如果不设置,默认是pageContext域范围内查找删除值。
用法示例:
<%
request.setAttribute("age",20);
// 删除age
request.removeAttribute("age");
%>
<c:set var="age" value="20" scope="request"></c:set>
<c:remove var="age" scope="request"/>
request.setAttribute("age",20);
// 删除age
request.removeAttribute("age");
%>
<c:set var="age" value="20" scope="request"></c:set>
<c:remove var="age" scope="request"/>
在WebRoot/jstl目录下新建remove.jsp
实例代码如下:
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
<h4>传统方式</h4>
<%
request.setAttribute("name", "美美");
request.removeAttribute("name");
%>
<c:set var="name" value="小凤" scope="page"></c:set>
${ name }
<c:remove var="name" scope="page"/>
${name }
</body>
</html>
<c:catch>标签
- <c:catch>标签用于捕获嵌套在标签中的内容抛出的异常,其语法格式如下:<c:catch [var="varName"]> nested actions </c:catch>
- var属性用于标识<c:catch>标签捕获的异常对象,它将保存在page这个web域中。
关键代码:
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ page contentType="text/html;charset=gb2312" %>
<c:catch var="myex“ >
<%
10/0;
%>
</c:catch>
异常:<c:out value="${myex}" /> ${myex}<br />
异常 myex.getMessage:<c:out value="${myex.message}" /><br />
异常 myex.getCause:<c:out value="${myex.cause}" /><br />
异常 myex.getStackTrace:<c:out value="${myex.stackTrace}" />
<%@ page contentType="text/html;charset=gb2312" %>
<c:catch var="myex“ >
<%
10/0;
%>
</c:catch>
异常:<c:out value="${myex}" /> ${myex}<br />
异常 myex.getMessage:<c:out value="${myex.message}" /><br />
异常 myex.getCause:<c:out value="${myex.cause}" /><br />
异常 myex.getStackTrace:<c:out value="${myex.stackTrace}" />
在WebRoot/jstl下新建catch.jsp
实例代码如下:
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
<h4>传统方式</h4>
<%
try{
}catch(Exception e){
e.printStackTrace();
}
%>
<h4>JSTL的方式</h4>
<c:catch var="e">
<%
int a = 10/0;
%>
</c:catch>
${ e.message }
</body>
</html>
<c:if>标签
<c:if test=""> 标签可以构造简单的"if-then"结构的条件表达式
属性名 | 是否支持EL | 属性类型 | 属性描述 |
test | true | boolean | 决定是否处理标签体中的内容的条件表达式 |
var | false | String | 用于指定将test属性的执行结果保存到某个Web域中的某个属性的名称 |
scope | false | String | 指定将test属性的执行结果保存到哪个Web域中 |
在WebRoot/jstl下新建if.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
<h4>传统方式</h4>
<%
int a = 10;
if(a >= 10){
out.print("a >= 10");
}else{
out.print("a < 10");
}
%>
<h4>JSTL方式</h4>
<c:set var="i" value="10" scope="page"></c:set>
<c:if test="${ i ge 10 }" var="x" scope="page">
i >= 10
</c:if>
<c:if test="${ i lt 10 }">
i < 10
</c:if>
${ x }
</body>
</html>
<c:choose>
<c:choose>标签用于指定多个条件选择的组合边界,它必须与<c:when>和<c:otherwise>标签一起使用。使用<c:choose>,<c:when>,<c:otherwise>三个标签,可以构造类似于"if-else if-else"的复杂条件判断结构
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ page contentType="text/html;charset=gb2312" %>
<c:set value="${param.count}" var="count“ /> pageContext(count,2)
<c:choose>
<c:when test="${count == 0}">
对不起,没有符合您要求的记录。
</c:when>
<c:otherwise>
符合您要求的记录共有${count}条.
</c:otherwise>
</c:choose>
<%@ page contentType="text/html;charset=gb2312" %>
<c:set value="${param.count}" var="count“ /> pageContext(count,2)
<c:choose>
<c:when test="${count == 0}">
对不起,没有符合您要求的记录。
</c:when>
<c:otherwise>
符合您要求的记录共有${count}条.
</c:otherwise>
</c:choose>
在WebRoot/jstl目录下新建choose.jsp
代码如下:
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
<h4>获取参数</h4>
<%= request.getParameter("username") %>
<h4>传统方式</h4>
<%
int a = 10;
if(a >= 10 ){
out.print("a >= 10");
}else if(a < 10){
out.print("a < 10");
}else{
out.print("其他");
}
%>
<h4>JSTL方式</h4>
<c:set var="i" value="10" scope="page"></c:set>
<c:choose>
<c:when test="${ i ge 10 }">
i >= 10
</c:when>
<c:when test="${ i lt 10 }">
i < 10
</c:when>
<c:otherwise>
其他
</c:otherwise>
</c:choose>
</body>
</html>
<c:forEach>标签
<c:forEach>标签用于对一个集合对象中的元素进行循环迭代操作,或者按指定的次数重复迭代执行标签体中的内容
属性名 | 是否支持EL | 属性类型 | 属性描述 |
var | false | String | 指定将当前迭代到的元素保存到page这个域中的属性名称 |
varStatus | false | String | 记住用于保存迭代信息的对象 |
items | true | 任何支持的类型 | 将要迭代的集合对象 |
begin | true | int | 如果指定items属性,就从集合中的第begin个元素开始进行迭代 ,begin的索引值从0开始编号,如果没有指定items属性,就从 begin指定的值开始迭代,直到end值时结束迭代 |
end | true | int | 与begin属性类似 |
step | true | int | 指定迭代的步长,即迭代因子的迭代增量 |
<c:forEach>遍历集合
遍历数组或者集合:
var:代表遍历的每一个元素
items:要迭代的集合对象
获取遍历的内容:${ xxx }
遍历Map集合:
var:代表key与value的关系entry
items:要迭代的map集合
获取遍历的内容:${ xxx.key } ----${ xxx.value }
遍历对象集合:
var:代表遍历的每一个对象
items:要迭代的集合
获取遍历的内容: ${ xxx.对象的属性}
<c:forEach>迭代数据
从1遍历到10:
var:定义变量
begin:从哪开始
end:到哪结束
step:迭代步长(相当于x+=step)
计算从1加到10的和:
定义变量sum,值为0
通过forEach循环1到10,每循环一次加到另一个变量中
在循环中间定义变量,每次计算到该变量中
<c:set var="sum" value="0" scope="page"></c:set>
<c:forEach begin="1" end="10" step="1" var="i">
<c:set var="sum" value="${sum + i}" scope="page"></c:set>
</c:forEach>
${sum }
varStatus属性:
<c:forEach begin="1" end="10" step="1" var="i">
<c:set var="sum" value="${sum + i}" scope="page"></c:set>
</c:forEach>
${sum }
varStatus属性:
记录迭代相关的信息:
属性 | 类型 | 意义 |
index | number | 现在指到成员的索引 |
count | number | 总共指到成员的总数 |
first | boolean | 现在指到的成员是否是第一个成员 |
last | boolean | 现在指到的成员是否是最后一个成员 |
<c:forEach begin="10" end="100" step="2" var="i" varStatus="status">
<c:if test="${status.count % 3 == 0}">
<font color="red">${i }</font>
</c:if>
<c:if test="${status.count % 3 != 0}">
<font color="blue">${i }</font>
</c:if>
</c:forEach>
在WebRoot/jstl目录下新建forEach.jsp
<c:if test="${status.count % 3 == 0}">
<font color="red">${i }</font>
</c:if>
<c:if test="${status.count % 3 != 0}">
<font color="blue">${i }</font>
</c:if>
</c:forEach>
在WebRoot/jstl目录下新建forEach.jsp
实例代码如下:
<%@page import="cn.itcast.vo.User"%>
<%@page import="java.util.HashMap"%>
<%@page import="java.util.Map"%>
<%@page import="java.util.ArrayList"%>
<%@page import="java.util.List"%>
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
<h4>遍历数组</h4>
<%
String [] arrs = {"美美","小凤","芙蓉","小苍"};
request.setAttribute("arrs", arrs);
%>
<!-- for(String s : arrs){ } -->
<c:forEach var="s" items="${ arrs }">
${ s }
</c:forEach>
<h4>遍历集合</h4>
<%
List<String> list = new ArrayList<String>();
list.add("美美");
list.add("小凤");
list.add("芙蓉");
list.add("小泽");
request.setAttribute("list", list);
%>
<c:forEach var="s" items="${ list }">
${ s }
</c:forEach>
<h4>遍历Map集合</h4>
<%
Map<String,String> map = new HashMap<String,String>();
map.put("aa", "美美");
map.put("bb", "小凤");
map.put("cc", "芙蓉");
request.setAttribute("map", map);
%>
<c:forEach var="entry" items="${ map }">
${ entry.key } -- ${ entry.value }
</c:forEach>
<h4>遍历对象的集合</h4>
<%
List<User> uList = new ArrayList<User>();
uList.add(new User("美美","123"));
uList.add(new User("小风","234"));
uList.add(new User("芙蓉","345"));
request.setAttribute("uList", uList);
%>
<c:forEach var="user" items="${ uList }">
${ user.username } -- ${ user.password }
</c:forEach>
<h4>迭代数据</h4>
<h4>迭代从1到10</h4>
<c:forEach var="i" begin="1" end="10" step="2">
${ i }
</c:forEach>
<h4>计算从1加到100的和</h4>
<c:set var="sum" value="0" scope="page"></c:set>
<c:forEach var="i" begin="1" end="100" step="1" varStatus="status">
<c:set var="sum" value="${ sum + i }"></c:set>
</c:forEach>
${ sum }
<h4>遍历10到100的偶数,每到第3个数,显示红色</h4>
<c:forEach var="i" begin="10" end="100" step="2" varStatus="status">
<c:choose>
<c:when test="${ status.first }">
<font color="blue">${ i }</font>
</c:when>
<c:when test="${ status.count % 3 eq 0 }">
<font color="red">${ i }</font>
</c:when>
<c:otherwise>
${ i }
</c:otherwise>
</c:choose>
</c:forEach>
</body>
</html>
<c:forTokens>
用来切分字符串
名称 | 说明 | EL | 类型 | 必须 | 默认值 |
var | 用来存放现在指到的成员 | N | String | 否 | 无 |
items | 被迭代的字符串 | Y | String | 是 | 无 |
delims | 定义用来分割字符串的字符 | N | String | 是 | 无 |
varStatus | 用来存放现在指到的相关成员信息 | N | String | 否 | 无 |
begin | 开始的位置 | Y | int | 否 | 0 |
end | 结束的位置 | Y | int | 否 | 最后一个成员 |
<c:forTokens items="${s}" delims="," var="e">
${e }
</c:forTokens>
语法格式:
<c:forTokens
items=要切分的字符串
delims=按着什么格式切分
var=定义变量
[varStatus="varStatusName"]
[begin="begin"]
[end="end"]
[step="step"]>
//body内容
</c:forTokens>
items=要切分的字符串
delims=按着什么格式切分
var=定义变量
[varStatus="varStatusName"]
[begin="begin"]
[end="end"]
[step="step"]>
//body内容
</c:forTokens>
在WebRoot/jstl目录下新建forTokens.jsp
代码如下:
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
<h4>分隔字符串</h4>
<c:set var="i" value="aa,bb,cc" scope="page"></c:set>
<c:forTokens items="${i }" delims="," var="x">
${ x }
</c:forTokens>
</body>
</html>
<c:param>标签
- 在JSP页面进行URL的相关操作时,经常要在URL地址后面附加一些参数。<c:param>标签可以嵌套在<c:import>、<c:url>或<c:redirect>标签内,为这些标签所使用的URL地址附加参数。
- <c:param>标签在为一个URL地址附加参数时,将自动对参数值进行URL编码,例如:如果传的参数值为“中国”,则将其转换为“%d6%d0%b9%fa”后再附加到URL地址后面,这也就是使用<c:param>标签的最大好处
- 示例:<c:param name="name" value="value" />
<c:import>标签
名称 | 说明 | EL | 类型 | 必须 | 默认值 |
url | 一文件被包含的地址 | Y | String | 是 | 无 |
context | 项目虚拟路径 | Y | String | 否 | 无 |
var | 储存被包含的文件的内容(以String类型存入) | Y | String | 否 | 无 |
scope | var变量的JSP范围 | N | String | 否 | page |
charEncoding | 被包含文件的内容的编码方式 | Y | String | 否 | 无 |
varReader | 储存被包含的文件的内容(以Reader类型存入) | N | String | 否 | 无 |
<c:import url="/jstl/foreach.jsp" context="/day8"></c:import>
<hr/>
<!-- 引入不显示,将内容保存另一个变量中 -->
<c:import url="/jstl/foreach.jsp" context="/day8" var="content" scope="page"></c:import>
${content }
<c:import url="http://java.sun.com" >
<c:param name="test" value="1234" />
</c:import>
在WebRoot/jstl/下新建 import.jsp(包含choose.jsp 并在choose.jsp 中获取参数)
实例代码如下:
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
<h4>JSTL方式</h4>
<c:import url="/jstl/choose.jsp" context="/day13" var="i" scope="page">
<c:param name="username" value="meimei"></c:param>
</c:import>
${ i }
</body>
</html>
<c:url>标签
<c:url>标签用于在JSP页面中构造一个URL地址,其主要目的是实现URL重写。URL重写就是将会话标识以参数形式附加在URL地址后面。(类似于Session追踪 尤其是当浏览器禁用cookie后,就是说实现了session追踪的功能)
属性名 | 是否支持EL | 属性类型 | 属性描述 |
value | true | String | 指定要构造的URL |
var | false | String | 指定将构造出的URL结果保存到Web域中的属性名称 |
scope | false | String | 指定将构造出的URL结果保存在哪个域中 |
在WebRoot/jstl/下新建url.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
<h4>JSTL方式</h4>
<c:url var="i" value="/jstl/choose.jsp" scope="request" context="/day13">
<c:param name="username" value="xiaofeng"></c:param>
</c:url>
<c:set var="s" value="刘勋" scope="session"></c:set>
<a href="${ i }">choose</a> <br>
i=${i } <br>
<%
String url = "/day12/index.jsp";
url = response.encodeURL(url);
%>
<!-- 将/day8/index.jsp 进行url重写,保存page范围 myurl中 -->
<c:url value="/index.jsp" context="/day13" var="myurl" scope="page" />
url= <%=url %> <br>
myurl=${myurl } <br>
<!-- 通过c:url 结合 c:param 对中文完成URL编码 -->
<c:url value="/login" context="/day13" var="myurl2" scope="page">
<c:param name="username" value="张三"></c:param>
</c:url>
myurl2=${myurl2 } <br>
</body>
</html>
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
<c:out value="${s }"></c:out>
<h4>获取参数</h4>
<%= request.getParameter("username") %>
<h4>传统方式</h4>
<%
int a = 10;
if(a >= 10 ){
out.print("a >= 10");
}else if(a < 10){
out.print("a < 10");
}else{
out.print("其他");
}
%>
<h4>JSTL方式</h4>
<c:set var="i" value="10" scope="page"></c:set>
<c:choose>
<c:when test="${ i ge 10 }">
i >= 10
</c:when>
<c:when test="${ i lt 10 }">
i < 10
</c:when>
<c:otherwise>
其他
</c:otherwise>
</c:choose>
</body>
</html>
禁用浏览器的cookie后,运行如下:点击choose跳转到choose.jsp
<c:redirect>标签
<c:redirect>标签用于实现请求重定向
属性名 | 是否支持EL | 属性类型 | 属性描述 |
url | true | String | 指定要转发或重定向到的目标资源的URL地址 |
context | true | String | 当要使用相对路径重定向到同一个服务器下的其他WEB应用程序中的 资源时,context属性指定其他WEB应用程序的名称 |
注意:如果重定向第三方的网站时要使用绝对路径(不能再使用Context)
<%
// 以前重定向
// response.sendRedirect("/day12/index.jsp");
%>
<c:redirect url="/index.jsp" context="/day12"></c:redirect>
// 以前重定向
// response.sendRedirect("/day12/index.jsp");
%>
<c:redirect url="/index.jsp" context="/day12"></c:redirect>
在WebRoot/jstl下新建redirect.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
<h4>JSTL方式</h4>
<c:redirect url="/jstl/choose.jsp" context="/day13">
<c:param name="username" value="furong"></c:param>
</c:redirect>
</body>
</html>
EL函数库
JSTL中的常用EL函数
EL函数是用来操作字符串的
由于在JSP页面中显示数据时,经常需要对显示的字符串进行处理,SUN公司针对一些常见处理定义了一套EL函数库供开发者使用。
这些EL函数在JSTL开发包中进行描述,因此在JSP页面中使用SUN公司的EL函数库,需要导入JSTL开发包,并在页面中导入EL函数库,如下所示:
在页面中使用JSTL定义的EL函数:
<%@taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn" %>
(可以在fn.tld文件中查看)
fn:toLowerCase
fn:toLowerCase函数将一个字符串中包含的所有字符转换为小写形式,并返回转换后的字符串,它接收一个字符串类型的参数,例如:
fn:toLowerCase("Www.IT315.org")的返回值为字符串"www.it315.org"
fn.toLowerCase("") 的返回值为空字符串
fn:toUpperCase
fn:toUpperCase函数将一个字符串中包含的所有字符转换为大写形式,并返回转换后的字符串,它接收一个字符串类型的参数。例如:
fn:toUpperCase("www.IT315.org")的返回值为字符串"WWW.IT315.ORG"
fn:toUpperCase("")的返回值为空字符串
fn:trim
fn:trim函数删除一个字符串的首尾的空格,并返回删除空格后的结果字符串,它接收一个字符串类型的参数。需要注意的是,fn:trim函数不能删除字符串中间位置的空格
例如:fn:trim(" www.it3 15.org ")的返回值字符串是"www.it 315.org"
fn:length
fn:length函数返回一个集合或数组大小,或返回一个字符串中包含的字符的个数,返回值是int类型。fn:length函数接收一个参数,这个参数可以是<c:forEach>标签的items属性支持的任何类型,包括任意类型的数组、java.util.Collection、java.util.Iterator、java.util.Enumeration、java.util.Map等类的实例对象和字符串。
如果fn:length函数的参数为null或者是元素个数为0的集合或数组对象,则函数返回0;如果参数是空字符串,则函数返回0;
fn:split
fn:split函数以指定字符串作为分隔符,将一个字符串分割成数组并返回这个字符串数组。
fn:split函数接收两个字符串类型的参数,第一个参数表示要分割的字符串,第二个参数表示作为分隔符的字符串。
例如:fn:split("www.it315.org",".")[1]的返回值为字符串"it315"
fn:join
fn:join函数以一个字符串作为分隔符,将一个字符串数组中的所有元素合并为一个字符串并返回合并后的结果字符串。fn:join函数接收两个参数,第一个参数是要操作的字符串数组,第二个参数是作为分隔符的字符串。
如果fn:join函数的第二个参数是空字符串,则fn:join函数的返回值直接将元素连接起来。例如:
假设stringArray是保存在Web域中的一个属性,它表示一个值为{"www","it315","org"}的字符串数组,则fn:join(stringArray,".")返回字符串"www.it315.org"
fn:join(fn:split("www,it315,org",","),".") 的返回值为字符串"www.it315.org"
fn:indexOf
fn:indexOf 函数返回指定字符串在一个字符串中第一次出现的索引值,返回值为int类型。fn:indexOf函数接收两个字符串类型的参数,如果第一个参数字符串中包含第二个参数字符串,那么不管第二个参数字符串在第一个参数字符串中出现几次,fn:indexOf函数总是返回第一次出现的索引值;如果第一个参数中不包含第二个参数,则fn:indexOf函数返回-1。如果第二个参数为空字符串,则fn:indexOf函数总是返回0。
例如:fn:indexOf("www.it315.org","t3") 的返回值为5
fn:contains
fn:contains函数检测一个字符串中是否包含指定的字符串,返回值为布尔类型。fn:contains函数在比较两个字符串是否相等时是大小写敏感的。
fn:contains函数接收两个字符串类型的参数,如果第一个字符串中包含第二个参数字符串返回true,否则返回false。如果第二个参数的值为空字符串,则fn:contains函数总是返回true。实际上,fn:contains(string,substring)等价于fn:indexOf(string,sunstring)!= -1
忽略大小写的EL函数:fn:containsIgnoreCase
fn:startsWith
fn:startsWith 函数用于检测一个字符串是否以指定的字符串开始的,返回值为布尔类型。
fn:startsWith 函数接收两个字符串类型的参数,如果第一个参数字符串以第二个参数字符串开始,则函数返回true,否则函数返回false。如果第二个参数为空字符串,则fn:startsWith函数总是返回true。例如:
fn:startsWith("www.it315.org","it315")的返回值为false
与之对应的EL函数:fn:endsWith
fn:replace
fn:replace函数将一个字符串中包含的指定字符串替换为其他的指定字符串,并返回替换后的结果字符串。fn:replace("www it315 org"," ",".")的返回值为字符串"www.it315.org"
fn:substring
fn:substring 函数用于截取一个字符串的子字符串并返回截取到的子字符串。fn:substring函数接收三个参数,第一个参数是用于指定要操作的源字符串,第二个参数是用于指定截取子字符串开始的索引值,第三个参数是用于指定截取子字符串结束的索引值,第二个参数和第三个参数都是int类型,其值都从0开始例如:
fn:substring("www.it315.org",4,9)的返回值为字符串"it315"
fn:substringAfter
fn:substringAfter函数用于截取并返回一个字符串中的指定字符串第一次出现之后的子字符串。fn:substringAfter函数接收两个字符串类型的参数,第一个参数表示要操作的源字符串,第二个参数表示指定的子字符串。
fn:substringAfter("www.it315.org",".")的返回值为字符串"it315.org"
与之对应的EL函数为:fn:substringBefore
自定义EL函数开发步骤
EL自定义函数开发与应用包括以下三个步骤:
编写一个Java类,方法必须是静态方法。
在WEB-INF目录下新建一个tld的文件。
没有标签的提示,复制http://java.sun.com/xml/ns/j2ee , 合并名称
设置2.0,设置url和shortname
编写标签库描述符(tld)文件,在tld文件配置自定义函数
使用function标签配置自定义函数。
使用name标签配置方法名(可以任意)
使用function-class标签配置类的全路径
使用function-signature 标签配置返回值类型(中间有空格)方法名称(参数类型)
在JSP页面中导入和自定义函数
开发EL function注意事项
编写标签库描述文件后,需要将它放置到<web应用>\WEB-INF目录中或WEB-INF目录下的除了classes和lib目录之外的任意子目录中。
TLD文件中的<uri>元素用指定该TLD文件的URI,在JSP文件中需要通过这个URI来引入该标签库描述文件。
<function>元素用于描述一个EL自定义函数,其中:
<name>子元素用于指定EL自定义函数的名称。
<funtion-class>子元素用于指定完整的Java类名。
<function-signature>子元素用于指定Java类中的静态方法的签名,方法签名必须指明方法的返回类型以及各个参数的类型,各个参数之间用逗号分隔。
实例如下:
在src下新建一个cn.itcast.el的包,在包内新建ElDemo1.java
代码如下:
package cn.itcast.el;
public class ElDemo1 {
public static String sayHello(String name){
return "hello "+name;
}
}
在WebRoot/WEB-INF下新建myfn的tld文件 并进行配置:配置后的代码如下:
<?xml version="1.0" encoding="UTF-8"?>
<taglib version="2.0" xmlns="http://java.sun.com/xml/ns/j2ee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-jsptaglibrary_2_0.xsd">
<tlib-version>1.0</tlib-version>
<short-name>myfn</short-name>
<uri>http://www.itcast.cn/1110/myfn</uri>
<!-- 配置自定义的EL函数 -->
<function>
<!-- 配置方法名称 -->
<name>sayHi</name>
<!-- 方法所在的类 -->
<function-class>cn.itcast.el.ElDemo1</function-class>
<!-- 配置方法的签名 -->
<function-signature>java.lang.String sayHello(java.lang.String)</function-signature>
</function>
</taglib>
在WebRoot根目录下新建el文件夹,在里面新建demo.jsp<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn" %>
<%@ taglib uri="http://www.itcast.cn/1110/myfn" prefix="myfn" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
${ fn:length("abcdefg") }
${ fn:toUpperCase("abcdefg") }
${ myfn:sayHi("小风") }
</body>
</html>
自定义标签
自定义标签简介
自定义标签主要用于移除JSP页面中的Java代码,提高代码的复用性
使用自定义标签移除jsp页面在奇偶那个的Java代码,只需要完成以下步骤:
- 编写一个实现Tag接口的Java类(标签处理器)
- 编写标签库描述符(tld)文件,在tld文件中对标签处理器类描述成一个标签
- 参考Tomcat中example项目中的JSP部分
因为企业业务需求是多种多样的,所以常见开源框架只能提供通用的Java代码功能,如果实现既定业务逻辑功能,需要自定义标签。通过自定义标签(简化复杂业务开发)
简单标签
由于传统标签使用三个标签接口来完成不同的功能,显得过于繁琐,不利于标签技术的推广,SUN公司为降低标签技术的学习难度,在JSP2.0中定义了一个更为简单、便于编写和调用的SimpleTag接口来实现标签的功能。实现SimpleTag接口的标签通常称为简单标签。
SimpleTag方法介绍
- setJspContext方法:用于把JSP页面的pageContext对象传递给标签处理器对象。
- setParent方法:用于把父标签处理器对象传递给当前标签处理器对象。
- getParent方法:用于获得当前标签的父标签处理对象。
- setJspBody方法:用于把代表标签体的JspFragment对象传递给标签处理器对象。即传入标签体缓存对象(封装了标签体内容)
- doTag方法:用于完后所有标签逻辑,包括输出、迭代、修改标签体内容等。在doTag方法中可以抛出javax.servlet.jsp.SkipPageException异常,用于通知WEB容器不再执行JSP页面中位于结束标记后面的内容,这等效于在传统标签的doEndTag方法中返回Tag.SKIP_PAGE常量的情况。
简单标签库开发常用功能,实现SimpleTag接口标签类(SimpleTag JSP2.0 之后为了简化标签开发提供的)
编写简单的标签类,只需要继承SimpleTagSupport类
setJspContext 传入pageContext对象
setParent 传入父标签对象
setJSPBody 传入标签体缓存对象(封装了标签体内容)
doTag (执行标签,在该方法中编写标签代码逻辑)
在setJSPBody方法中传入对象JSPFragment,该对象封装了标签体内容,控制标签体内容输出
最重要方法invoke(Writer out) 意义将标签体内容输出到指定字符输出流中。
注意:在简单标签库中<bodyContent>不能写JSP,而必须写scriptless
SimpleTag接口方法的执行顺序:
当web容器开始执行标签时,会调用如下方法完成标签的初始化:
- WEB容器调用标签处理器对象的setJspContext方法,将代表JSP页面的pageContext对象传递给标签处理器对象。
- WEB容器调用标签处理器对象的setParent方法,将父标签处理器对象传递给这个标签处理器对象。注意,只有在标签存在父标签的情况下,WEB容器才会调用这个方法。
- 如果调用标签时设置了属性,容器将调用每个属性对应的setter方法把属性值传递给标签处理器对象。如果标签的属性值是EL表达式,则WEB容器首先计算表达式的值,然后把值传递给标签处理器对象。
- 如果简单标签有标签体,容器将调用setJSPBody方法把代表标签体的JspFragment对象传递进来。
执行标签时:
- 容器调用标签处理器的doTag() 方法,开发人员在方法内通过操作JspFragment对象,就可以实现是否执行、迭代、修改标签体的目的。
JSPFragment类
(1)javax.servlet.jsp.tagext.JspFragment类是在JSP2.0中定义的,它的实例对象代表JSP页面中的一段符合JSP语法规范的JSP片段,这段JSP片段中不能包含JSP脚本元素。
(2)WEB容器在处理简单标签的标签体时,会把标签体内容用一个JspFragment对象表示,并调用标签处理器对象的setJSPBody方法把JspFragment对象传递给标签处理器对象。JspFragment类中只定义了两个方法,如下所示:
getJspContext方法:用于返回代表调用页面的JspContext对象——pageContext
public abstract void invoke(java.io.Writer out) 输出标签内容
用于执行JspFragment对象所代表的JSP代码片段
参数out用于指定将JspFragment对象的执行结果写入到哪个输出流对象中,如果传递给参数out的值为null,则 将执行结果写入到JspContext.getOut()方法返回的输出流对象中。(简而言之,可以理解为写给浏览器)
invoke方法详解
JspFragment.invoke方法是JspFragment最重要的方法,利用这个方法可以控制是否执行和输出标签体的内容、是否迭代执行标签体的内容或对标签体的执行结果进行修改后再输出。例如:
- 在标签处理器中如果没有调用JspFragment.invoke方法,其结果就相当于忽略标签体内容;
- 在标签体处理器中重复调用JspFragment.invoke方法,则标签体内容会被重复执行;
- 若想在标签处理器中修改标签体内容,只需在调用invoke方法时指定一个可取出结果数据的输出流对象(例如:StringWriter),让标签体的执行结果输出到该输出流对象中,然后从该输出流对象中取出数据进行修改后在输出到目标设备,即可达到修改标签体的目的。
例:控制标签后的jsp页面是否执行
doTag
throw new SkipPageException
TLD配置
<tag>
<name>demo2</name>
<tag-class>simple.MyTag2</tag-class>
<body-content>empty</body-content>
</tag>
自定义标签入门
需求:自定义标签<myc:print>在页面中输出hello
步骤一:编写标签类
编写一个类,继承SimpleTagSupport
重写两个方法,doTag()和setJspContext(JspContext pc)
通过JspContext 对象可以获取out对象向外输出内容
步骤二:提供tld文件进行配置
通过<tag>标签配置自定义标签
配置标签名称<name>print</name>
配置标签的类<tag-class>xxx.xxx.xxx</tag-class>
配置标签体的内容<body-content>empty</body-content>
步骤三:在JSP页面中引入该标签库
自定义带有标签体的标签
需求:自定义标签<myc:out>在页面中输出内容
步骤一:编写标签类
编写一个类,继承SimpleTagSupport
重写两个方法,doTag()和setJspContext(JspContext pc)
在doTag()中通过getJspBody()获取JspFragment标签体,调用invoke(null)
步骤二:提供tld文件进行配置
通过<tag>标签配置自定义标签
配置标签名称<name>out</name>
配置标签的类<tag-class>xxx.xxx.xxx</tag-class>
配置标签体的内容<body-content>scriptless</body-content>
步骤三:在JSP的页面中引入该标签库
Body-content的配置
<body-content>元素的可选值有:
- empty:不能有标签体内容
- JSP:标签体内容可以是任何东西:EL、JSTL、<%= %>、<%%>,以及HTML;但不建议使用Java代码段,SimpleTag已经不再支持使用<body-content>JSP</body-content>
- scriptless:标签体内容不能是Java代码段,但可以是EL、JSTL等。
- tagdependent:标签体内容不做运算,由标签处理类自行处理,无论标签体内容是EL、JSP、JSTL,都不会做运算。
自定义带有属性的标签
需求:自定义标签<myc:if>在页面中输出内容
步骤一:编写标签类
编写一个类,继承SimpleTagSupport类。
提供页面的属性,设置成boolean类型。并提供set方法,页面的属性与类中的属性名称必须相同。
重写doTag()方法,判断属性是否为true,通过getJspBody()获取JspFragment标签体,调用invoke(null)
步骤二:提供tld文件进行配置
通过<tag>标签配置自定义标签
配置标签名称<name>if</name>
配置标签的类<tag-class>xxx.xxx.xxx</tag-class>
配置标签体的内容<body-content>scriptless</body-content>
配置属性<attribute>在属性中配置其他信息</attribute>
步骤三:在JSP页面中引入标签库
attribute的配置
(1)配置属性名称
<name>test</name>
(2)属性是否是必须的
<required>true</required>
(3)配置是否支持EL表达式
<rtexprvalue>true</rtexprvalue>
(4)配置属性的类型
<type>boolean</type>
在TLD中描述标签属性attribute
元素名 | 是否必须指定 | 描述 |
description | 否 | 用于指定属性的描述信息 |
name | 是 | 用于指定属性的名称。属性名称是大小写敏感的,并且不能以jsp、 _jsp、java和sun开头 |
required | 否 | 用于指定在JSP页面中调用自定义标签时是否必须设置这个属性。其 取值包括true和false,默认值是false,true表示必须设置,否则可以 设置也可以不设置该属性。 |
rtexprvalue | 否 | rtexprvalue是runtime expression value(运行时表达式)的英文简写, 用于指定属性值是一个静态值或动态值。其取值包括true和false,默认值 是false,false表示只能为该属性指定静态文本值,例如"123"; true表示可 以为该属性指定一个JSP动态元素,动态元素的结果作为属性值,例如 JSP表达式<%=value %> |
type | 否 | 用于指定属性值的Java类型。默认是String |
标签的一个属性,自定义标签所具有的每个属性
都要对应一个<attribute>元素 。
<attribute>
<description>description</description>
<name>aaaa</name>
<required>true</required>
<rtexprvalue>true</rtexprvalue>
<type>ObjectType</type>
</attribute>
实例如下:
在WEB项目的src目录下新建cn.itcast.tag包,在包内新建三个标签实现类
TagDemo1.java (没有标签体的自定义标签)
package cn.itcast.tag;
import java.io.IOException;
import javax.servlet.jsp.JspContext;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.PageContext;
import javax.servlet.jsp.tagext.SimpleTagSupport;
/**
* 对外输出Hello
* @author Administrator
*
*/
public class TagDemo1 extends SimpleTagSupport{
private PageContext pc;
public void doTag() throws JspException, IOException {
pc.getOut().write("Hello");
}
/**
* 服务器默认先执行该方法
*/
public void setJspContext(JspContext pc) {
this.pc = (PageContext) pc;
}
}
TagDemo2.java (有标签体 处理标签体内容)package cn.itcast.tag;
import java.io.IOException;
import java.io.StringWriter;
import javax.servlet.jsp.JspContext;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.PageContext;
import javax.servlet.jsp.tagext.JspFragment;
import javax.servlet.jsp.tagext.SimpleTagSupport;
/**
* 带有标签主体
* @author Administrator
*
*/
public class TagDemo2 extends SimpleTagSupport{
private PageContext pc;
public void doTag() throws JspException, IOException {
JspFragment jf = getJspBody();
StringWriter sw = new StringWriter();
//通过invoke方法将标签体内容写入到参数Writer对象sw中
jf.invoke(sw);
// 获取标签体内容
String content = sw.toString().toUpperCase();
pc.getOut().print(content);
}
public void setJspContext(JspContext pc) {
this.pc = (PageContext)pc;
}
}
TagDemo3.java (有属性 有标签体的自定义标签)package cn.itcast.tag;
import java.io.IOException;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.tagext.SimpleTagSupport;
/**
* 类似<c:if>标签,带有属性的
* @author Administrator
*
*/
public class TagDemo3 extends SimpleTagSupport{
private boolean test;
public void setTest(boolean test) {
this.test = test;
}
public void doTag() throws JspException, IOException {
if(test){
getJspBody().invoke(null);
}
}
}
在WebRoot/WEB-INF 目录下新建myc.tld文件
配置内容如下:
<?xml version="1.0" encoding="UTF-8"?>
<taglib version="2.0" xmlns="http://java.sun.com/xml/ns/j2ee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-jsptaglibrary_2_0.xsd">
<tlib-version>1.0</tlib-version>
<short-name>myc</short-name>
<uri>http://www.itcast.cn/1110/myc</uri>
<!-- 配置自定义标签 -->
<tag>
<!-- 配置标签名称 -->
<name>print</name>
<!-- 配置标签的类 -->
<tag-class>cn.itcast.tag.TagDemo1</tag-class>
<!-- 配置标签主体 -->
<body-content>empty</body-content>
</tag>
<!-- 配置自定义标签 -->
<tag>
<!-- 配置标签名称 -->
<name>out</name>
<!-- 配置标签的类 -->
<tag-class>cn.itcast.tag.TagDemo2</tag-class>
<!-- 配置标签主体 -->
<body-content>scriptless</body-content>
</tag>
<!-- 配置自定义标签 -->
<tag>
<!-- 配置标签名称 -->
<name>if</name>
<!-- 配置标签的类 -->
<tag-class>cn.itcast.tag.TagDemo3</tag-class>
<!-- 配置标签主体 -->
<body-content>scriptless</body-content>
<!-- 配置属性 -->
<attribute>
<!-- 配置属性名称 -->
<name>test</name>
<!-- 属性是否是必须的 -->
<required>true</required>
<!-- 是否支持EL表达式 -->
<rtexprvalue>true</rtexprvalue>
<!-- 属性的类型 -->
<type>boolean</type>
</attribute>
</tag>
</taglib>
在WebRoot下新建tag文件夹,新建tag.jsp 测试自定义标签内容<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib uri="http://www.itcast.cn/1110/myc" prefix="myc" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
<myc:print/>
<myc:out>
liuxun1993
</myc:out>
<c:set var="i" value="10"></c:set>
<myc:if test="${ i eq 10 }">
美美
</myc:if>
</body>
</html>
启动服务器,运行结果如下:上一篇: 开发带标签体的标签实战
下一篇: JS实现的瀑布流