摘要Java Annotations主要用来标注deprecated的代码
在这篇文章中
它们用来把方法调用的控制权移交给一个轻量级框架中负责处理一系列方法调用的组件
因此
正确的初始化和设置等操作被委派给客户端应用而不是类
以做到设置和控制都可以调整
对于开发者来说复杂的应用通常有很多初始化问题需要处理许多不同的步骤无非是建立面板配置服务之类而这些事情的难点在于有一些步骤需要重复另一些则不需要把这种管理问题交给类自己处理是非常麻烦的因为逻辑可能会变化另外现代软件设计强调分离职责简单来说我们的目的是把做什么和怎么做分离开来
这篇文章展示给大家如何使用 Annotations来做初始化控制这种做法超越了简单的标注它介绍了一个小的API可以用它来开发你自己的phaseable Annotations或者在这种新特性上给你提供一些灵感
Annotations
Annotations是JSE 引入的新语言特性通常 Annotations允许开发者用一种跟运行代码无关的次要信息来标注类方法以及成员这样就可以使用类似评价的 Annotations比如好方法坏方法或者更详细一些不推荐的方法覆写的方法这些用法的可能性是无穷的不过请注意方法或类跟标注实际可能不相关比如不推荐的如果想知道关于 Annotations的更多详细讨论请阅读Java Tiger: A Developers Notebook
因为 Annotations可以用来描述用例或者实体比如方法和类的意思所以这是一种语法棒棒糖反过来这些附加信息也可以被其他东西(比如框架)用于各种各样的动作比如生成文档(Javadoc)或者像这里讨论的作为一种特殊内容来控制行为比如对象的生命周期
生命周期管理
生命周期管理通常发生在中间件环境中比如应用服务器这种思想是把对象的创建使用以及销毁跟对象本身分开例如在一个发布不同服务的应用服务器中它通常不关心所请求的特殊服务(译注此处的意思应该是应用服务器对所有请求都一视同仁)调用服务的机制或多或少的采用了同一种方案这取决于应用的状态呼叫者以及其他参数一些必要的变量但是在一个易于管理的环境中基本的算法通常是一系列操作的顺序链在Java客户端应用中必须处理mask的显示或者form允许用户输入或修改数据
示例问题
在Java应用中mask通常用于数据收集以及在CRUD(create read update delete)周期中处理数据用户可以修改删除或者新增加一些数据跟一个简单的商务问题一样我们需要管理在客户端应用中如何显示mask这样我们把显示从操作链中分离了出来像下面这样
创建mask在这一状态中最好只安排一次
初始化在这一状态数据从文件和数据库等地方找回并填充到mask的字段中
激活这里用户放弃对mask的控制
在现实中涉及到很多方面访问验证控制依赖等等
Phases
在这篇讨论中我提到了每一步操作的phase基本思想非常简单我们把类方法标注成操作链中的phases然后把这些方法的调用交给服务(框架)来做实际上这种方法并不仅限于生命周期管理它可以用做商务流程中所有调用的控制机制
我们使用的 Annotations简单的命名为Phase我们使用它来把一个方法标注成操作链的一部分在下面的代码里你可以看到 Annotations的声明与接口很接近
@Retention(RetentionPolicyRUNTIME)
@Target({ElementTypeMETHOD})
public @interface Phase {
int index();
String displayName() default ;
}
我们简单看一下代码在头两行你看到 Annotations跟另外两个 Annotations一起使用刚看上去时这有点混乱但是这两行很简单的就指定了 AnnotationsPhase只允许并且应该保留到编译后之所以增加这两个 Annotations是因为有些 Annotations可能只会在编译期间被使用并且可能指向类或者成员
@interface是一个 Annotations的标准描述在接下来的代码中index和displayName——不只声明了一个成员还声明了一个方法——也是Java的新语法如果没提供初始值的话 displayName将被赋予了一个空字符串作为初始值同时这个displayName能够被用来作为监测用途叫做progress bar index()是必须的它告诉框架这些phase可以被缺省的执行
像我早先说的那样我们应该把这个逻辑从对象中分离出来所以我们定义了一个必须实现的接口以用于调用管理这个接口可以被一个客户端对象实现为了达到管理的目的我们定义了一个通用的标记接口所有的phaseable接口必须从这里继承这样框架就可以通过一个唯一的访问点来管理类
public interface Phased {
}
这个接口的具体实现会看起来像下面的代码那样这里接口定义了一个mask或者一个form它们包含几个操作这些操作必须像上面的描述那样被定义
public interface PhasedMask extends Phased {
@Phase(index=)
public void construct();
@Phase(index=)
public void initialize();
@Phase(index=displayName=Activating)
public void activate();
}
你可以看到如何使用 Annotations它写在方法声明之前并使用一个介绍性的@sign它的属性index需要提供圆括号请注意因为 Annotations并不是一个Java声明所以结尾不能出现分号现在我们需要一个类来来把这些东西联结起来并且试试我们刚才定义的phase
Phaser
主要处理类也许应该被称为Phaser(喂我们不都挺喜欢星际旅行吗?)它执行全部的phase并且为用户提供简单的监视机制这个类的实现并不包含在这篇文章里当然你可以从资源找到框架代码的下载
一个Phaser拥有一个实现了一些具体的PhasedXxxx接口并且管理phase调用的对象
假设我们有一个像这样的MyMask类
public class MyMask implements PhasedMask {
@Phase(index = )
public void construct() {
// Do the layout
}
@Phase(index = )
public void initialize() {
// Fill the mask with data
}
@Phase(index = )
public void activate() {
// Activate the listeners and allow the user to interact with the mask
}
// Business code
}
现在我们可以像下面那样调用这些PhasedMask方法
Phaser phaser = new Phaser( phasedMask );
phaserinvokeAll();
这样方法construct()initialize()和activate()就都被调用了
那么我们如何控制phase呢?我们跳过构造阶段因为当我们第二次调用phasedMask()时并不需要再布置一次本意是我们不需要construct()被调用多次因为我们把这个方法用索引标注所以我们可以简单的跳过这个索引并且告诉Phaser应该执行哪些phase
Phaser phaser = new Phaser( phasedMask );
phaserinvoke( new int[] {} );
这样就好了不过不够清晰谁会记得phase的索引实际代表什么?幸运的是我们可以像下面这样写得详细一点
Phaser phaser = new Phaser( phasedMask );
phaserinvoke( new String[] {initializeactivate} );
这里我们使用从接口继承来的方法名当然如果需要的话我们可以重新安排phase所以为了交换顺序我们可以这样写
Phaser phaser = new Phaser( phasedMask );
phaserinvoke( new String[] {activateinitialize} );
这个好象没什么意义但是当某个设置中一些phase是紧耦合的时这种做法是有用的
因为我们在这里通过反射来调用方法所以存在很多抛出异常的情况Phaser会捕捉这些异常并包装成所谓的PhaserException所以如果一个方法调用失败(比如是私有的)Phaser的invoke()方法会抛出一个包含着最初异常的PhaseException如果对反射知之不多请看边栏的Notes on Reflection
你也许会给Phaser增加一个PhaseListener来观察里面发生了什么并在漫长的phase调用过程中反馈给用户一些信息
PhaseListener listener = new PhaseListener() {
public void before( PhaseEvent e ) {
// This is called before the Phaser invokes a phase
}
public void after( PhaseEvent e ) {
// This is called after the Phaser has successfully invoked a phase
}
};
Phaser phaser = new Phaser( phasedMask );
phaseraddPhaseListener( listener );
phaserinvoke( new String[] {initializeactivate} );
讨论和总结
在这篇文章中你看到了如何利用 Annotations来管理被分成几个phase的类的生存周期为了使这些类能够被框架组件所管理它们必须简单的实现一个接口这个接口从Phased派生而来并且用Phase Annotations标注了方法管理通过Phaser类来完成这个类能够调用被标注方法并能控制调用的顺序还提供了一种事件处理机制来观察Phaser的工作
这种方法也显示了一种比Javadoc的 Annotations使用更进一步的用法它们不只用于生命周期管理也可以用于常规的对象初始化
实现类不关心它们的方法被调用的顺序如果你在设计中保持这种思想你就可以更灵活的使用这些类
如果phase必须重新排列或者忽略这些行为会发生在实现类中
像任何工具一样它有一些缺点如果接口必须改变或者新接口必须保持向后兼容性以保证源代码完全可用那么实现类必须改变这种方案缺少参数和返回值的支持参数必须在phase调用前被完全提供同样因为大量使用了反射所以会成为一个高性能要求的系统中的瓶颈
最后调用链对IDE来说是不明晰的换句话说[请把你最喜欢的JavaIDE列出来]不可能显示给开发者在编译时什么方法在哪里被调用了
关于作者
Norbert Ehreke是Impetus Unternehmensberatung GmbH旗下一名高级开发者这家咨询公司位于德国的法兰克福他负责框架开发并在PerlJavaC#上都有所涉猎他曾经就学于德国柏林的Technical University (TU)德国马里兰的University of Maryland以及瑞士苏黎世的Eidgenoessische Technische Hochschule (ETH)他从柏林的TU获得了系统工程硕士学位他的兴趣包括面向对象编程(Java Perl C#)面向切面编程(AOP)以及最近的面向语言编程(LOP)在空闲时他喜欢山地自行车烹饪和电影
资源
Javaworld:
Matrix:
下载这篇文章的源代码
/annotations/jwannotationszip
获得Phaser框架(抱歉Javadoc现在只有德文版的我会尽快把文档本地化的)
关于反射的笔记
Java提供了在运行时分析类的能力你可以获取一个类指出它的成员方法以及它实现的借口你也可以找出这些的参数也就是一个方法的参数类型返回类型以及方法和成员的名字并且你可以访问你找到的成员也就是你可以调用方法操作公有成员等等
这是一种非常强大的能力因为我们可以处理编译时我们没引入的类这种Java特性是我在这篇文章中讨论的框架类的基础