目录

1. JSP 简介

以往响应数据只能通过 Servlet 进行输出,都是硬编码在程序里,维护和变更起来非常不灵活,写起来也超累。

PrintWriter out = resp.getWriter();
out.print("<html>");
out.print("<head>");
out.print("    <title>Title</title>");
out.print("</head>");
out.print("<body>");
out.print("</body>");
out.print("</html>");

能不能我们就写前端页面,里面也能写 Servlet,然后由 Tomcat 自动生成具体的 Servlet 类。

刚好 JSP 就是这样,主要用来展示页面,顺带能执行 Java 代码,但业务逻辑还是 Servlet 写。JSP 全称 Jakarta Server Pages 以前叫 JavaServer Pages,也是一个 Java 当中的规范。在 Tomcat 中最新稳定版是实现了 3.1 版本,2024 年最新规范是 4.0。

Servlet SpecJSP SpecEL SpecWebSocket SpecAuthentication Spec (JASPIC)Apache Tomcat VersionLatest Released VersionSupported Java Versions
6.14.06.02.23.111.0.x11.0.0-M19 (alpha)17 and later
6.03.15.02.13.010.1.x10.1.2411 and later
4.02.33.01.11.19.0.x9.0.898 and later

Apache Tomcat® - Which Version Do I Want?

这是 JSP 3.1 API - Apache Tomcat® 10.1.24 对应 JSP API 文档,在后续使用过程中需要结合规范一起查阅使用。

创建 index.jsp。

<%--
  Created by IntelliJ IDEA.
  User: gbb
  Date: 2024/6/6
  Time: 11:08
  To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>

</body>
</html>

为了方便调试需要添加 Tomcat JSP 和 Servlet 实现类 jsp-api.jar,以支持 JSP 相关的语法,添加 servlet-api.jar 支持 Servlet 请求,原因是 JSP 本质上就是 Servlet 因此需要 Servlet 作为依赖。

部署后访问页面 Tomcat 会自动生成 index_jsp.java,并编译成 class 文件,而 index_jsp.java 这个类继承自 org.apache.jasper.runtime.HttpJspBase,最终发现 HttpJspBase 是继承自 HttpServlet,那说说明 JSP 就是 Servlet。

/*
 * Generated by the Jasper component of Apache Tomcat
 * Version: Apache Tomcat/10.0.22
 * Generated at: 2024-06-06 05:44:13 UTC
 * Note: The last modified time of this file was set to
 *       the last modified time of the source file after
 *       generation to assist with modification tracking.
 */
package org.apache.jsp;

import jakarta.servlet.*;
import jakarta.servlet.http.*;
import jakarta.servlet.jsp.*;

