JAVA CDI 学习(2) - Scope 生命周期
在上一节中,我们已经知道了如何用@Inject实现基本注入,这一节研究Bean实例注入后的“生命周期”,web application中有几种基本的生命周期(不管哪种编程语言都类似)
1、Application 生命周期
即:web application启动后,处于该生命周期级别的对象/变量,将一直存在,可以被所有web应用的用户共同访问,通常用来做网站计数器,实现流量访问之类。直到web 应用停止或重新启动,该对象才被销毁。简单来说:只要web application处于激活状态,不论你换什么浏览器,不论你是否关闭页面,这个对象都会一直存在。
2、Session 生命周期
每次我们在某种类型的浏览器(比如:IE或Firefox)里,请求web application的某个页面时,就会生成Session,只要浏览器不关闭,Session就能持续有效(哪怕你把当前Tab标签页面给关掉,或者在当前url地址栏,输入一个其它不相关的网址,跳到其它网站,然后再回过来访问web app,只要Session不超时,Session仍有效)。说得更白一点:按F5刷新,该对象/变量不会被自动销毁,除非Session过期。
注:Session是跟浏览器有关的,如果在FireFox里打开web Application的某个url,再到IE里打开同样的url,这二个浏览器里的Session是不同的。
3、Request 生命周期
即:只有本次http请求才有效,通俗点讲,如果你定义一个变量的生命周期是Request级别,刷新一次页面后,该变量就被初始化(重新投胎)了。
为了演示上面的几种情况,我们创建一个新的Dynamic Website,仍然用Maven来管理,项目结构如下:
model包下,创建了几个类,先来看基类BaseItem
package model; import java.io.Serializable; public class BaseItem implements Serializable { private static final long serialVersionUID = -8431052435964580554L; private long counter; public void addCounter() {
counter++; } public long getCounter() {
return counter;
} public long getHashCode() {
return hashCode();
} }
BaseItem
注:SessionScoped使用时,要求Bean实现序列化接口,否则运行会报错,建议要注入的Bean,全都实现Serializable接口。
其它几个类都继承自这个类,只是在类上应用了不同的注解
ApplicationBean
package model; import javax.enterprise.context.*; @ApplicationScoped
public class ApplicationBean extends BaseItem { /**
*
*/
private static final long serialVersionUID = -2434044044797389734L; }
ApplicationScoped
SessionBean
package model; import javax.enterprise.context.*; @SessionScoped
public class SessionBean extends BaseItem { /**
*
*/
private static final long serialVersionUID = -4285276657265507539L; }
SessionBean
RequestBean
package model; import javax.enterprise.context.*; @RequestScoped
public class RequestBean extends BaseItem { /**
*
*/
private static final long serialVersionUID = -2625124180601128375L; }
RequestBean
SingletonBean
package model; import javax.inject.Singleton; @Singleton
public class SingletonBean extends BaseItem { /**
*
*/
private static final long serialVersionUID = 283705326745708570L; }
SingletonBean
注:@Singleton算是一个“伪”Scope,顾名思义,它将以单例模式注入一个唯一的对象实例。从使用效果上看,这跟@ApplicationScoped类似.
同样,为了跟前端JSF交互,我们再来一个“Controller”
package controller; import javax.inject.*;
import model.*; @Named("Index")
public class IndexController { @Inject
private ApplicationBean applicationBean; @Inject
private SessionBean sessionBean; @Inject
private RequestBean requestBean; @Inject
private SingletonBean singletonBean; public ApplicationBean getApplicationBean() {
applicationBean.addCounter();
return applicationBean;
} // public long getApplicationBeanCount(){
// applicationBean.addCounter();
// return applicationBean.getCounter();
// } public SessionBean getSessionBean() {
sessionBean.addCounter();
return sessionBean;
} public RequestBean getRequestBean() {
requestBean.addCounter();
return requestBean;
} public SingletonBean getSingletonBean() {
singletonBean.addCounter();
return singletonBean;
} }
IndexController
我们注入了上述四种生命周期的Bean,并提供了getter方法,最后准备一个简单的xhtml页面,作为UI展现
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:ui="http://java.sun.com/jsf/facelets"> <h:head>
<title>cdi scope sample</title>
</h:head>
<body>
Application Bean:#{Index.applicationBean.counter} ,
hashCode:#{Index.applicationBean.hashCode}
<br /> Session Bean:#{Index.sessionBean.counter} ,
hashCode:#{Index.sessionBean.hashCode}
<br /> Request Bean:#{Index.requestBean.counter} ,
hashCode:#{Index.requestBean.hashCode}
<br /> Singleton Bean:#{Index.singletonBean.counter} ,
hashCode:#{Index.singletonBean.hashCode}
<br />
</body>
</html>
index.xhtml
在Jboss里部署后,可以用http://localhost:8080/cdi-scope-sample/index.jsf 访问,下面是运行截图:
大家可以F5刷新下看看变化,然后多开几个Tab页,访问同样的网址,F5刷新,然后把浏览器关掉,再重新打开浏览器,访问同样的网址再比较一下
4、Conversation 生命周期
这个实在不知道中文如何翻译,Conversation的字面含义是“会话、会谈”,但在计算机领域里,一般已经公认将“Session”翻译成“会话”,所以Conversion这个词就不便再翻译成“会话”了,还是直接Conversation这个词吧。
我们在web开发中,经常会用到ajax,page1上的ajax向另一个页面page2发起请求时,会建立client到server的短时连接,如果想在ajax请求期间,让多个page之间共同访问一些变量(或对象),请求结束时这些对象又自动销毁(注:显然SessionScoped、ApplicationScoped、RequestScoped都不太适合这种需求),这时可以考虑使用ConversionScoped.
要使用ConversionScoped,必须在Controller(即ManagedBean)上,显式Inject一个Conversation类实例,而且要显式begin/end 该Conversion,每次生成Conversation实例时,系统会分配一个id给当前Conversation,多个页面之间根据唯一的cid来匹配使用哪个Conversation范围内的Bean对象,如果指定的id不对(比如:根据该cid找不到对应的conversation),系统会报错.
好了,开始码砖:
4.1 先在model包下,新建一个ConversationBean
package model; import javax.enterprise.context.*; @ConversationScoped
public class ConversationBean extends BaseItem { /**
*
*/
private static final long serialVersionUID = -4212732314401890050L; }
ConversationBean
4.2 在controller包下,新建一个ConversationController
package controller; import javax.enterprise.context.Conversation;
import javax.faces.context.FacesContext;
import javax.inject.*; import model.ConversationBean; @Named("Conversation")
public class ConversationController { @Inject
private ConversationBean conversationBean; @Inject
private Conversation conversation; public ConversationBean getConversationBean() {
return conversationBean;
} public Conversation getConversation() {
return conversation;
} /**
* 开始conversation
*/
public void beginConversation() {
//仅当前页面未被post提交,且conversation是"瞬时"性时,才开始conversation
if (!FacesContext.getCurrentInstance().isPostback()
&& conversation.isTransient()) {
conversation.begin();
}
} public String endConversation(){
//如果Conversation不是“瞬时”的,则结束conversion,同时所有ConversationScoped的对象也会销毁
if (!conversation.isTransient()) {
conversation.end();
}
//然后返回第1页
return "page1?faces-redirect=true";
} /**
* 供Ajax调用的方法
*/
public void addCounter() {
conversationBean.addCounter(); } /**
* 转到第2页
*/
public String gotoPage2() {
// 注:faces-redirect=true会自动把conversion的id通过url parameter传递到page2
return "page2?faces-redirect=true";
} }
ConversationController
4.3 webapp里创建几个页面
page1.xhtml
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:ui="http://java.sun.com/jsf/facelets"> <h:head>
<title>ConversionScoped Page1</title>
<f:event listener="#{Conversation.beginConversation}"
type="preRenderView"></f:event>
</h:head>
<body>
<h1>ConversionScoped Page1</h1>
<h:form>
Conversation Bean:#{Conversation.conversationBean.counter},Conversion Id:#{Conversation.conversation.id}<br />
<br />
<h:commandLink value="AddCounter">
<f:ajax listener="#{Conversation.addCounter}" render="@form" />
</h:commandLink>
<h:commandLink value="Go to Page2" action="page2"></h:commandLink>
</h:form>
</body>
</html>
page1.xhtml
注:
a.<f:event listener="#{Conversation.beginConversation}" type="preRenderView"></f:event> 通过这句代码,该页面加载时,会先调用ConversationController中的beginConversation方法,启动conversation
b.通过AddCounter这个按钮发起ajax请求,调用ConversationController中的addCounter()方法,点击之后,conversationBean实例的计数器将+1
page2.xhtml
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:ui="http://java.sun.com/jsf/facelets"> <h:head>
<title>ConversionScoped Page2</title>
</h:head>
<body>
<h1>ConversionScoped Page2</h1>
Conversation Bean:#{Conversation.conversationBean.counter},Conversion
Id:#{Conversation.conversation.id}
<br />
<br />
<h:link value="Back to Page1" outcome="page1">
<f:param name="cid" value="#{Conversation.conversation.id}" />
</h:link>
<h:link value="Go to Page3" outcome="page3">
<f:param name="cid" value="#{Conversation.conversation.id}" />
</h:link>
</body>
</html>
page2.xhtml
在page1上将计数器加1后,点击 Go to Page2,将跳到page2,同时会把cid自动传过来(通过ConversationController中的return "page2?faces-redirect=true";),然后在page2上显示已经改变的计数器值。
page3.xhtml
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:ui="http://java.sun.com/jsf/facelets">
<h:head>
<title>ConversionScoped Page3</title>
</h:head>
<body>
<h1>ConversionScoped Page3</h1>
Conversation Bean:#{Conversation.conversationBean.counter},Conversion
Id:#{Conversation.conversation.id}
<br />
<br />
<h:form>
<a href="page2.jsf?cid=#{Conversation.conversation.id}">Back to
Page2</a>
<h:commandLink value="EndConversation"
action="#{Conversation.endConversation}"></h:commandLink>
</h:form>
</body>
</html>
page3.xhtml
这个页面,仍然继续Conversation生命周期内的conversationBean计数器值,然后通过EndConversation这个链接,点击后,调用ConversationController中的endConversation方法,结束Conversation,同时所有该ConversationScoped范围内的Bean将被销毁,最后再返回到page1
运行截图:
上图是page1刚打开的情形,注意这时Conversaion Id是1(系统自动分配的),然后把AddCounter随便点击几下
可以看到计数器变成了3,然后点击 Go to Page2,跳到第2页,如下图:
注意地址栏里,自动带上了?cid=1,这个很重要,没有这个id,在page2上,就无法自动找到刚才的conversation,你可以尝试把cid的值在地址栏里改下,然后观察下报错的信息
这是page3,点击EndConversation结束本次Conversation,将跳回到page1
注意:因为刚才的conversation已经end掉了,所以再次进入page1时,系统又重新注入了一个全新的Conversation实例,此时的cid为2
另外,刚接触jsf的朋友,可以留意下page1到page3上的Go to PageX的link处理,我刻意用了多种处理方式,比如: <h:commandLink>、<h:link>、以及最常规的<a>链接,以体现同一问题的处理,我们可以有多种选择。
5、生命周期的“混用”问题
如果一个Bean在设计时,被标识为@ApplicationScoped,而注入使用它的Controller类,本身又是其它类型的生命周期,比如@RequestScoped,结果会怎样?
比如有一个Bean,代码如下:
package model; import java.io.Serializable; import javax.enterprise.context.*; @ApplicationScoped
public class MyBean implements Serializable { private static final long serialVersionUID = -4190635663858456460L;
private long counter = 0; public void addCounter() {
counter++; } public long getCounter() {
return counter;
} public long getHashCode() {
return hashCode();
} }
MyBean
使用它的控制器ScopeController代码如下:
package controller; import java.io.Serializable; import javax.enterprise.context.*; import javax.inject.Inject;
import javax.inject.Named; import model.MyBean; @Named("Scope")
@RequestScoped
public class ScopeController implements Serializable { private static final long serialVersionUID = 2717400174254702790L;
@Inject
private MyBean myBean; public MyBean getMyBean() {
myBean.addCounter();
return myBean;
} }
ScopeController
再来一个页面scope.xhtml验证一下:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:ui="http://java.sun.com/jsf/facelets"> <h:head>
<title>scope test</title>
</h:head>
<body> My Bean's counter:#{Scope.myBean.counter} ,
My Bean's hashCode:#{Scope.myBean.hashCode}
<br />
</body>
</html>
scope.xhtml
最后运行的结果,myBean实例仍然是一个ApplicationScoped的对象。可以这么理解:本质上讲,Controller也只是一个Bean而已,上面的代码虽然把ScopeController这个Bean标注为RequestScoped,但因为myBean是ApplicationScoped的,本次页面访问结束后,ScopeController实例已经被释放了(因为它是RequestScoped生命周期,本次请求一结束,它就销毁),但MyBean实例还将继续存在(因为它是ApplicationScoped,整个webapp生命周期内都将继续存在).
但有时候,这可能不符合我们的期望,在Controller上加@RequestScoped标识的本意是希望每次请求都能产生一个新的对象(包括Controller里使用的其它资源),修改MyBean.java吗?这显然不好,如果MyBean被很多其它类使用了,修改MyBean会影响所有调用它的代码,一个简单的解决办法是使用@New注释,比如下面这样:
@Inject
@New
private MyBean myBean;
@New
这样就告诉系统,每次需要注入得到MyBean实例时,都强制生成一个全新的实例。
继续折腾:如果把MyBean.java上的@ApplicationScoped去掉,然后在Controller里@Inject的地方,加上@ApplicationScoped(即:把@ApplicationScoped从MyBean.java上移到ScopeController.java里),类似下面这样:
import java.io.Serializable; public class MyBean implements Serializable {
@Named("Scope")
@RequestScoped
public class ScopeController implements Serializable { private static final long serialVersionUID = 2717400174254702790L;
@Inject
@ApplicationScoped
private MyBean myBean;
也许,你会觉得应该跟刚才运行的结果相同,但是实际测试下来,myBean对象,仍然跟最外面的ScopeController一样,是Request生命周期,所以如果你确实希望某个Bean在设计时,就决定它的生命周期,@XXXScoped建议直接使用在Bean类本身,而非@Inject的地方。
附:示例源码下载 cdi-scope-sample.zip
JAVA CDI 学习(2) - Scope 生命周期的更多相关文章
- Java Web学习笔记-Servle生命周期
Servlet会在服务器启动或第一次请求该Servlet的时候开始生命周期,在服务器停止的时候结束生命周期. 无论请求多少次Servlet,最多只有一个Servlet实例.多个客户端并发请求Servl ...
- 第24章 java线程(3)-线程的生命周期
java线程(3)-线程的生命周期 1.两种生命周期流转图 ** 生命周期:**一个事物冲从出生的那一刻开始到最终死亡中间的过程 在事物的漫长的生命周期过程中,总会经历不同的状态(婴儿状态/青少年状态 ...
- Java多线程之线程的生命周期
Java多线程之线程的生命周期 一.前言 当线程被创建并启动以后,它既不是一启动就进入了执行状态,也不是一直处于执行状态.在线程的生命周期中,它要经过新建(New).就绪(Runnable).运行(R ...
- Vue – 基础学习(1):对生命周期和钩子函的理解
一.简介 先贴一下官网对生命周期/钩子函数的说明(先贴为敬):所有的生命周期钩子自动绑定 this 上下文到实例中,因此你可以访问数据,对属性和方法进行运算.这意味着你不能使用箭头函数来定义一个生命周 ...
- Net6 DI源码分析Part5 在Kestrel内Di Scope生命周期是如何根据请求走的?
Net6 DI源码分析Part5 在Kestrel内Di Scope生命周期是如何根据请求走的? 在asp.net core中的DI生命周期有一个Scoped是根据请求走的,也就是说在处理一次请求时, ...
- JAVA CDI 学习(1) - @Inject基本用法
CDI(Contexts and Dependency Injection 上下文依赖注入),是JAVA官方提供的依赖注入实现,可用于Dynamic Web Module中,先给3篇老外的文章,写得很 ...
- JAVA CDI 学习(3) - @Produces及@Disposes
上一节学习了注入Bean的生命周期,今天再来看看另一个话题: Bean的生产(@Produces)及销毁(@Disposes),这有点象设计模式中的工厂模式.在正式学习这个之前,先来看一个场景: 基于 ...
- java线程基础巩固---线程生命周期以及start方法源码剖析
上篇中介绍了如何启动一个线程,通过调用start()方法才能创建并使用新线程,并且这个start()是非阻塞的,调用之后立马就返回的,实际上它是线程生命周期环节中的一种,所以这里阐述一下线程的一个完整 ...
- Java多线程 2 线程的生命周期和状态控制
一.线程的生命周期 线程状态转换图: 1.新建状态 用new关键字和Thread类或其子类建立一个线程对象后,该线程对象就处于新生状态.处于新生状态的线程有自己的内存空间,通过调用start方法进入就 ...
随机推荐
- Sumlime Text编辑文件后快速刷新浏览器
作为Web开发人员,我们经常会这么做:在编辑器中调整代码,保存文件,切换到浏览器,然后刷新浏览器页面来查看结果.在代码编辑过程中,我们需要重复进行很多次这些操作. 如果你使用的是Sublime Tex ...
- WPF学习之路(七)应用程序和窗口
手动创建应用程序 1.创建Empty Project 2.添加引用 3.添加 ManualApp.cs 并添加下面的代码 [STAThread] public static void Main() { ...
- js平滑返回顶部代码
随便找的一个,使用时直接调用gotoTop就行了,至于调速度之类的我没试,有兴趣的自己试试吧 注意:如果你想改变这个函数的名称千万不要忘了要同时改变第37行的那个gotoTop /** * JavaS ...
- 记录一些在用wcf的过程中走过的泥巴路 【第一篇】
自从转移战场之后,比以前忙多了,博客也没能及时跟上,原本准备继续mvc系列,但是在那边技术比较陈旧还没能用得上,话说有3年没接触这玩意了,东西也 都忘了差不多了,既然再次接触,我也就继续温习温习,记录 ...
- HTTP状态码206和416
HTTP 2xx范围内的状态码表明了:"客户端发送的请求已经被服务器接受并且被成功处理了". TTP/1.1 200 OK是HTTP请求成功后的标准响应 HTTP/1.1 206状 ...
- android 利用线程刷新UI方法
新建线程new Thread(new Runnable() 线程方法:public void run() private void setAddWidgetEnabled(boolean enable ...
- zookeeper 相关学习资料
zookeeper的配置:http://www.cnblogs.com/yuyijq/p/3438829.html zookeeper运维:http://blog.csdn.net/hengyunab ...
- Windows Azure文件共享服务--File Service
部署在Windows Azure上的虚拟机之间如何共享文件?例如:Web Server A和Web Server B组成负载均衡集群,两个服务器需要一个共享目录来存储用户上传的文件.通常,大家可能首先 ...
- java使用IO读写文件总结
每次用到IO的读写文件都老忘记写法,都要翻过往笔记,今天总结下,省的以后老忘.java读写文件的IO流分两大类,字节流和字符流,基类分别是字符:Reader和Writer:字节:InputStream ...
- hdu-5493 Queue(二分+树状数组)
题目链接: Queue Time Limit: 4000/2000 MS (Java/Others) Memory Limit: 65536/65536 K (Java/Others)Total ...