Java EE - JSP 小结
Table of Contents
前言
在接触 Servlet 和 JSP 之前我一直觉得两者中应该是 Servlet 比较难,接触了之后才发现,JSP 的东西怎么那么多啊?
但是,进行了简单的梳理后,发现 JSP 的东西虽然多,但是理清了以后其实也不是很难。
这篇博客的内容便是对 JSP 相关内容的简单总结。
注意:
- 这篇博客中按照自己的理解将 JSP 的相关内容分为了四大元素1:脚本元素、指令、EL 表达式、标记
- 这是篇偏向总结的博客,关于 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 函数的创建:
你需要编写一个有 公共静态 方法的 Java 类,比如:
public class Example {
public static double method() {
return Math.random();
}
}
然后,你需要编写一个 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 文件可以放在那些地方:
- 直接放在
WEB-INF
目录下 - 直接放在
WEB-INF
目录的一个子目录下,比如说WEB-INF/tlds
- 在
WEB-INF/lib
下的一个 JAR 文件中的META-INF
目录中 - 在
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>
上面这个标记文件:
- 通过
attribute
指令声明了name
属性,这个属性的值必须给出 - 通过
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 从而得到作用域属性和请求及响应。
一个简单的简单标记处理器需要:
- 实现 doTag 方法
- 对于所有在 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>
标记的属性与体
在自定义标记的时候我们可以限制标记的属性与体的形式:
当属性的
rtexprvalue
为false
时,属性值就只能是字符串字面量,为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 小结的更多相关文章
- Java EE JSP内置对象及表达式语言
一.JSP内置对象 JSP根据Servlet API规范提供了一些内置对象,开发者不用事先声明就可使用标准变量来访问这些对象. JSP提供了9种内置对象: (一).request 简述: JSP编程中 ...
- Java EE JSP编程基础
一.JSP编程介绍 JSP是实现普通静态HTML和动态HTML混合编码的技术,可以说是Servlet的一种变形,相比Servlet它更像普通的Web页面.JSP在第一次运行时会花费很长时间,原因在与其 ...
- Java EE.JSP.脚本
脚本是<%与%>之间Java语言编写的代码块. 1.输出表达式 <%=表达式%>输出表达式的计算结果. 2.注释 1)输出到客户端的注释:<!-comment-> ...
- Java EE.JSP.概述
JSP最终会被转换成标准Servlet,该转换过程一般出现在第一次请求页面时. JSP页面的主要组成部分如下: HTML 脚本:嵌入Java代码 指令:从整体上控制Servlet的结构 动作:引入现有 ...
- Java EE - Servlet 小结
Table of Contents 前言 Servlet 的生命周期 Servlet 的初始化 ServletContext & ServletConfig 请求的处理 HttpServlet ...
- Java EE.JSP.内置对象
JSP根据Servlet API 规范提供了某些内置对象,开发者不用事先声明就可以使用标准的变量来访问这些对象.JSP提供了九中内置对象:request.response.out.session.ap ...
- Java EE.JSP.动作组件
常见的JSP动作组件有以下几种: 1)<jsp:include>:在页面被请求的时候引入一个文件 2)<jsp:param>:在动作组件中引入参数信息 3)<jsp:fo ...
- Java EE.JSP.指令
JSP的指令是从JSP向Web容器发送消息,它用来设置页面的全局属性,如输出内容类型等. JSP的指令的格式为:<%@ 指令名 属性="属性值"%> 1.page指令 ...
- eclipse java ee jsp tomcat Server文件夹给删了,怎么办?
右键 选择属性 选择Switch location 就可以了
随机推荐
- Linux同步目录 保留文件修改时间和权限 rsync
scp copy文件夹的时候,会强行覆盖文件,导致增量同步的时候不方便,而rsync则能很好解决这个问题. rsync -avz ubuntu@192.168.1.208:/home/ubuntu/m ...
- 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', ...
- 编译安装 mysql 5.5,运行 cmake报错Curses library not found
是因为 curses库没有安装,执行下面的语句即可 yum -y install ncurses-devel 如果上述命令的结果是no package,则使用下面的命令安装 apt-get insta ...
- OnCollisionEnter和OnTriggerEnter
之前对这两个的用法不是特别清楚,重新学习了下,再做个测试看看效果如何: 1.新建一个场景test 2.添加一个cube,点击Inspector面板会发现系统已经默认添加了Box collisder组件 ...
- echarts学习笔记(部分angular及ant-design)
1.在项目中修改ng-zorro组件默认样式的一些方法: 类名等 前加::ng-deep: 类名等 前加:root: 类名等 前加:host /deep/: 2.echarts横轴自定义时间粒度 两种 ...
- 字节流, FileOutputStream类,FileInputStream类,复制文件,字符流
字节输出流OutputStream OutputStream此抽象类,是表示输出字节流的所有类的超类.操作的数据都是字节 基本方法: 子类可继承调用以上方法 FileOutputStream类 构造方 ...
- System.Web
如果 using System.Web:还是调用不出来其中的类,请在引用的位子添加 System.Web 引用,有的版本不自带这个命名空间. 类: HttpResponse类 用于绘画验 ...
- Core Location :⽤用于地理定位
Core Location :⽤用于地理定位 在移动互联⽹网时代,移动app能解决⽤用户的很多⽣生活琐事,⽐比如 导航:去任意陌⽣生的地⽅方 周边:找餐馆.找酒店.找银⾏行.找电影院 在上述应⽤用中, ...
- windows tensorflow 简单安装
需要64位系统windows, 下载64位的python3.5 官网下载地址:https://www.python.org/downloads/release/python-352/ 百度云下载地址: ...
- Hibernate进阶学习4
Hibernate进阶学习4 深入学习hibernate的查询语句 测试HQL查询 package com.hibernate.test; import com.hibernate.domain.Cu ...