每个IDE都允许您管理文件和构建项目。不仅仅局限于此,因为它可以在比大多数IDE所拥有的典型文件中心视图更密切的层次上处理您所编写的代码。作为开发人员,您可以只编写少量代码,而让Eclipse来完成查找、改变、切换、移动、等待这些繁杂的工作,从而获得好处。
有了Eclipse这个工具,您就可以把精力集中在手边真正的任务上:编写能完成预定功能的代码。
从UI开始
首先请注意,本文所涉及的是新的Eclipse3,它在某些方面与较早的2.1版,以及WSAD和RAD不同。
Eclipse看上去与其他大多数的IDE很类似,因为基本的用户界面(UI)已被各地的开发人员所熟悉——除了那些仍在使用Emacs或vi的人,但他们还是可以在Eclipse中找到吸引人的东西,Eclipse对这两个键绑定都是支持的。
基本的Eclipse用户界面如图 1所示。在屏幕的左边,可以看到开发资源列表:源代码、jars,以及组成您的最新开发项目的其他成分。实际上,列表被划分成您当前正在开发的不同项目,它们可以是相互依赖的也可以是完全独立的。
Eclipse将项目数据保存在两个文件中,分别是.project文件和.classpath文件。.project文件保存关于项目中包含哪些文件、如何使用它们、如何构建项目等方面的信息,以及更多的详细信息。任何项目中都有.classpath文件,该文件告诉Eclipse在构建过程中使用什么作为CLASSPATH变量。
所有当前的项目都存在于当前的Workspace中。Eclipse中的Workspace是一个包含了许多文件的目录,可能还包含了项目所需的资源。Eclipse从一个默认Workspace开始;不过,您可以很方便地将它设置为打开多个Workspace。要打开一个不同的Workspace,只需要在启动可执行文件时使用-data标志即可,例如:
eclipse.exe -data E:/shared/workspaces/artemis中间部分是一个源代码查看器,它可以显示当前打开的文件的源代码。注意,它是多重标签的(tabbed),所以您可以一次打开多个文件。右边是当前打开文件的概要。Eclipse列出了类、方法和文件的其他部分。双击一个条目会跳转到相应的位置。最后,在底部,是另一个多重标签的区域,用于传输关于Eclipse所做的、所发现的或者具有问题的事情的信息。注意所有这些下面的标签都是视图,您可以任意拖动,还可以将它们堆叠起来组成堆栈。信息视图是独立的,如果您希望同时看到它们而且您的屏幕足够大,您可以将它们铺开。
视图被分组为透视图,透视图通常是面向环境的。这里有一个用于处理Java项目的Java透视图,一个用于追踪运行时问题的Debug透视图,一个用于管理CVS连接的CVS透视图,等等。实际上,透视图和视图的数目是可扩展的,Eclipse的几乎每个部分都是可扩展的。IDE为增加和扩充现有功能提供了多种方法,这些功能为Eclipse社团的许多人所利用。
Eclipse的所有资源和项目都具有属性。在左边列表中的任意一项上右击,再选择“Properties”,就可以看到更详细的信息。对于文件,您可以看到文件自身的一些基本信息,比如修改日期、位置和权限。对于项目,有更多高级选项,允许您设置Eclipse构建项目的方式。
如果单击“Java Build Path”,您将会看到四个控制构建行为的标签,第一个是“Source”,它允许您设置Eclipse将在哪里查找源文件,还可指定哪些源文件是要用的哪些是可以忽略的。下一个标签是“Projects”,它允许您设置当前项目所依赖的项目,这样,您可以在一个项目中设置一些库或公共代码,并从其他项目简单地包含该项目。
下一个标签是“Libraries”,它允许您将JAR文件、库和类文件夹添加到类路径中。最后,您可以指定该项目将导出哪些资源,这样,该项目的属性就可以应用于导入该项目的其他项目。
Eclipse是在中间大型源视图中进行构建的,所有的编辑也是在那里进行的。但是有一些事情要注意,Eclipse提供的不仅仅是简单的文本视图。首先,文本的左边有一个小槽,Eclipse用它来隐藏传递有用信息的图标。一个红色的“X”会显示在Eclipse不能编译的行的旁边,Eclipse认为可能存在错误的代码旁会有黄色的“warning”标志,如未使用的导入语句(见图1)。
其次,存在问题的代码被加上了适当颜色的下划线。同样,红色下划线代表编译错误,黄色代表警告。最后,滚动条的右边有另一个小槽。颜色在这里又一次被用来传达信息:红色块标识有错误的代码区域,黄色块标识警告代码区域。单击色块会将光标直接移至问题代码。将鼠标悬停在这三个带颜色的条目(图标、下划线或块——图1中显示了这三者)上,可以打开相关问题的描述。右击会出现上下文菜单。
动态构建
使用Eclipse进行Java开发,您首先注意到的就是它的编译是多么智能。进入Preferences(WindowMenu > Preferences)并启用动态构建(Workbench > Build Automatically)。现在,每当您保存文件时,Eclipse将会自动编译该文件以及依赖于该文件的文件。因此,当您想运行您的应用程序时,它基本上已经保存好并且是最新版本。当您处理大型代码基时,这可以节约不少时间。如果您不得不使用其他IDE,那么您首先失去的就是该特性的简单性。
当您使用Eclipse时,您将会一次又一次地感受到这样一个底层原理:IDE了解代码。Eclipse不仅仅是一个能在以文件为中心的上处理代码和其他事情的编译器,它还能够查看源代码并聪明地加以处理。它的智能编译能力通过它能够明白表达在导入语句中的依赖性而得到了证明。
智能搜索
Eclipse具有非常智能的搜索特性(Search Menu > Search),允许您搜索文件中、整个项目中或一个文件组中出现的任何字符串。这个特性很好,但是还在变得更好。Java搜索(Search Menu > Java)允许您将搜索限定为只查找类、方法、类型或其他Java了解的类别。Eclipse可以找到以“set”开头的所有方法实现,您可以轻易地找到您所有的写存取方法。更好的是,您可以在任意方法调用上控件单击(control-click)从而进入它的实现。或控件单击一个类名进入它的实现。另外,您可以在方法上右击找到所有它被使用的地方(References > Project)。Eclipse对代码的了解有助于它导航大型代码基,而您会从中获益。
“TODO”任务链接创建
Eclipse还可以提醒您仍需执行的任务。许多开发人员在代码中添加TODO注释来提醒他们还有特定的任务要完成,或者特定的情况要处理。Eclipse又一次利用它对Java结构的知识找到任何以TODO开头的注释,并将它添加到一个特殊的Tasks视图(打开Window Menu > Show View > Other > Basic > Tasks就可看到)。这样,Eclipse可以提供一个全面的待完成的任务列表。
此外,您还可以配置Eclipse去识别您定义的其他标签。打开Preferences(Window Menu > Preferences)并浏览到“Task Tags”页面(Java > Task Tags)。您就可以添加新标签,删除已有标签,设置不同标签的属性,为代码指定Eclipse提供的默认值。
代码编写
您将会发现Eclipse实际上可以为您编写代码。右击源文件,在“Source”子菜单下,提供了Eclipse可以修改或扩展代码的一些方法,这会去做一些枯燥工作,从而把您解脱出来。其中最有用的代码编写能力是“Generate Getters and Setters”选项,它将会扫描一个类以查找域并为这些域产生适当的存取方法。
如果您选择了它,将会弹出一个窗口,让您指定要为之产生存取方法的域,以及产生什么存取方法(get,set,或两者)。您还可以指定产生的方法的权限(public,private等)。相似地,Eclipse可以基于超类及方法而产生构造函数,以覆盖或封装超类方法。
重构
Eclipse最强大的一个特性就是它对重构的健壮支持。选中一个方法并右击鼠标,您将会看到“Refactor”子菜单。如果您使用该方法来改变一个方法名,Eclipse将找到该方法的所有调用并将这些调用改变为使用新的方法名。您也可以改变方法签名,具体方法是通过添加参数,同时令Eclipse改变所有现有调用来添加那个参数,使用的是您指定的值。当然,您也可以删除您认为不再有用的参数,或重新排列参数使界面更一致。重构也可以工作在类的层次,您可以将内部的类拖至它们自己的文件中,或将类移至其他的包中。
健壮的撤销支持
另一个非常好的特性叫做“Local History”,这是一种极简单的本地CVS存储库。每保存一次文件,Eclipse将会记住这一保存,并允许您在需要的时候退回到该保存。在默认状态下,它会记录过去7天的保存(可以在Window Menu > Preferences > Workbench > Local History中修改这个默认设置)。
当您采用新特性,却破坏了不相干的特性时,撤销机制可以挽回局面。您可以快速地在两个版本间做一个异同比较,看看您都做出了哪些改动,并可以将改动退回到前一个版本,甚至昨天的版本。更好的是,您可以只回退特定的方法,而保留其他方法中的修改。
JUnit集成
Eclipse可以很容易与JUnit框架一起使用。如果您有按JUnit标准编写的类,您可以在左边的Resources视图中选择类并运行测试(Run Menu > Run As > JUnit Test)。Eclipse将会自动找到定义的测试套件并运行其中的所有测试,使用它自己的定制UI来显示测试的运行及发现的错误。常见的JUnit绿色线在测试失败的情况下会变成红色,Eclipse将会为您显示导致出错的栈追踪信息。
让Eclipse来做吧
您看,Eclipse提供了如此多的特性来自动完成一些枯燥繁重的工作,使开发人员的生活更轻松。由于平台的架构,为您提供了很好的机会来找到一个插件帮助您完成特定的任务。如果找不到,您可以用Eclipse来编写它!
你已经看到了,向导是由一个或多个页面组成的。这些页面扩展了WizardPage类,并实现了IWizardPage接口。为了定制单独的页面,你必须了解很多方法。下面是一些重要的方法:
· Constructor:用于实例化页面。
· dispose():重载它用于实现清除代码。
· createControl(Composite parent):重载它来给页面添加控件。
· IWizard getWizard():用于获取父向导对象。对于调用getDialogSettings()是有用处的。
· setTitle(String title):调用它来设置显示在向导标题区域中的字符串。
· setDescription(String description):调用它来提供标题下面显示的文本内容。
· setImageDescriptor(ImageDescriptor image):调用它来提供页面右上方出现的图片(用于代替默认的图片)。
· setMessage(String message):调用它来显示描述字符串下方的消息文本。这些文本是用于警告或提示用户的。
· setErrorMessage(String error):调用它来高亮度显示描述字符串下方的消息文本。它一般意味着向导不能继续,除非错误被修正。
· setPageComplete(boolean complete):如果为true,Next按钮就可视。
· performHelp():重载它来提供内容敏感的帮助信息。当点击Help按钮的时候向导会调用它。
编写向导的代码
有了这些方法之后,我们就能够开发出具有极大的灵活性的向导了。我们现在修改以前建立的Invokatron向导,给它添加一个页面来请求用户输入初始的文档数据。我们还给向导添加了一个图片。新代码是粗体的:
public class InvokatronWizard extends Wizard
implements INewWizard {
private InvokatronWizardPage page;
private InvokatronWizardPage2 page2;
private ISelection selection;
public InvokatronWizard() {
super();
setNeedsProgressMonitor(true);
ImageDescriptor image =AbstractUIPlugin.imageDescriptorFromPlugin("Invokatron", "icons/InvokatronIcon32.GIF");
setDefaultPageImageDescriptor(image);
}
public void init(IWorkbench workbench,IStructuredSelection selection) {
this.selection = selection;
}
在构造函数中,我们打开了进度条,并设置了向导的图片。你可以并保存下面的图片:

请把这个图片保存在Invokatron/icons文件夹之下。为了更容易载入这个图片,我们使用了便捷的AbstractUIPlugin.imageDescriptorFromPlugin()方法。
请注意:你应该知道,尽管这个向导是INewWizard类型的,但是并非所有的向导都是用于建立新文档的。你可以参考其它一些资料来了解如何建立"独立的"向导的信息。
下面是addPages()方法:
public void addPages() {
page=new InvokatronWizardPage(selection);
addPage(page);
page2 = new InvokatronWizardPage2(selection);
addPage(page2);
}
在这个方法中,我们添加了一个新页面(InvokatronWizardPage2),我们在后面编辑它。下面是用户点击向导的"完成"按钮的时候执行的一些方法:
public boolean performFinish() {
//首先把所有的页面数据保存在变量中
final String containerName = page.getContainerName();
final String fileName =page.getFileName();
final InvokatronDocument properties = new InvokatronDocument();
properties.setProperty(InvokatronDocument.PACKAGE,page2.getPackage());
properties.setProperty(InvokatronDocument.SUPERCLASS,page2.getSuperclass());
properties.setProperty(InvokatronDocument.INTERFACES,page2.getInterfaces());
//现在调用完成(finish)方法
IRunnableWithProgress op =new IRunnableWithProgress() {
public void run(IProgressMonitor monitor)
throws InvocationTargetException {
try {
doFinish(containerName, fileName,properties,monitor);
} catch (CoreException e) {
throw new InvocationTargetException(e);
} finally {
monitor.done();
}
}
};
try {
getContainer().run(true, false, op);
} catch (InterruptedException e) {
return false;
} catch (InvocationTargetException e) {
Throwable realException =e.getTargetException();
MessageDialog.openError(getShell(),"Error",realException.getMessage());
return false;
}
return true;
}
为了保存数据,我们必须做一个后台事务。该事务是由向导的容器(工作台)来执行的,并且必须实现IRunnableWithProgress接口,包含(唯一)一个run()方法。传递进来的IProgressMonitor允许我们报告事务的进度。实际的数据保存工作在一个辅助方法(doFinish())中进行:
private void doFinish(String containerName,String fileName, Properties properties,
IProgressMonitor monitor)
throws CoreException {
// 建立一个示例文件
monitor.beginTask("Creating " + fileName, 2);
IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot();
IResource resource = root.findMember(new Path(containerName));
if (!resource.exists() || !(resource instanceof IContainer)) {
throwCoreException("Container \"" + containerName + "\" does not exist.");
}
IContainer container =(IContainer)resource;
final IFile iFile = container.getFile(new Path(fileName));
final File file =iFile.getLocation().toFile();
try {
OutputStream os = new FileOutputStream(file, false);
properties.store(os, null);
os.close();
} catch (IOException e) {
e.printStackTrace();
throwCoreException("Error writing to file " + file.toString());
}
//确保项目已经刷新了,该文件在Eclipse API 之外建立
container.refreshLocal(IResource.DEPTH_INFINITE, monitor);
monitor.worked(1);
monitor.setTaskName("Opening file for editing...");
getShell().getDisplay().asyncExec(new Runnable() {
public void run() {
IWorkbenchPage page =PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage();
try {
IDE.openEditor(page,iFile,true);
} catch (PartInitException e) {
}
}
});
monitor.worked(1);
}
我们还做了很多工作:
· 我们检索了自己希望保存文件的位置(用Eclipse的IFile类)。
· 我们还获取了该File。
· 我们把属性保存到了这个位置。
· 接着我们让Eclipse工作台刷新项目,这样就可以显示该文件了。
· 我们最后调度了一个事务,它在以后执行。这个事务包括在编辑器中打开那个新文件。
· 在整个过程中,我们通过调用IProgressMonitor对象(它是作为参数传递进来的)的方法来提示用户目前的进展情况。
最后一个方法是一个辅助的方法,当该文件保存失败的时候,它在向导中显示错误信息:
private void throwCoreException(String message) throws CoreException {
IStatus status =new Status(IStatus.ERROR,"Invokatron",IStatus.OK,message,null);
throw new CoreException(status);
}
}
向导可以捕获CoreException异常,接着可以把它所包含的Status对象显示给用户看。向导不会被关闭。
下一步,我们编写InvokatronWizardPage2。它的整个类都是全新的:
public class InvokatronWizardPage2 extends WizardPage {
private Text packageText;
private Text superclassText;
private Text interfacesText;
private ISelection selection;
public InvokatronWizardPage2(ISelection selection) {
super("wizardPage2");
setTitle("Invokatron Wizard");
setDescription("This wizard creates a new"+" file with *.invokatron extension.");
this.selection = selection;
}
private void updateStatus(String message) {
setErrorMessage(message);
setPageComplete(message == null);
}
public String getPackage() {
return packageText.getText();
}
public String getSuperclass() {
return superclassText.getText();
}
public String getInterfaces() {
return interfacesText.getText();
}
上面的构造函数设置了页面的标题(在标题栏下方高亮度显示)和描述(在页面标题的下方显示)。我们还有一些辅助方法。 updateStatus处理页面特定的错误信息的显示。如果没有错误信息,就意味着页面完成了;因此,"下一步"按钮就可以使用了。还有数据字段内容的getter(获取)方法。下面是createControl()方法,它建立了页面的所有可视化组件:
public void createControl(Composite parent) {
Composite controls =new Composite(parent, SWT.NULL);
GridLayout layout = new GridLayout();
controls.setLayout(layout);
layout.numColumns = 3;
layout.verticalSpacing = 9;
Label label =new Label(controls, SWT.NULL);
label.setText("&Package:");
packageText = new Text(controls,SWT.BORDER | SWT.SINGLE);
GridData gd = new GridData(GridData.FILL_HORIZONTAL);
packageText.setLayoutData(gd);
packageText.addModifyListener(
new ModifyListener() {
public void modifyText(ModifyEvent e) {
dialogChanged();
}
});
label = new Label(controls, SWT.NULL);
label.setText("Blank = default package");
label = new Label(controls, SWT.NULL);
label.setText("&Superclass:");
superclassText = new Text(controls,SWT.BORDER | SWT.SINGLE);
gd = new GridData(GridData.FILL_HORIZONTAL);
superclassText.setLayoutData(gd);
superclassText.addModifyListener(new ModifyListener() {
public void modifyText(ModifyEvent e) {
dialogChanged();
}
});
label = new Label(controls, SWT.NULL);
label.setText("Blank = Object");
label = new Label(controls, SWT.NULL);
label.setText("&Interfaces:");
interfacesText = new Text(controls,SWT.BORDER | SWT.SINGLE);
gd = new GridData(GridData.FILL_HORIZONTAL);
interfacesText.setLayoutData(gd);
interfacesText.addModifyListener(
new ModifyListener() {
public void modifyText(ModifyEvent e) {
dialogChanged();
}
});
label = new Label(controls, SWT.NULL);
label.setText("Separated by ','");
dialogChanged();
setControl(controls);
}
为了编写这段代码,你必须了解SWT(请你自己查看一些这方面的资料)。基本上,这个方法建立了标签和字段,并把它们放置到网格布局上。字段发生改变的时候,就调用dialogChanged()来验证它的数据:
private void dialogChanged() {
String aPackage = getPackage();
String aSuperclass = getSuperclass();
String interfaces = getInterfaces();
String status = new PackageValidator().isValid(aPackage);
if(status != null) {updateStatus(status);
return;
}
status = new SuperclassValidator().isValid(aSuperclass);
if(status != null) {updateStatus(status);
return;
}
status = new InterfacesValidator().isValid(interfaces);
if(status != null) {updateStatus(status);
return;
}
updateStatus(null);
}
}
这个工作是在三个工具类--PackageValidator、SuperclassValidator和 InterfacesValidator的帮助下完成的。接下来我们编写这些类。
验证可以在插件的用户输入数据的任何部分中进行。因此,把验证代码放入可重复使用的类中是有意义的,这样就不用把它复制到多个位置。下面是一个验证类的例子。
public class InterfacesValidator implements ICellEditorValidator
{
public String isValid(Object value)
{
if( !( value instanceof String) )
return null;
String interfaces = ((String)value).trim();
if( interfaces.equals(""))
return null;
String[] interfaceArray = interfaces.split(",");
for (int i = 0; i < interfaceArray.length; i++)
{
IStatus status = Conventions.validateJavaTypeName(interfaceArray[i]);
if (status.getCode() != IStatus.OK)
return "Validation of interface " + interfaceArray[i] + ": " + status.getMessage();
}
return null;
}
}
其它的验证类与它非常类似。
类库中的另外一个极好的类是JavaConventions,它为我们验证数据!它包含了很多验证方法,例如:
· validateJavaTypeName() 检查类和接口的名称。
· validatePackageName() 检查程序包的名称。
· validateFieldName() 检查数据成员的名称。
· validateMethodName() 检查方法的名称。
· validateIdentifierName() 检查变量的名称。
现在我们不需要ICellEditorValidator接口,但是在以后的文章中,我们是需要它的。
结果
到目前为止,我们拥有了一个可以工作的向导,它拥有一张图片和两个页面,第二个页面建立了原来的Invokatron文档。图2显示了结果:

