Table of Contents

  1. 前言
  2. JSP 与 Servlet
    1. JSP 初始化参数
  3. 脚本元素
    1. page 指令
    2. 禁用脚本元素
  4. EL 表达式
    1. EL 函数
    2. taglib 指令
  5. 标记
    1. TLD 文件的位置
    2. 标准动作
    3. 第三方标记库
    4. 定制标记
      1. 标记文件
      2. 简单标记处理器
      3. 标记的属性与体
  6. include 指令
  7. 结语

前言

在接触 Servlet 和 JSP 之前我一直觉得两者中应该是 Servlet 比较难,接触了之后才发现,JSP 的东西怎么那么多啊?

但是,进行了简单的梳理后,发现 JSP 的东西虽然多,但是理清了以后其实也不是很难。

这篇博客的内容便是对 JSP 相关内容的简单总结。

注意:

  1. 这篇博客中按照自己的理解将 JSP 的相关内容分为了四大元素1:脚本元素、指令、EL 表达式、标记
  2. 这是篇偏向总结的博客,关于 JSP 各个元素的使用的内容并不多

JSP 与 Servlet

JSP 和 Servlet 的关系十分紧密,可以说 Servlet 是学习 JSP 的前置基础,这可能也是诸多教程将 Servlet 放在 JSP 前面的原因之一。

默认情况下,在初次访问某个 JSP 的时候,这个 JSP 将会被容器编译为 Servlet2,然后和一般的 Servlet 一样,容器将会创建这个从 JSP 编译过来的 Servlet 的实例。

然后将 请求对象响应对象 交给这个实例的 Service 方法进行处理。

也就是说,你编写的 JSP 在最后将会被编译为 Java 代码,其中的 HTML 将会被当做字符串直接写入响应,其他元素也会进行相应的处理。

这便意味着,JSP 可以和一般的 Servlet 一样访问 ServletContext,也可以拥有自己的 ServletConfig!

进一步的,只要进行简单的处理,我们就可以直接在 JSP 中访问请求、响应、上下文等对象。

JSP 初始化参数

JSP 初始化参数的配置和 Servlet 初始化参数的配置基本相同,只需要一点简单的修改:

<servlet>
<servlet-name>name</servlet-name>
<servlet-class>...</servlet-class>
<init-param>
<param-name>...</param-name>
<param-value>...</param-value>
</init-param>
</servlet>

上面这是配置 Servlet 初始化参数的方式,JSP 只需要将 servlet-class 修改为 jsp-file 就可以了:

<servlet>
<servlet-name>name</servlet-name>
<jsp-file>/example.jsp</jsp-file>
<init-param>
<param-name>...</param-name>
<param-value>...</param-value>
</init-param>
</servlet>

脚本元素

脚本元素应该是很多人初学 JSP 接触的第一个 JSP 元素,其组成为:scriptlets(<% %>)、scriptlet expressions(<%= %>) 和 scriptlet declarations(<%! %>)。

脚本元素 scriptlets 的功能为:嵌入 Java 代码,当 JSP 被编译为 Servlet 时,相应的 Java 代码会被直接嵌入到 Service 方法体中。

脚本元素 scriptlet expressions 的功能为:将 Java 表达式的结果转化为字符串写入响应。

脚本元素 scriptlet declarations 的功能为:声明这个 JSP 对应的 Servlet 的字段与方法。

这里我们可以来看一个简单的例子,对于如下的 JSP 文件来说:

<html>
<head>
<title>Example</title>
<meta http-equiv="content-type" content="text/html" charset="utf-8" />
</head>
<body>
<!-- scriptlets -->
<%
for (int i = 0; i < 10; ++i) {
out.println(i);
}
%> <!-- scriptlet expressions -->
<%= Math.random() %> <!-- scriptlet declarations -->
<%!
int count = 0; public int getCount() {
return count;
}
%>
</body>
</html>

将编译后的结果简化可以得到如下代码:

