8.2.2 ServletContextAttributeListener

当一个ServletContext范围的属性添加删除或者替换时,ServletContextAttributeListener接口的实现类会接收到消息。这个接口定义了如下三个方法:

1
2
3
void attributeAdded(ServletContextAttributeEvent event)
void attributeRemoved(ServletContextAttributeEvent event)
void attributeReplaced(ServletContextAttributeEvent event)
  • attributeAdded方法在一个ServletContext范围的属性被添加时被容器调用。
  • attributeRemoved方法在一个ServletContext范围的属性被删除时被容器调用。
  • attributeReplaced方法在一个ServletContext范围的属性被新的属性值替换时被容器调用。

这三个方法都能从参数列表中获取到一个ServletContextAttributeEvent的对象,通过这个ServletContextAttributeEvent对象可以获取属性的名称和值。
ServletContextAttributeEvent类继承自ServletContextAttribute,并且增加了下面两个方法分别用于获取该属性的名称和值:

1
2
java.lang.String getName()
java.lang.Object getValue()

8.2 ServletContext监听器

ServletContext简介

  • ServletContext官方叫servlet上下文。服务器会为每一个工程创建一个ServletContext对象。
  • ServletContext对象是一个全局的储存信息的空间,项目启动时该对象就存在了,项目关闭时才会释放该对象。

ServletContext的监听接口

ServletContext的监听器接口有两个:ServletContextListenerServletContextAttributeListener

8.2.1 ServletContextListener

Servlet 容器启动或终止Web 应用时,会触发ServletContextEvent事件,该事件由ServletContextListener来处理。
也就是说:ServletContextListener能对ServletContext的创建和销毁做出响应。

响应ServletContext的创建

ServletContext初始化时,容器会调用所有注册了ServletContextListeners接口的实例的contextInitialized方法。该方法如下:

1
void contextInitialized(ServletContextEvent event)

响应ServletContext的销毁

ServletContext将要销毁时,容器会调用所有注册了ServletContextListeners接口的实例的contextDestroyed方法。该方法如下:

1
void contextDestroyed(ServletContextEvent event)

contextInitialized方法和contextDestroyed方法都会从容器获取到一个ServletContextEventjavax.servlet.ServletContextEvent是一个java.util.EventObject的子类,它定义了一个访问ServletContextgetServletContext方法:

1
ServletContext getServletContext()

通过这个方法能够轻松地获取到ServletContext

实例

这个项目名为:app08a

下面的AppListener.java类实现了ServletContextListener接口,它在ServletContext刚创建时,将一个保存国家编码和国家名的Map放置到ServletContext中。

AppListener.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
package app08a.listener;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.ServletContext;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.annotation.WebListener;
//@WebListener注解表示当前类是一个事件监听器
@WebListener
public class AppListener
implements
ServletContextListener
{
@Override
public void contextDestroyed(ServletContextEvent sce)
{}
// 在项目载入服务器的时候被调用
@Override
public void contextInitialized(ServletContextEvent sce)
{
// 通过ServletContextEvent参数的getServletContext方法
// 获取ServletContext对象
ServletContext servletContext = sce.getServletContext();
//创建一个Map
Map<String,String> countries =
new HashMap<String,String>();
//添加数据到Map中
countries.put("ca", "Canada");
countries.put("us", "United States");
//设置该map对象为servletContext的`countries`属性
servletContext.setAttribute("countries", countries);
System.out.println("ServletContextListener."
+ "contextInitialized方法被调用");
}
}

在实现的contextInitialized方法中。先通过调用getServletContext方法从容器获得ServletContext实例,然后创建了一个Map用于保存国家编码和国家名,再将这个Map放置到ServletContext里。
在实际开发中,往往是把数据库里的数据放置到ServletContext里。

下面的countries.jsp用到了这个监听器。

countries.jsp页面

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<html>
<head>
<title>Country List</title>
</head>
<body>
We operate in these countries:
<ul>
<c:forEach items="${countries}" var="country">
<li>${country.value}</li>
</c:forEach>
</ul>
</body>
</html>

