zoukankan      html  css  js  c++  java
  • JAVA CDI 学习(2)

    上一节中,我们已经知道了如何用@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

     1 package model;
     2 
     3 import java.io.Serializable;
     4 
     5 public class BaseItem implements Serializable {
     6 
     7     private static final long serialVersionUID = -8431052435964580554L;
     8 
     9     private long counter;
    10 
    11     public void addCounter() {
    12         counter++;
    13 
    14     }
    15 
    16     public long getCounter() {
    17         return counter;
    18     }
    19 
    20     public long getHashCode() {
    21         return hashCode();
    22     }
    23 
    24 }
    BaseItem

    注:SessionScoped使用时,要求Bean实现序列化接口,否则运行会报错,建议要注入的Bean,全都实现Serializable接口。

    其它几个类都继承自这个类,只是在类上应用了不同的注解

    ApplicationBean

     1 package model;
     2 
     3 import javax.enterprise.context.*;
     4 
     5 @ApplicationScoped
     6 public class ApplicationBean extends BaseItem {
     7 
     8     /**
     9      * 
    10      */
    11     private static final long serialVersionUID = -2434044044797389734L;
    12 
    13 
    14 
    15 }
    ApplicationScoped

    SessionBean

     1 package model;
     2 
     3 import javax.enterprise.context.*;
     4 
     5 @SessionScoped
     6 public class SessionBean extends BaseItem {
     7 
     8     /**
     9      * 
    10      */
    11     private static final long serialVersionUID = -4285276657265507539L;
    12 
    13 
    14 
    15 }
    SessionBean

    RequestBean

     1 package model;
     2 
     3 import javax.enterprise.context.*;
     4 
     5 @RequestScoped
     6 public class RequestBean extends BaseItem {
     7 
     8     /**
     9      * 
    10      */
    11     private static final long serialVersionUID = -2625124180601128375L;
    12 
    13 
    14 
    15 }
    RequestBean

     SingletonBean

     1 package model;
     2 
     3 import javax.inject.Singleton;
     4 
     5 @Singleton
     6 public class SingletonBean extends BaseItem {
     7 
     8     /**
     9      * 
    10      */
    11     private static final long serialVersionUID = 283705326745708570L;
    12 
    13 
    14 
    15 }
    SingletonBean

    注:@Singleton算是一个“伪”Scope,顾名思义,它将以单例模式注入一个唯一的对象实例。从使用效果上看,这跟@ApplicationScoped类似.

    同样,为了跟前端JSF交互,我们再来一个“Controller”

     1 package controller;
     2 
     3 import javax.inject.*;
     4 import model.*;
     5 
     6 @Named("Index")
     7 public class IndexController {
     8 
     9     @Inject
    10     private ApplicationBean applicationBean;
    11 
    12     @Inject
    13     private SessionBean sessionBean;
    14 
    15     @Inject
    16     private RequestBean requestBean;
    17 
    18     @Inject
    19     private SingletonBean singletonBean;
    20 
    21     public ApplicationBean getApplicationBean() {
    22         applicationBean.addCounter();
    23         return applicationBean;
    24     }
    25     
    26 //    public long getApplicationBeanCount(){
    27 //        applicationBean.addCounter();
    28 //        return applicationBean.getCounter();        
    29 //    }
    30 
    31     public SessionBean getSessionBean() {
    32         sessionBean.addCounter();
    33         return sessionBean;
    34     }
    35 
    36     public RequestBean getRequestBean() {
    37         requestBean.addCounter();
    38         return requestBean;
    39     }
    40 
    41     public SingletonBean getSingletonBean() {
    42         singletonBean.addCounter();
    43         return singletonBean;
    44     }
    45 
    46 }
    IndexController

    我们注入了上述四种生命周期的Bean,并提供了getter方法,最后准备一个简单的xhtml页面,作为UI展现

     1 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
     2 <html xmlns="http://www.w3.org/1999/xhtml"
     3     xmlns:h="http://java.sun.com/jsf/html"
     4     xmlns:f="http://java.sun.com/jsf/core"
     5     xmlns:ui="http://java.sun.com/jsf/facelets">
     6 
     7 <h:head>
     8     <title>cdi scope sample</title>
     9 </h:head>
    10 <body>
    11     Application Bean:#{Index.applicationBean.counter} ,
    12     hashCode:#{Index.applicationBean.hashCode}
    13     <br /> Session Bean:#{Index.sessionBean.counter} ,
    14     hashCode:#{Index.sessionBean.hashCode}
    15     <br /> Request Bean:#{Index.requestBean.counter} ,
    16     hashCode:#{Index.requestBean.hashCode}
    17     <br /> Singleton Bean:#{Index.singletonBean.counter} ,
    18     hashCode:#{Index.singletonBean.hashCode}
    19     <br />
    20 </body>
    21 </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

     1 package model;
     2 
     3 import javax.enterprise.context.*;
     4 
     5 @ConversationScoped
     6 public class ConversationBean extends BaseItem {
     7 
     8     /**
     9      * 
    10      */
    11     private static final long serialVersionUID = -4212732314401890050L;
    12 
    13 }
    ConversationBean

    4.2 在controller包下,新建一个ConversationController

     1 package controller;
     2 
     3 import javax.enterprise.context.Conversation;
     4 import javax.faces.context.FacesContext;
     5 import javax.inject.*;
     6 
     7 import model.ConversationBean;
     8 
     9 @Named("Conversation")
    10 public class ConversationController {
    11 
    12     @Inject
    13     private ConversationBean conversationBean;
    14 
    15     @Inject
    16     private Conversation conversation;
    17 
    18     public ConversationBean getConversationBean() {
    19         return conversationBean;
    20     }
    21 
    22     public Conversation getConversation() {
    23         return conversation;
    24     }
    25 
    26     /**
    27      * 开始conversation
    28      */
    29     public void beginConversation() {
    30         //仅当前页面未被post提交,且conversation是"瞬时"性时,才开始conversation
    31         if (!FacesContext.getCurrentInstance().isPostback()
    32                 && conversation.isTransient()) {
    33             conversation.begin();
    34         }
    35     }
    36     
    37     
    38     public String endConversation(){
    39         //如果Conversation不是“瞬时”的,则结束conversion,同时所有ConversationScoped的对象也会销毁
    40         if (!conversation.isTransient()) {
    41             conversation.end();
    42         }
    43         //然后返回第1页
    44         return "page1?faces-redirect=true";
    45     }
    46 
    47     /**
    48      * 供Ajax调用的方法
    49      */
    50     public void addCounter() {
    51         conversationBean.addCounter();
    52 
    53     }
    54 
    55     /**
    56      * 转到第2页
    57      */
    58     public String gotoPage2() {
    59         // 注:faces-redirect=true会自动把conversion的id通过url parameter传递到page2
    60         return "page2?faces-redirect=true";
    61     }
    62 
    63 }
    ConversationController

    4.3 webapp里创建几个页面
    page1.xhtml

     1 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
     2 <html xmlns="http://www.w3.org/1999/xhtml"
     3     xmlns:h="http://java.sun.com/jsf/html"
     4     xmlns:f="http://java.sun.com/jsf/core"
     5     xmlns:ui="http://java.sun.com/jsf/facelets">
     6 
     7 <h:head>
     8     <title>ConversionScoped Page1</title>
     9     <f:event listener="#{Conversation.beginConversation}"
    10         type="preRenderView"></f:event>
    11 </h:head>
    12 <body>
    13     <h1>ConversionScoped Page1</h1>
    14     <h:form>
    15     Conversation Bean:#{Conversation.conversationBean.counter},Conversion Id:#{Conversation.conversation.id}<br />
    16         <br />
    17         <h:commandLink value="AddCounter">
    18             <f:ajax listener="#{Conversation.addCounter}" render="@form" />
    19         </h:commandLink>
    20         &nbsp;
    21         <h:commandLink value="Go to Page2" action="page2"></h:commandLink>
    22     </h:form>
    23 </body>
    24 </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

     1 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
     2 <html xmlns="http://www.w3.org/1999/xhtml"
     3     xmlns:h="http://java.sun.com/jsf/html"
     4     xmlns:f="http://java.sun.com/jsf/core"
     5     xmlns:ui="http://java.sun.com/jsf/facelets">
     6 
     7 <h:head>
     8     <title>ConversionScoped Page2</title>
     9 </h:head>
    10 <body>
    11     <h1>ConversionScoped Page2</h1>
    12     Conversation Bean:#{Conversation.conversationBean.counter},Conversion
    13     Id:#{Conversation.conversation.id}
    14     <br />
    15     <br />
    16     <h:link value="Back to Page1" outcome="page1">
    17         <f:param name="cid" value="#{Conversation.conversation.id}" />
    18     </h:link>
    19     &nbsp;
    20     <h:link value="Go to Page3" outcome="page3">
    21         <f:param name="cid" value="#{Conversation.conversation.id}" />
    22     </h:link>
    23 </body>
    24 </html>
    page2.xhtml

    在page1上将计数器加1后,点击 Go to Page2,将跳到page2,同时会把cid自动传过来(通过ConversationController中的return "page2?faces-redirect=true";),然后在page2上显示已经改变的计数器值。
    page3.xhtml

     1 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
     2 <html xmlns="http://www.w3.org/1999/xhtml"
     3     xmlns:h="http://java.sun.com/jsf/html"
     4     xmlns:f="http://java.sun.com/jsf/core"
     5     xmlns:ui="http://java.sun.com/jsf/facelets">
     6 <h:head>
     7     <title>ConversionScoped Page3</title>
     8 </h:head>
     9 <body>
    10     <h1>ConversionScoped Page3</h1>
    11     Conversation Bean:#{Conversation.conversationBean.counter},Conversion
    12     Id:#{Conversation.conversation.id}
    13     <br />
    14     <br />
    15     <h:form>
    16         <a href="page2.jsf?cid=#{Conversation.conversation.id}">Back to
    17             Page2</a>&nbsp;
    18         <h:commandLink value="EndConversation"
    19             action="#{Conversation.endConversation}"></h:commandLink>
    20     </h:form>
    21 </body>
    22 </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,代码如下:

     1 package model;
     2 
     3 import java.io.Serializable;
     4 
     5 import javax.enterprise.context.*;
     6 
     7 @ApplicationScoped
     8 public class MyBean implements Serializable {
     9 
    10     private static final long serialVersionUID = -4190635663858456460L;
    11     private long counter = 0;
    12 
    13     public void addCounter() {
    14         counter++;
    15 
    16     }
    17 
    18     public long getCounter() {
    19         return counter;
    20     }
    21 
    22     public long getHashCode() {
    23         return hashCode();
    24     }
    25 
    26 }
    MyBean

     使用它的控制器ScopeController代码如下:

     1 package controller;
     2 
     3 import java.io.Serializable;
     4 
     5 import javax.enterprise.context.*;
     6 
     7 import javax.inject.Inject;
     8 import javax.inject.Named;
     9 
    10 import model.MyBean;
    11 
    12 @Named("Scope")
    13 @RequestScoped
    14 public class ScopeController implements Serializable {
    15 
    16     private static final long serialVersionUID = 2717400174254702790L;
    17     @Inject
    18     private MyBean myBean;
    19 
    20     public MyBean getMyBean() {
    21         myBean.addCounter();
    22         return myBean;
    23     }
    24 
    25 }
    ScopeController

     再来一个页面scope.xhtml验证一下:

     1 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
     2 <html xmlns="http://www.w3.org/1999/xhtml"
     3     xmlns:h="http://java.sun.com/jsf/html"
     4     xmlns:f="http://java.sun.com/jsf/core"
     5     xmlns:ui="http://java.sun.com/jsf/facelets">
     6 
     7 <h:head>
     8     <title>scope test</title>
     9 </h:head>
    10 <body>
    11     
    12     My Bean's counter:#{Scope.myBean.counter} ,
    13     My Bean's hashCode:#{Scope.myBean.hashCode}
    14     <br />
    15 </body>
    16 </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注释,比如下面这样:

    1     @Inject
    2     @New
    3     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 {
    1 @Named("Scope")
    2 @RequestScoped
    3 public class ScopeController implements Serializable {
    4 
    5     private static final long serialVersionUID = 2717400174254702790L;
    6     @Inject
    7     @ApplicationScoped
    8     private MyBean myBean;
    View Code

    也许,你会觉得应该跟刚才运行的结果相同,但是实际测试下来,myBean对象,仍然跟最外面的ScopeController一样,是Request生命周期,所以如果你确实希望某个Bean在设计时,就决定它的生命周期,@XXXScoped建议直接使用在Bean类本身,而非@Inject的地方。

    附:示例源码下载 cdi-scope-sample.zip

  • 相关阅读:
    asyncio异步 loop.run_in_executor操作同步方法变成异步操作
    pandas水平拆分dataframe
    vscode 运行python程序设置参数
    dbeaver 连接oracle11g 驱动问题
    python3 使用数据描述器,验证字段类型
    postgres 列转行操作记录
    python3 读取照片写入数据库postgres
    个人作业——软件工程实践总结作业
    2019 SDN上机第7次作业
    2019 SDN上机第6次作业
  • 原文地址:https://www.cnblogs.com/yjmyzz/p/javaee-cdi-bean-scope.html
Copyright © 2011-2022 走看看