高级 Swing于Java Bean

来源:互联网 发布:油画颜料 知乎 编辑:程序博客网 时间:2024/05/21 07:33
动态绑定事件
Swing
的事件模型的优点就在于它的灵活性。你可以调用方法给组件添加或删除事件。
Button 
可以连不止一个listener。通常组件是以多播(multicast)方式处理事件的,也就是说你可以为一个事件注册多个listener。但是对于 一些特殊的,以单播(unicast)方式处理事件的组件,这么做就会引发TooManyListenersException了。 程序运行的时候能动态地往Button b2上面添加或删除listener。你应该已经知道加listener的方法了,此外每个组件还有一个能用来删listener的 removeXXXListener( )方法。 
这种灵活性为你带来更大的便利。值得注意的是,listener的添加顺序并不一定就是它们的调用顺序(虽然绝大多数JVM确实是这么实现的)
将业务逻辑(business logic)与用户界面分离开来
一 般情况下,设计类的时候总是强调一个类"只作一件事情"。涉及用户界面的时候更是如此,因为你很可能会把"要作什么""要怎样显示"给混在一起了。这种 耦合严重妨碍了代码的复用。比较好的做法是将"业务逻辑(business login)"GUI分离开来。这样不仅方便了业务逻辑代码的复用,也简化了GUI的复用。还有一种情况,就是多层系统(multitiered systems),也就是说”业务对象(business object)"完全贮存在另一台机器上。业务规则的集中管理能使规则的更新立即对新交易生效,因此这是这类系统所追求的目标。但是很多应用程序都会用到 这些业务对象,所以它们绝不能同特定的显示模式连在一起。它们应该只做业务处理,别的什么都不管。树立了将UI同业务逻辑相分离的观点之后,当你再碰到用Java去维护遗留下来的老代码时,也能稍微轻松一点。
范式,内部类,Swing事件模型,还能继续用下去的AWT事件模型,以及那些要我们用老办法用的新类库的功能,所有这些都使程序设计变得更混乱了。现在就连大家写乱七八糟的代码的方式也变得五花八门了。这些情况都是事实,但是你应该总是使用最简单也最有条理的解决方案:Listener(通常要写成内部类)来处理事件。用了这个模型,你可以少写很多"让我想想这个事件是谁发出的"这种代码。所有代码都在解决问题,而不是在做类型检查。这是最佳的编程风格,写出来的代码不仅便于总结,可读性和可维护性也高。
并发与Swing
Swing 程序的时候,你很可能会忘了它还正用着线程。虽然你并没有明确地创建过Thread对象,但它所引发的问题却会乘你不备吓你一跳。绝大多数情况下,你写的 Swing或其他带窗口显示的GUI程序都是事件驱动的,而且除非用户用鼠标或键盘点击GUI组件,否则什么事都不会发生。
只要记住Swing有一个事件分派线程就行了,它会一直运行下去,并且按顺序处理Swing的事件。如果你想确保程序不会发生死锁或者竞争的情形,那么倒是要考虑一下这个问题。
重访Runnable
在第13章,我曾建议大家在实现Runnable接口时一定要慎重。 当然如果你设计的类必须继承另一个类而这个类又得有线程的行为,那么选择Runnable还是对的。
不同的JVM,在如何实现线程方面,存在着巨大的性能和行为差异。
管理并发
当你用main方法或另一个线程修改Swing组件的属性时,一定要记住,有可能事件分派线程正在和你竞争同一个资源。
看来线程遇到Swing的时候,麻烦也跟着来了。要解决这个问题,你必须确保Swing组件的属性只能由事件分派线程来修改。
这 要比听上去的容易一些。Swing提供了两个方法,SwingUtilities.invokeLater( )SwingUtilities.invokeandWait( ),你可以从中选一个。它们负责绝大多数的工作,也就是说你不用去操心那些很复杂的线程同步的事了。
这两个方法都需要runnable对象作参数。当Swing的事件处理线程处理完队列里的所有待处理事件之后,就会启动它的run( )方法了。
能用这两个方法来设置Swing组件的属性。
可视化编程与JavaBeans
看到现在你已经知道Java在代码复用方面的价值了。复用程度最高的代码是类,因为它是由一组紧密相关的特征(字段field)和行为(方法)组成的,它既能以合成(composition),也能以继承的方式被复用。
继承和多态是面向对象编程的基础,但是在构建应用程序的时候,绝大多数情况下,你真正需要的是能帮你完成特定任务的组件。你希望能把这些组件用到设计里面,就像电子工程师把芯片插到电路板上一样。同样,也应该有一些能加速这种"模块化安装"的编程方法。
Microsoft Visual Basic"可视化编程(Visual programming)"赢得了初次成功——非常巨大的成功,紧接着是第二代的Borland Delphi(直接启发了JavaBean的设计。有了这些工具,组件就变得看得见摸的着了,而组件通常都表示像按钮,文本框之类的可视组件,因此这样一来组件编程也变得有意义了。实际上组件的外观,通 常是设计时是什么样子运行时也就这样,所以从控件框(palette)里往表单上拖放组件也就成了可视化编程的步骤了。而当你在这么做的时候,应用程序构 造工具在帮你写代码,所以当程序运行时,它就会创建那些组件了。
通常简单地把组件拖到表单上还不足以创建程序。你还得修改一些特征,比如 它的颜色,上面的文字,所连接的数据库等等。这些在设计时可以修改的特征被称为属性(properties)。你可以在应用程序的构建工具里控制组件的属 性。当程序创建完毕,这些配置信息也被保存下来,这样程序运行时就能激活这些配置信息了。
看到现在你或许已经习惯这样来理解对象了,也就是对象不仅是一组特征,还是一组行为。设计的时候,可视组件的行为部分的表现为事件,也就是说"是些能发生在这个组件上的事情"。一般来说你会用把代码连到事件的方法来决定事件发生时该做些什么。
下 面就是关键部分了:应用程序的构建工具用reflection动态地查询组件,找出这个组件支持哪些属性和事件。一旦知道它是谁,构建工具就能把这些属性 显示出来,然后让你作修改了(创建程序的时候会把这些状态保存下来),当然还有事件。总之,只要你在事件上双击鼠标或者其他什么操作,编程工具就会帮你准 备好代码的框架,然后连上事件。现在,你只要编写事件发生时该执行的代码就可以了。
编程工具帮你做了这么多事,这样你就能集中精力去解决程序的外观和功能问题了,至于把各部分衔接起来的细节问题,就交给构建工具吧。可视化编程工具之所以能获得如此巨大的成功,是因为它能极大的提高编程的效率,当然这一点首先体现在用户界面,但是其它方面往往也受益颇丰。
JavaBean是干什么用的?
言 归正传,组件实际上是一段封装成类的代码。关键在于,它能让应用程序的构建工具把自己的属性和事件提取出来。创建VB组件的时候,程序员必须按照特定的约 定,用相当复杂的代码把属性和事件发掘出来。Delphi是第二代的可视化编程工具,而且整个语言是围绕着可视化编程设计的,所以用它创建可视化组件要简 单得多。但是Java凭借其JavaBean在可视化组件的创建技术领域领先群雄。Bean只是一个类,所以你不用为创建一个Bean而去编写任何额外的 代码,也不用去使用特殊的语言扩展。事实上你所要做的只是稍稍改变一下方法命名的习惯。是方法的名字告诉应用程序构建工具,这是一个属性,事件还是一个普 通的方法。JDK文档把命名规范(naming convention)错误地表述成"设计模式(design pattern)”。这真是不幸,设计模式(请参阅www.BruceEckel.com上的Thinking in Patterns (with Java))本身已经够让人伤脑筋的了,居然还有人来混淆视听。重申一遍,这算不上是什么设计模式,只是命名规范而已,而且还相当简单。

对于名为 xxx的属性,你通常都得创建两个方法:getXxx( )setXxx( )。注意构建工具会自动地将"get""set"后面的第一个字母转换成小写,以获取属性的名字。"get"所返回的类型与”set"所使用的参数的类 型相同。属性的名字同"get"和”set"方法返回的类型无关。 对于boolean型的属性,你既可以使用上述的"get""set"方法,也可以用"is"来代替"get"。 Bean的常规方法无需遵循上述命名规范,但它们都必须是public的。 用Swinglistener来处理事件。就是我们讲到现在一直在用的这个方案:用addBounceListener (BounceListener)removeBounceListener(BounceListener)来处理BounceListener。绝 大多数情况下,内置的事件和监听器已经可以满足你的需要了,但是你也可以创建你自己的事件和监听器接口。 
第一点回答了你在比较新旧代码时或许会注意的一个问题:很多方法的名字都有了一些很小的,但明显没什么意义的变化。现在你应该知道了,为了把组件做成JavaBean,绝大多数修改是在同"get""set"的命名规范接轨。

Introspector提取BeanInfo 

当你把Bean从控件框(palette)里拖到表单上的时候,JavaBean架构中最重要的一环就开始工作了。应用程序构建工具必须能创建这个Bean(有默认构造函数的话就可以了),然后在不看Bean源代码的情况下提取所有必须的信息,然后创建属性表和事件句柄。

从 第十章看,我们已经能部分地解决这个问题了:Javareflection机制可以帮我们找出类的所有方法。我们不希望像别的可视化编程语言那样用特殊 的关键字来解决JavaBean的问题,因此这是个完美的解决方案。实际上给Java加上reflection的主要原因,就是为了支持JavaBean (虽然也是为了支持"对象的序列化(object serializaiton)""远程方法调用(remote method invocation)"。所以也许你会想设计应用程序构建工具的人会逐个地reflect Bean,找出所有的方法,再在里面挑出Bean的属性和事件。

这么做当然也可以,但是Java为我们提供了一个标准的工具。这不仅使 Bean的使用变得更方便了,而且也为我们创建更复杂的Bean指出了一条标准通道。这个工具就是Introspector,其中最重要的方法就是 static getBeanInfo( )。当你给这个方法传一个Class对象时,它会彻底盘查这个类,然后返回一个BeanInfo对象,这样你就可以通过这个对象找出Bean的属性,方法 和事件了。[AutoPage]

通常你根本不用为此操心;绝大多数Bean都是从供应商那里直接买过来的,更何况你也不必知道它在底层都玩了什么花样。你只要直接把Bean放到表单上,然后配置一下属性,再写个程序处理一下事件就可以了。

一个更复杂的Bean

所有的字段都是private的这是Bean的通常做法——也就是说做成"属性"之后,通常只能用方法来访问了。

JavaBeans
和同步

只要你创建了Bean,你就得保证它能在多线程环境下正常工作,这就是说:
只 要允许,所有Beanpublic方法都必须是synchronized。当然这会影响性能(不过在最新版本的JDK里,这种影响已经明显下降了)。如 果性能下降确实是个问题,那么你可以把那些不致于引起问题的方法的synchronized给去掉,但是要记住,会不会引发问题不是一眼就能看出来的。这 种方法首先是要小(就像上面那段程序里的getCircleSize( )),而且/或是"原子操作",就是说这个方法所调用的代码如此之少,以至于执行期间对象不会被修改了。所以把这种方法做成非synchronized 的,也不会对性能产生什么重大影响。所以你应该把Bean的所有public方法都做成synchronized,只有在有绝对必要,而且确实对性能提高 有效的时候,才能把synchronized移掉。 当你将多播事件发送给一队对此感兴趣的listener时,必须做好准备,listener会随时加入或从队列中删除。 
第一个问题很好解决,但第二个问题就要好好想想了。

paintComponent( )
也没有synchronized。决定覆写方法的时候是不是该加synchronized不像决定自己写的方法那样清楚。。这里,好像paintComponent()加不加synchronized一样都能工作。但必须考虑的问题有:
这 个方法是否会修改对象的"关键"变量?变量是否”关键"的判断标准是,它们是否会被其它线程所读写。(这里,读写实际上都是由synchronized方 法来完成的,所以你只要看这一点就可以了)在这段程序里,paintComponent( )没有修改任何东西。 这个方法是否与这种"关键"变量的状态有关?如果有一个synchronized方法修改了这个方法要用到的变量,那么最好是把这个方法也作成 synchronized的。基于这点,你或许会发现cSize是由synchronized方法修改的,因此paintComponent( )也应该是synchronized。但是这里你应该问问"如果在执行paintComponent( )的时候,cSize被修改了,最糟糕的情况是什么呢?"如果问题并不严重,而且转瞬即逝的,那么为了防止synchronized所造成的性能下降,你 完全可以把paintComponent( )做成非synchronized的。 第三个思路是看基类的paintComponent( )是不是synchronized,答案是""。这不是一个万无一失的判断标准,只是一个思路。就拿上面那段程序说吧,paintComponent( )里面混了一个通过synchronized方法修改的cSize字段,所以情况也改变了。但是请注意,synchronized不会继承;也就是说派生 类覆写的基类synchronized方法不会自动成为synchronized方法。 paint( )paintComponent( )是那种执行得越快越好的方法。任何能够提升性能的做法都是值得大力推荐的,所以如果你发觉不得不对这些方法用synchronized,那么很有可能是 一个设计失败的信号。 
main( )
的测试代码是根据BangBeanTest修改而得的。为了演示BangBean2的多播功能,它多加了几个监听器。

封装Bean

要 想在可视化编程工具里面用JavaBean,必须先把它放入标准的Bean容器里。也就是把所有Beanclass文件以及一份申明"这是一个 Bean""manifest"文件打成一个JAR的包。manifest文件是一种有一定格式要求的文本文件。对于BangBean,它的 manifest文件是这样的:

Manifest-Version: 1.0

Name: bangbean/BangBean.class

Java-Bean: True

第一行表明manifest的版本,除非Sun今后发通知,否则就是1.0。第二行(空行忽略不计)特别提到了BangBean.class文件,而第三行的意思是"这是y一个Bean"。没有第三行,应用程序构建工具不会把它看成Bean

唯 一能玩点花样的地方是,你必须在"Name:"里指明正确的路径。如果你翻到前面去看BangBean.java,就会发觉它属于bangbean package(因此必须放到classpath的某个目录的"bangbean"的子目录里),而manifestname也必须包含这个 package的信息。此外还必须将manifest文件放到package路径的根目录的上一层目录里,这里就是将manifest放到 "bangbean"子目录的上一层目录里。然后在存放manifest文件的目录里打入下面这条jar命令:

jar cfm BangBean.jar BangBean.mf bangbean

这里假定JAR文件的名字是BangBean.jar,而manifest文件的名字是BangBean.mf

或 许你会觉得有些奇怪,"我编译BangBean.java的时候还生成了一些别的class文件,它们都放到哪里去了?"是的,它们最后都放在 bangbean子目录里,而上面那条jar命令的最后一个参数就是bangbean。当你给jar一个子目录做参数时,它会将整个子目录都打进JAR文 件里(这里还包括BangBean.java的源代码——你自己写Bean的时候大概不会想把源代码打进包吧)。此外如果你把刚做好的JAR文件解开,就 会发现你刚写的那个manifest已经不在里面了,取而代之的是jar自己生成的(大致根据你写的),名为MANIFEST.MFmanifest文 件,而且它把它放在META-INF子目录里 (意思是“meta-information”)。如果你打开这个manifest文件,就会发现jar给每个文件加了条签名的信息,就像这样:

Digest-Algorithms: SHA MD5 

SHA-Digest: pDpEAG9NaeCx8aFtqPI4udSX/O0=

MD5-Digest: O4NcS1hE3Smnzlp2hj6qeg==

总之,你不必为这些事担心。你作修改的时候可以只改你自己写的manifest文件,然后重新运行一遍jar,让它来创建新的JAR文件。你也可以往JAR文件里加新的Bean,只是要把它们的信息加到manifest里面就行了。

值得注意的是,你应该为每个Bean创建一个子目录。这是因为当你创建JAR文件的时候,你会把子目录的名字交给jar,而jar又会把子目录里的所有东西都放进JAR。所以FrogBangBean都有它们自己的子目录。

等 你把Bean封装成JAR文件之后,你就能把它们用到支持BeanIDE里了。这个步骤会随开发工具的不同有一些差别,不过Sun在他们的"Bean Builder"里提供了一个免费的JavaBean的测试床(可以到java.sun.com/beans去下载)。要把Bean加入 BeanBuiler,只要把JAR文件拷贝到正确的目录里就行了。

Bean的高级功能

你已经知道做一个Bean有多简单了,但是它的功能并不仅限于此。JavaBean的架构能让你很快上手,但是经过扩 展,它也可以适应更复杂的情况。这些用途已经超出了本书的范围,但是我会做一个简单的介绍。你可以在java.sun.com/beans上找到更多的细 节。

属性是一个能加强的地方。在我们举的例子里,属性都是单个的,但是你也可以用一个数组来表示多个属性。这被称为索引化的属性 (indexed property)。你只要给出正确的方法(还是要遵循方法的命名规范)Introspector就能找到索引化的属性,这样应用程序构建工具就能作出 正确的反映了。

属性可以被绑定,也就是说它们能通过PropertyChangeEvent通知其它对象。而其它对象能根据Bean的变化,修改自己的状态。 

属 性是可以被限制的,也就是说如果其他对象认为属性的这个变化是不可接受的,那么它们可以否决这个变化。BeanPropertyChangeEvent 通知其他对象,而其他对象则用PropertyVetoException来表示反对,并且命令它将属性的值恢复到原来的状态。

你也可以修改Bean在设计时的表示方式
你 可以为Bean提供自定义的属性清单。当用户选择其它Bean的时候,构建工具会提供普通属性清单,但是当他们选用你的Bean时,它会提供你定义的清 单。 你可以为属性创建一个自定义的编辑器,这样虽然构建工具用的是普通的属性清单,但当用户要编辑这个特殊属性时,编辑器就会自动启动了。 你可以为Bean提供一个自定义的BeanInfo类,它返回的信息,可以同Introspector默认提供的BeanInfo不同。 还可以把所有FeatureDescriptor"专家(expert)"模式打开,看看基本功能和高级功能有什么区别。




http://hi.baidu.com/fecasmoy123/blog/item/d8afa89796a46a6855fb9687.html