在接触 Servlet 和 JSP 之前我一直觉得两者中应该是 Servlet 比较难,接触了之后才发现,JSP 的东西怎么那么多啊?
但是,进行了简单的梳理后,发现 JSP 的东西虽然多,但是理清了以后其实也不是很难。
这篇博客的内容便是对 JSP 相关内容的简单总结。
注意:
JSP 和 Servlet 的关系十分紧密,可以说 Servlet 是学习 JSP 的前置基础,这可能也是诸多教程将 Servlet 放在 JSP 前面的原因之一。
默认情况下,在初次访问某个 JSP 的时候,这个 JSP 将会被容器编译为 Servlet2,然后和一般的 Servlet 一样,容器将会创建这个从 JSP 编译过来的 Servlet 的实例。
然后将 请求对象 和 响应对象 交给这个实例的 Service
方法进行处理。
也就是说,你编写的 JSP 在最后将会被编译为 Java 代码,其中的 HTML 将会被当做字符串直接写入响应,其他元素也会进行相应的处理。
这便意味着,JSP 可以和一般的 Servlet 一样访问 ServletContext,也可以拥有自己的 ServletConfig!
进一步的,只要进行简单的处理,我们就可以直接在 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 代码简单处理后直接放到代码中,算是四大元素中最没有逼格的一个 @_@
由于指令和另外三大元素都有关系,而且有些元素对指令的依赖还很大,因此,指令的内容将会向这样拆分开来讲解。
使用指令时,我们是通过指令的 属性 来影响这个 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 表达式很简单,尤其是对于使用者来说,只需要记住一些简单的语法便可以直接上手使用,不需要像脚本元素那样需要会 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 函数是很简单的,只需要使用 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
指令的使用更多是在使用标记的时候,但是 EL 函数却需要使用 taglib 指令来使用……
这个指令的常见形式如下:
<%@ taglib prefix="your-prefix" uri="..." %>
指令的 prefix
属性可以自己随便定义,而 uri
也只是一个标识,不一定需要是具体的路径,只要和 TLD 文件中定义的 uri
相同就可以了。
标记应该是 JSP 中最复杂的一部分,在我的理解中,标准动作、第三方标记库、定制标记都属于标记。
这就意味着标记这一节需要掌握的东西很多,而且需要分清楚不同的内容之间的区别。
在进一步了解标记之前需要先来看看 TLD 文件可以放在那些地方:
WEB-INF
目录下WEB-INF
目录的一个子目录下,比如说 WEB-INF/tlds
WEB-INF/lib
下的一个 JAR 文件中的 META-INF
目录中WEB-INF/lib
下的一个 JAR 文件中的 META-INF
目录的子目录中只要你将 TLD 文件放在这些目录中,容器就可以找到你自己定义的标记与 EL 函数。
标记的语法比 EL 表达式还要简单,使用上的问题主要集中在标记的作用、属性与标记体上,因此这里将会略过快速标准动作的相关内容。
标准动作中存在一个比较特殊的动作:
<prefix:name>
<jsp:attribute name="attributeName">value</jsp:attribute>
</prefix:name>
这个动作的特殊之处在于:即使父标记要求体为空,也任然可以通过
其他一些常用的标准动作:
<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 从而得到作用域属性和请求及响应。
一个简单的简单标记处理器需要:
简单标记处理器在 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 表达式、脚本表达式、标准动作
标记体的内容通过 body-content
进行限定,其值包括:
值 | 含义 |
---|---|
empty | 标记不能有体,但还是可以通过标准动作 |
scriptless | 标记体不能有脚本元素(默认) |
tagdependent | 标记体将被看做纯文本 |
JSP | 标记体支持一切 JSP 元素 |
对于标记文件来说,rtexprvalue 可以通过 attribute 指令的 rtexprvalue 属性设置,而标记文件的体不能有脚本元素,因此值只能是 scriptless、tagdependent 和 empty,可以通过 tag 指令的 body-content 属性进行设置。
我们可以通过 include 指令将一个资源的内容增加到一个 JSP 中,作用相似的还有标准动作 <jsp:include>
和 JSTL 标记 <c:import>
.
其中,include 指令告诉容器复制目标文件中的所有内容,并粘贴到使用这个指令的位置,这个过程在 JSP 编译为 Servlet 时便完成了:
<%@ include file="xxx.jsp" %>
而
<jsp:include page="xxx.jsp" />
<c:import url="xxx" />
这篇博客目前来说达到了自己的目的:梳理 JSP 相关的内容。
当然了,这是篇偏向总结的博客,关于 JSP 各个元素的使用的内容并不多,更多的是我在学习过程的感觉比较重要的内容的记录,以及较为困惑的内容的梳理。
……
1 其实算上 HTML 的话可以分为五大元素,但是 HTML 还是当做背景板好了
2 对于 Tomcat 来说,编译得到的类文件可以在 tomcat/work/Catalina/localhost/{your-web-app}
找到
3 Deployment Descriptor - 部署描述文件,也就是常用的 web.xml
文件
4 标记库描述文件,文件后缀为 tld
原文:https://www.cnblogs.com/rgbit/p/10701238.html