public final class index_jsp extends org.apache.jasper.runtime.HttpJspBase
    implements org.apache.jasper.runtime.JspSourceDependent,
                 org.apache.jasper.runtime.JspSourceImports {

  private static final jakarta.servlet.jsp.JspFactory _jspxFactory =
          jakarta.servlet.jsp.JspFactory.getDefaultFactory();

  private static java.util.Map<java.lang.String,java.lang.Long> _jspx_dependants;

  private static final java.util.Set<java.lang.String> _jspx_imports_packages;

  private static final java.util.Set<java.lang.String> _jspx_imports_classes;

  static {
    _jspx_imports_packages = new java.util.HashSet<>();
    _jspx_imports_packages.add("jakarta.servlet");
    _jspx_imports_packages.add("jakarta.servlet.http");
    _jspx_imports_packages.add("jakarta.servlet.jsp");
    _jspx_imports_classes = null;
  }

  private volatile jakarta.el.ExpressionFactory _el_expressionfactory;
  private volatile org.apache.tomcat.InstanceManager _jsp_instancemanager;

  public java.util.Map<java.lang.String,java.lang.Long> getDependants() {
    return _jspx_dependants;
  }

  public java.util.Set<java.lang.String> getPackageImports() {
    return _jspx_imports_packages;
  }

  public java.util.Set<java.lang.String> getClassImports() {
    return _jspx_imports_classes;
  }

  public jakarta.el.ExpressionFactory _jsp_getExpressionFactory() {
    if (_el_expressionfactory == null) {
      synchronized (this) {
        if (_el_expressionfactory == null) {
          _el_expressionfactory = _jspxFactory.getJspApplicationContext(getServletConfig().getServletContext()).getExpressionFactory();
        }
      }
    }
    return _el_expressionfactory;
  }

  public org.apache.tomcat.InstanceManager _jsp_getInstanceManager() {
    if (_jsp_instancemanager == null) {
      synchronized (this) {
        if (_jsp_instancemanager == null) {
          _jsp_instancemanager = org.apache.jasper.runtime.InstanceManagerFactory.getInstanceManager(getServletConfig());
        }
      }
    }
    return _jsp_instancemanager;
  }

  public void _jspInit() {
  }

  public void _jspDestroy() {
  }

  public void _jspService(final jakarta.servlet.http.HttpServletRequest request, final jakarta.servlet.http.HttpServletResponse response)
      throws java.io.IOException, jakarta.servlet.ServletException {

    if (!jakarta.servlet.DispatcherType.ERROR.equals(request.getDispatcherType())) {
      final java.lang.String _jspx_method = request.getMethod();
      if ("OPTIONS".equals(_jspx_method)) {
        response.setHeader("Allow","GET, HEAD, POST, OPTIONS");
        return;
      }
      if (!"GET".equals(_jspx_method) && !"POST".equals(_jspx_method) && !"HEAD".equals(_jspx_method)) {
        response.setHeader("Allow","GET, HEAD, POST, OPTIONS");
        response.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED, "JSP 只允许 GET、POST 或 HEAD。Jasper 还允许 OPTIONS");
        return;
      }
    }

    final jakarta.servlet.jsp.PageContext pageContext;
    jakarta.servlet.http.HttpSession session = null;
    final jakarta.servlet.ServletContext application;
    final jakarta.servlet.ServletConfig config;
    jakarta.servlet.jsp.JspWriter out = null;
    final java.lang.Object page = this;
    jakarta.servlet.jsp.JspWriter _jspx_out = null;
    jakarta.servlet.jsp.PageContext _jspx_page_context = null;


    try {
      response.setContentType("text/html;charset=UTF-8");
      pageContext = _jspxFactory.getPageContext(this, request, response,
                  null, true, 8192, true);
      _jspx_page_context = pageContext;
      application = pageContext.getServletContext();
      config = pageContext.getServletConfig();
      session = pageContext.getSession();
      out = pageContext.getOut();
      _jspx_out = out;

      out.write("\r\n");
      out.write("\r\n");
      out.write("<html>\r\n");
      out.write("<head>\r\n");
      out.write("    <title>Title</title>\r\n");
      out.write("</head>\r\n");
      out.write("<body>\r\n");
      out.write("\r\n");
      out.write("</body>\r\n");
      out.write("</html>\r\n");
    } catch (java.lang.Throwable t) {
      if (!(t instanceof jakarta.servlet.jsp.SkipPageException)){
        out = _jspx_out;
        if (out != null && out.getBufferSize() != 0)
          try {
            if (response.isCommitted()) {
              out.flush();
            } else {
              out.clearBuffer();
            }
          } catch (java.io.IOException e) {}
        if (_jspx_page_context != null) _jspx_page_context.handlePageException(t);
        else throw new ServletException(t);
      }
    } finally {
      _jspxFactory.releasePageContext(_jspx_page_context);
    }
  }
}

还可以看到 jsp 里的内容都在 _jspService 方法里被转换成 Servlet 代码。<%@ page contentType="text/html;charset=UTF-8" language="java" %> 被转成 response.setContentType("text/html;charset=UTF-8");。而 HTML 则是 out.write("...")

在 JSP 中要写 Java 需要用 <% ... %> 包裹起来,这会被转换成对应表达式,不包裹起来就是被当做字符串输出响应体中。

JSP 生命周期和 Servlet 的一模一样。

常见的指令如下

1.注释

<%--这是注释内容,生成式不会转换到类里--%>

2.添加类成员

<%! ... %>,中间的内容是在类中的,因此不能直接写表达式,类里面只能写方法和成员变量。

<%!
    private static final String test = "va";
%>

3.向页面上输出内容

<%=表达式%>,JSP 会将这句话转换成 out.print(表达式);,直接运行表达式在响应体中当做字符串输出。

<%="计算结果:" + 1 + 1%>

1.1 指令