countries.jsp页面使用了JSTLforEach标签来迭代读取名为countriesmap里的数据。
JSTL的使用步骤请看这篇文章

运行效果

运行这个项目,查看控制台可以看到项目启动的时候就调用了contextInitialized方法了如下图所示:
这里有一张图片

可以通过下面的URL来访问这个JSP页面:
http://localhost:8080/app08a/countries.jsp
浏览器显示效果如下图所示:
这里有一张图片

小结

  • 每个web项目启动时,容器会为该Web项目创建对应一个对应的ServletContext对象.
  • 关闭该Web项目时,容器会销毁该Web项目对应的ServletContext对象.
  • ServletContext对象的创建和销毁会触发ServletContextEvent事件
  • ServletContextListeners接口的实例可以处理ServletContextEvent事件。
    • ServletContextListeners接口的实例的contextInitialized方法在ServletContext对象创建时候被调用。
    • ServletContextListeners接口的实例的contextDestroyed方法在ServletContext对象销毁的时候被调用

8.1 监听器接口和注册

监听器接口简介

监听器接口主要在 javax.servlet javax.servlet.http的包中。有以下这些接口:

  • javax.servlet.ServletContextListener:它能够响应ServletContext生命周期事件,它提供了ServletContext创建之后和ServletContext关闭之前的会被调用的方法。
  • javax.servlet.ServletContextAttributeListener:它能够响应ServletContext范围的属性添加删除替换事件。
  • javax.servlet.http.HttpSessionListener:它能够响应HttpSession的创建、超时和失效事件。
  • javax.servlet.http.HttpSessionAttributeListener:它能响应HttpSession范围的属性添加、删除、替换事件。
  • javax.servlet.http.HttpSessionActivationListener:它在一个HttpSession激活或者失效时被调用。
  • javax.servlet.http.HttpSessionBindingListener:可以实现这个接口来保存HttpSession范围的属性。当有属性从HttpSession添加或删除时,HttpSessionBindingListener 接口能够做出响应。
  • javax.servlet.ServletRequestListener:它能够响应一个ServletRequest的创建或删除。
  • javax.servlet.ServletRequestAttributeListener:它能响应ServletRequest范围的属性值添加、删除、修改事件。
  • javax.servlet.AsyncListener:一个用于异步操作的监听器,在第11章会进行更详细的介绍。

如何编写监听器

编写一个监听器,只需要写一个Java类来实现对应的监听器接口就可以了Servlet 3.0Servlet 3.1中提供了两种注册监听器的方法第一种是使用WebListener注解。例如:

1
2
3
@WebListener
public class ListenerClass implements ListenerInterface {
}

第二种方法是在部署描述文档中增加一个listener元素。

1
2
3
</listener>
<listener-class>fully-qualified listener class</listener-class>
</listener>

你可以在一个应用中添加多个监听器,这些监听器是同步工作的

第8章 监听器概述

Servlet API提供了一系列的事件和事件监听接口。上层的servlet/JSP应用能够通过调用这些API进行基于事件驱动的开发。这里监听的所有事件都继承自java.util.Event对象。**监听器接口可以分为三类:ServletContextHttpSession ServletRequest **。
本章介绍如何在servlet/JSP应用中使用监听器。Servlet 3.0中出现的新监听器接口——javax.servlet.AsyncListener 将在第11章进行介绍。

7.5 invoked动作

invoke动作元素和doBody类似,**在tag file中,可以使用它来调用一个fragment(代码片段)**。

invoke动作的属性

invoke动作元素也有多个属性,表7.7展示了invoke动作元素中的全部属性,其中fragment属性是必须的。

属性 描述
fragment 要调用的fragment的名称
var 用于保存片段主体内容的变量值,标签体内容会以java.lang.String类型保存在这个var变量内。varvarReader属性只能出现一个。
varReader 用于保存标签主体内容的变量值,主体内容会以java.io.Reader``类型保存在这个varReader变量内。varvarReader属性只能出现一个
scope 变量保存到的作用域

实例

template.tag