public final class hello_jsp {
int count = 0; public int getCount() {
return count;
} public void _jspService(final javax.servlet.http.HttpServletRequest request, final javax.servlet.http.HttpServletResponse response)
throws java.io.IOException, javax.servlet.ServletException { out.write("<html>\r\n");
out.write(" <head>\r\n");
out.write(" <title>Example</title>\r\n");
out.write(" <meta http-equiv=\"content-type\" content=\"text/html\" charset=\"utf-8\" />\r\n");
out.write(" </head>\r\n");
out.write("\t<body>\r\n");
out.write(" <!-- scriptlets -->\r\n");
out.write(" "); // scriptlets
for (int i = 0; i < 10; ++i) {
out.println(i);
} out.write("\r\n");
out.write("\r\n");
out.write(" <!-- scriptlet expressions -->\r\n");
out.write(" ");
// scriptlet expressions
out.print( Math.random() );
out.write("\r\n");
out.write("\r\n");
out.write(" <!-- scriptlet declarations -->\r\n");
out.write(" ");
out.write("\r\n");
out.write("\t</body>\r\n");
out.write("</html>\r\n");
}
}

可以很清楚的看到脚本元素编译成的 Java 代码!

然后是脚本元素可以使用的隐式对象,这些对象就声明在 Service 方法体中:

// request, response, pageContext, session, application, config, out, page
public void _jspService(final javax.servlet.http.HttpServletRequest request, final javax.servlet.http.HttpServletResponse response)
throws java.io.IOException, javax.servlet.ServletException { final javax.servlet.jsp.PageContext pageContext;
javax.servlet.http.HttpSession session = null;
final javax.servlet.ServletContext application;
final javax.servlet.ServletConfig config;
javax.servlet.jsp.JspWriter out = null;
final java.lang.Object page = this; pageContext = _jspxFactory.getPageContext(this, request, response,
null, true, 8192, true);
application = pageContext.getServletContext();
config = pageContext.getServletConfig();
session = pageContext.getSession();
out = pageContext.getOut();
}

如果是 错误页面 的话,还会有一个 exception 隐式对象,但常用的隐式对象都在上面了。

可以看到,脚本元素的原理还是很简单的,就是将 Java 代码简单处理后直接放到代码中,算是四大元素中最没有逼格的一个 @_@

page 指令

由于指令和另外三大元素都有关系,而且有些元素对指令的依赖还很大,因此,指令的内容将会向这样拆分开来讲解。

使用指令时,我们是通过指令的 属性 来影响这个 JSP 页面的编译与使用,而使用脚本元素意味着我们编写的就是 Java 代码,因此可以通过 page 指令的 import 属性告诉容器这个 JSP 需要那些而外的依赖,容器将会把定义的 import 语句增加到生成的 Servlet 类代码中。

比如这样的一个 page 指令:

<%@ page import="java.util.List, java.util.Map" %>

生成的 Servlet 类代码中将会包含:

import java.util.List;
import java.util.Map;

page 指令还用其他一些属性,比如属性 pageEncoding 可以设置当前页面的编码,避免中文乱码。

禁用脚本元素

当我们在 DD3 添加如下配置的时候就会使得脚本元素无法使用(用了就会出错):

<jsp-config>
<jsp-property-group>
<url-pattern>*.jsp</url-pattern>
<scripting-invalid>true</scripting-invalid>
</jsp-property-group>
</jsp-config>

EL 表达式

EL 表达式很简单,尤其是对于使用者来说,只需要记住一些简单的语法便可以直接上手使用,不需要像脚本元素那样需要会 Java。

当然了,EL 表达式也仅仅只是表达式,当容器遇到 EL 表达式时,会计算这个表达式的结果并将其写入响应。

比如说 EL 表达式 ${1 + 3}, Tomcat 容器的处理方式是:

out.write((java.lang.String) org.apache.jasper.runtime.PageContextImpl.proprietaryEvaluate("${1 + 3}", java.lang.String.class, (javax.servlet.jsp.PageContext)_jspx_page_context, null));

因此,EL 表达式难以完成复杂的逻辑操作,这时,我们便可以使用 EL 函数或标记。

EL 函数

使用 EL 函数是很简单的,只需要使用 taglib 指令告诉容器你使用的 EL 函数来自什么地方:

<%@ taglib prefix="mine" uri="xxx" %>

${mine:random()}

困难的地方在于 EL 函数的创建:

  1. 你需要编写一个有 公共静态 方法的 Java 类,比如:

    public class Example {
    public static double method() {
    return Math.random();
    }
    }
  2. 然后,你需要编写一个 TLD4 文件建立 EL 函数和静态方法的映射:

    <taglib>
    <uri>xxx</uri> <function>
    <name>random</name>
    <function-class>Example</function-class>
    <function-signature>
    double method()
    </function-signature>
    </function> </taglib>

