当前位置: 代码迷 >> JavaScript >> JSF的加减法与Seam(2)之对java的改进
  详细解决方案

JSF的加减法与Seam(2)之对java的改进

热度:333   发布时间:2012-10-26 10:30:59.0
JSF的加减法与Seam(二)之对java的改进
引用

题记:Seam对开发的简化,对各种不同软件的统一能力,我想其主要来源不是创造了conversation域,不是使用了元注解,不是依赖于JSF,不是使用了反射机制,而是一种更加全局的域管理机制。那些其它因素都是为了这一目的服务的,或多或少的使这一目的的实现变得更加方便可靠。如果说Seam有什么决定性的区别于其它框架的东西,那就是它的全局域管理机制。

说明:1楼和2楼铺垫了很多基础的东西。如果没有耐性,可以把它们略过去,直接从第3楼读起。
另: 文章里的命,生命周期(lifecycle),上下文(context),域(scope)在这里指同一内容,可以互相替换。


不说Seam诞生的大环境和Seam产生的语言基础,只是说Seam本身的功能,大概也可以,不过我认为非常多的外在功能都只是某些环境外在因素和基本内在因素所决定了的。大环境和内在可能定了之后,细节的东西只是做就可以了。所以没有办法,还是得绕开Seam本身说些题外话。

JSF的加减法与Seam(一)http://www.iteye.com/topic/137027大概说了一下 Seam诞生的环境,这是外在机会,是融合各种技术的可能性,是广的纬度。 这篇文章说说java上的可能性,是何让Seam具有了融合的本事,是内在能力,从深的纬度上说吧。

其实也不深,因为说JAVA,其实得从最基本的说起:

1. Java 对象的命

1.1 概述
这个命呢,就是生命周期,就是什么时候生下来可以用它,什么时候死了不能再用了。一个对象在内存中的时间和生命周期是不一定相等的,因为即使一个对象完全没有办法再用了,它仍然可能还在内存里。比如
void doIt() {
Object o = new Object();
o = null;
}

这个方法执行完了之后,o是没办法再用了,但是o可能仍然还在内存里,因为内存的垃圾回收有延时,术语叫做best effort,意思就是我尽我最大能力去回收了,但是回收回得来回不来,得看情况。这篇文章里说到的生命周期,是不考虑这个延时的,如果我没有办法再用这个对象了,我就认为它已经死掉了。

1.2 假如无状态
我举一个非常理想化的例子,假如java的所有的对象的都不能有状态,那对生命周期会有什么影响呢?
如果没有状态,那么所有对象都只能用方法调用。假设我有类A,B,C;而a,b,c是它们的对象。底下老这么说费事,凡是大写都是类,凡是小写都是对象。而a.getB()则假设a有属性b并且有它的getter和setter方法。
如果所有对象的状态不能改变,则只能是这样:

public static void main() {
  A a = new A();
  a.doSomthing();
}
class A() {
  doSomething() {
    B b = new B();
    b.doSomething();
  }
}
class B() {
  doSomehing() {
    C c = new C();
    c.doSomething;
  }
}


每个对象都得在某个方法里被创建(出生),而且必须在那个方法内被销毁(死亡)。那么所有的对象的生命都是和调用它的方法一样长的。这样产生了一个现象就是所有的被调用的对象一定比调用它的对象的命短,而且它出生于调用它的对象出生之后,死亡于调用它的对象死亡之前。 我们把一段生命周期称为“域”,则前者存在的域一定被包含在后者存在的域中。
如何断定的呢? 因为调用者的那个方法的域(也就是命,也就是生命周期)一定包含于调用者本身存在的域──因为任何时候我调用那个方法,拥有该方法的那个对象一定存在,否则该方法也就不能存在了(静态方法是特殊情况等会再说)。而被调用者存在的域一定被包含在调用者方法的域中,这刚才已经讨论了。所以:
对于任意 a通过它的方法doXYZ()来调用b来说:
b的域 包含于(被包含)  doXYZ()的域,  而 doXYZ() 的域 包含于 a的域。
由此可知 b的域包含于a的域。
以此类推,如果a调用b,b调用c,c调用d…… 那么后者的域总是被包含在前者的域里,也就是说越成为被调用者,命越短,越成为调用者,命越长。且被调用者存在时,调用者必然存在。
8 楼 d2up 2007-11-06  
分析得很不错,看了觉得有点矛塞顿开的感觉。
9 楼 zaya 2007-11-06  
dingyuan 写道

