Servlet&JSP的那些事儿(五)

我们在Servlet&JSP的那些事儿(二)中讨论过init()方法,其中提到了再init()方法中有一个类型为ServletConfig的参数,Servlet容器通过这个参数向Servlet传递配置信息。Servlet使用ServletConfig对象从Web应用程序的配置信息中获取以名-值对形式提供的初始化参数。另外,在Servlet中,还可以通过ServletConfig对象获取描述Servlet运行环境的ServletContext对象,使用该对象,Servlet可以和它的Servlet容器进行通信。现在我们来深入了解一下为什么要使用ServletConfig参数以及如何使用它的问题。

比如我希望我的email地址能出现在由servlet生成的网页上,不过我的email可能要变,但是我又不想因为这个重新编译我的servlet代码,那么我改如何呢?我想在部署描述文件(DD)中配置我的email地址,而不是把它硬编码到servlet类中。也即,我不希望这样:

PrintWriter out = response.getWriter();
out.println("shan@email.com");
硬编码email地址很不好,因为如果我要改变email,就需要重新编译一遍servlet类,这样很麻烦,所以可以在DD文件(web.xml)中做如下部署:

<servlet>
	<servlet-name>TestServlet</servlet-name>
	<servlet-value>com.shan.web.TestServlet</servlet-value>

	<init-param>
		<param-name>email</param-name>
		<param-value>shan@email.com</param-value>
	</init-param>
</servlet>
在servlet代码中,添加如下语句:
out.println(getServletConfig().getInitParameter("email"));
getServletConfig()方法返回一个ServletConfig,getInitParameter("email")返回名为email的参数值。容器初始阿华一个servlet时,会为这个servlet创建一个唯一的ServletConfig。容器从DD读出servlet初始化参数,并把这些参数交给ServletConfig,然后把ServletConfig传递给servlet的init()方法。注意,不能从servlet构造方法中调用getServletConfig()方法,因为在容器调用init()方法之前,它还不是一个完整的servlet。

那么在容器如何初始化servlet参数时,发生了什么呢?

1)容器在创建一个servlet时,它会读取DD,包括servlet初始化参数<init-param>。
2)容器为这个servlet创建一个新的ServletConfig。
3)为每个servlet初始化参数创建一个String名/值对。
4)容器向ServletConfig提供名/值对初始化参数的引用。
5)容器创建servlet类的一个新实例。
6)容器调用servlet的init()方法,传入ServletConfig的引用。

从这个过程中我们可以看出,容器建立servlet时,它会读取DD,并为ServletConfig创建名/值对。之后,容器不会再读取初始化参数了。所以,不能在servlet生命周期中改变email地址,这种方法还是太笨拙了。不过比起硬编码,好了一些。如果需要改变email地址,重新部署应用即可。

JSP能得到servlet初始化参数吗?

ServletConfig用于servlet配置,而不是JSPConfig。所以,想让应用的其他部分使用你在DD中配置的servlet初始化参数的信息,就需要使用我们在Servlet&JSP的那些事儿(四)中讲到到请求转发机制。只需要在请求中设置一个属性,接收转发请求的JSP就可以得到这个消息。

String email = getServletConfig().getInitParameter("email");
request.setAttribute("email",email);
RequestDispatcher rd=request.getRequestDispatcher("/test.jsp");
rd.fordward(request,response);
但是我们可能需要在整个应用中使用这个地址。一种方法是让servlet读取初始化参数,然后把它们保存起来,这样应用的其他部分都能使用,但是这样一来,我们必须知道应用部署的时候首先运行哪个servlet,而且如果更改应用,一切都会被搞砸。所以,这样是不行的。

鉴于此,我们采用上下文初始化参数。它和servlet初始化参数很类似,只不过上下文初始化参数对整个web应用而不是一个servlet可用。所以,servlet和jsp都可以访问上下文初始化参数。我们需要在web.xml中做如下修改:

<servlet>
	<servlet-name>TestServlet</servlet-name>
	<servlet-value>com.shan.web.TestServlet</servlet-value>
</servlet>

<context-param>
	<param-name>email</param-name>
	<param-value>shan@email.com</param-value>
</context-param>
注:<context-param>是针对整个web应用的,所以不嵌套在<servlet>中。

