3.8 插件开发常见的问题
3.8.1 InvocationTargetException异常的处理
InvocationTargetException是一种包装由调用方法或构造方法所抛出异常的受查异常。这个异常并不是Eclipse插件开发特有的,而是标准JDK中的,它定义在java.lang.reflect包下。在进行Java开发的时候很少会接触到这个异常,不过在进行Eclipse插件开发中则不同,很多API都声明抛出此类异常,因此必须对此异常进行处理。
例如,我们开发一个方法用来统一处理异常:
private static void handleException(Exception e)
{
MessageDialog.openError(Activator.getDefault().getWorkbench()
.getDisplay().getActiveShell(), "error", e.getMessage());
e.printStackTrace();
}
我们发现当传递来的参数e为InvocationTargetException 的时候弹出的对话框中的消息是空的,查看InvocationTargetException 的源码得知InvocationTargetException 并没有覆盖getMessage方法,所以消息当然是空的了。我们需要调用InvocationTargetException 的getTargetException方法得到要被包装的异常,这个异常才是真正我们需要的异常。修改代码如下所示:
private static void handleException(Exception e)
{
String msg = null;
if (e instanceof InvocationTargetException)
{
Throwable targetEx = ((InvocationTargetException) e)
.getTargetException();
if (targetEx != null)
{
msg = targetEx.getMessage();
}
} else
{
msg = e.getMessage();
}
MessageDialog.openError(Activator.getDefault().getWorkbench()
.getDisplay().getActiveShell(), "error", msg);
e.printStackTrace();
}
3.8.2 Adaptable与Extension Object/Interface模式
Java是强类型的静态语言,这种特性在给我们的系统带来稳定性的同时也给我们系统的灵活性带来了问题,而如果Java能吸取动态语言的一些特性,则能够使得系统架构变得更加灵活。下面举一个例子。
由于某种需要,我们编写了一个CowNewFile类来表示磁盘文件,代码如下:
public class CowNewFile
{
private String file;
public CowNewFile(String file)
{
super();
this.file = file;
}
public String getFile()
{
return file;
}
}
当使用此类与Java IO类库交互的时候出现了问题,因为Java IO只能接受java.io.File类型的文件,因此为CowNewFile类增加一个方法toJavaFile,用这个方法来将CowNewFile转化成java.io.File类型;当我们使用此类与Java的Net类库交互的时候又出现问题了,因为Java Net只接受URL类型的格式,因此为CowNewFile类增加一个方法toURL,用这个方法来将CowNewFile转化成java.net.URL类型;我们想使用一个能自动显示类的属性的第三方开源类,不过这个类要求类必须实现它的IPropertyDescription接口,因此又让类实现了IPropertyDescription接口;我们想使用一个另外项目组同事开发的一个接口来进行值校验,不过这个接口要类实现IValidator接口,因此我们又让类实现了IValidator接口……。
随着系统的进化CowNewFile增加了很多toJavaFile这样的方法,实现了很多IPropertyDescription这样的接口,代码变得臃肿无比,还可能出现接口的命名冲突、实现冲突等一系列的问题。
面向对象思想中有一条重要原则,那就是单一职责原则(Single Responsibility Principle,SRP)。就一个类而言,应该仅有一个引起其变化的原因。大致的意思就是要把职责分配到不同的对象当中去。如果我们把每一个职责的变化看成是变化的一个轴线,当需求变化时,该变化会反映为类的职责的变化。如果一个类承担了多于一个的职责,那么引起它变化的原因就多于一个轴线,也就是把这些职责耦合到了一起,当变化发生时,设计会遭受到意想不到的破坏。
在上边这个例子中的CowNewFile类承担了过多的责任:要能把自己转化成File、URL等与自己没有直接关系的对象,要让自己有显示类属性的能力、自我校验的能力等。该是对其动刀子的时候了。
(1) 定义接口IAdaptable:
public interface IAdaptable
{
public Object getAdapter(Class adapter);
}
(2) 定义类CowNewFilePropertyDescription,实现IPropertyDescription接口:
class CowNewFilePropertyDescription implements IPropertyDescription
{
private CowNewFile file;
public CowNewFilePropertyDescription(CowNewFile file)
{
super();
this.file = file;
}
public String[] getPropertyValues()
{
return new String[] { "file=" + file };
}
}
(3) 定义类CowNewFileValidator,实现IValidator接口:
class CowNewFileValidator implements IValidator
{
private CowNewFile cnFile;
public CowNewFileValidator(CowNewFile file)
{
super();
this.cnFile = file;
}
public boolean isValidate()
{
String fileStr = cnFile.getFile();
if (fileStr == null || fileStr.trim().length() <= 0)
{
return false;
}
File file = new File(fileStr);
return file.exists();
}
}
(4) 修改CowNewFile类:
public class CowNewFile implements IAdaptable
{
private String file;
public CowNewFile(String file)
{
super();
this.file = file;
}
public String getFile()
{
return file;
}
public Object getAdapter(Class adapter)
{
if (adapter.equals(File.class))
{
return new File(file);
} else if(adapter.equals(URL.class))
{
try
{
return new File(file).toURL();
} catch (MalformedURLException e)
{
throw new IllegalArgumentException(e);
}
} else if (adapter.equals(IPropertyDescription.class))
{
return new CowNewFilePropertyDescription(this);
} else if (adapter.equals(IValidator.class))
{
return new CowNewFileValidator(this);
}
return null;
}
}
(5) 编写测试代码:
CowNewFile cnFile = new CowNewFile("c:/test.txt");
File file = (File) cnFile.getAdapter(File.class);
try
{
InputStream inStream = new FileInputStream(file);
} catch (FileNotFoundException e)
{
e.printStackTrace();
}
URL url = (URL) cnFile.getAdapter(URL.class);
System.out.println(url.getProtocol());
IPropertyDescription propDesc = (IPropertyDescription) cnFile
.getAdapter(IPropertyDescription.class);
ObjectPropertyDispalyer.showObject(propDesc);
IValidator validator = (IValidator) cnFile.getAdapter(IValidator.class);
DataValidator.validate(validator);
可以看到整个改造的核心就是IAdaptable接口,这个接口把从CowNewFile拆分出去的职责类适配成CowNewFile。我们交互的时候还是和CowNewFile类交互,不用意识到其他类的存在,直接向它索取IPropertyDescription接口等接口或者类即可,CowNewFile再把这些请求转发给合适的类。
Eclipse中的类非常繁多,经常需要向一个类请求各种服务,因此Eclipse中就大量的使用了Extension Object/Interface模式,很多类都直接或间接地实现了IAdaptable接口(定义在org.eclipse.core.runtime包下),这在后边的程序中将会有相应的代码实例。
看到这里您已经明白为什么在“设定构建路径实战”一节中我们要设定“adaptable=true”了,因为也许我们选择的对象没有实现IJavaProject接口,但是它可以通过getAdapter来适配成IJavaProject,那么我们的菜单也应该可以使用。从这一点可以看出我们的代码写得是有错误的,因为直接把选择的对象转换成了IJavaProject,正确的方式应该是首先判断它是不是IJavaProject的实例,如果不是的话再判断能否通过getAdapter适配成IJavaProject,这一点工作就留给读者去完成吧。
3.8.3 千万不要使用internal包
Eclipse源码中有很多包名中含有internal的类(比如org.eclipse.ui.internal.EditorSite),这些类是Eclipse的内部实现,不是向外提供的API,Eclipse不保证在后续版本中不对这些类进行修改。这些类都通过公共的API向外提供调用它的接口,完全没有必要调用它们,很多Eclipse插件在新版本Eclipse中无法运行的原因大部分都是因为这些插件使用了internal包中的类,而这些类在新版本没有继续提供。
3.8.4 打开视图
在程序中我们有的时候需要得到某个视图或者打开某个视图,方法如下:
IWorkbenchPage page = Activator.getActivePage();
IViewPart part = page.findView("org.eclipse.ui.views.PropertySheet");
这样就得到了视图的实例part,如果需要此视图特有的方法,则需要把part 强制转换成相应的视图对象。注意findView中的参数表示视图的id,而不是视图的类名。findView只对打开的视图有效,如果视图没有打开则返回值为null,需要调用代码去打开这个视图:
part = page.showView("org.eclipse.ui.views.PropertySheet");
3.8.5 查找扩展点的实现插件
插件在启动的时候都向平台扩展注册表中注册自己,因此如果需要查找实现了某个扩展点的插件,我们只要向平台扩展注册表(通过Platform.getExtensionRegistry()方法取得)发出请求即可,下面的代码是查找实现了“视图”扩展点的实现插件:
IExtensionRegistry registry = Platform.getExtensionRegistry();
IExtensionPoint point =
registry.getExtensionPoint("org.eclipse.ui.views");
if (point == null) return;
IExtension[] extensions = point.getExtensions();
这里的extensions 就是所有的“视图”扩展点的插件信息数组。
3.8.6 项目nature
Eclipse中的项目有很多种,比如Java项目、C++项目、WTP项目、Python项目,这些项目的不同之处是每种项目都有自己不同的特性,为了方便地标识和辨认这些特性,Eclipse为项目维护了一个特性标识数组,插件只要读取这个数组就可以知道此项目是否拥有某个特性。比如Java相关的插件如果在非Java项目中被调用,由于项目没有Java特性,所以调用就会不成功了。一些插件还可以改变项目的nature,比如SpringIDE就提供了一个为项目增加Spring特性的功能。为项目增加特性不仅涉及到修改项目的特性标识数组,还会进行项目的初始化等操作,不过本例中只讲解如何修改项目的nature。下面就演示如何为项目增加一个“cownewStudio”的natureId:
IProject project = ... ;
IProjectDescription projectDesc = project.getDescription();
String[] oldIds = projectDesc.getNatureIds();
String[] newIds = new String[oldIds.length];
System.arraycopy(oldIds,0, newIds, 0, oldIds.length);
newIds[oldIds.length] = "cownewStudio";
projectDesc.setNatureIds(newIds);
3.8.7 透视图开发
经常听到一些刚刚学会插件开发的朋友这样说:我会做Eclipse插件了,不过前面的路看起来好长呀,我现在做的只是一个小窗口而已,什么时候才能做一个透视图呀,那样我的插件就看起来像模像样了。
在很多人心中,Eclipse中的“透视图”是个比视图、编辑器等更高级的东西,因为一个透视图中经常管理着大量的菜单、视图、编辑器等,因此也就认为透视图开发难度非常大。
透视图包含一组视图和编辑器并可以方便地对它们进行布局,其实透视图做的工作并不多,大部分工作都是由菜单、视图、编辑器等来完成的,透视图的作用只是将一些视图打开并摆好位置、显示菜单、添加快捷键等工作,并没有做任何与功能相关的操作。
下面演示一个“演示透视图”的开发,在这个透视图中我们打开“包资源管理器”、“JavaDoc”、“属性”三个视图。
新建一个扩展,透视图从org.eclipse.ui.perspectives扩展点扩展,配置文件部分如下:
<extension
point="org.eclipse.ui.perspectives">
<perspective name="演示透视图"
icon="icons/emf.ico"
class="cownew.cownew.perspectiveTest.TestPerspective"
id="cownew.cownew.perspectiveTest.TestPerspective">
</perspective>
</extension>
新建一个类TestPerspective,实现IPerspectiveFactory接口,代码如下:
package cownew.cownew.perspectiveTest;
import org.eclipse.ui.IPageLayout;
import org.eclipse.ui.IPerspectiveFactory;
public class TestPerspective implements IPerspectiveFactory
{
public void createInitialLayout(IPageLayout layout)
{
layout.addView("org.eclipse.jdt.ui.PackagesView",
IPageLayout.LEFT, 0.3f, IPageLayout.ID_EDITOR_AREA);
layout.addView("org.eclipse.jdt.ui.JavadocView", IPageLayout.TOP,
0.3f, IPageLayout.ID_EDITOR_AREA);
layout.addView("org.eclipse.ui.views.PropertySheet",
IPageLayout.BOTTOM, 0.3f,
IPageLayout.ID_EDITOR_AREA);
}
}
IPageLayout 的addView方法的作用是向透视图中添加视图,它需要以下4个参数:
l 视图的唯一标识,与plugin.xml中定义的一致。
l 参考部分中的相对位置,可以是IPageLayout.TOP、IPageLayout.BOTTOM、IPageLayout.LEFT或IPageLayout.RIGHT。
l 参考部分中当前占有的空间比率,值范围在0.05f~0.95f之间。
l 参考部分唯一标识;例中使用的是编辑区域(IPageLayout.ID_EDITOR_AREA)。
运行以后,效果如图3.22所示。

