2.9.2 配置Filter

前面已经提到,Filter可以认为是Servlet的“增强版”,因此配置Filter与配置Servlet非常相似,都需要配置如下两个部分。

  1. 配置Filter
  2. 配置Filter拦截URL模式。

区别在于:Servlet通常只配置一个URL,而Filter可以同时拦截多个请求的URL。因此,在配置FilterURL模式时通常会使用模式字符串,使得Filter可以拦截多个请求。

与配置Servlet相似的是,配置Filter同样有两种方式。

  1. Filter类中通过注解进行配置。
  2. web.xml文件中通过配置文件进行配置。

上面Filter类的粗体字代码使用@WebFilter配置该Filter的名字为log,它会拦截向/*发送的所有的请求。

@WebFilter注解属性

@WebFilter修饰一个Filter类,用于对Filter进行配置,它支持如表2.3所示的常用属性。

属性 说明
asyncSupported 指定该Filter是否支持异步操作模式。关于Filter的异步调用请参考2.12节
dispatcherTypes 指定该Filter仅对那种dispatcher模式的请求进行过滤。该属性支持ASYNCERRORFORWARDINCLUDEREQUEST这5个值的任意组合。默认值为同时过滤5种模式的请求
displayName 指定该Filter的显示名
filterName 指定该Filter的名称
initParams 用于为该Filter配置参数
servletnames 该属性值可指定多个Servlet的名称,用于指定该Filter仅对这几个Servlet执行过滤
urlPatterns/value 这两个属性的作用完全相同。都指定该Filter所拦截的URL

web.xml文件中配置Filter与配置Servlet非常相似,需要为Filter指定它所过滤的URL,并且也可以为Filter配置参数
如果不使用注解配置该Filter,则可换成在web.xml文件中为该Filter增加如下配置片段:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<!-- 定义Filter -->
<filter>
<!-- Filter的名字,相当于指定@WebFilter
的filterName属性 -->
<filter-name>log</filter-name>
<!-- Filter的实现类 -->
<filter-class>lee.LogFilter</filter-class>
</filter>
<!-- 定义Filter拦截的URL地址 -->
<filter-mapping>
<!-- Filter的名字 -->
<filter-name>log</filter-name>
<!-- Filter负责拦截的URL,相当于指定@WebFilter
的urlPatterns属性 -->
<url-pattern>/*</url-pattern>
</filter-mapping>

上面的粗体字代码用于配置该Filter,从这些代码中可以看出配置Filter与配置Servlet非常相似,只是配置Filter时指定url-Pattern/*,即表示该Filter会拦截所有用户请求。该Filter并未对客户端请求进行额外的处理,仅仅在日志中简要记录请求的信息。
为该Web应用提供任意一个JSP页面,并通过浏览器来访问该JSP页面,即可在Tomcat的控制台看到如图2.39所示的信息。

实际上FilterServlet极其相似,区别只是FilterdoFilter()方法里多了一个FilterChain的参数,通过该参数可以控制是否放行用户请求。在实际项目中,FilterdoFilter()方法里的代码就是从多个Servletservice()方法里抽取的通用代码,通过使用Filter可以实现更好的代码复用。
假设系统有包含多个Servlet,这些Servlet都需要进行一些的通用处理:
比如权限控制、记录日志等,这将导致在这些Servletservice()方法中有部分代码是相同的,为了解决这种代码重复的问题,可以考虑把这些通用处理提取到Filter中完成,这样各Servlet中剩下的只是特定请求相关的处理代码,而通用处理则交给Filter完成。图2.40显示了Filter的用途。
由于FilterServlet如此相似,所以FilterServlet具有完全相同的生命周期行为,且Filter也可以通过<init-param>元素或@WebFilterinitParams属性来配置初始化参数,获取Filter的初始化参数则使用FilterConfiggetInitParameter()方法。

下面将定义一个较为实用的Filter,该Filter对用户请求进行过滤,Filter将通过doFilter()方法来设置request编码的字符集,从而避免每个JSPServlet都需要设置;而且还会验证用户是否登录,如果用户没有登录,系统直接跳转到登录页面。

下面是该Filter的源代码

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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
package lee;

import javax.servlet.*;
import javax.servlet.http.*;
import javax.servlet.annotation.*;
import java.io.*;

@WebFilter(
filterName="authority",
urlPatterns={"/*"},
initParams={
@WebInitParam(name="encoding", value="GBK"),
@WebInitParam(name="loginPage", value="/login.jsp"),
@WebInitParam(name="proLogin", value="/proLogin.jsp")
}
)
public class AuthorityFilter implements Filter
{
// FilterConfig可用于访问Filter的配置信息
private FilterConfig config;
// 实现初始化方法
public void init(FilterConfig config)
{
this.config = config;
}
// 实现销毁方法
public void destroy()
{
this.config = null;
}
// 执行过滤的核心方法
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException,ServletException
{
// 获取该Filter的配置参数
String encoding = config.getInitParameter("encoding");
String loginPage = config.getInitParameter("loginPage");
String proLogin = config.getInitParameter("proLogin");
// 设置request编码用的字符集
request.setCharacterEncoding(encoding); // ①
HttpServletRequest requ = (HttpServletRequest)request;
HttpSession session = requ.getSession(true);
// 获取客户请求的页面
String requestPath = requ.getServletPath();
// 如果session范围的user为null,
// 且用户请求的既不是登录页面,也不是处理登录的页面
if( session.getAttribute("user") == null
&& !requestPath.endsWith(loginPage)
&& !requestPath.endsWith(proLogin))
{
// forward到登录页面
request.setAttribute("tip" , "您还没有登录");
request.getRequestDispatcher(loginPage).forward(request, response);
}
// "放行"请求
else
{
chain.doFilter(request, response);
}
}
}

上面FilterdoFilter()方法开始的三行代码:

1
2
3
String encoding = config.getInitParameter("encoding");
String loginPage = config.getInitParameter("loginPage");
String proLogin = config.getInitParameter("proLogin");

用于获取Filter的配置参数,
代码:

1
request.setCharacterEncoding(encoding);

按配置参数设置了request编码所用的字符集.

接下来的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
if( session.getAttribute("user") == null
&& !requestPath.endsWith(loginPage)
&& !requestPath.endsWith(proLogin))
{
// forward到登录页面
request.setAttribute("tip" , "您还没有登录");
request.getRequestDispatcher(loginPage).forward(request, response);
}
// "放行"请求
else
{
chain.doFilter(request, response);
}

判断session范围内是否有user属性——没有该属性即认为没有登录,如果既没有登录,而且请求地址也不是登录页和处理登录页,系统直接跳转到登录页面。
通过@WebFilterinitParams属性可以为该Filter配置初始化参数,@WebFilterinitParams属性可以接受多个@WebInitParam,每个@WeblnitParam指定一个初始化参数。

web.xml中为Filter配置参数

web.xml文件中也使用<init-param>元素为该Filter配置参数,与配置Servlet初始化参数完全相同。
如果不使用注解配置该Filter,打算在web.xml文件中配置该Filter,则该Filter的配置片段如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<!-- 定义Filter -->
<filter>
<!-- Filter的名字 -->
<filter-name>authority</filter-name>
<!-- Filter的实现类 -->
<filter-class>lee.AuthorityFilter</filter-class>
<!-- 下面三个init-param元素配置了三个参数 -->
<init-param>
<param-name>encoding</param-name>
<param-value>GBK</param-value>
</init-param>
<init-param>
<param-name>loginPage</param-name>
<param-value>/login.jsp</param-value>
</init-param>
<init-param>
<param-name>proLogin</param-name>
<param-value>/proLogin.jsp</param-value>
</init-param>
</filter>

上面的配置片段中粗体字代码为该Filter指定了三个配置参数,指定loginPage/login.jsp,ProLogic/proLogin.jsp,这表明,如果没有登录该应用,普通用户只能访问/login.jsp/proLogin.jsp页面。只有当用户登录该应用后才可自由访问其他页面。

2.9 Filter介绍

Filter可认为是Servlet的一种“加强版”,它主要用于对用户请求进行预处理,也可以对HttpServletResponse进行后处理,是个典型的处理链。Filter也可对用户请求生成响应,这一点与Servlet相同,但实际上很少会使用Filter向用户请求生成响应。

使用Filter的流程

使用Filter完整的流程是:

  • Filter对用户请求进行预处理,
  • 接着将请求交给Servlet进行处理并生成响应,
  • 最后Filter再对服务器响应进行后处理。

Filter用途

Filter有如下几个用处:

  • HttpServletRequest到达Servlet之前,拦截客户的HttpservletRequest
  • 根据需要检查HttpservletRequest,也可以修改HttpServletRequest头和数据。
  • HttpservletResponse到达客户端之前,拦截HttpServletResponse
  • 根据需要检查HttpServletResponse,也可以修改HttpServletResponse头和数据

Filter有如下几个种类。

  • 用户授权的Filter:Filter负责检查用户请求,根据请求过滤用户非法请求。
  • 日志Filter:详细记录某些特殊的用户请求
  • 负责解码的Filter:包括对非标准编码的请求解码。
  • 能改变XML内容的XSLTFilter等。
  • Filter可负责拦截多个请求或响应:一个请求或响应也可被多个Filter拦截。

创建Filter步骤

创建一个Filter只需两个步骤

  1. 创建Filter处理类。
  2. web.Xml文件中配置Filter或用注解配置Filter

2.9.1 创建Filter类

Filter接口方法

创建Filter必须实现javax.servlet.Filter接口,在该接口中定义了如下三个方法:

  1. void init(FilterConfig config):用于完成Filter的初始化。
  2. void destroy():用于Filter销毁前,完成某些资源的回收。
  3. void dofilter(ServletRequest request, ServletResponse response, FilterChain chain):实现过滤功能,该方法就是对每个请求及响应增加的额外处理。

LogFilter.java

下面介绍一个日志Filter,这个Filter负责拦截所有的用户请求,并将请求的信息记录在日志中

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
37
38
39
40
41
42
43
44
package lee;

import javax.servlet.*;
import javax.servlet.http.*;
import javax.servlet.annotation.*;
import java.io.*;

@WebFilter(filterName = "log", urlPatterns = { "/*" })
public class LogFilter implements Filter {
// FilterConfig可用于访问Filter的配置信息
private FilterConfig config;

// 实现初始化方法
public void init(FilterConfig config) {
this.config = config;
}

// 实现销毁方法
public void destroy() {
this.config = null;
}

// 执行过滤的核心方法
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
// ---------下面代码用于对用户请求执行预处理---------
// 获取ServletContext对象,用于记录日志
ServletContext context = this.config.getServletContext();
long before = System.currentTimeMillis();
System.out.println("开始过滤...");
// 将请求转换成HttpServletRequest请求
HttpServletRequest hrequest = (HttpServletRequest) request;
// 输出提示信息
System.out.println("Filter已经截获到用户的请求的地址: " + hrequest.getServletPath());
// Filter只是链式处理,请求依然放行到目的地址
chain.doFilter(request, response);
// ---------下面代码用于对服务器响应执行后处理---------
long after = System.currentTimeMillis();
// 输出提示信息
System.out.println("过滤结束");
// 输出提示信息
System.out.println("请求被定位到" + hrequest.getRequestURI() + " 所花的时间为: " + (after - before));
}
}

上面的程序中实现了doFilter方法,实现doFilter方法就可实现对用户请求进行预处理,也可实现对服务器响应进行后处理

预处理和后处理的分界线为是否调用了chain.doFilte(r),执行该方法之前,即对用户请求进行预处理;执行该方法之后,即对服务器响应进行后处理.
在上面的请求Filter中,仅在日志中记录请求的URL,对所有的请求都执行chain do Filter( request, reponse)方法,当Filter对请求过滤后,依然将请求发送到目的地址。

使用Filter进行权限检查

如果需要检查权限,可以在Filter中根据用户请求的Httpsession,判断用户权限是否足够。如果权限不够,直接调用重定向即可,无须调用chain.doFilter(request,reponse)方法。

2.8.7 动态属性的标签

前面介绍带属性标签时,那些标签的属性个数是确定的,属性名也是确定的,绝大部分情况下这种带属性的标签能处理得很好,但在某些特殊情况下,需要传入自定义标签的属性个数是不确定的,属性名也不确定,这就需要借助于动态属性的标签了.
动态属性标签比普通标签多了如下两个额外要求。

  1. 标签处理类还需要实现DynamicAttributes接口
  2. 配置标签时通过<dynamic-attributes>子元素指定该标签支持动态属性

下面是一个动态属性标签的处理类。

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
package lee;

import javax.servlet.jsp.tagext.*;
import javax.servlet.jsp.*;
import java.io.*;
import java.util.*;

public class DynaAttributesTag extends SimpleTagSupport
implements DynamicAttributes {
// 保存每个属性名的集合
private ArrayList<String> keys = new ArrayList<String>();
// 保存每个属性值的集合
private ArrayList<Object> values = new ArrayList<Object>();

@Override
public void doTag() throws JspException, IOException {
JspWriter out = getJspContext().getOut();
// 此处只是简单地输出每个属性
out.println("<ol>");
for (int i = 0; i < keys.size(); i++) {
String key = keys.get(i);
Object value = values.get(i);
out.println("<li>" + key + " = " + value + "</li>");
}
out.println("</ol>");
}

@Override
public void setDynamicAttribute(String uri, String localName, Object value)
throws JspException {
// 添加属性名
keys.add(localName);
// 添加属性值
values.add(value);
}
}

上面的标签处理类实现了DynamicAttributes接口,就是动态属性标签处理类必须实现的接口,实现该接口必须实现setDynamicAttribute()方法,该方法用于为该标签处理类动态地添加属性名和属性值。

标签处理类使用ArrayList<String>类型的keys属性来保存标签的所有属性名,使用ArrayList<Object>类型的values属性来保存标签的所有属性值。
配置该标签时需要额外地指定<dynamic-attributes>子元素,表明该标签是带动态属性的标签。

下面是该标签的配置片段。

1
2
3
4
5
6
7
8
<!-- 定义接受动态属性的标签 -->
<tag>
<name>dynaAttr</name>
<tag-class>lee.DynaAttributesTag</tag-class>
<body-content>empty</body-content>
<!-- 指定支持动态属性 -->
<dynamic-attributes>true</dynamic-attributes>
</tag>

上面的配置片段指定该标签支持动态属性。
一旦定义了动态属性的标签,接下来在页面中使用该标签时将十分灵活,完全可以为该标签设置任意的属性,如以下页面片段所示。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<%@ page contentType="text/html; charset=UTF-8" language="java" errorPage="" %>
<!-- 导入标签库,指定mytag前缀的标签,
由http://www.crazyit.org/mytaglib的标签库处理 -->
<%@ taglib uri="http://www.crazyit.org/mytaglib" prefix="mytag"%>
<!DOCTYPE html>
<html>
<head>
<title>自定义标签示范</title>
</head>
<body bgcolor="#ffffc0">
<h2>下面显示的是自定义标签中的内容</h2>
<h4>指定两个属性</h4>
<mytag:dynaAttr name="crazyit" url="crazyit.org"/><br/>
<h4>指定四个属性</h4>
<mytag:dynaAttr 书名="疯狂Java讲义" 价格="99.0"
出版时间="2008年" 描述="Java图书"/><br/>
</body>
</html>

上面的页面片段中使用mytag:dynaAttr标签时十分灵活:可以根据需要动态地传入任意多个属性,如以上代码所示。不管传入多少个属性,这个标签都可以处理得很好,使用浏览器访问该页面将看到如图2.38所示的效果

2.8.3 使用标签库

JSP页面中确定指定的标签需要两点。

  1. 标签库URI:确定使用哪个标签库。
  2. 标签名:确定使用哪个标签。

使用标签库步骤

使用标签库分成以下两个步骤。

  1. 导入标签库:使用taglib编译指令导入标签库,就是将标签库和指定前缀关联起来
  2. 使用标签:在JSP页面中使用自定义标签。

taglib编译指令语法格式

taglib的语法格式如下:

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

其中

  • uri属性指定标签库的UR,这个URI可以确定一个标签库。
  • prefix属性指定标签库前缀,即所有使用该前缀的标签将由此标签库处理。

使用标签的语法格式如下:

1
2
3
<tagPrefix:tagName tagAttribute="tagValue" ...>
<tagbody/>
</tagPrefix: tagName>

如果该标签没有标签体,则可以使用如下语法格式

1
<tagPrefix:tagName tagAttribute="tagValue" .../>

上面使用标签的语法里都包含了设置属性值,前面介绍的HelloWorldTag标签没有任何属性,所以使用该标签只需用<mytag:helloWorld/>即可。其中mytagtaglib指令为标签库指定的前缀,而helloWorld是标签名。
下面是使用helloWorld标签的JSP页面代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<%@ page contentType="text/html; charset=GBK" language="java" errorPage="" %>
<!-- 导入标签库,指定mytag前缀的标签,
由URI为http://www.crazyit.org/mytaglib的标签库处理 -->
<%@ taglib uri="http://www.crazyit.org/mytaglib" prefix="mytag"%>
<!DOCTYPE html>
<html>
<head>
<title>自定义标签示范</title>
</head>
<body bgcolor="#ffffc0">
<h2>下面显示的是自定义标签中的内容</h2>
<!-- 使用标签 ,其中mytag是标签前缀,根据taglib的编译指令,
mytag前缀将由URI为http://www.crazyit.org/mytaglib的标签库处理 -->
<mytag:helloWorld/><br/>
</body>
</html>

以上页面的代码:

1
<%@ taglib uri="http://www.crazyit.org/mytaglib" prefix="mytag"%>

指定了URIhttp://www.crazyit.org/mytaglib的标签库,并将这个标签库的前缀设置为mytag,
代码

1
<mytag:helloWorld/>

表明使用mytag前缀对应标签库里的helloWorld标签。
浏览该页面将看到如图2.34所示的效果。

2.8.6 以页面片段作为属性的标签

JSP2规范的自定义标签还允许直接将一段“页面片段”作为属性,这种方式给自定义标签提供了更大的灵活性.
以“页面片段”为属性的标签与普通标签区别并不大,只有两个简单的改变。

  1. 标签处理类中定义类型为JspFragment的属性,该属性代表了“页面片段”。
  2. 使用标签库时,通过<jsp:attribute>动作指令为标签的属性指定值

下面的程序定义了一个标签处理类,该标签处理类中定义了一个JspFragment类型的属性,即表明该标签允许使用“页面片段”类型的属性。

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
package lee;

import javax.servlet.jsp.tagext.*;
import javax.servlet.jsp.*;
import java.io.*;

public class FragmentTag extends SimpleTagSupport {
private JspFragment fragment;

// fragment的setter和getter方法
public void setFragment(JspFragment fragment) {
this.fragment = fragment;
}

public JspFragment getFragment() {
return this.fragment;
}

@Override
public void doTag() throws JspException, IOException {
JspWriter out = getJspContext().getOut();
out.println("<div style='padding:10px;border:1px solid black;" + ";border-radius:20px'>");
out.println("<h3>下面是动态传入的JSP片段</h3>");
// 调用、输出“页面片段”
fragment.invoke(null);
out.println("</div");
}
}

上面的程序中定义了fragment成员变量,该成员变量代表了使用该标签时的“页面片段”,配置该标签与配置普通标签并无任何区别,增加如下配置片段即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<tag>
<!-- 定义标签名 -->
<name>fragment</name>
<!-- 定义标签处理类 -->
<tag-class>lee.FragmentTag</tag-class>
<!-- 指定该标签不支持标签体 -->
<body-content>empty</body-content>
<!-- 定义标签属性:fragment -->
<attribute>
<name>fragment</name>
<required>true</required>
<fragment>true</fragment>
</attribute>
</tag>

从上面标签库的配置片段来看,这个自定义标签并没有任何特别之处,就是一个普通的带属性标签,该标签的标签体为空.
由于该标签需要一个fragment属性,该属性的类型为JspFragment,因此使用该标签时需要使用<jsp:attribute>动作指令来设置属性值,如以下代码片段所示。

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
<%@ page contentType="text/html; charset=UTF-8" language="java" errorPage="" %>
<!-- 导入标签库,指定mytag前缀的标签,
由http://www.crazyit.org/mytaglib的标签库处理 -->
<%@ taglib uri="http://www.crazyit.org/mytaglib" prefix="mytag"%>
<!DOCTYPE html>
<html>
<head>
<title>自定义标签示范</title>
</head>
<body bgcolor="#ffffc0">
<h2>下面显示的是自定义标签中的内容</h2>
<mytag:fragment>
<jsp:attribute name="fragment">
<%-- 使用jsp:attribute标签传入fragment参数(该注释不能放在fragment内) %-->
<%-- 下面是动态的JSP页面片段 --%>
<mytag:helloWorld/>
</jsp:attribute>
</mytag:fragment>
<br/>
<mytag:fragment>
<jsp:attribute name="fragment">
<%-- 下面是动态的JSP页面片段 --%>
${pageContext.request.remoteAddr}
</jsp:attribute>
</mytag:fragment>
</body>
</html>

由于程序指定了fragment标签的标签体为empty,因此程序中fragment开始标签和fragment结束标签之间只能使用jsp:attribute子元素,不允许出现其他内容,甚至连注释都不允许.

测试

上面的代码片段中的jsp:attribute标签用于为标签的fragment属性赋值,
第一个例子使用了另一个简单标签来生成页面片段;
第二个例子使用了JSP2EL来生成页面片段;在浏览器中浏览该页面,将看到如图2.37所示的效果。

2.8.4 带属性的标签

前面的简单标签既没有属性,也没有标签体用法、功能都比较简单。实际上还有如下两种常用的标签。

  • 带属性的标签
  • 带标签体的标签。

正如前面介绍的,带属性标签必须为每个属性提供对应的settrgettr方法。带属性标签的配置方法与简单标签也略有差别,下面介绍一个带属性标签的示例:

QueryTag.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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
package lee;

import javax.servlet.jsp.tagext.*;
import javax.servlet.jsp.*;
import java.io.*;
import java.sql.*;

public class QueryTag extends SimpleTagSupport {
// 定义成员变量来代表标签的属性
private String driver;
private String url;
private String user;
private String pass;
private String sql;

// driver的setter和getter方法
public void setDriver(String driver) {
this.driver = driver;
}

public String getDriver() {
return this.driver;
}

// url的setter和getter方法
public void setUrl(String url) {
this.url = url;
}

public String getUrl() {
return this.url;
}

// user的setter和getter方法
public void setUser(String user) {
this.user = user;
}

public String getUser() {
return this.user;
}

// pass的setter和getter方法
public void setPass(String pass) {
this.pass = pass;
}

public String getPass() {
return this.pass;
}

// sql的setter和getter方法
public void setSql(String sql) {
this.sql = sql;
}

public String getSql() {
return this.sql;
}

// 执行数据库访问的对象
private Connection conn = null;
private Statement stmt = null;
private ResultSet rs = null;
private ResultSetMetaData rsmd = null;

// conn的setter和getter方法
public void setConn(Connection conn) {
this.conn = conn;
}

public Connection getConn() {
return this.conn;
}

// stmt的setter和getter方法
public void setStmt(Statement stmt) {
this.stmt = stmt;
}

public Statement getStmt() {
return this.stmt;
}

// rs的setter和getter方法
public void setRs(ResultSet rs) {
this.rs = rs;
}

public ResultSet getRs() {
return this.rs;
}

// rsmd的setter和getter方法
public void setRsmd(ResultSetMetaData rsmd) {
this.rsmd = rsmd;
}

public ResultSetMetaData getRsmd() {
return this.rsmd;
}


public void doTag() throws JspException, IOException {
try {
// 注册驱动
Class.forName(driver);
// 获取数据库连接
conn = DriverManager.getConnection(url, user, pass);
// 创建Statement对象
stmt = conn.createStatement();
// 执行查询
rs = stmt.executeQuery(sql);
rsmd = rs.getMetaData();
// 获取列数目
int columnCount = rsmd.getColumnCount();
// 获取页面输出流
Writer out = getJspContext().getOut();
// 在页面输出表格
out.write("<table border='1' bgColor='#9999cc' width='400'>");
// 遍历结果集
while (rs.next()) {
out.write("<tr>");
// 逐列输出查询到的数据
for (int i = 1; i <= columnCount; i++) {
out.write("<td>");
out.write(rs.getString(i));
out.write("</td>");
}
out.write("</tr>");
}
} catch (ClassNotFoundException cnfe) {
cnfe.printStackTrace();
throw new JspException("自定义标签错误" + cnfe.getMessage());
} catch (SQLException ex) {
ex.printStackTrace();
throw new JspException("自定义标签错误" + ex.getMessage());
} finally {
// 关闭结果集
try {
if (rs != null)
rs.close();
if (stmt != null)
stmt.close();
if (conn != null)
conn.close();
} catch (SQLException sqle) {
sqle.printStackTrace();
}
}
}
}

上面这个标签稍微复杂一点,它包含了5个属性,如程序中粗体字代码所示(为标签处理类定义成员变量即可代表标签的属性),程序需要为这5个属性提供settergetter方法。
该标签输出的内容依然由doTag()方法决定,该方法会根据SQL语句查询数据库,并将查询结果显示在当前页面中。

对于有属性的标签,需要为tag元素增加attribute子元素,每个attribute子元素定义一个标签属性。attribute子元素通常还需要指定如下几个子元素:

  • name:设置属性名,子元素的值是字符串内容
  • required:设置该属性是否为必需属性,该子元素的值是truefalse
  • fragment:设置该属性是否支持JSP脚本、表达式等动态内容,子元素的值是truefalse

为了配置上面的QueryTag标签,需要在mytaglib.TLD文件中增加如下配置片段。

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
37
38
39
40
<!-- 定义第二个标签 -->
<tag>
<!-- 定义标签名 -->
<name>query</name>
<!-- 定义标签处理类 -->
<tag-class>lee.QueryTag</tag-class>
<!-- 定义标签体为空 -->
<body-content>empty</body-content>

<!-- 配置标签属性:driver -->
<attribute>
<name>driver</name>
<required>true</required>
<fragment>true</fragment>
</attribute>
<!-- 配置标签属性:url -->
<attribute>
<name>url</name>
<required>true</required>
<fragment>true</fragment>
</attribute>
<!-- 配置标签属性:user -->
<attribute>
<name>user</name>
<required>true</required>
<fragment>true</fragment>
</attribute>
<!-- 配置标签属性:pass -->
<attribute>
<name>pass</name>
<required>true</required>
<fragment>true</fragment>
</attribute>
<!-- 配置标签属性:sql -->
<attribute>
<name>sql</name>
<required>true</required>
<fragment>true</fragment>
</attribute>
</tag>

上面代码分别为该标签配置了driverurluserpasssql五个属性,并指定这5个属性都是必需属性,而且属性值支持动态内容.
配置完毕后,就可在页面中使用标签了,先导入标签库,然后使用标签。使用标签的JSP页面片段如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<%@ page contentType="text/html; charset=UTF-8" language="java" errorPage="" %>
<!-- 导入标签库,指定mytag前缀的标签,
由http://www.crazyit.org/mytaglib的标签库处理 -->
<%@ taglib uri="http://www.crazyit.org/mytaglib" prefix="mytag"%>
<!DOCTYPE html>
<html>
<head>
<title>自定义标签示范</title>
</head>
<body bgcolor="#ffffc0">
<h2>下面显示的是查询标签的结果</h2>
<!-- 使用标签 ,其中mytag是标签前缀,根据taglib的编译指令,
mytag前缀将由http://www.crazyit.org/mytaglib的标签库处理 -->
<mytag:query
driver="com.mysql.jdbc.Driver"
url="jdbc:mysql://localhost:3306/javaee"
user="root"
pass="32147"
sql="select * from news_inf"/><br/>
</body>
</html>

测试

在浏览器中浏览该页面,效果如图2.35所示。

JSP页面中只需要使用简单的标签,即可完成“复杂”的功能:执行数据库查询,并将查询结果在页面上以表格形式显示。这也正是自定义标签库的目的以简单的标签,隐藏复杂的逻辑。
当然,并不推荐在标签处理类中访问数据库,因为标签库是表现层组件,它不应该包含任何业务逻辑实现代码,更不应该执行数据库访问,它只应该负责显示逻辑。

第三方标签库

  • JSTLSun提供的一套标签库,这套标签库的功能非常强大。
  • 另外,DisplayTagApache组织下的一套开源标签库,主要用于生成页面并显示效果

2.8 JSP2的自定义标签

JSP规范的1.1版中增加了自定义标签库规范,自定义标签库是一种非常优秀的表现层组件技术。通过使用自定义标签库,可以在简单的标签中封装复杂的功能.
为什么要使用自定义标签呢?主要是为了取代丑陋的JSP脚本。在HTML页面中插入JSP脚本有如下几个坏处

  • JSP脚本非常丑陋,难以阅读。
  • JSP脚本和HTML代码混杂,维护成本高。
  • HTML页面中嵌入JSP脚本,导致美工人员难以参与开发

出于以上三个原因,Web开发需要一种可在页面中使用的标签,这种标签具有和HTML标签类似的语法,但又可以完成JSP脚本的功能——这种标签就是JSP自定义标签。

JSP1.1规范中开发自定义标签库比较复杂,JSP2规范简化了标签库的开发,在JSP2中开发标签库只需如下几个步骤

  1. 开发自定义标签处理类。
  2. 建立一个*.td文件,每个*.td文件对应一个标签库,每个标签库可包含多个标签。
  3. JSP文件中使用自定义标签

标签库是非常重要的技术,通常来说,初学者、普通开发人员自己开发标签库的机会很少,但如果希望成为高级程序员,或者希望开发通用框架,就需要大量开发自定义标签了。所有的MVC框架,如Struts2SpringMVCJSF等都提供了丰富的自定义标签。

2.7.6 使用Servlet作为控制器

正如前面见到的,使用Servlet作为表现层的工作量太大,所有的HTML标签都需要使用页面输出流生成。因此,使用Servlet作为表现层有如下三个劣势

  1. 开发效率低,所有的HTML标签都需使用页面输出流完成。
  2. 不利于团队协作开发,美工人员无法参与Servlet界面的开发
  3. 程序可维护性差,即使修改一个按钮的标题,都必须重新编辑Java代码,并重新编译

在标准的MVC模式中,Servlet仅作为控制器使用。JavaEE应用架构正是遵循MVC模式的,对于遵循MVC模式的Java EE应用而言,JSP仅作为表现层(view)技术,其作用有两点:

  • 负责收集用户请求参数。
  • 将应用的处理结果、状态数据呈现给用户。

Servlet则仅充当控制器(Controller)角色,它的作用类似于调度员:所有用户请求都发送给Servlet,Servlet调用Model来处理用户请求,并调用JSP来呈现处理结果;或者Servlet直接调用JSP将应用的状态数据呈现给用户。
Model通常由JavaBean来充当,所有业务逻辑、数据访问逻辑都在Model中实现。实际上隐藏在Model下的可能还有很多丰富的组件,例如DAO组件、领域对象等。

程序示例 简单登录验证

下面介绍一个使用Servlet作为控制器的MVC应用,该应用演示了一个简单的登录验证。

login.jsp

下面是本应用的登录页面

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
<%@ page contentType="text/html; charset=UTF-8" language="java" errorPage="" %>
<!DOCTYPE html>
<html>
<head>
<title> 用户登录 </title>
</head>
<body>
<!-- 输出出错提示 -->
<span style="color:red;font-weight:bold">
<%
if (request.getAttribute("err") != null)
{
out.println(request.getAttribute("err") + "<br/>");
}
%>
</span>
请输入用户名和密码:
<!-- 登录表单,该表单提交到一个Servlet -->
<form id="login" method="post" action="login">
用户名:<input type="text" name="username"/><br/>
密&nbsp;&nbsp码:<input type="password" name="pass"/><br/>
<input type="submit" value="登录"/><br/>
</form>
</body>
</html>

以上页面使用JSP脚本输出错误提示,该页面其实是一个简单的表单页面,用于收集用户名及密码,并将请求提交到指定Servlet,该Servlet充当控制器角色。
根据严格的MVC规范,上面的login.jsp页面也不应该被客户端直接访问,客户的请求应该先发送到指定Servlet,然后由Servlet将请求forward到该JSP页面

LoginServlet.java

控制器Servlet的代码如下:

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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
package lee;

import javax.servlet.*;
import javax.servlet.http.*;
import javax.servlet.annotation.*;
import java.io.PrintWriter;
import java.io.IOException;
import java.sql.*;

@WebServlet(
name = "login",
urlPatterns =
{"/login"})
public class LoginServlet extends HttpServlet
{
// 响应客户端请求的方法
public void service(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, java.io.IOException
{
String errMsg = "";
// Servlet本身并不输出响应到客户端,因此必须将请求转发到视图页面
RequestDispatcher rd;
// 获取请求参数
String username = request.getParameter("username");
String pass = request.getParameter("pass");
try
{
// Servlet本身并不执行任何的业务逻辑处理,它调用JavaBean处理用户请求
// MySQL5.0
// DbDao dd = new DbDao("com.mysql.jdbc.Driver",
// "jdbc:mysql://localhost:3306/liuyan", "root", "32147");
// Mysql8.0
DbDao dd = new DbDao("com.mysql.cj.jdbc.Driver",
"jdbc:mysql://localhost:3306/liuyan?serverTimezone=GMT",
"root", "root");

// 查询结果集
ResultSet rs = dd.query(
"select pass from user_inf" + " where name = ?", username);
if (rs.next())
{
// 用户名和密码匹配
if (rs.getString("pass").equals(pass))
{
// 获取session对象
HttpSession session = request.getSession(true);
// 设置session属性,跟踪用户会话状态
session.setAttribute("name", username);
// 获取转发对象
rd = request.getRequestDispatcher("/welcome.jsp");
// 转发请求
rd.forward(request, response);
} else
{
// 用户名和密码不匹配时
errMsg += "您的用户名密码不符合,请重新输入";
}
} else
{
// 用户名不存在时
errMsg += "您的用户名不存在,请先注册";
}
} catch (Exception e)
{
e.printStackTrace();
}
// 如果出错,转发到重新登录
if (errMsg != null && !errMsg.equals(""))
{
rd = request.getRequestDispatcher("/login.jsp");
request.setAttribute("err", errMsg);
rd.forward(request, response);
}
}
}

控制器负责接收客户端的请求,它既不直接对客户端输出响应,也不处理用户请求,只调用JavaBean来处理用户请求,如程序中粗体字代码所示;JavaBean处理结束后,Servlet根据处理结果,调用不同的JSP页面向浏览器呈现处理结果。
上面Servlet使用@WebServlet注解为该Servlet配置了URL/login,因此向/login发送的请求将会交给该Servlet处理。

DbDao.java

下面是本应用中DaDao的源代码。

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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
package lee;

import java.sql.*;

public class DbDao {
private Connection conn;
private String driver;
private String url;
private String username;
private String pass;

public DbDao() {
}

public DbDao(String driver, String url, String username, String pass) {
this.driver = driver;
this.url = url;
this.username = username;
this.pass = pass;
}

// 下面是各个成员属性的setter和getter方法
public void setDriver(String driver) {
this.driver = driver;
}

public void setUrl(String url) {
this.url = url;
}

public void setUsername(String username) {
this.username = username;
}

public void setPass(String pass) {
this.pass = pass;
}

public String getDriver() {
return (this.driver);
}

public String getUrl() {
return (this.url);
}

public String getUsername() {
return (this.username);
}

public String getPass() {
return (this.pass);
}

// 获取数据库连接
public Connection getConnection() throws Exception {
if (conn == null) {
Class.forName(this.driver);
conn = DriverManager.getConnection(url, username, this.pass);
}
return conn;
}

// 插入记录
public boolean insert(String sql, Object... args) throws Exception {
PreparedStatement pstmt = getConnection().prepareStatement(sql);
for (int i = 0; i < args.length; i++) {
pstmt.setObject(i + 1, args[i]);
}
if (pstmt.executeUpdate() != 1) {
return false;
}
pstmt.close();
return true;
}

// 执行查询
public ResultSet query(String sql, Object... args) throws Exception {
PreparedStatement pstmt = getConnection().prepareStatement(sql);
for (int i = 0; i < args.length; i++) {
pstmt.setObject(i + 1, args[i]);
}
return pstmt.executeQuery();
}

// 执行修改
public void modify(String sql, Object... args) throws Exception {
PreparedStatement pstmt = getConnection().prepareStatement(sql);
for (int i = 0; i < args.length; i++) {
pstmt.setObject(i + 1, args[i]);
}
pstmt.executeUpdate();
pstmt.close();
}

// 关闭数据库连接的方法
public void closeConn() throws Exception {
if (conn != null && !conn.isClosed()) {
conn.close();
}
}
}

上面DaDao负责完成查询、插入、修改等操作。从上面这个应用的结构来看,整个应用的流程非常清晰,下面是MVC中各个角色的对应组件:

  • M:Model,即模型,对应JavaBean
  • V:View,即视图,对应JSP页面。
  • C:Controller,即控制器,对应Servlet

data.sql

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
DROP DATABASE IF EXISTS liuyan;
CREATE DATABASE liuyan;

USE liuyan;

CREATE TABLE user_inf
(
id INT AUTO_INCREMENT PRIMARY KEY,
NAME VARCHAR(255),
pass VARCHAR(255)
);

INSERT INTO user_inf
VALUES(NULL,'crazyit','123');
INSERT INTO user_inf
VALUES(NULL,'tiger','123');
;

2.7.5 访问Servlet的配置参数

配置Servlet时,还可以增加额外的配置参数。通过使用配置参数,可以实现提供更好的可移植性,避免将参数以硬编码方式写在程序代码中。

Servlet配置参数方式

Servlet配置参数有两种方式。

  1. 通过aWebServletinitParams属性来指定。
  2. 通过在web.xml文件的<servlet>元素中添加<init-param>子元素来指定

第二种方式与为JSP配置初始化参数极其相似,因为JSP的实质就是Servlet,而且配置JSP的实质就是把JSPServlet使用。

ServletConfig

访问Servlet配置参数通过ServletConfig对象完成,ServletConfig提供如下方法:

  • java.lang.String getInitParameter(java.lang.String name):用于获取初始化参数。

JSP的内置对象config就是此处的ServletConfig

程序示例

下面的Servlet将会连接数据库,并执行SQL查询,但程序并未直接给出数据库连接信息,而是将数据库连接信息放在web.xml文件中进行管理。

TestServlet.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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
package lee;

import java.sql.*;
import javax.servlet.http.*;
import javax.servlet.*;
import javax.servlet.annotation.*;
import java.io.*;

@WebServlet(
name = "testServlet",
urlPatterns = { "/testServlet" },
initParams = {
@WebInitParam(name = "driver", value = "com.mysql.jdbc.Driver"),
@WebInitParam(name = "url", value = "jdbc:mysql://localhost:3306/javaee"),
@WebInitParam(name = "user", value = "root"),
@WebInitParam(name = "pass", value = "32147") }
)
public class TestServlet extends HttpServlet {
// 重写init方法
public void init(ServletConfig config) throws ServletException {
// 重写该方法,应该首先调用父类的init()方法
super.init(config);
}

// 响应客户端请求的方法
public void service(HttpServletRequest request, HttpServletResponse response)
throws ServletException, java.io.IOException {
try {
// 获取ServletConfig对象
ServletConfig config = getServletConfig();
// 通过ServletConfig对象获取配置参数:dirver
String driver = config.getInitParameter("driver");
// 通过ServletConfig对象获取配置参数:url
String url = config.getInitParameter("url");
// 通过ServletConfig对象获取配置参数:user
String user = config.getInitParameter("user");
// 通过ServletConfig对象获取配置参数:pass
String pass = config.getInitParameter("pass");
// 注册驱动
Class.forName(driver);
// 获取数据库连接
Connection conn = DriverManager.getConnection(url, user, pass);
// 创建Statement对象
Statement stmt = conn.createStatement();
// 执行查询,获取ResuletSet对象
ResultSet rs = stmt.executeQuery("select * from news_inf");
response.setContentType("text/html;charSet=gbk");
// 获取页面输出流
PrintStream out = new PrintStream(response.getOutputStream());
// 输出HTML标签
out.println("<html>");
out.println("<head>");
out.println("<title>访问Servlet初始化参数测试</title>");
out.println("</head>");
out.println("<body>");
out.println("<table bgcolor=\"#9999dd\" border=\"1\"" + "width=\"480\">");
// 遍历结果集
while (rs.next()) {
// 输出结果集内容
out.println("<tr>");
out.println("<td>" + rs.getString(1) + "</td>");
out.println("<td>" + rs.getString(2) + "</td>");
out.println("</tr>");
}
out.println("</table>");
out.println("</body>");
out.println("</html>");
} catch (Exception e) {
e.printStackTrace();
}
}
}

@WebServlet的initParams属性

ServletConfig获取配置参数的方法和ServletContext获取配置参数的方法完全一样,只是ServletConfig是取得当前Servlet的配置参数,而ServletContext是获取整个Web应用的配置参数。

以上程序中@WebServlet中的initParams属性:

@WebServlet(
    name = "testServlet", 
    urlPatterns = { "/testServlet" }, 
    initParams = {
        @WebInitParam(name = "driver", value = "com.mysql.jdbc.Driver"),
        @WebInitParam(name = "url", value = "jdbc:mysql://localhost:3306/javaee"),
        @WebInitParam(name = "user", value = "root"), 
        @WebInitParam(name = "pass", value = "32147") }
)

用于为该Servlet配置参数,initParams属性值的每个@WebInitParam配置一个初始化参数.

@WebInitParam的属性

每个@WebInitParam可指定如下两个属性。

  • name:指定参数名。
  • value:指定参数值。

servlet元素的子元素init-param的子元素

类似地,在web.xml文件中为Servlet配置参数使用<init-param>元素,该元素可以接受如下两子元素:

  • param-name:指定配置参数名。
  • param-Value:指定配置参数值。

对应的web.xml配置

下面是该Servletweb.xml文件中的配置片段。

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
<servlet>
<!-- 配置Servlet名 -->
<servlet-name>testServlet</servlet-name>
<!-- 指定Servlet的实现类 -->
<servlet-class>lee.TestServlet</servlet-class>
<!-- 配置Servlet的初始化参数:driver -->
<init-param>
<param-name>driver</param-name>
<param-value>com.mysql.jdbc.Driver</param-value>
</init-param>
<!-- 配置Servlet的初始化参数:url -->
<init-param>
<param-name>url</param-name>
<param-value>jdbc:mysql://localhost:3306/javaee</param-value>
</init-param>
<!-- 配置Servlet的初始化参数:user -->
<init-param>
<param-name>user</param-name>
<param-value>root</param-value>
</init-param>
<!-- 配置Servlet的初始化参数:pass -->
<init-param>
<param-name>pass</param-name>
<param-value>32147</param-value>
</init-param>
</servlet>
<servlet-mapping>
<!-- 确定Servlet名 -->
<servlet-name>testServlet</servlet-name>
<!-- 配置Servlet映射的URL -->
<url-pattern>/testServlet</url-pattern>
</servlet-mapping>

以上配置片段的粗体字代码配置了4个配置参数,Servlet通过这4个配置参数就可连接数据库在浏览器中浏览该Servlet,可看到数据库査询成功(如果数据库的配置正确)。

2.7.4 load-on-startup Servlet

上一节中已经介绍过,创建Servlet实例有两个时机:用户请求之时或应用启动之时。应用启动时就创建Servlet,通常是用于某些后台服务的Servlet,或者需要拦截很多请求的Servlet;这种Servlet通常作为应用的基础Servlet使用,提供重要的后台服务。
配置load-on-startupServlet有两种方式。

  • web.xml文件中通过<servlet>元素的<load-on-startup>子元素进行配置
  • 通过@WebServlet注解的loadOnStartup属性指定。

<load-on-startup>元素或loadOnStartup属性都只接收一个整型值,这个整型值越小,Servlet就越优先实例化。

使用注解配置load-on-startup的Servlet

TimerServlet.java

下面是一个简单的Servlet,该Servlet不响应用户请求,它仅仅执行计时器功能,每隔一段时间会在控制台打印出当前时间。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package lee;

import javax.servlet.http.*;
import javax.servlet.*;
import javax.servlet.annotation.*;

import javax.swing.*;
import java.awt.event.*;
import java.util.Date;

@WebServlet(loadOnStartup = 1)
public class TimerServlet extends HttpServlet {
public void init(ServletConfig config) throws ServletException {
super.init(config);
Timer t = new Timer(1000, new ActionListener() {
public void actionPerformed(ActionEvent e) {
System.out.println(new Date());
}
});
t.start();
}
}

这个Servlet没有提供service()方法,这表明它不能响应用户请求,所以无须为它配置URL映射由于它不能接收用户请求,所以只能在应用启动时实例化。

以上程序中的注解:

1
@WebServlet(loadOnStartup = 1)

在web.xml中配置load-on-startup的Servlet

即可将该Servlet配置了load-on-startup Servlet.除此之外,还可以在web.xml文件中增加如下配置片段。

1
2
3
4
5
6
7
8
9
<servlet>
<!-- Servlet名 -->
<servlet-name>timerServlet</servlet-name>
<!-- Servlet的实现类 -->
<servlet-class>lee.TimerServlet</servlet-class>
<!-- 配置应用启动时,创建Servlet实例
,相当于指定@WebServlet的loadOnStartup属性-->
<load-on-startup>1</load-on-startup>
</servlet>

以上配置片段中粗体字代码指定Web应用启动时,web容器将会实例化该Servlet,且该Servlet不能响应用户请求,将一直作为后台服务执行:每隔1秒钟输出一次系统时间。