Annotations能够帮助你去掉应用组件之间的耦合
摘要
Model-View-Controller (MVC)是一UY件架?它可以将应用E序的数据模?用户接口,以及控制逻辑分开, 使其成ؓ独立的组?q样一?对其中一个组件的修改而生的对其他组件的影响能够被降到最? 在这文章中,你将学习到如何用annotation来设计一个几乎能够完全去掉model和view之间耦合的承的MVC的框?
版权声明QQ何获得Matrix授权的网站,转蝲时请务必保留以下作者信息和链接
作?Riccardo Govoni;wenzi_33
原文:http://www.matrix.org.cn/resource/article/44/44204_Annotations+MVC.html
关键?annotations;MVC;Framework
当设计一个应用程序时, 清晰的分该E序的不同逻辑lg, L被证明是有益? 同时也存在许多不同的模式来帮助开发者实现这个目标。其中最有名同时也最常用的自然是Model-View-Controller (MVC)? 它能够将每个应用E序(或者应用程序的一部分)分成三个不同功能的组?q且定义了把他们联结在一L规则。Swing本n是Zq个模式?而且每个使用Struts,q个行的开发Web应用框架的h也都了解隐藏在MVC后面的理?
q篇文章介绍了怎么样通过使用annotation而增加一个新的组件来加强MVC,使其能够更加方便地去掉models跟views之间的耦合。这文章介l了一个叫Stamps的开源库, 它是ZMVClg之上?但它去除了所有在开发MVC时所需? 在models, views和controllers之间建立联系的负担?BR>
基础知识: MVC和annotations
正如MVCq个名字所指出? Model-View-Controller模式一个应用程序分成以下三个组?
·Model: 包含了数据模型和所有用来确定应用程序状态的信息?它一般来说是有条理的q且独立于其他组件的?BR>·View: 从不同于model的角度出?它定义了存储在模型中数据的展现方式。它通常被认为是你的应用E序的用L?或者GUI),或者以Web应用Z,场景是你通过览器看到的面?BR>·Controller: 它代表应用程序的逻辑部分。在q里,它定义了一个用户如何和应用E序q行交互q且也定义了用户行ؓ是如何映到model的改变?BR>
q些lg紧密的联pd一? 用户影响view, 反过来view通知controller来更新model.最lmodel又更新view来反映它的新状态。图1展Cq种典型的MVCl构?BR>