/app07a/WebContent/WEB-INF/tags/template.tag是一个模板文件,使用这个文件可以渲染不同的jsp页面,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<%@tag description="template 1" pageEncoding="UTF-8"%>
<!-- 使用名称为header的片段 -->
<%@attribute name="header" fragment="true"%>
<%@attribute name="footer" fragment="true"%>
<%@attribute name="body" fragment="true"%>
<%@attribute name="title" fragment="true"%>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html;
charset=utf-8" />
<title><jsp:invoke fragment="title" /></title>
</head>
<body>
<!--填入JSP页面中名称为header的片段中的具体内容 -->
<jsp:invoke fragment="header" />
<jsp:invoke fragment="body" />
<jsp:invoke fragment="footer" />
</body>
</html>

templateTest1.jsp

/app07a/WebContent/templateTest1.jsp,这个文件中定义了多个代码片段,如下所示;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<%@page contentType="text/html" pageEncoding="UTF-8"%>
<%@ taglib prefix="t" tagdir="/WEB-INF/tags/"%>
<t:template>
<jsp:attribute name="footer">
<hr color="green">
templateTest1.jsp的尾部信息.
<hr color="green">
</jsp:attribute>
<jsp:attribute name="body">
<hr color="blue">
templateTest1.jsp的正文信息.
<hr color="blue">
</jsp:attribute>
<jsp:attribute name="header">
<hr color="red">
templateTest1.jsp的头部信息.
<hr color="red">
</jsp:attribute>
<jsp:attribute name="title">
templateTest1
</jsp:attribute>
</t:template>

通过URL:http://localhost:8080/app07a/templateTest1.jsp可以访问,显示效果如下:
这里有一张图片

templateTest2.jsp

/app07a/WebContent/templateTest2.jsp,这个文件同样使用模板文件来渲染:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<%@page contentType="text/html" pageEncoding="UTF-8"%>
<%@ taglib prefix="t" tagdir="/WEB-INF/tags/"%>
<t:template>
<jsp:attribute name="footer">
<hr color="green">
<strong>templateTest2.jsp的尾部信息.</strong>
<hr color="green">
</jsp:attribute>
<jsp:attribute name="title">
templateTest2
</jsp:attribute>
<jsp:attribute name="body">
<hr color="blue">
<strong>templateTest2.jsp的正文信息.</strong>
<hr color="blue">
</jsp:attribute>
<jsp:attribute name="header">
<hr color="red">
<strong>templateTest2.jsp的头部信息.</strong>
<hr color="red">
</jsp:attribute>
</t:template>

URL:http://localhost:8080/app07a/templateTest2.jsp
显示效果:
这里有一张图片

小结

  • 使用片段可以使多个jsp页面具有相同的风格。
  • doBody动作执行标签体中的所有内容
  • invoke动作执行标签体中指定的内容

7.4 doBody

doBody动作元素只能在tag file中使用,它用来调用一个标签的标签体中的内容。在7.3.5小节中我们已经使用到了doBody动作元素,现在我们来介绍更详细的内容。
doBody动作元素也可以有属性。你可以通过这些属性来指定某个变量来接收标签体中的内容,如果不使用这些指令,那么**doBody动作元素会把标签体之中的内容写到JSP页面的JspWriter上**。

doBody动作元素的属性

doBody动作元素的属性见下表所示,所有的**这些属性都是非必须的**。

属性 描述
var 用于保存标签体内容的变量值,标签体内容会以java.lang.String类型保存到这个var变量内。varvarReader属性只能出现一个
varReader 用于保存标签体内容的变量值,标签体内容会以java.io.Reader类型保存到这个varReader变量内。varvarReader属性只能出现一个
scope 变量保存到的作用域

实例

HTTP Referer简介

HTTP Refererheader的一部分,当浏览器向web服务器发送请求的时候,一般会带上Referer,**referer告诉服务器当前页面是从哪个页面链接过来的**,服务器藉此可以获得一些信息用于处理。比如从我主页上有一个到朋友主页上的链接,他的服务器可以通过HTTP Referer中统计出每天有多少用户通过点击我主页上的链接访问他的网站。

实例说明

下面的这个例子说明了如何用doBody来调用标签本体内容并将内容保存在一个叫作referer的变量中。