前面看到的 <%@ page contentType="text/html;charset=UTF-8" language="java" %> 这种叫指令,会被翻译成 Servlet 代码,格式为 <%@指令名称 属性名称=属性值 属性名称=属性值%......>,根据指令名称有 page、include、taglib 这三种,主要就是学习这些指令对应的属性如何使用。

1.1.1 page

page 指令。

<%--
contentType 属性用于设置响应头 Content-Type 的值
language 属性用来告诉 Java 这个脚本使用 Java 规范来翻译,
而且默认值就是 java,因此 language 属性可以不用写上
如果写成其他值,则会报错。
--%>
<%@page contentType="text/html;charset=UTF-8" language="java" %>

<%--设置响应头 Content-Type charset 编码属性--%>
<%@page encoding="UTF-8" %>

<%--导入 Java 类,要注意要导入的类一定是在某个包下,没有包名无法成功导入--%>
<%@page import="package.class1" %>
<%@page import="package.class2, package.class3" %>

<%--关闭 Session--%>
<%page session="true"%>
<%--启用 Session--%>
<%page session="false"%>

<%--页面异常跳转到指定资源--%>
<%@page errorPage="/error.html" %>

<%--启动 exception 内置对象,来操作异常,默认不启用 --%>
<%@page isErrorPage="false"%>

<%--禁用当前 JSP 页面中所有 EL 表达式。不写此属性,默认值是 false 表示启用 EL 表达式--%>
<%@page isELIgnored="true"%>

1.1.2 include

include 指令,通过 file 属性来包含指定文件,这里文件路径必须填相对路径,而且这个属性中不能通过请求参数来动态包含指定文件。

1.测试静态包含

创建 static-include.jsp

<%--静态包含--%>
<%@ include file="realtiveURLspec.jsp" %>
static-include.jsp

创建 realtiveURLspec.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>test</title>
</head>
<body>
    <script>alert(1)</script>
</body>
</html>

访问 static-include.jsp,自动生成转换文件 static_002dinclude_jsp.java,里面会发现也包含 realtiveURLspec.jsp 的内容,并且还是按包含顺序排放。

out.write("\r\n");
out.write("\r\n");
out.write("\r\n");
out.write("\r\n");
out.write("<html>\r\n");
out.write("<head>\r\n");
out.write("    <title>test</title>\r\n");
out.write("</head>\r\n");
out.write("<body>\r\n");
out.write("    <script>alert(1)</script>\r\n");
out.write("</body>\r\n");
out.write("</html>\r\n");
out.write("\r\n");
out.write("static-include.jsp");

那到底可以包含哪些内容呢?其实没有限制一般都是一些 html 或者 jsp,如果 jsp 里面有其他 Java 代码一样会被执行,所以需要注意被包含的文件,别在语法上有冲突,比如包含和被包含的 jsp 中都定义同名变量,由于最后被放在一个 Java 代码中,同名被定义铁定报错。

2.测试动态文件包含

创建 dynamic-include.jsp。

<jsp:include page="realtiveURLspec.jsp" flush="true"></jsp:include>
static-include.jsp

创建 realtiveURLspec.jsp

<html>
<head>
    <title>test</title>
</head>
<body>
    <%= "test"%>
</body>
</html>

访问 dynamic-include.jsp 自动翻译生成 realtiveURLspec.java 和 dynamic-include.java 两个源文件,这跟静态的不太一样,各自独立,但是不影响最终展示效果。

jsp:include 标签里面还可以用子标签 jsp:param 想被包含的 jsp 中传参。

将 dynamic-include.jsp 改造,向 realtiveURLspec.jsp 传递三个参数,两个固定值的参数 parm1 和 parm2,一个动态参数 parm3,从请求参数中获取 testaa 的值。

<%@ page contentType="text/html;charset=UTF-8" %>
<jsp:include page="realtiveURLspec.jsp" flush="true">
    <jsp:param name="parm1" value="value1"/>
    <jsp:param name="parm2" value="value2"/>
    <jsp:param name="parm3" value="<%= request.getParameter(\"testaa\")%>"/>
</jsp:include>
static-include.jsp

改造 realtiveURLspec.jsp,被包含时输出这些参数。

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>test</title>
</head>
<body>
    <%= request.getParameter("parm1")%>
    <%= request.getParameter("parm2")%>
    <%= request.getParameter("parm3")%>
</body>
</html>

