Servlet&JSP的那些事儿(六)

什么是属性?

Servlet&JSP的那些事儿(五)中,我们了解了ServletContext监听者如何在获取上下文初始化参数后创建一个对象,以及如何将对象作为一个属性存储到ServletContext,以便web应用的其他部分能得到该对象。属性就是一个对象,设置(或称之为绑定)到另外3个servlet API对象中-SservletContext、HttpServletRequest(或HttpServletResponse)、HttpSession。可以把它简单的认为是一个映射实例对象中的名/值对(名是一个String,值是一个Object)。在实际中,我们不关心它如何实现,只关心属性的作用域。通俗的讲,就是看它能活多久。

属性和参数的区别

属性不是参数,它们之间的区别如表1。

 属性参数
类型应用/上下文
请求
会话(注:没有特定于servlet的属性,只需要使用实例变量。
应用/上下文初始化参数
请求参数
servlet初始化参数(注:没有会话参数的说法。
设置方法setAttribute(String name, Object value)不能设置应用和servlet初始化参数,他们都在web.xml中设置。
返回类型ObjectString
获取方法getAttribute(String name)(注:不要忘了强制转换,因为返回类型是ObjectgetInitParameter(String name)
表1 属性和参数的区别

三个作用域:上下文、请求和会话

上下文属性,web应用中的每一部分都能访问。会话属性,只有能访问特定HttpSession的部分才能访问。而请求属性,只有能访问特定ServletRequest的部分才能访问。具体的作用域见表2。

 可访问性
(谁能看到)
作用域
(能存活多久)
适用性
Context(上下文)
(不是线程安全的)
web应用的所有部分,包括servlet,jsp,servlet-contextlistener等。ServletContext的生命周期,这意味着所部属应用的生命期。如果服务器或应用关闭,上下文则撤销,其属性也相应撤销。你希望整个应用共享的资源,包括数据库连接、JNDJ查找名、email地址等。
HttpSession(会话)
(不是线程安全的)
访问这个特定会话的所有servlet或jsp。注意,会话从一个客户请求扩展到可能跨同一个客户的多个请求,这些请求可能到达多个servlet。会话的生命期。会话可以通过编程撤销,也可能只是因为超时而撤销。与客户会话有关的资源和数据,而不只是与一个请求相关的资源。它要与客户完成一个持续的会话。购物车是一个典型例子。
Request(请求)
(是线程安全的)
应用中能直接访问请求对象的所有部分。基本上说,这意味着接收所转发请求的jsp和servlet(使用RequestDispatcher),另外还有与请求相关的监听者。请求的生命周期。这说明会持续到servlet的service()方法结束。也即,线程/栈处理这个请求的生命周期。将模型信息从控制器传递到视图,或者传递特定于客户请求的任何数据。

表2 属性作用域

上下文作用域不是安全的

因为应用中的每一部分都能访问上下文属性,而这意味着可能有多个servlet。多个servlet则说明你可能有多个线程,因为请求时并发处理的,每个请求在一个单独的线程中处理。例如如下语句:

getServletContext().setAttribute("boo","12");
getServletContext().setAttribute("foo","24");

out.println(getServletContext().getAttribute("boo"));
out.println(getServletContext().getAttribute("foo"));
在一个复杂项目中运行的时候,第一次运行的时候,可能会输出预期结果12 24,第二次输出可能会是12,,36了。可能的原因是:servlet A设置了上下文属性"boo"的值为"12","foo"的值为"24"。之后线程B,即servlet B成为活动线程(此时线程A回到可运行但未运行的状态),并设置上下文属性"foo"的值为"36"(值"24"丢了)。这时线程A又重新成为活动线程,它取得"foo"的值,结果就输出了36。

如何让上下文属性做到线程安全?

既然上下文属性不是线程安全的,那么我们该如何改进呢?有人说使用同步服务的方法。也即对doGet方法做如下修改:

public synchronized void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException,IOException {
	response.setContentType("text/html;charset=utf-8");         
	PrintWriter out=response.getWriter();
		
	getServletContext().setAttribute("boo","12");
	getServletContext().setAttribute("foo","24");

	out.println(getServletContext().getAttribute("boo"));
	out.println(getServletContext().getAttribute("foo"));
}
即给doGet方法添加synchronized关键字。但此方法意味着servlet中一次只能运行一个线程,但是并不能阻止其他servlet或jsp访问这个属性。同步服务方法会防止同一个servlet中的其他线程访问上下文属性,但是不能阻止另外一个servlet的访问。所以,不是需要对servlet加锁,而是需要对上下文加锁,也即,应该做如下修改:

public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException,IOException {
	response.setContentType("text/html;charset=utf-8");         
	PrintWriter out=response.getWriter();
	
	synchronized(getServletContext()) {
		getServletContext().setAttribute("boo","12");
		getServletContext().setAttribute("foo","24");

		out.println(getServletContext().getAttribute("boo"));
		out.println(getServletContext().getAttribute("foo"));
	}
}
保护上下文属性的一般做法是对上下文对象本身同步,如果访问上下文的每一个人都必须先得到上下文对象的锁,就能保证一次只有一个线程可以得到或设置上下文属性。但是,只有当处理这些上下文属性的其他代码也对ServletContext同步时,这种做法才起作用。如果一段代码没有请求锁,那么这段代码还是能自由访问上下文属性。

会话属性是安全的吗?

貌似还没有介绍会话呢。会话会在下一篇讨论。其实会话就是一个对象,用于维护与一个用户的会话状态。对于同一个用户的多个请求,会话会跨这些请求持久存储。先来看看只有一个用户的情况,如果只有一个用户,而且一个客户一次只有一个请求,这就说明会话是线程安全的吗?会有一个用户一次有多个请求的情况发生吗?元芳,你怎么看?

用户有可能打开一个新的浏览器窗口,在这种情况下,容器还是为一个用户使用同样的会话,尽管它来自另一个浏览器实例。所以,会话属性不是线程安全的。那么如何保护这些会话属性不受多线程的破坏呢?呵呵,只需要像上下文属性一样,对所有访问这些会话属性的代码都进行同步即可。那么,该对谁同步呢?必须对HttpSession同步!代码如下

public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException,IOException {
	response.setContentType("text/html;charset=utf-8");         
	PrintWriter out=response.getWriter();
	
	HttpSession session = request.getSession();
	synchronized(session) {
		session .setAttribute("boo","12");
		session .setAttribute("foo","24");

		out.println(getServletContext().getAttribute("boo"));
		out.println(getServletContext().getAttribute("foo"));
	}
}

为了防止同步产生大量的开销,所以,一定要在最短的时间内完成同步目标,要让同步块尽可能小。

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值