在servlet代码中,添加如下语句:

out.println(getServletContext().getInitParameter("email"));
或者

ServletContext context = getServletContext();
out.println(context.getInitParameter("email"));
注意servlet初始化参数和上下文初始化参数的区别。整个web应用中只有一个ServletContext,而且web应用中所有部分都能访问它。不过,应用中的每个servlet都有自己的ServletConfig。部署web应用时,容器会建立一个ServletContext(注:如果你的应用分布在多个服务器上,那么web应用实际上可以有多个ServletContext,一个ServletContext的确只对应一个应用,但前提是应用在一个JVM中。),这个上下文对web应用中的每个servlet和jsp都可用。

说到这里,如果我们希望应用初始化参数是一个数据库DataSource呢?

上下文初始化参数只能是String。毕竟我们不能把一个对象塞到xml部署描述文件中去。如果整个web应用有一个main方法就好了,可以添加一些在servlet或jsp之前运行的代码。虽然servlet没有main方法,但是却有监听者(listener)。我们想要的实际上是监听一个初始化事件,这样就能得到上下文初始化参数,并在应用为客户服务之前运行一些代码。但是,应用中的哪部分能做监听者的工作呢?我们不希望让一个servlet来做这样的工作,毕竟它的任务不是这个。

我们需要的其实是一个ServletContextListener。它能监听ServletContext一生中的两个关键事件:初始化(创建)和撤销。它能做到:

在上下文初始化时(应用部署时)得到通知。

  • 从ServletContext得到上下文初始化参数。
  • 使用初始化参数查找名建立一个数据库连接
  • 把数据库连接作为一个属性,使得web应用的各部分都能访问。
在上下文撤销时(应用取消部署或结束)得到通知。
  • 关闭数据库连接。

实例,一个简单的ServletContextListener

因为我们现在不想建立一个数据库,所以不使用连接数据库的例子。我们的这个实例会将String初始化参数转化为一个对象-Dog。监听者的任务是得到有关狗品种的上下文初始化参数(藏獒、松狮、萨摩、中华田园犬等),然后使用这个String来构造一个Dog对象,监听者再把这个Dog对象保存到一个ServletContex属性中,以便servlet获取。问题的关键是servlet能访问一个共享的应用对象(Dog),而且不用读上下文参数,所以这个共享应用对象是不是数据库链接就没有关系了。

创建一个监听者类:

package com.shan.listen;

import com.shan.model.Dog;
import javax.servlet.*;

public class MyServletContextListener implements ServletContextListener {
	public void contextInitialized(ServletContextEvent event) {
		ServletContext context = event.getServletContext();
		String dogBreed = context.getInitParameter("breed");
		Dog dog = new Dog(dogBreed);
		context.setAttribute("dog",dog);
	}
	
	public void contextDestroyed(ServletContextEvent event) {
		
	}
}
其中,Dog类代码如下:

package com.shan.model;

public class Dog {
	private String breed = null;
	public Dog(String breed) {
		this.breed = breed;
	}

	public String getBreed(){
		return breed;
	}
}
编写servlet类,代码如下:

package com.shan.web;

import com.shan.model.Dog;
import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;

public class TestListenerServlet extends HttpServlet {
	public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException,IOException {
		response.setContentType("text/html;charset=gb2312");         
		PrintWriter out=response.getWriter();
		
		out.println("一个监听者设置context属性的测试:");
		out.println("<br/>");
		Dog dog = (Dog)getServletContext().getAttribute("dog");
		out.println("狗的品种是:"+dog.getBreed());
		out.close();
	}
		
	public void doPost(HttpServletRequest request,HttpServletResponse response) throws ServletException,IOException {
		doGet(request,response);
	}
}
注:不能忘记强制转换类型(Dog)。

最后编写部署描述文件web.xml

<?xml version='1.0' encoding='utf-8'?>
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
                      http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
  version="3.0"
  metadata-complete="true">
	<servlet>
		<servlet-name>TestListenerServlet</servlet-name>
		<servlet-class>com.shan.web.TestListenerServlet</servlet-class>
	</servlet>

	<servlet-mapping>
		<servlet-name>TestListenerServlet</servlet-name>
		<url-pattern>/ListenTest.do</url-pattern>
	</servlet-mapping>

	<context-param>
		<param-name>breed</param-name>
		<param-value>藏獒</param-value>
	</context-param>

	<listener>
		<listener-class>
			com.shan.listen.MyServletContextListener
		</listener-class>
	</listener>