关键在于这个 TLD 文件中的内容,TLD 文件中的 uri 就是 taglib 指令使用的 uri, 而 function 部分告诉容器可以在什么地方找到这个函数。

也就是说是通过 TLD 文件的 function 标签建立 EL 函数名和实际的函数之间的映射。

某种程度上,EL 函数也不复杂,但主要问题在于 EL 函数的映射是借助 TLD 文件建立的,在 JSP 中使用也需要使用 taglib 指令,这和 标记 混杂在了一起。

taglib 指令

taglib 指令的使用更多是在使用标记的时候,但是 EL 函数却需要使用 taglib 指令来使用……

这个指令的常见形式如下:

<%@ taglib prefix="your-prefix" uri="..." %>

指令的 prefix 属性可以自己随便定义,而 uri 也只是一个标识,不一定需要是具体的路径,只要和 TLD 文件中定义的 uri 相同就可以了。

标记

标记应该是 JSP 中最复杂的一部分,在我的理解中,标准动作、第三方标记库、定制标记都属于标记。

这就意味着标记这一节需要掌握的东西很多,而且需要分清楚不同的内容之间的区别。

TLD 文件的位置

在进一步了解标记之前需要先来看看 TLD 文件可以放在那些地方:

  1. 直接放在 WEB-INF 目录下
  2. 直接放在 WEB-INF 目录的一个子目录下,比如说 WEB-INF/tlds
  3. WEB-INF/lib 下的一个 JAR 文件中的 META-INF 目录中
  4. WEB-INF/lib 下的一个 JAR 文件中的 META-INF 目录的子目录中

只要你将 TLD 文件放在这些目录中,容器就可以找到你自己定义的标记与 EL 函数。

标准动作

标记的语法比 EL 表达式还要简单,使用上的问题主要集中在标记的作用、属性与标记体上,因此这里将会略过快速标准动作的相关内容。

标准动作中存在一个比较特殊的动作:jsp:attribute,这个动作可以用来设置其父标记的属性值:

<prefix:name>
<jsp:attribute name="attributeName">value</jsp:attribute>
</prefix:name>

这个动作的特殊之处在于:即使父标记要求体为空,也任然可以通过 jsp:attribute 来设置父标记的属性值。

其他一些常用的标准动作:

<jsp:include>、<jsp:param>、<jsp:forward>、<jsp:useBean>、<jsp:setProperty>、<jsp:getProperty>

第三方标记库

这里的第三方标记库包括 JSTL,虽然说 JSTL 被叫做标准标记库,但它不是和标准动作不一样,不是内置的标记。

使用时和其他第三方标记库一样,需要将包含标记库的 jar 放到 WEB-INF/lib 目录。

如果你解压包含 JSTL 的 jar,就可以看到前面说的在 jar/META-INF 目录下的 TLD 文件了。

JSTL 使用时通常使用如下形式的 taglib 指令:

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

这个 uri 可以在 jstl.jar/META-INF/c.tld 文件中发现:

<taglib>
<uri>http://java.sun.com/jsp/jstl/core</uri>
</taglib>

定制标记

定制标记有三种方式:标记文件、简单标记处理器和传统标记处理器,这篇博客将只涉及标记文件和简单标记处理器。

标记文件

标记文件更像是可以通过标记语法进行包含的 JSP 文件,使用它的 tablib 指令也和一般的指令存在一定区别:

<%@ taglib prefix="prefix" tagdir="xxx" %>

假如你的标记文件是直接放在 WEB-INF/tags 目录或其子目录中,那么就可以通过 tagdir 属性指定标记文件的位置,使用时就可以通过 <prefix:tagFileName> 的方式使用。

如果你的标记文件在 jar 中,那么你就需要一个 TLD 文件来描述你的标记文件的位置:

<tagfile>
<name>tagName</name>
<path>/META-INF/tags/...</path>
</tagfile>

然后通过 uri 指定引用的标记文件,使用时通过 <prefix:tagName> 的方式使用。

在标记文件中,我们可以通过 attribute 指令声明属性,通过 tag 指令声明标记文件的体的限制(这两个指令只能在标记文件中使用):

<%@ attribute name="name" required="true" %>
<%@ tag body-content="tagdependent" %> <p>Hello ${name}, <jsp:doBoby /></p>