图2:定制的向导
闪亮的发明
我们可以看到,通常是数据应用程序的。外表(Presentation)也是很重要的。丑陋的发明难以出售,但是闪亮的发明可能容易出售。但是数据是我们这些程序员实现的非常本质的东西。
在本文中,我们首先决定了自己将处理的数据。然后,我们以定制向导的方式来获取这些数据。下一篇文章将继续讲解显示的问题,包括定制的编辑器和属性页面。
配置
重新启动后,关闭安装/更新透视图。打开一个使用的工程.如果你已经完成了Developer's Notebook,一书中的例子,那么就有几个目录可供你选择,这里以书中第三章中的例子来说明。第三章是可以在线免费获得的样章,你还可以从该书的站点下载所有例子的源代码。
如果你打算使用其中的一个例子来新建一个Eclipse工程,选择文件(File) ->新建( New )-> 工程(Project),选定工程类型,然后单击下一步(Next),填入该工程名(我填的是"Hibernate Ch3",如图11所示),不要复选使用缺省检查框(Use default),这样你可以告诉Eclipse从哪里找到已经存在的工程目录,单击浏览按钮(Browse)来定位目录。选定工程目录后可以单击完成(Finish)来创建工程。不过,一般情况下我喜欢单击下一步(Next)来复查Eclipse为此工程所作的设置(当然,如果发现有些配置不对,总是可以选择回退来修改这些设置。不过,我总是发现,如果有一个库文件丢失或是其它一些原因,会有非常多的错误和警告信息)。