假设你有一个卖玩具的商城网站,并且在多个搜索引擎上做了这个玩具网站的广告。现在你想要知道每个搜索引擎为玩具网站带来的流量有多少转化成了购买行为。为了做到这点,你可以记录每个网站首页访问的referer头部信息,使用一个tag file来将referer头信息保存到session属性中。如果某个用户在后续购买了产品,就可以从session属性中获得referer头信息,并记录在数据库中。
这个例子包含了两个表示搜索引擎的HTML文件(searchEngine.html,searchEngine2.html)、两个JSP文件(main.jsp viewReferer.jsp)以及一个tag file (doBodyDemo.tag)。main.jsp页面是玩具网站的首页,使用了doBodyDemo标签来保存referer头信息。viewReferer.jsp页面用来查看收集到的referer头信息。**如果直接通过URL访问main.jsp,那么referer头信息即为null**。因此你必须通过searchEngine.html来链接到main.jsp页面。

doBodyDemo.tag

1
<jsp:doBody var="referer" scope="session"/>

没错,doBodyDemo.tag只有一行:一个doBody动作元素。它指定了一个叫作referersession属性来保存标签的标签体中的内容。

main.jsp

1
2
3
4
5
6
7
8
9
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib prefix="tags" tagdir="/WEB-INF/tags" %>
上一个页面的URL:${header.referer}
<br/>
<tags:doBodyDemo>
${header.referer}
</tags:doBodyDemo>
<a href="viewReferer.jsp">显示session中的referer属性</a>

main.jsp页面通过文本和EL表达式输出referer头信息(上一个页面的URL)如下:

1
2
上一个页面的URL:${header.referer}
<br/>

main.jsp页面使用了doBodyDemo标签,标签体也输出了referer头信息:

1
2
3
<tags:doBodyDemo>
${header.referer}
</tags:doBodyDemo>

紧接着,输出一个指向ViewReferer页面的链接:

1
<a href="viewReferer.jsp">显示session中的referer属性</a>

viewReferer.jsp文件

1
2
3
4
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!-- 显示referer有界变量 -->
上一个页面的URL:${sessionScope.referer}

viewReferer.jsp页面通过EL表达式将referer中保存的值打印了出来。

searchEngine.html

1
2
<h3>搜索引擎1</h3>
<a href="main.jsp">访问主页</a>

可以通过以下URL访问 searchEngine.html来查看结果:
http://localhost:8080/app07a/searchEngine.html
结果如下图所示:
这里有一张图片
现在点击这个链接跳转到main.jsp页面,main.jsp中获取的referer头信息将包含searchEngine.htmlURL地址。如下图所示:
这里有一张图片
main.jsp页面调用了doBodyDemo元素标签,doBodyDemo标签中通过doBody动作将内容存储在名为referersession属性中。
接下来,点击main.jsp中的View链接,就可以在viewReferer.jsp页面中看到session中的这个内容了,如下图所示.
这里有一张图片

searchEngine2.html

1
2
<h3>搜索引擎2</h3>
<a href="main.jsp">访问主页</a>

searchEngine2.html用来模拟广告所在的第二个页面
可以通过以下URL访问 searchEngine2.html来查看结果:
http://localhost:8080/app07a/searchEngine2.html
这里有一张图片
点击访问主页链接:即可链接到商城主页(main.jsp)显示效果如下:
这里有一张图片

7.3.5 variable指令

通过variable可以将tag file中的一些值传递JSP页面tag file中的variable指令和标签库描述文件中的variable元素类似,它用于定义那些需要传递到JSP页面的变量。
tag file支持多个variable指令,这意味着可以传递多个值到JSP页面
相对而言,attribute指令的作用与variable相反,attribute指令用于将值从JSP页面传递到tag file
variable指令的语法如下:

1
<%@ variable attribute1="value1" attribute2="value2" ... %>

variable属性

