2.6 Servlet
就当时功能来说,Servlet所提供的功能就是一个Java版本的CGI。它依旧是一个具有跨平台特性、100%纯Java的Server-Side程序。Java Servlet API定义了Servlet和服务器之间的一个标准接口,这使得Servlet具有跨服务器平台的特性。
2.6.1 Servlet简介
Servlet通过创建一个框架扩展服务器的能力,采用请求-响应模式提供Web服务。当客户机发送请求至服务器时,服务器将请求信息发送给Servlet,Servlet生成响应内容并将其传给服务器,然后再由服务器将响应返回给客户端。图2.22展示了Servlet的生命周期,定义了一个Servlet如何被加载、初始化,以及它怎样提供服务和最后销毁的过程。
图2.23展示了Servlet如何接收客户端请求、处理请求,最后返回响应的流程。
图2.22 Servlet生命周期
图2.23 Servlet响应请求序列图
2.6.2 创建Servlet
所有的Servlet必须直接或间接地实现javax.servlet.Servlet接口,这样才能在Servlet服务器或称为Servlet容器、Servlet引擎上运行,更简单的方法是继承HttpServlet类。实例2-13给出一个Servlet的类,功能是在用户请求中获取name参数,然后向客户端输出一个HTML网页。
【实例2-13】Servlet示例HelloServlet
01 package com.book.web3; 02 import java.io.*; 03 import javax.servlet.*; 04 import javax.servlet.http.*; 05 06 public class HelloServlet extends HttpServlet { 07 @Override 08 public void service(ServletRequest req, ServletResponse res) 09 throws IOException, ServletException { 10 String name = req.getParameter("name"); //获取传递过来的name属性的值 11 res.setContentType("text/html"); 12 PrintWriter out = res.getWriter(); //输出html文件内容 13 out.println("<html>"); 14 out.println("<body>"); 15 out.println("<head>"); 16 out.println("<title>Hello Reader!</title>"); 17 out.println("</head>"); 18 out.println("<body>"); 19 out.println("<h1>Hello " + name + "!</h1>"); //显示相应的信息 20 out.println("</body>"); 21 out.println("</html>"); 22 } 23 }
【代码剖析】上面代码中首先在第10行获取参数name的值,然后在第11行通过方法setContentType()设置返回页面的类型,最后在第12行到21行间创建出out对象,并通过该对象把相应内容输出到返回页面。
还需要在web.xml中配置这个Servlet,让服务器可以根据用户请求的地址定位到这个Servlet类,如实例2-14。定义这个Servlet为“helloreader”,在mapping中定义了可以通过“/helloreader”这个URL来访问页面。
【实例2-14】Servlet在web.xml中的配置
01 <!--定义Servlet--> 02 <servlet> 03 <servlet-name>helloreader</servlet-name> 04 <servlet-class>com.book.web3.HelloServlet</servlet-class> 05 </servlet> 06 <!--定义Servlet与URL映射关系--> 07 <servlet-mapping> 08 <servlet-name>helloreader</servlet-name> 09 <url-pattern>/helloreader</url-pattern> 10 </servlet-mapping>
【代码剖析】上面代码中首先通过标签<servlet>来定义关于Servlet的类,然后通过标签<servlet-mapping>来设置Servlet类的映射。
【运行程序】浏览该页面,结果如图2.24所示。
2.6.3 过滤器
图2.24 Servlet结果图
一个过滤器(Filter)是一个可以传送请求或修改响应的对象。过滤器并不是Servlet,它们并不实际创建一个请求。它们是请求到达一个Servlet前的预处理程序,在响应离开Servlet后的后处理程序。一个过滤器能够做如下工作:
1)在一个Servlet被调用前截获该调用。
2)在一个Servlet被调用前检查请求。
3)修改在实际请求中提供了可定制请求对象的请求头和请求数据。
4)修改在实际响应中提供了可定制响应对象的响应头和响应数据。
5)在一个Servlet被调用之后截获该调用。
一个过滤器实现java.servlet.Filter接口并定义它的3个方法,见表2.9。
表2.9 Filter接口函数表
有一个实际的例子,在Tomcat 4.0发布中被命名为ExampleFilter,实例2-15使用这个过滤器可以计算执行某个Servlet所用时间并记录在日志中。
【实例2-15】Filter示例
01 package com.book.web3; 02 import java.io.*; 03 import javax.servlet.*; 04 import javax.servlet.http.*; 05 /* 06 * 实现一个过滤器 07 */ 08 public class TimerFilter implements Filter { 09 private FilterConfig config = null; //定义变量FilterConfig 10 public void init(FilterConfig config) throws ServletException { //初始化方法 11 this.config = config; //赋值变量config 12 } 13 public void destroy() { //销毁方法 14 config = null; 15 } 16 /* 17 * filter必须实现的方法 18 */ 19 public void doFilter(ServletRequest request, ServletResponse response, 20 FilterChain chain) throws IOException, ServletException { 21 long before = System.currentTimeMillis(); //定义过滤前的时间 22 chain.doFilter(request, response); 23 long after = System.currentTimeMillis(); //定义过滤后的时间 24 String name = ""; 25 if (request instanceof HttpServletRequest) { 26 name = ((HttpServletRequest) request).getRequestURI(); 27 } 28 // 记录时间 29 config.getServletContext().log(name + ": " + (after - before) + "ms"); 30 } 31 }
【代码剖析】如果要实现过滤器,则必须重写doFilter()方法。在该方法中,首先获取执行Servlet之前和之后的时间,然后获取请求的URI,最后输出相应的信息。
使用此过滤器,还要在web.xml文件中用<filter>标签部署它:
<filter> <filter-name>timerFilter</filter-name> <filter-class>TimerFilter</filter-class> </filter>
过滤器被越来越广泛地应用,当前热门的开发组件webWork和后文将重点介绍的Struts2就大量使用了过滤器。
2.6.4 监听器
监听器(Listener)可以监听客户端的请求、服务端的操作等。通过监听器,可以自动激发一些操作,如监听在线用户数量,当增加一个HttpSession时,给在线人数加1。监听器还有一个好处是可以在不修改现有系统基础上增加Web应用程序生命周期事件的跟踪。
编写监听器需要实现相应的接口。常用的监听接口如下:
(1)ServletContextAttributeListener
监听对ServletContext属性的操作,比如增加、删除、修改。
(2)ServletContextListener
监听ServletContext。当创建ServletContext时,激发contextInitialized方法;当销毁ServletContext时,激发contextDestroyed方法。
(3)HttpSessionListener
监听HttpSession的操作。当创建一个Session时,激发sessionCreated方法;当销毁一个Session时,激发sessionDestroyed方法。
(4)HttpSessionAttributeListener
监听HttpSession中的属性的操作。当在Session中增加一个属性时,激发attributeAdded方法;当在Session中删除一个属性时,激发attributeRemoved方法;当在Session属性被重新设置时,激发attributeReplaced方法。
下面的例子是利用HttpSessionListener接口计算在线用户数量,实例2-16和实例2-17展示的是如何编写这个监听器。
【实例2-16】Listener示例OnlineCounter.java
01 package com.book.web3; 02 //一个简单的计数程序 03 public class OnlineCounter { 04 private static long online = 0; 05 public static long getOnline() { 06 return online; 07 } 08 // 累加 09 public static void raise() { 10 online++; 11 } 12 // 减1 13 public static void reduce() { 14 online--; 15 } 16 }
【代码剖析】在上述代码中创建了一个关于在线人数的类。在该类中有一个表示在线人数的变量online,然后又创建了两个方法raise()和reduce(),前者用来实现在线人数加1功能,后者实现在线人数减1功能。
【实例2-17】Listener示例OnlineCounterListener.java
01 package com.book.web3; 02 import javax.servlet.http.HttpSessionEvent; 03 import javax.servlet.http.HttpSessionListener; 04 //实现一个监听器程序 05 public class OnlineCounterListener implements HttpSessionListener { 06 public void sessionCreated(HttpSessionEvent hse) { 07 // 收到一个创建session事件, 计数器加1 08 OnlineCounter.raise(); 09 } 10 11 public void sessionDestroyed(HttpSessionEvent hse) { 12 // 收到一个销毁session事件, 计数器减1 13 OnlineCounter.reduce(); 14 } 15 }
【代码剖析】在上述代码中重写了sessionCreated()方法,在该方法中调用类OnlineCounter的raise()方法实现每创建一个Session对象,就让在线人数加1。同时也重写了sessionDestroyed()方法,在该方法中调用类OnlineCounter的reduce()方法,实现每销毁一个Session对象,就让在线人数减1。
使用此过滤器,还要在web.xml文件中用<listener>标签部署它。如下所示:
<listener> <listener-class>com.book.web3.OnlineCounterListener</listener-class> </listener>