图 11. 创建一个需要使用Hibernate的新工程
在当前情况下,我的谨慎有点多余。Eclipse准确的算出了目录是如何组织以及是用来干什么的,找到我为使用Hibernate和 HSQLDB 而的第三方库(下载和安装的详细过程可以参看书中第一章)。如此聪明的适应能力是Eclipse优点之一。图12显示新工程已经打开,准备好可以用来做实验。从这个图中也可以推断Eclipse不喜欢调整窗口大小使其小到形成合适的屏幕布局。从现在开始,显示的屏幕截图只显示窗口的一部分,而不是完整的窗口。

图 12. 使用Chapter 3例子的工程
下一个需要做的工作是创建一个Hibernate配置文件,提供给Hibernate Synchronizer使用。在src目录中已经有了一个hibernate.properties文件,这是书中例子使用的配置。这里有个问题,坏消息是Hibernate Synchronizer只能使用样式的Hibernate配置文件。这样,就需要把hibernate.properties中的内容移植到XML样式的配置文件hibernate.cfg.xml中。好消息是,这正是Hibernate Synchronizer创建配置文件向导第一次大显身手的时候。选择文件(File) ->新建(New) -> 其它(Other),然后在弹出对话框选取刚可用的Hibernate类,选取 Hibernate Configuration File,然后单击下一步(Next).