spring有生命周期的概念吧。。。spring容器管理对象的创建和消亡。。。。
我感觉最近多了很多seam的抢手啊



呵呵,那我就是其中之一了。

其实很多框架都有生命周期的概念,但是Seam所有的是“更全局化”的生命周期管理。

就像在Spring之前就已经有了反射机制,但是是Spring的大规模使用反射,才使EE世界向轻量级转变;在Seam之前也早已经有了生命周期管理,但是Seam对生命周期管理的大规模使用,同样会引发编程模式的变革。

新的东西往往未必最好,用得最好方是最好。


nihongye 写道
这么多种context,有点恐怖。虽然解决一些问题,但整个系统失去简单性。

正常使用,request,session,application,conversation就可以了。 前三个域别得系统都有,第四个域可以解决一些其它框架不能解决的问题,比如hibernate的OSIV问题,比如可以对浏览器的每个Tab分别保存状态,比如可以在一个用户session里面放置多个并行且相互独立的状态线,并且可以在这些状态线间随意切换,比如可以把对数据库的访问作更有效的缓存,减少hit。
10 楼 zaya 2007-11-06  
3.1.3 接口即类

在软件开发的时候,往往很难决定一项功能究竟是做成实实在在的类,还是做成接口以防以后的扩展或者替换。做成类得话能省去很多麻烦,但是会造成依赖它的类对该类的signature(类名,方法名等等)有绑死的依赖。如果被依赖类被非常多的类依赖得话,以后万一想作改动就会造成大面积的牵动。而如果对每个子功能都作成接口得话,则会大大增加系统的复杂度。

在以往应用的做法中,一般是只把预计以后可能变动的功能提出来被接口(或抽象类)封装,而对可能不会发生变动的功能就使用直接类的方式。这种方式是在过去的编程模式下比较好的一种折衷,但是它也有弊端。人的预计能力是有限的,很常见预计变动的功能最后没有变动,而预计不大可能发生变动的功能结果最后却发现需要替换。

在全局域管理下,有一种新的更方便的解决方法。对于代码:
@Name("config")
public class Config {
    @In Parser parser;
    private List<Module> modules;
    public void loadParser(){
        modules = parser.parser()
    }
    public String get(String moduleName, String configName) {
        //通过指定配置模块的名称和配置项的名称来查找某个配置的设定
    }
}

