JSF生命周期 JSF生命周期将一个servlet请求分为6个阶段: Restore view Apply request values Process validations Update model values Invoke application Render response 每个阶段之前和之后,都会抛出事件。 ------------------------------------------------------ JSF可以处理两种请求:初始的请求(就是GET方式)和postback。
GET方式: 此时只有两个阶段:Restore view和Render response。 Restore view阶段没做什么,只是创建了一个空的 Render response:此阶段检查URL,得到viewID,定位关联的模板。 组件被创建,通过encoding每个组件,准备好了response。 Encoding可以生成标记,如XHTML, 生成的response包括绑定到Server端组件的元素。
没有Seams时,组件必须在faces-config.xml中定义。
整个reesponse被渲染完毕时,UI组件树被序列化,发送到客户端,或保存在服务端session中。 保存在客户端,需要占用大量带宽,建议保存在服务端。
在web.xml中定义: <context-param> <param-name>javax.faces.STATE_SAVING_METHOD</param-name> <param-value>server</param-value> </context-param>
PostBack: 验证错误,或conversation错误,或“immediate”事件,或调用FacesContext的renderResponse()方法,控制都会跳到Render Response阶段。 如果调用FacesContext的responseComplete()方法,则Render Response也会被跳过 Restore View阶段根据存储的状态恢复组件。 Apply Request Values阶段,捕获form提交的值,写入组件树,寻找触发的时间,将他们排队。 下两个阶段处理大量表单数据,最终注入到对象的属性中。
在Invoke Application阶段,控制转移给application,调用按钮或链接所触发的方法。 这个方法可以称为action handler,另外,还有一些action listener可以被调用。 后者只是简单的执行,不会触发,声明转向。
action handler执行后,根据导航规则决定下一个页面是哪个。 如果action handler返回值是null,或该方法是void,或返回值没有匹配的规则,那么当前页面会再次渲染一次。
反之,转移到对应的页面。 如果存在<redirect/>,那么是redirect过去的(通过GET方式访问),而不是直接渲染该页面(默认是这个)。 <navigation-rule> <from-view-id>/register.xhtml</from-view-id> <navigation-case> <from-action>#{registerAction.register}</from-action> <from-outcome>success</from-outcome> <to-view-id>/welcome.xhtml</to-view-id> <redirect/> </navigation-case> </navigation-rule>
JSF生命周期的缺点: <navigation-rule> <from-view-id>/CourseEdit.xhtml</from-view-id> Licensed to Jaroslaw Gilewski <jgilewski@unizeto.pl> <navigation-case> <from-action>#{courseHome.update}</from-action> <to-view-id>/Course.xhtml</to-view-id> </navigation-case> </navigation-rule> 更新完后,浏览器显示的是Course.xhtml,但是地址栏中仍然是CourseEdit.seam, 这是因为JSF提交到本页面,但是选择了不同的模板显示。
原始的导航规则 只能根据viewID,action handler返回值和EL表达式进行导航, 但是它不能识别context等其他范围的变量。
Seam的页面导向的生命周期 使用pages.xml,在WEB-INF目录下。 也可以拆分为多个,如页面为aa.xhtml,那么对应的配置文件为aa.page.xml。
禁用Seam的导航规则: WEB-INF/components.xml中添加<web:redirect-filter disabled="true"/>
<page view-id="/FacilityEdit.xhtml"> <navigation from-action="#{facilityHome.persist}"> <rule if-outcome="persisted" if="#{facilityHome.addCourse}"> <redirect view-id="/CourseEdit.xhtml"/> <param name="courseFrom" value="Facility"/> #2 <message severity="INFO"> Please enter course information for #{facilityHome.instance.name}. #3 </message> </redirect> </rule> <rule if-outcome="persisted" if="#{!facilityHome.addCourse}"> #4 <redirect view-id="/Facility.xhtml"/> </rule> </navigation> </page>
Evaluate 计算后面的值,然后去匹配if-outcome,这样就不必管action的返回值了。 <page view-id="/FacilityEdit.xhtml"> <navigation from-action="#{facilityHome.persist}" evaluate="#{facilityHome.lastStateChange}"> <rule if-outcome="persisted" if="#{facilityHome.addCourse}"> ... </rule> <rule if-outcome="persisted" if="#{!facilityHome.addCourse}"> ... </rule> </navigation> </page>
Begin 将临时conversation提升为long-running Conversation。如果已经有long的,就报错。 Join 类似begin,只是简单标示没有long的,就不会报错了。 End 将long的降级为临时的conversation.在此次渲染完毕就结束了 Nest 如果有long了,挂起它,创建一个新的long,放入堆栈。如果没有,则将临时提升为long。 None 销毁当前conversation,之前的留下,同时创建一个新的临时conversation。 传播属性就决定了conversation的状态。
标记@Begin的方法成功执行之后,才会提升long-running conversation。 有两种情况@Begin不会生效,不会提升long-running conversation 该方法是非void的返回类型,但是返回了null, 该方法抛出异常时。 @End同样。
以下几种写法都可以启动long-running conversation. <page view-id="/CourseList.xhtml"> <navigation from-action="#{courseWizard.addCourse}"> <begin-conversation/> <redirect view-id="/coursewizard/selectFacility.xhtml"/> </navigation> </page>
<page view-id="/coursewizard/selectFacility.xhtml" action="#{conversation.begin}"/> <page view-id="/coursewizard/selectFacility.xhtml"> <begin-conversation/> </page>
在UI组件TAG中也可以控制conversation的传递。 可以在别的标签内嵌套用<s:conversationPropagation>(只是在URL中增加项目而已)或直接用<s:link>或<s:button>。
<h:commandButton action="#{courseWizard.addCourse}" value="Add course..."> <s:conversationPropagation propagation="begin"/> </h:commandButton>
<s:button action="#{courseWizard.addCourse}" propagation="begin" value="Add course..."/> 如果不需要提交form,那么<s:link>和<s:button>是极好的替代品,它们内置了conversation的控制属性。
@Begin的方法执行第二次会报错,因为long-running conversation已经存在。 需要用join。
@Begin(join = true) public void addCourse() { course = new Course(); }
<page view-id="/CourseList.xhtml"> <navigation from-action="#{courseWizard.addCourse}"> <begin-conversation join="true"/> <redirect view-id="/coursewizard/selectFacility.xhtml"/> </navigation> </page>
如果使用的是#{conversation.begin},那么什么都不用做,它本身是join safe的。
组件中,只要将属性改为join即可。 <s:button action="#{courseWizard.addCourse}" propagation="join" value="Add course..."/> 推荐使用<page>,支持条件绑定……
<page view-id="/CourseList.xhtml"> <navigation from-action="#{courseWizard.addCourse}"> <begin-conversation if="#{!conversation.longRunning}"/> <redirect view-id="/coursewizard/selectFacility.xhtml"/> </navigation> </page> Conversation 的保持。 也就是如何在下个request时恢复conversation。 诀窍就是传递conversation id,可能是通过URL,也可能通过JSF的组件树。 Seam发现request中有conversation令牌,就通过它恢复已有的conversation,使用它处理请求。 存在long conversation的话,Seam自动添加令牌到JSF组件树,后续的提交表单时就会自动提交过来。 如果没有提交JSF组件树,那么还可以通过URL取得。
最初的页面访问都是从GET开始的,Seam不知道conversation id,所以创建临时conversation。 默认的参数名是conversationId。
<a href="preview.seam?conversationId=#{conversation.id}" target="_blank">Preview</a>
<h:outputLink value="preview.seam" target="_blank"> <f:param name="conversationId" value="#{conversation.id}"/> <h:outputText value="Preview"/> </h:outputLink>
参数名可以自定义,如添加 <core:manager conversation-id-parameter="cid"/> 那么默认的conversationId就不起作用了,需要用cid。
Seam提供了一个特殊的TAG,<s:conversationId>,可以给JSF LINK自动添加参数。 <h:outputLink value="preview.seam" target="_blank"> <s:conversationId/> <h:outputText value="Preview"/> </h:outputLink>
但是,<s:link>和<s:button>更聪明,它们会自动添加的,nothing need to do. 但是记住,二者不会提交表单,用作普通的链接什么的还是不错的。 Conversation范围的组件必须实现java.io.Serializable接口,因为它要放在session中,特别是分布式的session。
@Conversational public String submitBasicInfo() { // ... } 有了此标记,此方法只能在long-running conversation中被调用,否则报错。 容器 一个类如何称为组件 Seam, 用@Name注解,或在components.xml中声明 EJB 3, 用@Stateful, @Stateless, 或 @MessageDriven 注解 JSF, 作为 managed-beans 在faces-config.xml 中声明 Spring, 作为Spring bean 在applicationContext.xml 中声明 servlet container, 在web.xml中声明servlets, filters and listeners
组件,组件实例,二者类似与java的类和实例对象的关系。
根据组件,实例化出一个组件对象后,它就作为一个属性被存在了指定的Context中。 组件对象,就是一个Java对象而已,但是,Seam可以通过拦截器对它进行管理。当调用它时,Seam就可以根据组件的注解进行一些额外的动作。 如注入,管理事务,执行安全约束,调用生命周期方法,等等等……
当请求使用组件时,如果组件实例已经有了,那么就用它,没有就新建一个。 Seam容器就像一个组件对象工厂。
Seam中定义组件可以用注解,也可以用xml定义,推荐用注解。
@Name,组件的名字,有了它,一个类就称为组件啦。 @Scope,组件所在的范围 @Role(@Roles),定义交替的name/scope对,可以创建平行的实例,用作不同的用途。 多个@Role必须包括在@Roles内。 @Startup,如果是application级别的,那么容器启动时就创建组件对象,如果是session级别的,那么session开始时,就创建。 @Namespace 绑定一个URI,给xml用的。 @Install,条件性的安装组件,如某个类的存在,其它组件的存在,或debug模式 @Autocreate 当Context变量第一个次被请求时,就创建对象,即使不是用它。
基本上,@Name和@Scope就够了,其他都是辅助性的。
一个组件可能是: JavaBean(POJO) JPA entity class EJB 组件(Stateless session bean、Stateful session bean、Message driven bean) Groovy class Spring bean
默认的Scope: EJB stateful session bean :conversation JPA entity class :conversation EJB stateless session bean :stateless EJB message driven bean :stateless JavaBean (POJO) :event
组件扫描器只扫描特定的classpath或jar文件: Classpath的根目录有seam.properties文件 META-INF目录有seam.properties文件或组件描述文件(如components.xml) 可以大大节省扫描时间。
扫描器发现class有@Name注解后,默认的动作就是把它做成组件。 @Install可以设置这个条件。
@Install(value=false) 默认是true,是否安装组件。 @Install(dependencies =”xxx”) 指定名字的组件必须被安装了 @Install(classDependencies =”aa.bb.cc”) 指定的类必须存在 @Install(genericDependencies =”aa.bb.cc”) 指定的类必须作为组件存在 @install(precedence=30) 如果组件同名的话,值大的优先,默认20 @install(debug=true) 默认为false,是否debug模式时才安装组件。
Seam的一些内置组件就是@Install(false)的,需要时可以打开它。 Seam managed persistence context jBPM session factory POJO cache Asynchronous dispatcher (Quartz, EJB 3, Spring) non-JTA transaction manager JMS topic publisher Spring context loader GWT service integration Meldware (embeddable mail server) configuration
比如,可以定义一个接口,和两个类(组件名相同),分别实现不同的功能。 @Name("jsfAdapter") @Install(classDependencies = "com.sun.faces.context.FacesContextImpl") public class SunJsfAdapter implements JsfAdapter { ... }
@Name("jsfAdapter") @Install(classDependencies = "org.apache.myfaces.context.servlet.ServletFacesContextImpl") public class MyFacesJsfAdapter implements JsfAdapter { ... } Seam会自动调用合适的组件。
Seam的debug模式是org.jboss.seam.core.init组件的debug属性控制的,可以在 Components.xml中使用<core:init debug="true"/>来控制。
Deubg模式时,Seam将激活@Install(debug=true)的组件,debug组件优先级较高。 这样,debug时调用debug的组件,非debug时调用正式的组件。
优先级,Seam所有的内置组件的优先值都是Install.BUILT_IN (0),所以,可以轻松覆盖它们。未指定的话,就是默认的Install.APPLICATION (20)。
如果两个组件的名字和优先级都相同,将会报错。
|