访问 localhost/dynamic-include.jsp?testaa=444,成功传递参数

<html>
<head>
    <title>test</title>
<body>
    value1
    value2
    444
</body>
</html>

static-include.jsp

1.2 JSP 与 Servlet 联动

怎么让 JSP 展示 Servlet 业务逻辑的数据。

用户点击前端,请求使发到 Servlet,通过 Servlet 处理完请求和业务逻辑后,将最终的数据封装起来,通过请求转发来共享请求域,JSP 在请求域中取出来数据进行展示。

2. JSP 内置对象

在 service 里面有九大内置对象可以直接调用,分别是四个作用域 pageContextrequestsessionapplication,响应处理对象 outresponseServlet,配置对象 config,异常对象 exception,当前 this 对象 page,Servlet 配置对象 config

pageContext 页面作用域

对应类:jakarta.servlet.jsp.PageContext

在 JSP 中四大作用域范围最小。

request 请求作用域

对应类:jakarta.servlet.http.HttpServletRequest

session 会话作用域

对应类:jakarta.servlet.http.HttpSession

JSP 会自动执行 pageContext.getSession(); 创建 SESSION,可以用 <%@page session="false" %> 禁用内置对象 session。

application 应用作用域

对应类:jakarta.servlet.ServletContext

out

对应类:jakarta.servlet.jsp.JspWriter

控制响应体内容。

response

对应类:jakarta.servlet.http.HttpServletResponse

config

对应类:jakarta.servlet.ServletConfig

用于配置相关的信息。

page

对应类:java.lang.Object

当前页面 Servlet 对象,相当于 this。

Exception

对应类:java.lang.Throwable

处理页面上异常用。

当页面上发生异常了,可以用下面方法将异常打印在日志中,不会抛在页面上。

<%
execption.printStackTrace()
%>

3. EL 表达式

Expression Language 表达式语言,是 JSP 语法的一部分,也有对应的规范,2024 年目前最新的是 6.0 版本,作用是从 JSP 四个作用域中取属性值,并调用其 toString 方法转换成字符串输出到页面上,主要执行获取、转换、输出这三个动作,语法为 ${表达式}

比如 <%request.setAttribute("Objection", new user());%> 设置作用域,要输出到页面上可以用 <%=request.getAttribute("Objection")%>,这样写老长了,使用 EL 表达式 ${Objection} 就代表去可以简化这个过程,要写的代码少,清爽。

如果 Objection 有 getTest 方法,想在 EL 表达式中调用,需要写做 Objection.test 就可以,相当于方法名删掉 get,把大写 Test 转换成小写。

<%--调用 Objection 对象的 getTest 方法-->
${Objection.test}

调用其他方法还是写原有名称,比如里面有个 printName() 方法,跟平常对象调用没什么两样。如果要调用的属性名称写错了,取不到对象就自动返回,在页面返回空字符串,而不像 <%=request.getAttribute("...")%> 返回 null。

<%--调用 Objection 对象的 printName 方法-->
${Objection.printName()}

把点换成中括号也行,这些写法好处是属性名就算有特殊字符也可以取出来。

<%--调用 Objection 对象的 getTest 方法-->
${Objection["test"]}

<%--调用 Objection.sss 属性的对象-->
${Objection["sss"]}

中括号也可以获取数组、集合中的元素。

<%--获取数组、list 集合中第一个元素-->
${array[0]}

<%--获取 map 集合中 key 是 www 的值--%>
${map["www"]}

如果四个作用域中的属性名都一样,优先级是就近获取 pageContext -> request -> session -> application,实在找不到就往上找。就想在指定作用域中取可以加上这四个 EL 表达式中内置对象再获取。

${pageContextScope.test}
${requestScope.test}
${sessionScope.test}
${applicationScope.test}

EL 表达式还有个 pageContext 和 JSP 内置对象 pageContext 一样。

EL 表达式中内置对象 param、paramValues 用于获取请求参数。要注意 POST 或者 GET 都能获取。paramValues 本质上就是在用 ServletRequest.getParameterValues(String name),param 也是用 ServletRequest.getParameter(String name)

<%--获取请求参数 test 的值--%>
${param.test}

<%--获取请求参数名称为 test 的所有值,返回 String 数组,通过下标获取具体值--%>
${paramValues.test}