@Name("parser")
public class Parser {
   private static final String CONFIG_FILE = "org/javaeye/resources/config.properties";
   List<Module> parse() {//把配置文件解析,并把配置项放入若干Module内}
}


这是一个简单的例子,Config类是整个应用的配置类,其它类可以通过它获取某项配置的内容。配置文件可能比较大,所以被分割成若干模块(Module),通过指定模块名和在模块内的配置名来查找某项配置的内容。Config类依赖于Parser类,通过Parser来把一个配置文件加载并分割成若干模块。

回到我们刚才的问题,这里我们把被依赖类Parser做成了一个类而非接口,那么假如以后我们想实现其它种类的Parser该怎么办呢?以往应用中我们不得不更改所有依赖类里面和被依赖类的衔接代码,但是在Seam里,这样的代码不需要,我们只需要改动被依赖类就可以了。
public interface Parser {
    List<Module> parser();
}

@Name("obsoleteParser")
public class PropertyParser implements Parser{
   private static final String CONFIG_FILE = "org/javaeye/resources/config.properties";
   List<Module> parse() {//把配置文件解析,并把配置项放入若干Module内}
}

@Name("parser")
public class XMLParser implements Parser{
    private static final String CONFIG_FILE = "org/javaeye/resources/config.xml";
    List<Module> parse() {//把配置文件解析,并把配置项放入若干Module内}
}


我们看到,依赖类里面一行代码都没有被改动,我们就完成了所有替换。在实际应用中如果依赖类有非常多,那么这样的节省是巨大的。
我们也看到,在对被依赖类Parser进行变动的时候,我们把原来的类改成了接口,并把原来的实现放在了接口Parser的子类中。我们把原来的对property文件进行解析的实现命名为"obsoleteParser"表示这种实现已经作废,而把新的实现XMLParser命名为原来的名字parser。因为所有的依赖类都是根据"parser"这个名字来衔接的,对Parser类的改动不会影响依赖类的代码。

这里我们又看到了一个有趣的现象,在全局域管理的模式下,所有的类都变得像接口了。我们不用再考虑是否把某个功能抽象出来做成接口,直接用类实现就可以了。反正在以后把这些类重写成接口也是一件容易的事。

当然,接口的淡化并非是舍弃接口不用,这样搁置接口的能力,恰恰是接口本身的封装能力所决定了的。而且也并非所有的接口都可以这样使用。接口有两种,一种它的不同实现的替换是发生在编译时(compile time)的,就像上面的这个例子,是可以使用这样的方法的;一种它的不同实现的替换是发生在运行时(run time)的,则不可以使用这样的方法。

比如,如果一个应用有个User接口,它需要同时支持NormalUser和Administrator,这样的应用就不可能只写类不写接口。不到运行的时候,应用是无法知道究竟需要的是NormalUser 还是Administrator的。但是这样的应用是很好做决定是否使用接口的,通常这来自于功能需求而不是扩展与变动的需要。
11 楼 fkpwolf 2007-11-06  
个人认为LZ过多的写了些IOC方面的知识,conversation倒涉及的不多
12 楼 zaya 2007-11-07  
3.1.4 无依赖扩展

对于第三方的软件,如果它不能适合应用的需求,往往需要对其中某项功能某个类进行扩展。扩展本身并非困难的事,只需要写一个类扩展原来的类,或者扩展已有的接口就可以了。困难的是,在绝大多数情形下,被扩展的类都会被该库内的其它类所依赖。一旦扩展了该类,那也就意味着也需要更改那些依赖它的类。

在全局域管理的模式下,类和类之间并不直接交流,所有的类都和容器或者框架进行交流,这样每个类就被重新还原成了独自的个体。如果一个第三方库是全局域管理的模式构成,就会很容易扩展,只需要更改需要扩展的类就可以了。

当然,因为现在的联系由"类-类"转换为了"框架-类",仍然需要告诉框架这个类作了改动。然而这是非常容易的。只需要把库里原来的那个类的名字加在被扩展了的类的上面,并告诉框架扩展之后的类优先于原先的类,就行了。

在Seam中,这也有两种做法,一是通过元注解,一是通过xml配置文件。
Xml配置在所有应用中都大同小异,这里就不说了。说一下元注解的方法:
比如,假如上面小节里的Parser(更改之前的Parser,是类不是接口)是库里的类,我无法对其作改动,但是我仍然需要扩展它使它改为对Xml文件解析而非原来的对Property文件进行解析,该怎么做呢?
@Name("parser")
@Install(precedence=15)
public class XMLParser extends Parser {
    private static final String CONFIG_FILE = "org/javaeye/resources/config.xml";  
    List<Module> parse() {//把配置文件解析,并把配置项放入若干Module内}  
}

这里我把XMLParser起成了和原来Parser一样的名字"parser",但是这样就有两个重名的类了。为了告诉容器XMLParser才是parser名字下真正需要的类,这里加入了一行:
@Install(precedence=15)

precedence的值的高低决定了容器以哪个类作为这个名字的类,高的优先于低的。
取值15是因为在Seam里,默认的库类的值是10,而默认的应用代码类的值是20,这里因为是介于库和应用代码之间,取值15。
通常这是有固定的常量来表示的,在Seam中,定义了若干这样表示优先级的常量:
BUILD_IN = 0;
FRAMEWORK = 10;
APPLICATION = 20;
DEPLOYMENT = 30;
MOCK = 40;
我们看到,我们永远可以通过指定更高级别的优先级来覆写Seam中的任意一个组件。而扩展后的类,可以被放在任意的位置,放在哪个包里都不重要,因为Seam是根据名字而非包全称的类名来查找组件的。
13 楼 zaya 2007-11-07  
3.2 全局域管理模型的构筑因素

前面提到过,全局域管理并非是web世界所独有的,它是一个OO问题,牵涉到对对象,内存管理的方式。在全局域管理的模型下,一个对象变得像它的类了,类变得像接口了,动态方法变得像静态方法了,应用由网状的类与类之间的关系,变成了伞状的中心-周边的容器-类之间的关系。前面一直在写Seam,是因为Seam首先大规模引入这样的模型,很多对模型本身的说明必须考虑Seam的实现方式。下面会舍弃Seam这个例子而考虑更抽象的内容,探讨一下对全局域管理模型的一些其它思考。

3.2.1 构筑因素

3.2.1.1 加载-销毁
3.2.1.2 类配置
3.2.1.3 容器连接点
3.2.1.4 域事件
3.2.1.5 预设域
14 楼 dingyuan 2007-11-07  
spring,又叫spring容器,叫做容器本身,就充当了对类的创建和管理,它虽然默认没有coversation的生命周期,但自己也可以自定义类似的生命周期叫spring自己管理
15 楼 dingyuan 2007-11-07  
感觉seam的优势在域模型建模上,backingbean和ejb同为一体,model和control合为一体,充血模型,设计思路上可以有更多的变化。。。
希望有人能结合seam,针对开发上的设计方案提出探讨
传统的ssh,3层架构action+service+dao,虽然到目前看来略显麻烦,但我觉得却是最保险,扩展性最好,能适用于几乎所有的扩展需求。。

代替这种架构,seam如何更好的做到?seam给的例子在设计上都过于简单,只是对seam能是实现什么功能上进行演示

要有最佳时间,要真的敢用,感觉还要有更多关于结合seam框架,对系统架构方面的探讨
16 楼 zaya 2007-11-07  
dingyuan 写道
感觉seam的优势在域模型建模上,backingbean和ejb同为一体,model和control合为一体,充血模型,设计思路上可以有更多的变化。。。
希望有人能结合seam,针对开发上的设计方案提出探讨
传统的ssh,3层架构action+service+dao,虽然到目前看来略显麻烦,但我觉得却是最保险,扩展性最好,能适用于几乎所有的扩展需求。。

代替这种架构,seam如何更好的做到?seam给的例子在设计上都过于简单,只是对seam能是实现什么功能上进行演示

要有最佳时间,要真的敢用,感觉还要有更多关于结合seam框架,对系统架构方面的探讨


看看Seam的wiki项目吧,是用Seam写的,准备用于生产的。Gnu LGPL license。

Seam和Spring的方式有非常大的不同,这些Seam文档里都写得很多,可以参考一下。
17 楼 dzzhu 2007-11-07  
Conversation也不是什么稀奇的事,Spring Webflow和Apache Shale里面的Dialog也都能做类似的事。

Seam确实做了很多事,但既然有人徒省事买现成的品牌机,也必然会有人根据自己的喜好买兼容机。

就Web层来讲,Facelet+A4J+Shale+Myfaces是比较舒服的解决方案,当然,你要做好心理准备,不啃透JSF的生命周期管理,状态管理和被设计的极其灵活的Phase机制就会让你云里雾里。

业务层来说,Seam的风格挺不错,简洁又不失灵活,不过先进的东西不一定流行,企业应用真正关心的事务处理EJB2可以干,持久层上也有太多选择,实在没有什么理由迁移到EJB3,而Web应用领域现在又是.NET和PHP之类动态语言的天下,更不需要JSF和EJB这样的东东。Seam实在是有一身好本领施展不开阿。
18 楼 nihongye 2007-11-07  
zaya 写道
3.1.3 接口即类

在软件开发的时候,往往很难决定一项功能究竟是做成实实在在的类,还是做成接口以防以后的扩展或者替换。做成类得话能省去很多麻烦,但是会造成依赖它的类对该类的signature(类名,方法名等等)有绑死的依赖。如果被依赖类被非常多的类依赖得话,以后万一想作改动就会造成大面积的牵动。而如果对每个子功能都作成接口得话,则会大大增加系统的复杂度。

在以往应用的做法中,一般是只把预计以后可能变动的功能提出来被接口(或抽象类)封装,而对可能不会发生变动的功能就使用直接类的方式。这种方式是在过去的编程模式下比较好的一种折衷,但是它也有弊端。人的预计能力是有限的,很常见预计变动的功能最后没有变动,而预计不大可能发生变动的功能结果最后却发现需要替换。

在全局域管理下,有一种新的更方便的解决方法。对于代码:
@Name("config")
public class Config {
    @In Parser parser;
    private List<Module> modules;
    public void loadParser(){
        modules = parser.parser()
    }
    public String get(String moduleName, String configName) {
        //通过指定配置模块的名称和配置项的名称来查找某个配置的设定
    }
}

@Name("parser")
public class Parser {
   private static final String CONFIG_FILE = "org/javaeye/resources/config.properties";
   List<Module> parse() {//把配置文件解析,并把配置项放入若干Module内}
}


这是一个简单的例子,Config类是整个应用的配置类,其它类可以通过它获取某项配置的内容。配置文件可能比较大,所以被分割成若干模块(Module),通过指定模块名和在模块内的配置名来查找某项配置的内容。Config类依赖于Parser类,通过Parser来把一个配置文件加载并分割成若干模块。

回到我们刚才的问题,这里我们把被依赖类Parser做成了一个类而非接口,那么假如以后我们想实现其它种类的Parser该怎么办呢?以往应用中我们不得不更改所有依赖类里面和被依赖类的衔接代码,但是在Seam里,这样的代码不需要,我们只需要改动被依赖类就可以了。
public interface Parser {
    List<Module> parser();
}

@Name("obsoleteParser")
public class PropertyParser implements Parser{
   private static final String CONFIG_FILE = "org/javaeye/resources/config.properties";
   List<Module> parse() {//把配置文件解析,并把配置项放入若干Module内}
}

@Name("parser")
public class XMLParser implements Parser{
    private static final String CONFIG_FILE = "org/javaeye/resources/config.xml";
    List<Module> parse() {//把配置文件解析,并把配置项放入若干Module内}
}


我们看到,依赖类里面一行代码都没有被改动,我们就完成了所有替换。在实际应用中如果依赖类有非常多,那么这样的节省是巨大的。
我们也看到,在对被依赖类Parser进行变动的时候,我们把原来的类改成了接口,并把原来的实现放在了接口Parser的子类中。我们把原来的对property文件进行解析的实现命名为"obsoleteParser"表示这种实现已经作废,而把新的实现XMLParser命名为原来的名字parser。因为所有的依赖类都是根据"parser"这个名字来衔接的,对Parser类的改动不会影响依赖类的代码。

这里我们又看到了一个有趣的现象,在全局域管理的模式下,所有的类都变得像接口了。我们不用再考虑是否把某个功能抽象出来做成接口,直接用类实现就可以了。反正在以后把这些类重写成接口也是一件容易的事。

当然,接口的淡化并非是舍弃接口不用,这样搁置接口的能力,恰恰是接口本身的封装能力所决定了的。而且也并非所有的接口都可以这样使用。接口有两种,一种它的不同实现的替换是发生在编译时(compile time)的,就像上面的这个例子,是可以使用这样的方法的;一种它的不同实现的替换是发生在运行时(run time)的,则不可以使用这样的方法。

比如,如果一个应用有个User接口,它需要同时支持NormalUser和Administrator,这样的应用就不可能只写类不写接口。不到运行的时候,应用是无法知道究竟需要的是NormalUser 还是Administrator的。但是这样的应用是很好做决定是否使用接口的,通常这来自于功能需求而不是扩展与变动的需要。

这应该称为一半的依赖注入。因为利用注解,所以叫一半,通过维护一个名字空间(你文里所说的全局域管理)来实现。名字空间的维护不是原因,依赖注入才是有趣现象的本质。
19 楼 zaya 2007-11-07  
不知道该如何回复……

我想,这篇文章是写给多少有些Seam基础的人看的,并非是对Seam的介绍文章,这里的观点不来自于任何已有的参考资料,如果是已有的东西的话我就不会写了。所以在问问题之前,多少了解一些Seam会比较有的放矢一些。

或许之后我会写一篇介绍Seam的入门文章来,但是实际上这样的东西已经很多了,在别得地方也可以找得到。如果您完全不了解Seam,我建议您先多少读一些相关资料再来看本文,或许会好一些。
20 楼 zaya 2007-11-07  
nihongye 写道

这应该称为一半的依赖注入。因为利用注解,所以叫一半,通过维护一个名字空间(你文里所说的全局域管理)来实现。名字空间的维护不是原因,依赖注入才是有趣现象的本质。


注解或是xml,只是配置方法不同,为何注解就是一半呢?

您的名字空间的说法我同意,任何两个紧耦合的事物,总是可以通过加一层mapping层来使之松散耦合,而名字空间就是这里的mapping层。其实从类变为接口与扩展类,spring也做得到,因为spring也有名字空间。但是对于仿静态方法调用,spring就无能为力了。spring虽然有名字空间,但是域管理是欠缺的。
21 楼 nihongye 2007-11-07  
zaya 写道
nihongye 写道

这应该称为一半的依赖注入。因为利用注解,所以叫一半,通过维护一个名字空间(你文里所说的全局域管理)来实现。名字空间的维护不是原因,依赖注入才是有趣现象的本质。


注解或是xml,只是配置方法不同,为何注解就是一半呢?

您的名字空间的说法我同意,任何两个紧耦合的事物,总是可以通过加一层mapping层来使之松散耦合,而名字空间就是这里的mapping层。其实从类变为接口与扩展类,spring也做得到,因为spring也有名字空间。但是对于仿静态方法调用,spring就无能为力了。spring虽然有名字空间,但是域管理是欠缺的。

因为注解指定了名字,这是为了方便而导致的一种限制。不是因为名字空间而能注入,而是对象提供了注入的途径,那么就能注入。
new Book(new 50张Page()) 用到了名字空间吗?没有.
22 楼 zaya 2007-11-08  
3.2 全局域管理模型的构筑因素

前面提到过,全局域管理并非是web世界所独有的,它是一个OO问题,牵涉到对对象,内存管理的方式。在全局域管理的模型下,一个对象变得像它的类了,类变得像接口了,动态方法变得像静态方法了,应用由网状的类与类之间的关系,变成了伞状的中心-周边的容器-类之间的关系。前面一直在写Seam,是因为Seam首先大规模引入这样的模型,很多对模型本身的说明必须考虑Seam的实现方式。下面会舍弃Seam这个例子而考虑更抽象的内容,探讨一下对全局域管理模型的一些其它思考。

既然全局域管理是OO问题,是对内存管理的方式,那么这种模式当对OO世界的所有语言都适用,甚至也可以运用这一原则编一种新的语言出来。自然,这一论断是非常理论化的,OO语言间也有很大的区别,在实际中是有很多局部因素需要考虑的。我个人自然不可能了解各种OO语言以及它们各自都有什么区别,我这里只想局限自己的话题,谈一下是否有可能从底层(比如一个J2SE框架,或者jvm扩展,或者一种新的语言)实现这样一个东西,如果实现得话,有哪些因素是必要考虑的,哪些则是可能的问题。

3.2.1 要求

3.2.1.1 加载-销毁
显而易见,运行时(run-time)的类加载和类销毁是一个必须的能力。因为任何一个对象都是和容器(或者框架,或者jvm扩展,或者新语言环境)交流,当它需要一个其它类的时候它向容器发出请求,由容器决定返回什么,这就决定了所有的被依赖类都必须在运行时动态加载。这一要求直接决定了一些OO语言无法支持。//TODO
3.2.1.2 类配置//TODO
3.2.1.3 容器连接点//TODO
3.2.1.4 域事件//TODO
3.2.1.5 预设域//TODO

3.2.2 可能问题

3.2.2.1 加载性能//TODO
3.2.2.2 大量对象//TODO
3.2.2.3 界定域//TODO
3.2.2.4 与底层的耦合//TODO



3.3 冲击与反思

3.3.1 测试框架
目前Seam的框架使用得是TestNG,虽然比起JUnit来说有所改进,但是仍然是对以往应用做测试的方法。建议域管理模型下构筑一种新的测试框架,该框架包括对域连接点Contexts的mock,也应当包括对各种域的mock,并且能够提供一种机制是自己写得mock可以方便得传给mock域。这样可以把参数传递的工作省下来。//TODO

(这篇文章就写到这里吧,我觉得太抽象了似乎跑题,或许以后会把抽象的内容提出来另发。)
23 楼 fangzhouxing 2007-12-05  
难得的好文章。
24 楼 Chamjoneu 2008-01-29  
借lz的人气问个问题:问什么我在一个Pageflow里, 如果想跳转到其他连接会报Illegal navigation的错误, 并且没有办法跳转过去, 何解啊?
<?xml version="1.0"?>

<pageflow-definition xmlns="http://jboss.com/products/seam/pageflow"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://jboss.com/products/seam/pageflow http://jboss.com/products/seam/pageflow-2.0.xsd"
	name="register">

	<start-state name="start">
		<transition to="account" />
	</start-state>

	<page name="account" view-id="/register/account.xhtml">
		<redirect />
		<transition name="next" to="checkPassword" />
	</page>

	<decision name="checkPassword"
		expression="#{userService.isValidateAccount}">
		<transition name="true" to="contact" />
		<transition name="false" to="account" />
	</decision>

	<page name="contact" view-id="/register/contactInfo.xhtml"
		no-conversation-view-id="/register/account.xhtml">
		<redirect />
		<transition name="prev" to="account" />
		<transition name="next" to="complete">
			<action expression="#{userService.saveUser}" />
		</transition>
	</page>

	<page name="complete" view-id="/register/complete.xhtml"
		no-conversation-view-id="/register/contactInfo.xhtml">
		<end-conversation />
		<redirect />
	</page>
</pageflow-definition>

25 楼 teacake 2008-03-03  
写的相当棒啊,让我对java对象的生死八字有了比较深刻的认识
26 楼 TrampEagle 2008-03-17  
赞一个。支持。
27 楼 afadgaeg 2008-08-13  
楼主思想深刻,不知seam+pojos有何优缺点,ejb3是好东西,但是有时候环境不允许,还请楼主指教一二。
  相关解决方案