属性 描述
name-given 变量名。在JSP页面的脚本和EL表达式中,可以使用该变量名。如果指定了name-from-attribute属性,那么name-given属性就不能出现了,反之亦然。name-given的值不能和同一个tag file中的属性名重复
name-from-attribute name-given属性类似,由标签属性的值来决定变量的名称。如果name-from-attributename-given属性同时出现或者都不出现的话会出现错误
alias 设定一个用来接收变量值的局部范围
variable-class 变量的类型。默认为java.lang.String
declare 设定该变量是否声明。默认值为false
scope 用于指定该变量的范围。可取的值为AT_BEGINAT_END、和NESTED。默认值为NESTED
description 用于描述该变量
你或许会奇怪,既然JSP页面可以调用JspWriter来接收变量值了,为什么还需要通过variable指令来传递变量值呢。那是因为通过JspWriter只能简单地将一个String传递到JSP页面,灵活性很差

实例

firstTag.tag

下面的firstTag.tag用于输出服务器长格式的当前日期。

1
2
3
4
5
6
7
<%@ tag import="java.util.Date" import="java.text.DateFormat"%>
<%
DateFormat dateFormat =
DateFormat.getDateInstance(DateFormat.LONG);
Date now = new Date(System.currentTimeMillis());
out.println(dateFormat.format(now));
%>

但是如果你还需要输出短格式的服务器日期的话,就必须再写一个tag file。写两个功能类似的tag file显然是冗余工作。如果使用变量指令,就没有这样的问题了,只需要在tag file中定义longDateshortDate两个变量就可以了

varDemo.tag

varDemo.tag提供了输出服务器当前日期长格式和短格式的两个功能,它定义了两个变量:longDateshortDate

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<%@ tag import="java.util.Date" import="java.text.DateFormat"%>
<%@ tag pageEncoding="utf-8"%>
<!-- 要向JSP页面传递的参数的名称 -->
<%@ variable name-given="longDate"%>
<%@ variable name-given="shortDate"%>
<!-- 引入核心标签库 -->
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<%
Date now = new Date(System.currentTimeMillis());
DateFormat longFormat = DateFormat.
getDateInstance(DateFormat.LONG);
DateFormat shortFormat = DateFormat
.getDateInstance(DateFormat.SHORT);
//给jspContext内置对象设置属性
jspContext.setAttribute("longDate", longFormat.format(now));
%>
<!-- 给shorDate参数设值 -->
<c:set var="shortDate"><%=shortFormat.format(now)%></c:set>
<!-- <mytags:variableDemo>xxxxxxx</mytags:variableDemo>
的标签体为xxxx-->
<!-- 执行标签体中的JSP代码 -->
<jsp:doBody />

注意,这里使用了jspContext.setAttribute方法来设置变量。jspContext是一个隐藏对象。JSTL中的set标签也能实现同样的功能。
同时需要注意的是,这里使用了doBody动作元素来执行<mytags:variableDemo>标签的标签体。关于doBodyinvoke动作元素我们将在下一节进行介绍。

varDemoTest.jsp页面

varDemoTest.jsp使用了varDemo.tag的标签。

1
2
3
4
5
6
7
8
9
10
<%@ page language="java" contentType="text/html; 
charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib prefix="mytags" tagdir="/WEB-INF/tags"%>
当前日期
<ul>
<mytags:variableDemo>
<li>长日期格式:${longDate}</li>
<li>短日期格式:${shortDate}</li>
</mytags:variableDemo>
</ul>

可以通过下面的URL来访问varDemoTest.jsp:
http://localhost:8080/app07a/variableDemoTest.jsp
显示效果如下图所示:
这里有一张图片
在很多情况下都需要用到变量。比如说,你希望实现一个这样的功能:根据产品标识从数据库中获取该产品的详细信息。你可以通过一个属性来传递产品标识。然后可以用多个变量来保存产品的详细信息,每个变量对应为产品的每个属性。最终,你会用到例如namepricedescriptionimageURL等变量。

7.3.4 attribute指令

attribute用于设定tag file中标签的属性。它和标签库描述文件中的attribute元素等效

attribute指令语法

下面是该指令的语法:

1
<%@ attribute attributeName1="value1" attributeName2="value2" ... %>

attribute指令的属性

attribute指令的属性如下表所示。其中只有name属性是必须的。