图13 打开Hibernate配置文件向导
打开向导时,保存文件的位置和在Eclipse中现在选择的文件有关。请确保把该文件保存在src 目录中。添加其余一些向导需要的信息,这些信息应该和配置文件的版本相一致,如图14中所示。值得注意的是,和用Ant来控制Hibernate的运行(书中使用就是这种方法)不同,这里你无法控制Hibernate运行时的当前工作目录,因此你需要在URL文件中使用路径的全称。我自己的添加的URL值(有点难看)为
:hsqldb:/Users/jim/Documents/Work/OReilly/Hibernate/Examples/ch03/data/music.
(如果有人知道怎么让Eclipse或是Hibernate Synchronizer使用一个工程特定的目录,你可以告诉我,我很想知道。因为我才开始使用Eclipse,是个新手。如果有人告诉我这是可能的,只是因为我不知道怎么做而已,我一点也不会感到吃惊)

图14 添加配置文件信息
添加Driver Class 的方法有点奇怪,你需要单击Browse按钮,然后开始输入driver的类名(译者注:你需要确定该driver类在该工程的类路径中)。如果你输入"jdbcD",窗口就会出现这个选择,很容易就可以从中选取一个。具体如图15所示。

图15 指定HSQLDB的driver类
只要添加如图14中那些属性值就可以。完成后单击Finish来完成创建配置文件。Hibernate Synchronizer 现在已经可以开始使用了。完成创建文件后,配置文件会打开,这时候你就可以看看Hibernate XML格式的配置文件的结构和细节。

