上一节中,我们已经知道了如何用@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>
&nbsp;
<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>
&nbsp;
<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>&nbsp;
<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 生命周期的更多相关文章

  1. Java Web学习笔记-Servle生命周期

    Servlet会在服务器启动或第一次请求该Servlet的时候开始生命周期,在服务器停止的时候结束生命周期. 无论请求多少次Servlet,最多只有一个Servlet实例.多个客户端并发请求Servl ...

  2. 第24章 java线程(3)-线程的生命周期

    java线程(3)-线程的生命周期 1.两种生命周期流转图 ** 生命周期:**一个事物冲从出生的那一刻开始到最终死亡中间的过程 在事物的漫长的生命周期过程中,总会经历不同的状态(婴儿状态/青少年状态 ...

  3. Java多线程之线程的生命周期

    Java多线程之线程的生命周期 一.前言 当线程被创建并启动以后,它既不是一启动就进入了执行状态,也不是一直处于执行状态.在线程的生命周期中,它要经过新建(New).就绪(Runnable).运行(R ...

  4. Vue – 基础学习(1):对生命周期和钩子函的理解

    一.简介 先贴一下官网对生命周期/钩子函数的说明(先贴为敬):所有的生命周期钩子自动绑定 this 上下文到实例中,因此你可以访问数据,对属性和方法进行运算.这意味着你不能使用箭头函数来定义一个生命周 ...

  5. Net6 DI源码分析Part5 在Kestrel内Di Scope生命周期是如何根据请求走的?

    Net6 DI源码分析Part5 在Kestrel内Di Scope生命周期是如何根据请求走的? 在asp.net core中的DI生命周期有一个Scoped是根据请求走的,也就是说在处理一次请求时, ...

  6. JAVA CDI 学习(1) - @Inject基本用法

    CDI(Contexts and Dependency Injection 上下文依赖注入),是JAVA官方提供的依赖注入实现,可用于Dynamic Web Module中,先给3篇老外的文章,写得很 ...

  7. JAVA CDI 学习(3) - @Produces及@Disposes

    上一节学习了注入Bean的生命周期,今天再来看看另一个话题: Bean的生产(@Produces)及销毁(@Disposes),这有点象设计模式中的工厂模式.在正式学习这个之前,先来看一个场景: 基于 ...

  8. java线程基础巩固---线程生命周期以及start方法源码剖析

    上篇中介绍了如何启动一个线程,通过调用start()方法才能创建并使用新线程,并且这个start()是非阻塞的,调用之后立马就返回的,实际上它是线程生命周期环节中的一种,所以这里阐述一下线程的一个完整 ...

  9. Java多线程 2 线程的生命周期和状态控制

    一.线程的生命周期 线程状态转换图: 1.新建状态 用new关键字和Thread类或其子类建立一个线程对象后,该线程对象就处于新生状态.处于新生状态的线程有自己的内存空间,通过调用start方法进入就 ...

随机推荐

  1. windows server 远程连接设置

    1设置远程连接数修改 服务器安装了Windows Server 2008,现在要增加远程连接,开启服务器上的远程桌面连接,使用管理员账户远程登录.默认情况下Windows Server 2008允许一 ...

  2. 谈一次php无限极分类的案例

    作者:白狼 出处:http://www.manks.top/php_tree_deep.html 本文版权归作者,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追 ...

  3. HTML基础(五)——-css样式表——样式属性——格式与布局

    一.position:fixed 锁定位置(相对于浏览器的位置),例如有些网站的右下角的弹出窗口. 示例: 二.position:absolute     绝对位置: 1.外层没有position:a ...

  4. [MySQL Reference Manual] 5 MySQL 服务管理

    5. MySQL 服务管理 5. MySQL 服务管理 5.1 The Mysql Server 5.2 Mysql 服务日志 5.2.1 选择General query log和slow query ...

  5. spring Quartz 调度

    Quartz 是开源任务调度框架中的翘首,它提供了强大任务调度机制,同时保持了使用的简单性.Quartz 允许开发人员灵活地定义触发器的调度时间表,并可以对触发器和任务进行关联映射.此外,Quartz ...

  6. WIN 程序员的 Linux 互斥类

    作者:黄山松,发表于cnblogs:http://www.cnblogs.com/tomview/ 对于一个 win 的程序员,要把在 win 下的程序移植到 linux 下,需要把一些平台相关的功能 ...

  7. php cli方式下获取服务器ip

    (未整理....) (1)php cli方式下获取服务器ip [php]  function getServerIp(){          $ss = exec('/sbin/ifconfig et ...

  8. Android Paint和Color类绘画实例

    要绘图,首先得调整画笔,待画笔调整好之后,再将图像绘制到画布上,这样才可以显示在手机屏幕上.Android 中的画笔是 Paint类,Paint 中包含了很多方法对其属性进行设置,主要方法如下: se ...

  9. RabbitMQ与Redis队列对比

    本文仅针对RabbitMQ与Redis做队列应用时的情况进行对比具体采用什么方式实现,还需要取决于系统的实际需求 简要介绍 RabbitMQ RabbitMQ是实现AMQP(高级消息队列协议)的消息中 ...

  10. 深入理解C语言

    语言只是一种工具,任何语言之间都是相通的,一通则百通,关键是要理解语言背后的思想,理解其思想,任何语言,拿来用就行了.语言没有好坏之分,任何语言既然存在自然有它存在的价值. 在一个到处是OOP的年代, ...