上面这个标记文件:

  1. 通过 attribute 指令声明了 name 属性,这个属性的值必须给出
  2. 通过 tag 指令说明了这个标记的题将会按原样取出放入 <jsp:doBody> 的位置

比如说,假如这个标记为 tag:example,那么如下内容:

<tag:example name="tony">${hello}</tag:example>

将会被翻译为:

<p>Hello tony, ${hello}</p>

简单标记处理器

简单标记处理器的实现还是比较简单的,只需要扩展 SimpleTagSupport 类就可以了:

public class MyTag extends SimpleTagSupport {
public void doTag() throws JspException, IOException {
...
}
}

标记处理器可以访问标记体、标记属性,也可以访问 PageContext 从而得到作用域属性和请求及响应。

一个简单的简单标记处理器需要:

  1. 实现 doTag 方法
  2. 对于所有在 TLD 文件中声明的属性,给出对应的 set 方法

简单标记处理器在 TLD 中的注册形式:

<tag>
<description>...</description>
<name>tagName</name>
<tag-class>package.className</tag-class>
<body-content>empty</body-content> <attribute>
<name>attrName</name>
<requirend>true</requirend>
<rtexprvalue>true</rtexprvalue>
</attribute>
</tag>

到目前为止,需要在 TLD 文件中注册的就有:EL 函数、标记文件、简单标记处理器:

<taglib>
<!-- EL 函数 -->
<function>...</function> <!-- 标记文件 -->
<tagfile>...</tagfile> <!-- 简单标记处理器 -->
<tag>...</tag>
</taglib>

标记的属性与体

在自定义标记的时候我们可以限制标记的属性与体的形式:

  • 当属性的 rtexprvaluefalse 时,属性值就只能是字符串字面量,为 true 时,可以有如下三个形式:

    <prefix:tag attr="${xxx}" %>
    <prefix:tag attr="<%= %>" %>
    <prefix:tag>
    <jsp:attribute name="attr">value</jsp:attribute>
    </prefix:tag>

    即:EL 表达式、脚本表达式、标准动作 jsp:attribute

  • 标记体的内容通过 body-content 进行限定,其值包括:

    含义
    empty 标记不能有体,但还是可以通过标准动作 设置属性
    scriptless 标记体不能有脚本元素(默认)
    tagdependent 标记体将被看做纯文本
    JSP 标记体支持一切 JSP 元素

对于标记文件来说,rtexprvalue 可以通过 attribute 指令的 rtexprvalue 属性设置,而标记文件的体不能有脚本元素,因此值只能是 scriptless、tagdependent 和 empty,可以通过 tag 指令的 body-content 属性进行设置。

include 指令

我们可以通过 include 指令将一个资源的内容增加到一个 JSP 中,作用相似的还有标准动作 <jsp:include> 和 JSTL 标记 <c:import>.

其中,include 指令告诉容器复制目标文件中的所有内容,并粘贴到使用这个指令的位置,这个过程在 JSP 编译为 Servlet 时便完成了:

<%@ include file="xxx.jsp" %>

jsp:include 和 <c:import> 都是在处理请求是动态的将资源内容增加到 JSP 中:

<jsp:include page="xxx.jsp" />
<c:import url="xxx" />

结语

这篇博客目前来说达到了自己的目的:梳理 JSP 相关的内容。

当然了,这是篇偏向总结的博客,关于 JSP 各个元素的使用的内容并不多,更多的是我在学习过程的感觉比较重要的内容的记录,以及较为困惑的内容的梳理。

……

Footnotes

1 其实算上 HTML 的话可以分为五大元素,但是 HTML 还是当做背景板好了

2 对于 Tomcat 来说,编译得到的类文件可以在 tomcat/work/Catalina/localhost/{your-web-app} 找到

3 Deployment Descriptor - 部署描述文件,也就是常用的 web.xml 文件

4 标记库描述文件,文件后缀为 tld