图16 生成的配置文件
要想测试配置文件是否可用,有一个又快又简单的方法:使用向导来创建一个影射文件。选择文件(File) -> 新建(New) -> 其它(Other),选取Hibernate类别,然后再选Hibernate 影射文件,单击下一步(Next)。向导出现的时候,其中有些属性已经自动填入了在配置文件中相应属性的值,单击Refresh(确保可以通过这些信息和你的数据库相连)。和数据库连接后,会显示库中的表,这里只有一个TRACK表。第一次使用的时候,不知什么原因,需要你指定包含HSQLDB的.jar文件的路径。好在你只需要指定一次。只要你认为工作正常(译者注:显示了数据库中有权限访问的表),单击Cancel。试验中使用已有的影射文件,不需要实际创建一个。
生成代码
这可能是你一直在等待的部分。我们能用这个插件来做什么?好,马上就开始。为影射文档提供一个新的菜单条目。
右击(如果是单键鼠标,在按住Control键的同时点鼠标键)一个影射文档,菜单条目中会显示几个和Hibernate相关的选择(如图17所示),其中有一个和synchronize有关,这是一个手工方法,可以让Hibernate Synchronizer产生和该影射文档相对应的数据访问对象。

图17 Synchronizer插件为影射文档提供的几个菜单项
Add Mapping Reference 选项也很有用,当你单击该项时,会把相应的影射文件增加到Hibernate配置文件中,表明该文件是影射文档,因此你不需要在源代码中增加任何信息要求相应的影射文件进行设置。现在让我们看看选取Synchronize Files后的结果。
事情开始变得有趣,出现了两个子包,一个是“base”的DAO,Hibernate Synchronizer所有,可以在任何时候重写,一个是继承那些DAO类的商业对象,不会被覆盖,也就给了我们一个机会,可以在其中加入商业逻辑(具体如图18中所示)。