</web-app>
貌似没有在xml元素来指明所监听的事件类型?其实容器会检查类,并注意监听者接口(或多个接口,一个监听者可以实现多个监听者接口),以此明确监听什么类型的事件。

我们建立web应用的过程和前面叙述的相同,唯一变化的是添加了新的java包com.shan.listen,用以存放监听类,最后的部署也和前面的相同,在此就不再赘述。

不过我们还需要执行如下语句来编译上述类(先切换到工程所在目录):

javac -d classes src\com\shan\model\Dog.java
javac -classpath D:\apache-tomcat-7.0.33\lib\servlet-api.jar;classes -d classes src\com\shan\listen\MyServletContextListener.java
javac -classpath D:\apache-tomcat-7.0.33\lib\servlet-api.jar;classes -d .\classes src\com\shan\web\TestListenerServlet.java
启动tomcat,从而部署应用。测试结果如下:

图1 实例运行结果

我们来讨论一下整个完整的场景。大概分为以下几步:

1)容器读取该应用的部署描述文件,包括<listen>和<context-param>元素。
2)容器为这个应用创建一个新的ServletContext,应用的所有部分都会共享这个上下文。
3)容器为每个上下文初始化参数创建一个String名/值对,这里假设只有一个参数。
4)容器将名/值参数的引用交给ServletContext。
5)容器创建MyServletContextListener类的一个新实例。
6)容器调用监听者的contextInitialized()方法,传入新的ServletContextEvent。这个事件对象有一个ServletContext引用,所以事件处理代码可以从事件上下文,并从上下文得到上下文初始化参数。
7)监听者向ServletContextEvent要ServletContext的一个引用。
8)监听者向ServletContext要上下文初始化参数"breed"。
9)监听者使用初始化参数来构造一个新的Dog对象。
10)监听者把Dog设置为ServletContext中的一个属性。
11)容器建立一个新的servlet(也就是说,利用初始化参数建立一个新的ServletConfig,为这个ServletConfig提供ServletContext的一个引用,然后调用servlet的init()方法)。
12)servlet得到一个请求,向ServletContext请求属性"dog"。
13)servlet在Dog上调用getBreed(),并将结果输出到HttpResponse。

除了上下文监听者接口之外,还有其他的监听者接口。

场景监听者接口事件类型
你想知道一个web应用上下文中是否增加、删除或替换了一个属性javax.servlet.ServletContextAttributeListener
attributeAdded
attributeRemoved
attributeReplaced
ServletContextAttributeEvent
你想知道有多少个并发用户。也就是说,你想跟踪活动的会话javax.servlet.http.HttpSessionListener
sessionCreated
sessionDestroyed
HttpSessionEvent
每次请求到来时你都想知道,以便建立日志javax.servlet.ServletRequestListener
requestInitialized
requestDestroyed
ServletRequestEvent
你想知道什么时候增加、删除或替换一个请求属性javax.servlet.ServletRequestAttributeListener
attributeAdded
attributeRemoved
attributeReplaced
ServletRequestAttributeEvent
你有一个属性类(这个类表示的对象将被放在一个属性中),而且你希望这个类型的对象绑定到一个会话或从会话删除时得到通知javax.servlet.http.HttpSessionBindingListener
valueBound
valueUnbound
HttpSessionBindingEvent
你想知道什么时候增加、删除或替换一个会话属性javax.servlet.http.HttpSessionAttributeListener
attributeAdded
attributeRemoved
attributeReplaced
HttpSessionBindingEvent(注:这里的命名不一致!)
你想知道是否创建或撤销了一个上下文javax.servlet.ServletContextListener
contextInitialized
contextDestroyed
ServletContextEvent
你有一个属性类,而且希望此类对象绑定的会话迁移到另一个JVM时得到通知javax.servlet.http.HttpSessionActivationListener
sessionDidActivate
sessionWillPassivate
HttpSessionEvent(注:这里的命名不一致!)

转载请注明出处:http://blog.csdn.net/iAm333

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值