图3.22 演示透视图
还可以调用addActionSet等方法向透视图中添加菜单、工具条等。
3.8.8 关于工具条路径
在运行Eclipse插件向导中的“Hello,World!”向导的时候,很多读者都会提出这样的问题:菜单和工具栏按钮只能显示在那个地方吗?能不能把菜单项加入主菜单的【文件】选项下呢?
下面来看一下plugin.xml文件:
<actionSet
label="样本操作集"
visible="true"
id="PlugTest.actionSet">
<menu
label="样本菜单(&M)"
id="sampleMenu">
<separator
name="sampleGroup">
</separator>
</menu>
<action
label="样本操作(&S)"
icon="icons/sample.gif"
class="plugtest.actions.SampleAction"
tooltip="Hello,Eclipse world"
menubarPath="sampleMenu/sampleGroup"
toolbarPath="sampleGroup"
id="plugtest.actions.SampleAction">
</action>
</actionSet>
menu标记中定义了一个新的菜单,并且用separator定义了子菜单。
action标记中menubarPath定义了此action对应的菜单项的路径,toolbarPath定义了此action对应的工具条的路径,这两个属性的值都为本插件特有的,只要修改它们就可以把它们放到其他位置。Eclipse的标准菜单都定义了固定的path,只要把我们的action的path指向它们即可,下面来稍作修改:
<actionSet
label="样本操作集"
visible="true"
id="PlugTest.actionSet">
<action
label="样本操作(&S)"
icon="icons/sample.gif"
class="plugtest.actions.SampleAction"
tooltip="Hello,Eclipse world"
menubarPath="file/fileStart"
toolbarPath="org.eclipse.ui.workbench.file/new.ext"
id="plugtest.actions.SampleAction">
</action>
</actionSet>
运行后效果图如图3.23所示,可以看到菜单已经放到了我们期望的位置。
![]()
图3.23 运行后的效果图
(1) 下面是常见的菜单的菜单路径。
l file/fileStart:【文件】的开始区。
l file/new/additions:【文件】的【新建】菜单内部的【附加】组。
l file/new.ext:【文件】的【新建】区。
l file/close.ext:【文件】的【关闭】区。
l file/save.ext:【文件】的【保存】区。
l file/print.ext:【文件】的【打印】区。
l file/open.ext:【文件】的【打开】区。
l file/import.ext:【文件】的【导入】区。
l file/additions:【文件】的【附加】区。
l file/mru:【文件】的【最近的文档】区。
l file/fileEnd:【文件】的【结束】区。
l edit/editStart:【编辑】的【开始】区。
l edit/undo.ext:【编辑】的【撤销】区。
l edit/cut.ext:【编辑】的【剪切】区。
l edit/find.ext:【编辑】的【查找】区。
l edit/add.ext:【编辑】的【添加】区。
l edit/fileEnd:【编辑】的【结束】区。
l edit/additions:【编辑】的【附加】区。
l org.eclipse.jdt.ui.refactoring.menu:【重构】区。
l project/projStart:【项目】的【开始】区。
l project/open.ext:【项目】的【打开】区。
l project/build.ext:【项目】的【建立】区。
l project/additions:【项目】的【附加】区。
l project/projEnd:【项目】的【结束】区。
l org.eclipse.ui.run:【运行】区。
l window/additions:【窗口】的【附加】区。
l window/additionsend:【窗口】的【结束】区。
l help/helpStart:【帮助】的【开始】区。
l help/group.main.ext:【帮助】的【主要组】区。
l help/group.tutorials:【帮助】的【教程组】区。
l help/group.tools:【帮助】的【工具组】区。
l help/group.updates:【帮助】的【更新组】区。
l help/helpEnd:【帮助】的【结束】区。
l help/additions:【帮助】的【附加】区。
l help/group.about.ext:【帮助】的【关于】区。
(2) 下面是常见的工具条的工具条路径。
l org.eclipse.ui.workbench.file/new.ext:【文件】的【新建】区。
l org.eclipse.ui.workbench.file/save.ext:【文件】的【保存】区。
l org.eclipse.ui.workbench.file/print.ext:【文件】的【打印】区。
l org.eclipse.ui.workbench.file/build.ext:【文件】的【建立】区。
l org.eclipse.ui.workbench.navigate:【导航】区。
l org.eclipse.debug.ui.launchActionSet:【启动】区。
l org.eclipse.search.searchActionSet:【搜索】区。
3.8.9 Eclipse的日志
在调试插件的时候经常遇到插件运行异常的情况,Eclipse默认并不会将异常打印到控制台而是记录到它的日志系统中去;我们把插件给用户使用的时候也许出现各种问题,这时候我们最希望得到的就是其系统日志,这样可以对插件的运行状况进行分析。查看日志的方法如下。
选择【窗口】|【显示视图】|【其他】|【PDE运行时】|【错误日志】命令,在这个视图中就显示了系统的日志。

图3.24 错误日志视图
每一条消息就是一条日志,双击每一条就可以查看消息的详细信息(对于异常消息一般显示的是异常堆栈),如图3.25所示。

图3.25 事件详细信息
还可以单击导出日志”图标
将日志导出成文件。建议在开发调试插件的时候打开此视图,以便及时发现系统运行异常。