图18 同步后的DAO,图中显示的是我们可以编辑的子类
和Hibernate的代码生成工具相比,用该插件生成了更多的类。这是优点,也可能是一些潜在的缺点,将在Trade-Offs 部分进行讨论。你可以在工程配置文件中选取要生成的类和它们所在的包的结构。我可以证明这点,但现在的发行版有个bug ,,无法访问Mac OS X上的配置界面。针对该bug的一个补丁已经做好了,但仍没有发布。
基于Hibernate Synchronizer网页上的例子,和以下这个类一起,用那些新的数据访问对象来试着把一些数据放入中。看起来和标准的Hibernate代码生成工具生成的版本(在Hibernate: A Developer's Notebook一书的39-40页)很相似,甚至更简单一些。因为Hibernate Synchronizer生成的类为你的每个数据库操作都创建和提交一个新事务,因此在与此类似的简单情况下,你不需要自己来设置事务(当然,如果你需要把一组操作作为一个单独事务,有很多方法可以做到这点)这里是新版本的代码。
package com.oreilly.hh;
import .sql.Time;
import java.util.Date;
import net.sf.hibernate.HibernateException;
import com.oreilly.hh.dao.TrackDAO;
import com.oreilly.hh.dao._RootDAO;
/**
* Try creating some data using the Hibernate Synchronizer approach.
*/
public class CreateTest2 {
public static void main(String[] args) throws HibernateException {
// Load the configuration file
_RootDAO.initialize();
// Create some sample data
TrackDAO dao = new TrackDAO();
Track track = new Track("Russian Trance", "vol2/album610/track02.",
Time.valueOf("00:03:30"), new Date(), (short)0);
dao.save(track);
track = new Track("Video Killed the Radio Star",
"vol2/album611/track12.mp3", Time.valueOf("00:03:49"), new Date(),
(short)0);
dao.save(track);
// We don't even need a track variable, of course:
dao.save(new Track("Gravity's Angel", "/vol2/album175/track03.mp3",
Time.valueOf("00:06:06"), new Date(), (short)0));
}
}
当我写这个的时候,有在手边真是太好了 ,我已经忘了当写书中例子的时候多么想念智能代码完成功能,有另外几件事情JDT也发挥了作用。
为了在Eclipse中运行这个简单的程序,需要设置一个新的运行配置。用CreateTest2.java作为当前文件,选择运行(Run )-> 运行...(Run...)。然后单击新建(New),因为该类有一个main() 方法,Eclipse推断出要运行该工程的当前类。Eclipse为新的运行配置取的名字,CreateTest2,很合适。屏幕窗口看起来如图19中所示,单击运行来在数据库中创建一些数据。

图19 准备在Eclipse中运行创建数据的测试程序
如果你确实按照上边说的来做,你会发现第一次的尝试运行失败。Hibernate抱怨配置文件中连一个映射文件都没有参考,为了运行程序,至少需要一个这样的文件。这也是为什么Buddy在图16底部用黄色下划线发出警告。可以很容易修改该错误,你只要在包资源浏览器(Package Explorer)中的Track.hbm.xml这个影射文档上单击右键,在Hibernate Synchronizer子菜单中选取Add Mapping Reference(如图17中所示),这样XMLBuddy就不会再抱怨XML文件有错误,程序也可以继续向前。不幸的是,没有向前推进我们所愿的那样远,下一个问题又出来了。Eclipse中显示的下一个错误是“不能在JNDI中找到JTA UserTransaction initial context”。不止我一个人犯这种错误,因为在a forum thread中有这样的讨论,而且到目前为止仍然没有找到一个解决方法。
既然我知道不需要使用JTA,我倒是很想知道为什么Hibernate竟然会使用JTA?打开Hibernate配置文件,如图16所示,看看是不是Hibernate Synchronizer在其中加入了可疑的内容。看了配置文件后,可以确定,有一些行看起来是罪魁祸首:
<property name="hibernate.transaction.factory_class">
net.sf.hibernate.transaction.JTATransactionFactory
</property>
<property name="jta.UserTransaction">
java:comp/UserTransaction
</property>
一旦把那些行变成注释后,再次运行程序。这次,也就是第三次运行成功。我在自己计算机上运行没有一点错误,数据已经保存到数据库中。运行 ant db 这个target(在Developer's Notebook一书的第一章有相应的解释)可以把表中所有的数据显示出来(不可否认,这也许有点简单),如图20中所示。如果你跟着这篇文章中顺序来做的,而不是跟着书中步骤一步一步来的,你需要先运行ant schema来创建数据库中的表,或是删除以前试验留下的数据。

图20 在Eclipse中运行Ant
你可以在Eclipse内运行Ant的target,方法是用右键单击包资源浏览器(Package Explorer)中的build.xml 文件,选择菜单中的运行Ant(Run Ant),然后在弹出对话框中选择你要运行的target,如图21所示。这个功能很cool。

图21 在Eclipse中运行Ant
查询数据相当简单、直白,即使Hibernate Synchronizer产生了很多辅助方法来使用指定查询,我认为这些没有什么用处,都是运行查询,然后返回包含结果的列表,而不是返回一个Query对象,让你直接使用该对象。这使你不能使用任何Query提供的、方便的、类型安全(type-safe)的参数设置方法,因为这个,我打算让_RootDAO对象提供一个Session对象,可以用“老式”的方法来使用Hibernate。公平来说,我认为如果编辑Hibernate Synchronizer 用来生成代码所使用的模板,就可以生成想要的任何方法,如果有一个项目,要用到该插件,可以肯定我会试着这么做。
实际上,进一步考虑,当你得到一个活动的Session时,你只能使用Query,而这些DAO对象已经提供为相应功能最佳的实现。如果你和我在例子中使用查询的方法一样,那就需要你自己来实现session管理。你能够把session管理内嵌于你自己所写的那一半DAO中,这样可以给你提供两方面的好处。(译者注:和有base的java POJO对象一样,对于DAO,该插件也生成一对类,一个base DAO给该插件用,一个是继承该base DAO的自定义DAO,你可以在其中添加商业逻辑)。这也是Hibernate Synchronizer把类分隔开来如此有用的另一个原因。对该插件的远见在下边做了一点研究
不管怎么说,下边是我第一次使用的代码,和书中48-49页上的代码功能基本一致:
package com.oreilly.hh;
import java.sql.Time;
import java.util.ListIterator;
import net.sf.hibernate.HibernateException;
import net.sf.hibernate.Query;
import net.sf.hibernate.Session;
import com.oreilly.hh.dao.TrackDAO;
import com.oreilly.hh.dao._RootDAO;
/**
* Use Hibernate Synchronizer's DAOs to run a query
*/
public class QueryTest3 {
public static void main(String[] args) throws HibernateException {
// Load the configuration file and get a session
_RootDAO.initialize();
Session session = _RootDAO.createSession();
try {
// Print the tracks that will fit in five minutes
Query query = session.getNamedQuery(
TrackDAO.QUERY_COM_OREILLY_HH_TRACKS_NO_LONGER_THAN);
query.setTime("length", Time.valueOf("00:05:00"));
for (ListIterator iter = query.list().listIterator() ;
iter.hasNext() ; ) {
Track aTrack = (Track)iter.next();
System.out.println("Track: \"" + aTrack.getTitle() +
"\", " + aTrack.getPlayTime());
}
} finally {
// No matter what, close the session
session.close();
}
}
}
TrackDAO为我们提供了一个很好的功能:静态常数,使用这个功能,可以用来进行指定查询(named query),这就消除了由于输入问题而导致运行时错误的任何机会。我欣赏这个功能。为该测试类设定运行配置,然后运行,输出结果正和我想的一样,如图22所示。

图22 Eclipse控制台窗口显示的查询结果
如上所述,这个类运行后,借助于Hibernate Synchronizer提供的模型,我想到有一个更好的方法可以实现这个功能。把查询放到TrackDAO中去,这里才是查询方法真正属于的地方,指定查询(named query)是和该DAO关联的映射文件的一个功能。
package com.oreilly.hh.dao;
import java.sql.Time;
import java.util.List;
import net.sf.hibernate.HibernateException;
import net.sf.hibernate.Query;
import net.sf.hibernate.Session;
import com.oreilly.hh.base.BaseTrackDAO;
/**
* This class has been automatically generated by Hibernate Synchronizer.
* For more information or documentation, visit The Hibernate Synchronizer page
* at http://www.binamics.com/hibernatesync or contact Joe Hudson at joe@binamics.com.
*
* This is the object class that relates to the TRACK table.
* Any customizations belong here.
*/
public class TrackDAO extends BaseTrackDAO {
// Return the tracks that fit within a particular length of time
public static List getTracksNoLongerThan(Time time)
throws HibernateException
{
Session session = _RootDAO.createSession();
try {
// Print the tracks that will fit in five minutes
Query query = session.getNamedQuery(
QUERY_COM_OREILLY_HH_TRACKS_NO_LONGER_THAN);
query.setTime("length", time);
return query.list();
} finally {
// No matter what, close the session
session.close();
}
}
}
以上代码,看起来更好(nice)、更为清晰(clean),QueryTest3中的main()方法更是得到了大大简化
public static void main(String[] args) throws HibernateException {
// Load the configuration file and get a session
_RootDAO.initialize();
// Print the tracks that fit in five minutes
List tracks = TrackDAO.getTracksNoLongerThan(Time.valueOf("00:05:00"));
for (ListIterator iter = tracks.listIterator() ;
iter.hasNext() ; ) {
Track aTrack = (Track)iter.next();
System.out.println("Track: \"" + aTrack.getTitle() +
"\", " + aTrack.getPlayTime());
}
}很清楚,这是在Hibernate Synchronizer中用到指定查询(named query)时所应采取的方法。很快测试一下就可以证实以上的代码输出同样的结果,而且这里的代码更好。
编辑映射文件
Synchronizer一个主要 引人之处是为映射文件提供的有专业水平的编辑器,你可以配置该编辑器,这样当你保存文件的时候,可以自动重新生成相应的数据对象,.这只是你最后才会用到的功能。即使不使用该插件的代码生成器,可能你还是会用这个编辑器。当你编辑影射文档时,它可以为映射文档中的元素提供智能完成功能,还有一个你可以操作的映射文档的大纲视图。
如果你从Developer's Notebook的源代码,然后想用该插件的映射文档编辑器来编辑该文件,需要耍一个小花招。在下载文件中,影射文档的扩展名是".hbm.",而该插件仅仅对以"hbm"为扩展名的文件才调用影射文档编辑器。理论上,你可以在中配置扩展名映射,以便两个扩展名都可以用该插件的编辑器,不过,我没有成功过,我在支持论坛上看到别人有同样的问题。因此,暂时来看,最好的办法就是重命名文件(如果用Ant来生成代码,确保修改build.xml文件的codegen这个target,使其也使用新扩展名)。
当我把Track.hbm.xml改名为Track.hbm时,包资源浏览器中该文件的图标更新为象Hibernate的logo。该文件的默认编辑器变为该插件的影射文档编辑器,如图23中所示。不知道什么原因,对这两个扩展名的文件,其它的Hibernate Synchronizer选项都可用,令人奇怪的是,只有“hbm”结尾的文件可以用其编辑器。

图23 Hibernate影射文档(扩展名为".hbm")的上下文菜单
编辑器对于你要在影射文档中增加的所有元素提供了上下文敏感的自动完成功能,图24举了两个例子。虽然如此,没有一个屏幕抓图能够真正显示如此功能的细节和有用之处。我鼓励你自己安装该插件,然后自己来试试这个编辑器.你很快就会发现当使用影射文档的时候,这个编辑器是多么有帮助。


图24,25 影射文档编辑器中的自动完成功能
大纲视图,象图26中所示,可以用图形的方式显示类的结构,被影射的元素,指定查询和其它一些出现在影射文档中内容,同时也提供了几个向导,帮助你创建新项


图26,27 影射编辑器的大纲视图以及“Add property”向导
编辑器内的上下文菜单中有一项是Format Source Code,你可以用来对文档进行清洁和改变文档结构。编辑器内也有很多灵巧和有用的功能,看看它如何“成长”是一件有趣的事情。对我来说,唯一的不满是当你完成XML属性的时候,该编辑器用非常不同于JDT在代码中使用的方法来帮助你管理引号,在它们之间切换有时令人迷失(JDT采用的方法可能只适于它自己,但一旦你信任它,这个方法看起来就有点魔力)
产生中的表
和我的第一印象-一切都可以通过影射文档得到-不同, Synchronizer现在没有为创建或更新数据库提供任何支持。支持论坛上已经张贴了一个这样的功能要求,如果我们将来看到这些功能,我不会感到惊奇。这种功能应该不是很难。暂时,你不得不采用其他方法,如果你想从影射文档生成数据库,你可以象Hibernate: A Developer's Notebook 一书中使用Ant一样来做到。下边描述的Hibernator插件支持在中更新数据库。或许,我应该研究一下是否能够同时安装这两个插件。
好了,我当然希望这个简单介绍的指南能够让你对这个插件的功能有一个大致的了解,当然,我没有提及它的所有功能。如果文中有些内容激起了你的兴趣,那就,安装,自己试试。
Trade-Offs
很清楚,你可以用Hibernate Synchronizer来做灵巧的事情。我会在我自己的Hibernate项目中使用该插件吗?这个想法有其它一些优缺点需要考虑,可能现在还不是做决定的时候,直到需要用Hibernate来取代自家酿(当然非常简单)的、已经在工作的轻量级O/R工具时才能做出决定。这是个足够重要的改变,我们一直推迟做出决定,直到有其它原因出现。下边的因素在我的决定中占有重要分量。
在安装部分已经提及,有几个涉及到许可证的问题,该插件的论坛对这个也有些讨论。现在所采用的许可证是作了适合自己的修改后的GNU GPL,删除了关于源代码共享的规定,保留了"copyleft"保护的其它方面。关于这个的合法性有些问题,作者正在找另外一个可用来替代的许可证。它的确切意图是保护该插件,不妨碍使用该插件生成代码的其它一些项目。不过还是值得仔细读一下现在的许可证,看看你是否相信该许可证已经达到其本来意图。否则,对你来说,会有很多风险
同样的讨论显示,作者本来想把该插件作为开源软件,但临时改变了主意,因为他觉得该插件还没有“琢磨”到足够给其他人以作为一个优秀的开源软件的程度。此后,他通过电子邮件和一些性急的人进行了交流,这些人的电子邮件非常讨厌,最终使他没有兴趣再分享整个源代码,真是令人感到悲哀。当然,和我们分享什么是他的权力。对于世界来说,这个插件是个礼物,作者不欠我们什么。但我希望其它用户的积极影响或许可以帮助说服他重新实行原来的计划-分享源代码。我真正重视可以得到源代码的工具,不仅是因为这是个很好的学习机会,而且意味着有了源代码,如果有需要,我(或其他人)可以马上修改出现的一些小问题。到目前为止,该插件的作者一直非常积极的回应用户的问题,但是没有人能够一个人做的象一个团队一样好,我们有些时候很忙,筋疲力尽,或是心情烦乱
Hibernate Synchronizer用它自己的模版和一套机制来生成你的数据访问类,这个事情有好的一面,也有坏的一面。好的一面在于它为你提供了比标准的Hibernate代码生成工具更多的功能。在自动产生的你所定义的数据对象的子类中嵌入商业逻辑,而不用害怕重新生成代码的时候有关商业逻辑的代码被覆盖,这是一个很大的额外好处。该插件生成的、使许多简单的类更简单的类提供了其它一些优秀的功能。
另一方面,这并不意味着当这个平台添加新功能或是有其它一些变化的时候,Hibernate Synchronizer生成的代码会滞后。这个插件的代码也很有可能在支持Hibernate很少用到的方式方面存在一些bug,因为使用该插件的用户群很小,仅有一个人对其进行更新,你可以从论坛中看到这种现象的证据。
和许多事情一样,由你决定是否潜在好处胜过风险。即使你没有使用代码生成器,或许你会发现影射文档编辑器极端有用。如果你只是使用编辑器的自动完成和协助功能,你可以关闭automatic synchronization。
如果你真的采用了这个插件,而且发现它很有用,毫无疑问,我鼓励你和作者联系,表达你的谢意,如果可能,可以考虑捐些钱以支持该插件继续开发。
其它一些插件
在我搜寻插件的过程中,我发现了其它两个插件,可以为在Eclipse中使用Hibernate提供支持(如果你知道其它一些插件,或是在将来的某一天偶遇其它的插件,我很有兴趣了解这些插件)。也许将来我会写一些关于这些插件的文章。
HiberClipse
HiberClipse看起来是另一个非常有用的工具,该插件采取的是数据库的工作流方法:你已经有了数据库,想用该插件构造Hibernate映射文件和类文件。这种事很常见,如果你面对这样的任务,我肯定会推荐你使用该插件。该插件提供了一个很cool的功能 :在Eclipse中提供给你一个正在使用的数据库的图形化显示的“关系视图”(应该指出,当你从一个现存的数据库开始工作时候,Hibernate Synchronizer也没有使你处于孤立无援的境地,New Mapping File Wizard 可以连接数据库,然后根据它找到的内容生成映射文件,如图28所示)

图28 Hibernate Synchronizer的创建映射文件向导
Hibernator
最后,Hibernator看起来向另一个方向倾斜,从你的Java代码来产生简单的Hibernate映射文档,然后可以由此创建(或是更新)数据库。另外,还提供了在Eclipse中进行数据库查询的能力。在这三个插件中,它处于开发的最早期,但是已经值得你时常关注,特别是该插件援引Hibernate开发小组的成员作为贡献者。
了解更多
如果这篇文章激起了你的兴趣,有很多资源可以让你深入了解那些主题,除了文中所连接的站点以外,有一些也许你会感兴趣。当然,包括我自己的书Hibernate: A Developer's Notebook。对于更有深度的参考资料,在线文档非常有帮助,特别是其中的参考手册,还有一本Hibernate开发人员自己写的Hibernate in Action即将发行,我自己也很期待读这本书。
察看原文