EL 表达式中内置对象 initParam,用于获取 web.xml 中 <context-param> 内配置的应用域参数,等同于用 ServletContext.getInitParameter(String name) 获取值。

<%--获取 <context-param> 内 <param-name> 为 test 的值,返回值是 String 类型。--%>
${initParam.test}

EL 表达式运算符

类别运算符备注
算数运算符+-*/%哪怕是字符串参与运算也尝试转成数字,转不了就抛异常。
关系运算符eq==
!=ne
>gt
>=ge
<lt
<=le
== 可用 eq 平替,其他的也是一样。判断是否相等本质上是调用对象的 equals 方法做比较。
逻辑运算符!not
&&and
or、`
`!not 都是将表达式运算结果 boolean 值取反。
条件运算符? :三元运算符,比如 1 == 1 ? true : false
取值运算符[].在数组中取值,比如 Object[0] 取 Object 中的第一个元素,或者是 Object["set.Age"]执行 Object 名为 set.Age 的方法 。
empty运算符empty判断对象值是否为空或者 null,这个空是指空字符串、数组、集合等等

4. JSTL 标签库

JSP Standard Tag Library (JSTL),替代了 <%表达式%> 这种 Java 代码和 HTML 混合的情况,提供了一种标签来替代 Java 代码的执行,可以很好的与 HTML 融合在一起,看起来比较美观。不过 JSTL 不在 JSP 默认实现中,需要单独下载包引入。

https://jakarta.ee/specifications/tags 找对应版本,这里最新是 3.0 版本,并且 3.0 介绍页 https://jakarta.ee/specifications/tags/3.0/ 专门写着 3.0 为 Release for Jakarta EE 10 发布,写到还要求 Java 版本最少 11 起步。

页面 Detail 标题内写着 Maven coordinates 里面就是对应各个版本 Maven 仓库地址。因为最新版规范是 3.0 那么我们就下载 3.0 版本的 jar 包 jakarta.servlet.jsp.jstl-api-3.0.0.jar,这个包下完还无法正常使用,它只定义了接口内容,要使用还需把实现引入。

根据网上信息发现 Maven 仓库,由 glassfish 的 jakarta.servlet.jsp.jstl 的 3.0.1 版本实现了 3.0 版本规范,怎么确定是实现的 3.0 呢?这里是通过引用看到依赖是 3.0.0 确认版本没错。

Provided Dependencies (4)

Category/LicenseGroup / ArtifactVersionUpdates
Java Spec EPL 2.0GPLjakarta.servlet.jsp.jstl » jakarta.servlet.jsp.jstl-api3.0.0

没问题后下载 jakarta.servlet.jsp.jstl-3.0.1.jar 添加到项目中即可完成依赖安装。

JSTL 有很多标签库,下面是所有标签库名称。

Functional AreaURIPrefix
corejakarta.tags.corec
XML processingjakarta.tags.xmlx
I18N capable formattingjakarta.tags.fmtfmt
relational db access (SQL)jakarta.tags.sqlsql
Functionsjakarta.tags.functionsfn

1.2. Multiple Tag Libraries

要想使用标签库中的标签,需要执行 taglib 指令引入对应标签库。

<%@ taglib uri="jakarta.tags.core" prefix="c" %>

taglib 属性 prefix,用来定义标签的前缀,比如 core 里面有个 if 标签,不加前缀是 <if></if>,加上标记后就变成 <c:if test=""></c:if>。JSTL 规范里默认使用 c 而已,大家随规范走,以后看到这个前缀就知道是哪个标签库里的标签。但是这个前缀是可以随意取的,没有限制。PS:前缀的作用是什么,为什么非得加上呢?

uri 对应一个 .tld 的 XML 文件,这个 TLD 全称是 Tag Library Descriptor (TLD),是对标签库中所有属性一个定义,可以理解成标签配置文件。PS:uri 属性 http://java.sun.com/jstl/core 和 jakarta.tags.core 区别是什么目前没搞清楚,但是看 JSTL 规范里使用的是 jakarta.tags.core。