Java EE - JSP 小结的更多相关文章

  1. Java EE JSP内置对象及表达式语言

    一.JSP内置对象 JSP根据Servlet API规范提供了一些内置对象,开发者不用事先声明就可使用标准变量来访问这些对象. JSP提供了9种内置对象: (一).request 简述: JSP编程中 ...

  2. Java EE JSP编程基础

    一.JSP编程介绍 JSP是实现普通静态HTML和动态HTML混合编码的技术,可以说是Servlet的一种变形,相比Servlet它更像普通的Web页面.JSP在第一次运行时会花费很长时间,原因在与其 ...

  3. Java EE.JSP.脚本

    脚本是<%与%>之间Java语言编写的代码块. 1.输出表达式 <%=表达式%>输出表达式的计算结果. 2.注释 1)输出到客户端的注释:<!-comment-> ...

  4. Java EE.JSP.概述

    JSP最终会被转换成标准Servlet,该转换过程一般出现在第一次请求页面时. JSP页面的主要组成部分如下: HTML 脚本:嵌入Java代码 指令:从整体上控制Servlet的结构 动作:引入现有 ...

  5. Java EE - Servlet 小结

    Table of Contents 前言 Servlet 的生命周期 Servlet 的初始化 ServletContext & ServletConfig 请求的处理 HttpServlet ...

  6. Java EE.JSP.内置对象

    JSP根据Servlet API 规范提供了某些内置对象,开发者不用事先声明就可以使用标准的变量来访问这些对象.JSP提供了九中内置对象:request.response.out.session.ap ...

  7. Java EE.JSP.动作组件

    常见的JSP动作组件有以下几种: 1)<jsp:include>:在页面被请求的时候引入一个文件 2)<jsp:param>:在动作组件中引入参数信息 3)<jsp:fo ...

  8. Java EE.JSP.指令

    JSP的指令是从JSP向Web容器发送消息,它用来设置页面的全局属性,如输出内容类型等. JSP的指令的格式为:<%@ 指令名 属性="属性值"%> 1.page指令 ...

  9. eclipse java ee jsp tomcat Server文件夹给删了,怎么办?

    右键 选择属性 选择Switch location 就可以了

随机推荐

  1. Linux同步目录 保留文件修改时间和权限 rsync

    scp copy文件夹的时候,会强行覆盖文件,导致增量同步的时候不方便,而rsync则能很好解决这个问题. rsync -avz ubuntu@192.168.1.208:/home/ubuntu/m ...

  2. ERROR 3077 (HY000): To have multiple channels, repository cannot be of type FILE; Please check the repository configuration and convert them to TABLE.

    在5.7.16搭建多源复制时,出现如下错误:   mysql> change master to master_host='192.168.56.156',master_user='repl', ...

  3. 编译安装 mysql 5.5,运行 cmake报错Curses library not found

    是因为 curses库没有安装,执行下面的语句即可 yum -y install ncurses-devel 如果上述命令的结果是no package,则使用下面的命令安装 apt-get insta ...

  4. OnCollisionEnter和OnTriggerEnter

    之前对这两个的用法不是特别清楚,重新学习了下,再做个测试看看效果如何: 1.新建一个场景test 2.添加一个cube,点击Inspector面板会发现系统已经默认添加了Box collisder组件 ...

  5. echarts学习笔记(部分angular及ant-design)

    1.在项目中修改ng-zorro组件默认样式的一些方法: 类名等 前加::ng-deep: 类名等 前加:root: 类名等 前加:host /deep/: 2.echarts横轴自定义时间粒度 两种 ...

  6. 字节流, FileOutputStream类,FileInputStream类,复制文件,字符流

    字节输出流OutputStream OutputStream此抽象类,是表示输出字节流的所有类的超类.操作的数据都是字节 基本方法: 子类可继承调用以上方法 FileOutputStream类 构造方 ...

  7. System.Web

    如果 using System.Web:还是调用不出来其中的类,请在引用的位子添加 System.Web  引用,有的版本不自带这个命名空间. 类: HttpResponse类       用于绘画验 ...

  8. Core Location :⽤用于地理定位

    Core Location :⽤用于地理定位 在移动互联⽹网时代,移动app能解决⽤用户的很多⽣生活琐事,⽐比如 导航:去任意陌⽣生的地⽅方 周边:找餐馆.找酒店.找银⾏行.找电影院 在上述应⽤用中, ...

  9. windows tensorflow 简单安装

    需要64位系统windows, 下载64位的python3.5 官网下载地址:https://www.python.org/downloads/release/python-352/ 百度云下载地址: ...

  10. Hibernate进阶学习4

    Hibernate进阶学习4 深入学习hibernate的查询语句 测试HQL查询 package com.hibernate.test; import com.hibernate.domain.Cu ...