属性 描述
name 用于设定该属性的名称。在一个tag file中,每个属性的名称必须是唯一的
required 用于设定该属性是否是必须的。值可以取truefalse,默认值为flase
fragment 用于设定该属性是否是fragment。默认值为false
rtexprvalue 用于设定该属性的值是否在运行时被动态计算。值可以取truefalse,默认值为true
type 用于设定该属性的类型,默认值为java.lang.String
description 用于设定该属性的描述信息,当鼠标放在该标签体上时会显示信息,类似于javadoc文档

实例

encode.tag文件可用于对一个字符串进行HTML编码。这个encode标签定义了一个input属性,该属性的类型是java.lang.String

encode.tag文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
<!-- 调用该自定义标签需要传入名为input的参数 -->
<%@ attribute name="input" required="true"%>
<!-- 声明一个方法 -->
<%!private String encodeHtmlTag(String tag)
{
if(tag == null) {
return null;
}
int length = tag.length();
StringBuilder encodedTag =
new StringBuilder(2 * length);
for(int i = 0;i < length;i++) {
char c = tag.charAt(i);
//如果是小于号
if(c == '<') {
//不加入这个符号
//加入转移符
//也可以说成:替换成对应的转移符
encodedTag.append("&lt");
} else if(c == '>') {
encodedTag.append("&gt");
} else if(c == '&') {
encodedTag.append("&amp");
} else if(c == '"') {
encodedTag.append("&qout");
} else if(c == ' ') {
encodedTag.append("&nbsp");
} else {
//不需要转义
encodedTag.append(c);
}
}
return encodedTag.toString();
}%>
<!-- 使用JSP表达式调用 -->
<%=encodeHtmlTag(input)%>

encodeTagTest.jsp使用了encode.tag定义的标签。

encodeTagTest.jsp文件

1
2
3
4
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib prefix="easy" tagdir="/WEB-INF/tags"%>
<easy:encode input="<strong>对不起,你加粗不了我</strong>" />

浏览器显示

可以通过以下URL来查看encodeTagTest.jsp 页面的效果:
http://localhost:8080/app07a/encodeTagTest.jsp
浏览器将显示下列文本:

1
<strong>对不起,你加粗不了我</strong>

7.3.3 taglib指令

可以通过taglib指令在tag file中使用自定义标签

taglib指令语法

taglib指令的语法如下:

1
<%@ taglib uri="tagLibraryURI" prefix="tagPrefix" %>

taglib属性说明

  • uri属性用来指定与前缀相关联的标签库描述文件的绝对路径或相对路径。
  • prefix属性用来定义自定义标签的前缀。

使用taglib指令

使用taglib指令,你可以像下面那样使用不包含content body的自定义标签:

1
<prefix:tagName/>

当然,也可以使用包含content body的自定义标签:

1
<prefix:tagName>body</prefix:tagName>

tag file中的taglib指令和JSP页面中的taglib指令是一样的。

Demo

firstTag.tag

1
2
3
4
5
6
7
<%@ tag import="java.util.Date" import="java.text.DateFormat"%>
<%
DateFormat dateFormat =
DateFormat.getDateInstance(DateFormat.LONG);
Date now = new Date(System.currentTimeMillis());
out.println(dateFormat.format(now));
%>

这个标签文件将会返回一个格式化的日期字符串.

taglibDemo.tag

1
2
3
4
<%@ tag pageEncoding="utf-8"%>
<%@ taglib prefix="simple" tagdir="/WEB-INF/tags"%>
服务器日期:
<simple:firstTag />

taglibDemo.tag通过导入了firstTag.tag来显示服务器日期。

taglibDemoTest.jsp

1
2
3
<%@page contentType="text/html; charset=utf-8" pageEncoding="utf-8"%>
<%@ taglib prefix="easy" tagdir="/WEB-INF/tags"%>
<easy:taglibDemo />

taglibDemoTest.jsp调用了taglibDemo.tag来显示服务器日期.

浏览器显示效果

访问下面的URL可以查看这个JSP页面的效果:
http://localhost:8080/app07a/taglibDemoTest.jsp
显示效果如下:

1
服务器日期: 2019年4月12日