?. 一个典型的MVCl构
作ؓJ2SE 5.0所提供的一个新的功能,annotations允许开发者往classesQmethodsQfieldsQ和其他E序元素中增加元数据。就像反机制一P之后很多应用E序Z某些原因能在q行时期获取q用那些元数据。因为J2SE 5.0只是定义了怎么L写和dannotationsQƈ没有说明在哪里用他们(象@Overrideq样的用于提前定义的例外Q,开发者拥有无I多的在许多不同场合使用他们的可能性:文档~写Q与对象相关的映,代码生成Q等{?. Annotations已经变的十分行Q以至于大多数框架和库都更新自己来支持他们。至于更多的关于MVC和annotations的信息请参见资源?BR>
越MVC: dispatcher
像前文提到的一Pmodels和views之间的一些耦合是必要的因ؓ后者必d映前者的状态。普通JavaE序使用直接或间接的耦合组件绑定在一赗直接耦合发生在当view和model之间有一个直接相关的时候,model包含一列需要维持的views。间接耦合通常发生在一个基于事件分z机制中。Model会在它状态改变时Ȁ发事Ӟ同时一些独立的views会将他们自己注册成事件侦听器?BR>
通常我们比较青睐间接耦合因ؓ它model完全不知道view的存在,相反view必须和model保持一定的联系从而将自己注册到model上。在q篇文章里我介l的框架是使用间接耦合Q但是ؓ了更好的降低lg之间的耦合Qview必须不知道model的存在;也就是说Qmodel和view没有被绑定在一赗?BR>
Z实现q个目标Q我已经定义了一个新的组Ӟ是dispatcherQ它能作Z个存在于views和models之间的分d。它能处理models和views双方之间的注册ƈ且分zmodelȀ发的事g到注册的views上。它使用java.beans.PropertyChangeEvent对象来表现由model传送到view的事Ӟ然而,q个框架的设计是_开攄Q它可以支持不同事gcd的实现?BR>
理注册的views列表的负担于是就从model上移开了,同时Q因为view只和q个独立于应用程序的dispatcher有关Qview不知道model的存在。如果你熟悉Struts内部Q你也许能够看出Struts的controller是在行这样一个Q务,它将Actions和他们关联的JSP(JavaServer Pages)表现面联系在一赗?BR>
现在Q我们所设计的MVC框架像?所描述的一栗Dispatcher在其中担当了一个于controller相称的角艌Ӏ?BR>

?.拥有额外dispatcherlg的改q的MVC框架
׃dispatcher必须是独立于应用E序的,所以必d义一些通用的联lmodels和views的规范。我们将使用annotations来实现这U联l,它将会被用来标注viewsq且定哪个view是受哪个model的媄响的Q及q种影响是怎么L。通过q种方式Qannotations像是脓在明信片上的邮票一P驱动dispatcher来执行传递model事g的Q?q就是这一框架名字的由??BR>
应用实例
我们用一个简单的计秒器应用程序做该框架的一个应用实例:它允许用戯|时间周期来记数和启?停止q个定时器?一旦过去规定的旉Q用户将会被询问是否取消或者重启这个定时器。这个应用程序的完全源代码可以从目主页上找到?BR>

?.一个简单的应用E序
q个modle是非常简单的Q它只存储两个属性:周期和已l过ȝU数。注意当它其中一个属性发生变化时它是如何使用java.beans.PropertyChangeSuppor来激发事件?BR>public class TimeModel {
public static final int DEFAULT_PERIOD = 60;
private Timer timer;
private boolean running;
private int period;
private int seconds;
private PropertyChangeSupport propSupport;
/**
* Getters and setters for model properties.
*/
/**
* Returns the number of counted seconds.
*
* @return the number of counted seconds.
*/
public int getSeconds() {
return seconds;
}
/**
* Sets the number of counted seconds. propSupport is an instance of PropertyChangeSupport
* used to dispatch model state change events.
*
* @param seconds the number of counted seconds.
*/
public void setSeconds(int seconds) {
propSupport.firePropertyChange("seconds",this.seconds,seconds);
this.seconds = seconds;
}
/**
* Sets the period that the timer will count. propSupport is an instance of PropertyChangeSupport
* used to dispatch model state change events.
*
* @param period the period that the timer will count.
*/
public void setPeriod(Integer period){
propSupport.firePropertyChange("period",this.period,period);
this.period = period;
}
/**
* Returns the period that the timer will count.
*
* @return the period that the timer will count.
*/
public int getPeriod() {
return period;
}
/**
* Decides if the timer must restart, depending on the user answer. This method
* is invoked by the controller once the view has been notified that the timer has
* counted all the seconds defined in the period.
*
* @param answer the user answer.
*/
public void questionAnswer(boolean answer){
if (answer) {
timer = new Timer();
timer.schedule(new SecondsTask(this),1000,1000);
running = true;
}
}
/**
* Starts/stop the timer. This method is invoked by the controller on user input.
*/
public void setTimer(){
if (running) {
timer.cancel();
timer.purge();
}
else {
setSeconds(0);
timer = new Timer();
timer.schedule(new SecondsTask(this),1000,1000);
}
running = !running;
}
/**
* The task that counts the seconds.
*/
private class SecondsTask extends TimerTask {
/**
* We're not interested in the implementation so I omit it.
*/
}
}
Controller只定义了用户可以执行的ƈ且能够从下列接口抽象出来的actions?BR>public interface TimeController {
/**
* Action invoked when the user wants to start/stop the timer
*/
void userStartStopTimer();
/**
* Action invoked when the user wants to restart the timer
*/
void userRestartTimer();
/**
* Action invoked when the user wants to modify the timer period
*
* @param newPeriod the new period
*/
void userModifyPeriod(Integer newPeriod);
}
你可以用你自己喜欢的GUI~辑器来画这个view。出于我们自w的情况Q我们只需要几个公qmethods可以提供够的功能来更新view的fieldsQ如下面的这个例子所C:
/**
* Updates the GUI seconds fields
*/
public void setScnFld(Integer sec){
// scnFld is a Swing text field
SwingUtilities.invokeLater(new Runnable() {
public void run() {
scnFld.setText(sec.toString());
}
});
}
在这里我们注意到我们正在使用POJOs (plain-old Java objects)Q同时我们不用遵守Q何编码习惯或者实现特定的接口(事gȀ发代码除?。剩下的只有定义组件之间的l定了?BR>
事g分派annotations
l定机制的核心就是@ModelDependent annotation的定义:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface ModelDependent {
String modelKey() default "";
String propertyKey() default "";
boolean runtimeModel() default false;
boolean runtimeProperty() default false;
}
q个annotation能被用在view的methods上,同时dispatcher也会使用q些提供的参?即modelKey和propertyKey)来确定这个view会响应的model事g。这个view既用modelKey参数来指定它感兴的可利用的models又用propertyKey参数来匹配分配的java.beans.PropertyChangeEvents的属性名U?BR>
View method setScnFld()因此被标注以下信?q里QtimeModel提供了用来将model注册到dispatcher上的key):
/**
* Updates the GUI seconds fields
*/
@ModelDependent(modelKey = "timeModel", propertyKey = "seconds")
public void setScnFld(final Integer sec){
// scnFld is a Swing text field
SwingUtilities.invokeLater(new Runnable() {
public void run() {
scnFld.setText(sec.toString());
}
});
}
׃dispatcher既知道modelȀ发的事g又知道事件本w-例如Q它知道兌的modelKey和propertyKeyQ这是唯一需要用来绑定views和models的信息。Model和view甚至不需要分享通信接口或者共用的数据库?BR>
借助我们讨论的绑定机Ӟ我们可以L的改变潜在的view而不改变其他M东西。下面的代码是按照用SWT(Standard Widget Toolkit)而不是Swing实现的同一个methodQ?BR>@ModelDependent(modelKey = "timeModel", propertyKey = "seconds")
public void setScnFld(final Integer sec){
Display.getDefault().asyncExec(new Runnable() {
public void run() {
secondsField.setText(sec.toString());
}
});
}
一个完全没有耦合的系l存在以下优点:View可以更加Ҏ地适应model地改变,管model通常都是E_圎ͼ相反view是经常被改变。加上系l可以通过使用GUI~辑器或者其他源码生成器来设计,避免了将生成C码与modelQview通信代码混合在一赗又׃modelQview的绑定信息是和源码关联的元数据,于是也相对容易把它应用到IDE生成的GUIs或者将已经存在的应用程序{化成q个框架。加之拥有单独的基础代码Qview和model可以被当作是独立lg来开发,q很可能化了应用E序的开发过E。组件测试也可以被简化,因ؓ每个lg可以被单独地试Qƈ且出于调试的目的Q我们可以用假的model和view来代替真实的lg?BR>
然而,q里也存在许多缺炏V因为现在当使用接口和公qclasses来绑定model和viewӞ我们不能再提供编译时期的安全性了Q可能出现的打字错误导致组件之间一个绑定的遗漏Q从而导致出现运行时期的错误?BR>
通过使用@ModelDependent的讨的modelKey和propertyKey元素Q你可以定义model和view之间静态的联系。然而,现实世界的应用程序证明view必须能够l常动态的适应变化的models和应用程序的状态:考虑到用L面的不同部分能够在应用程序的生命周期内被创造和删除。因此我介l怎么使用q个框架与其他常用技术一h处理此类情Ş?BR>
动态MVCl定
对于那些依赖XMLl定(或者其他一些基于配|文件的声明性绑?的框Ӟ存在一个问题那是静态绑定规则。在q些框架下,动态变化是不可能的Q于是通常开发者决定每ơ将冗余的绑定信息与一些用正绑定的判定法耦合在一赗?BR>
Z巧妙的解册个问题,Stamps框架提供了两U方式在q行时期改变l定?W一U方式是Qviews和models可以采用事g监听器与GUIH口部件联合的方式在dispatcher上注册和注销。这样允许特定的views只在需要他们的时候被通知到。例如,一个与应用E序有联pȝ监视控制台可以只在用戯求的时候与被它监视的对象绑定在一赗?BR>
W二U方式是利用@ModelDependent annotation提供的两个元素runtimeModel() ?runtimeProperty()。他们指明了某个定的model和它的分配事件会在运行时期被定。如果这两个讑֮中有一个是正确的,那么各自的key(modelKey 或propertyKey)会在view上被method调用来得到需要用的倹{例如:一个负责显CZl新channels (每个channel是一个model)的viewQ它׃赖于用户的输入来定需要绑定的channel?BR>
q种情Ş的实例如下:
// This method is invoked to display all the messages of one news channel
@ModelDependent(modelKey = "dynamicChannel", propertyKey = "allmessages" , runtimeModel = true)
public void setAllMessages(java.util.List messages) {
// Updates the user interface
}
public String getDynamicChannel() {
// Returns the channel requested by the user
}
附加的annotations
׃世界q不完美Q一些附加的annotations被定义来帮助解决现实世界的案例。@Namespace允许开发者ؓ了更好的理model domain其再细分成不同的部分。由于单独一个dispatcher可以处理多个modelsQmodel keys中将出现的冲H。因此,它能成的models和相关的views分到不同的但同属一个namespace下的domains中去, q样一来,他们׃会干扰对斏V?BR>
@Transform annotation提供了on-the-fly对象转化, 从包含在model事g中的对象到被receiving views接受的对象的。因而,q个框架可以适应已存的代码而不需要做M的改动。这个annotation接受一个注册在有效转化上的单一参数(被定义成一个特D接口的实现)?BR>
@Refreshable annotation能通过标注model的属性来支持前面讨论的动态连接和分离views。用这个annotationQ该框架可以处理静态和动态的MVC布局Q在不同的时间把不同的viewsl定到model上?BR>
要理解@Refreshable的用,我们必须回到之前的那个监控控制台的例子。这个控制台(用MVC的术语来说就是一个view)可以动态地l定和离开modelQ取决于用户的需要。当控制器连接到model的时候@Refreshable annotation可以被用来让q个控制器随时了解其model的状态。当一个viewq接到这个框架时Q它必须在当前model的状态下被更新。因此,dispatcher扫描modelL@Refreshable annotationsq且生成与view它本w从model普通接受到的相同的事g。这些事件接着被之前讨的绑定机制分z?BR>
分布式MVC|络
Dispatcher有一个很重的负担那就是它负责处理事g的传送周期中所有重型信息的传递:
· ModelȀ发一个事件用来确定它已经l历q的一些改? dispatcher处理通知model.
· Dispatcher扫描所有注册在它那里的views, L@ModelDependent annotations, q些annotations明确了views希望通知的改变及当每个model事g发生?需要在views上调用的method.
· 如果需?转化会被用于事件数据上.
· view method在被调用时会从被Ȁ发的事g里抽取参?接着view会更新自?
从另一个方面来?当一个新view在dispatcher上注册时:
· View告诉dispatcher有关modelKey的信?modelkey能确定它被q接到哪一个model?该model的事件将负责l装view)
· 如果需要,dispatcher扫描modelL@Refreshable annotationsq用他们来生要及时更新view假的model事g
· q些事g通过使用上述的顺序被分派, 接着view被更?
所有这些既不涉及view也不涉及model的工?他们站在他们各自的信息通信渠道的两?无所谓这些信息是在一个本地JVM内部传输q是在多个远E主Z的JVM之间传输.如果惛_本地应用E序转化成Client/Server应用E序所需的只是简单地改变dispatcher里面的逻辑,而model和view都不会受影响.下图是一个示?
?. 一个基于分布式|\建立的MVC.
如上图所C?单一的dispatcher被一个与model处在同一个host上的transmitter(it.battlehorse.stamps.impl.BroadcastDispatcher的一个instance)和一?或多? 与view处在同一个host上的receiver(it.battlehorse.stamps.impl.FunnelDispatcher)所取代. Stamps 框架默认的实C用了一个创ZJGroups上的消息传送层, JGroups是一个可靠的多点传送通信的工具包,象网l传输机?但是不同的实现和使用)一样工? 通过使用它可以获得一个稳定可靠的, 多协议的, p|警觉的通信.
Ҏ们应用程?dispatcher)初步建立的一个改? 使我们从一个单一用户界面的独立运行的应用E序转移C个多用户分布式的应用E序.当modelq入或离开q个|络(惌一个通信p|)的时?框架可以通知无数的监听接? 于是q程views可以采取适当的响?例如,昄一个警告信息给用户. q个框架也可以提供有用的methods来帮助将本地的controllers转化成远E的.
ȝ和摘?/SPAN>
仍有许多元素需要被探烦,像设计controllers的方式一?它在目前和dispatchersh一致的普遍?该框架假设普通的controller-modell定,׃前者需要知道如何去驱动后?未来的开发方向将是支持不同类型的views,例如使用一个Web览? |络警觉的applets,和Java与JavaScript的通信.
已经讨论的Stamps库说明如何在一个MVC架构中降低views和models之间的耦合以及q个框架可以有效的利用Java annotations绑定信息从实际开发程序组件分d.拥有隔离的绑定逻辑允许你在物理上将元g分离开q且能提供一个本地和一个client/serverl构而不需要改变应用逻辑或者表C层. q些目标提供对由一个象MVC一样坚固的设计模式与由annotations提供的功能强大的元数据结合在一h提供的可能性的z察.
关于作?/B>
Riccardo Govoni自从2003q开始就做ؓ一名J2EE的开发者在一家位于意大利北部的金融服务公司工?在那?他ؓ遗留的银行系l?数据库管?和繁重的数据处理d开发了许多Web前台. Govoni在J2EE多层应用E序斚w拥有丰富l验q且拥有关于Java GUI 库象Swing 和SWT详尽的知?他拥有物理学士学位.在闲暇时?Govoni在网l上四处L最新的Java新闻,或者与朋友?有时?他女朋友的狗一赯论新的项目主?
资源
· Stamps目主页,及相x码文件和文档:http://sourceforge.net/projects/stamps-mvc/
· Wikipedia上关于MVC的定?http://en.wikipedia.org/wiki/MVC
· 最有名的MVC实现之一, Struts Web 应用框架:http://struts.apache.org/
· JGoodies l定Q一个在Swing应用E序中解决model-view-controllerl定的不同方?https://binding.dev.java.net/
· TikeSwing, 一个基于Swing之上的开源框? 通过扩展Swing widgets来引入MVC “与l合Swing来用高层次的MVC和POJOs?Tomi Tuomainen(JavaWorld, 2005q六?:http://www.javaworld.com/javaworld/jw-06-2005/jw-0620-tikeswing.html
· Sun J2SE 5.0 annotations向导:http://java.sun.com/j2se/1.5.0/docs/guide/language/annotations.html
· J2SE 5.0 annotations 入门介绍: “Taming Tiger, W三部分,?Tarak Modi(JavaWorld, 2004q七?:http://www.javaworld.com/javaworld/jw-07-2004/jw-0719-tiger3.html
· “零距离: J2SE 5.0 Annotations,?Kyle Downey (ONJava.com, 2004q十?:http://www.onjava.com/pub/a/onjava/2004/10/06/anno1.html
· JGroups, 是一个可靠的多点传送通信的工具包, 在分布式Stamps MVC|络中象传输层一样工?http://www.jgroups.org/javagroupsnew/docs/index.html
· “一个Swing架构的一般了解? 作? Amy Fowler(Sun开发者网l?讨论Swing’s MVC的根?http://java.sun.com/products/jfc/tsc/articles/architecture/ 
]]>