<tag>
    <name>标签名</name>
    <tag-class>标签对应的实现类</tag-class>
    <body-content>标签里面能够写什么东西,比如 JSP,表示里面能写 JSP 语法的表达式</body-content>
    <description>
        对于标签的描述
    </description>
    <attribute>
        <name>标签属性名称</name>
        <required>属性名称是不是必须使用</required>
        <rtexprvalue>属性是否能够使用 EL 表达式 ${......}</rtexprvalue>
    </attribute>
    <attribute>
        <name>value</name>
        <required>false</required>
        <rtexprvalue>false</rtexprvalue>
    </attribute>
</tag>

4.1 core 标签库

最常用到的是 core,因为 JSP 只是做数据展示用,不需要用到什么 sql 处理之类库,这最好由后端做。如果还想学更多标签,推荐参照规范挨个练习。

常用的标签就是:if、foreach

4.1.1 if 标签

<c:if> 用于做判断,和基础语法中逻辑 if 一样。通过 TLD 文件可以看到 if 标签能够使用哪些属性。

test 属性,必填,值需要一个 boolean,如果为 true 就会把标签中的内容打印到响应体中,false 则不打印。

<c:if test="${1==1}">一确实等于一。</c:if>

<c:if test="${1==2}">一不等于二。</c:if>

var 属性,非必填,将test 判断的 boolean 结果默认存到 pageScope 作用域。

<c:if test="true" var="true1234" scope="page"></c:if>

scope 属性,非必填,以 var 的值作为属性,存到 page、request、session、context 这四大作用域其中一个。

<c:if test="true" var="true1234" scope="page">${pageScope.true1234}</c:if>
<c:if test="true" var="true1235" scope="request">${requestScope.true1235}</c:if>
<c:if test="true" var="true1236" scope="session">${sessionScope.true1236}</c:if>
<c:if test="true" var="true1237" scope="application">${applicationScope.true1237}</c:if>

要注意 JSTL 没有对应的 else 标签,只能通过 EL 表达式中比较运算符来控制表达式结果的布尔值,从而达到 else 的效果。

4.1.2 chose 标签

跟 switch 语法类似。结合 when 做条件判断,所有 when 的条件都不匹配,使用 otherwise 作为默认值。

<c:choose>
    <c:when test="${param.test > 199}">
        ${param.test} 大于 1
    </c:when>
    <c:when test="${param.test == 2}">
        ${param.test} 等于 2
    </c:when>
    <c:otherwise>
        ${param.test} 所有条件都不满足执行 otherwise
    </c:otherwise>
</c:choose>

使用时别陷入逻辑错误,when 条件要各自唯一互不影响,比如现有有两个条件二大于一和二等于二,分别对应执行不同内容,这时候只会执行二大于一的分支,因为前面条件按照顺序执行会发现分支一永远满足,不会执行二等于二的分支。。

<c:choose>
    <c:when test="${2 > 1}">
        2 大于 1
    </c:when>
    <c:when test="${2 == 2}">
        2 等于 2
    </c:when>
</c:choose>

4.1.3 forEach 标签

遍历数据用,两种用法,第一种结合 begin、end、setp、var 属性模拟传统的 for 循环,第二种结合 item 和 var 属性模拟 JDK5 新增的 for-each 循环。

for 循环。begin 属性是从什么数开始循环,最小的值是 0,end 属性是最后一个要循环的数,超过这个数就停止,setp 是步长,意思是每次循环执行几个数,var 属性是把当前循环的数存入 pageScope 作用域中。其实还有个 varStatus 属性,这个属性是把 var 属性 jakarta.servlet.jsp.jstl.core.LoopTagStatus 对象存入 pageScope 作用域,通过这个对象可以查询 var 属性对象的状态,用的少,等需要用再查文档。

<%--仅仅循环 0-9 十次--%>
<c:forEach begin="0" end="9" step="1" var="num">
    重复执行十次
</c:forEach>

<%--for 循环获取数组中的对象--%>
<%
    Collection<String> objs = new ArrayList<String>();
    objs.add("1a");
    objs.add("2b");
    objs.add("3c");
    request.setAttribute("objects", objs);
%>
<c:forEach begin="0" end="9" step="1" var="num" varStatus="testazzzz">
    ${objects[num]}
</c:forEach>

for-ech 循环。items 是要迭代的对象,每次迭代获取到的结果都放到 var 属性中,在标签体中方便使用。

<%--for-each--%>
<c:forEach items="${objects}" var="obj">
    ${obj}
</c:forEach>

最近更新:

发布时间:

摆哈儿龙门阵