Eclipse 4簡介
Eclipse SDK 4.x基於E4孵化器項目,是新一代構建基於Eclipse的工具和富客戶端桌面應用的平台。它使得開發和組裝基於Eclipse平台的應用和工具要更加容易。第一個版本(4.0)發布於2010年7月28日,4.1發布於2011年6月22日,2012年將發布Eclipse 4.2。Eclipse 3.8將和4.2同時發布,同時3.x也將停止更新。
Eclipse 4包含:
- 基於模型的用戶界面和用於程序樣式的基於CSS的聲明機制。這使得設計和自定義應用程序界面變得更加容易,也給UI布局帶來更大的靈活性,可以使UI看起來與IDE完全不同。
- 新的面向服務的編程模型,可以更容易地使用Eclipse平台提供的應用程序服務。
- 支持依賴注入。
- 提供了一個兼容層,已有的Eclipse 3.x程序也可以利用Eclipse 4應用程序平台的新功能。
架構概述
Eclipse 4應用程序平台與Eclipse 3.x應用程序平台非常類似。如全部JDT和PDE、大部分Platform都和Eclipse 3.x完全相同。E4AP與Eclipse 3.x平台的不同之處在於Workbench的實現(如org.eclipse.ui.workbench.plugin
),以及這個新實現所依賴的技術。在發布之前,這些技術(模型化的UI、依賴注入、基於服務的編程模型、基於CSS的樣式)稱為“e4”,而現在,我們叫它Eclipse 4應用程序平台(E4AP)。在E4AP上端,4.0 Workbench提供了一個3.x Workbench APIs的實現,稱為兼容層,為已有的Eclipse 3.x應用提供向后兼容。
模型化UI
Eclipse 4應用程序的布局現在完全支持模型。它與Web頁的DOM類似,描述用戶界面的布局和結構,盡管還包含其他對用戶不可見的元素(如命令和handler)。
模型化UI為自定義應用程序外觀提供非常靈活的方式。應用程序的結構比3.x的透視圖工廠和擴展更容易理解,因為每個元素的容器模型都設計得更好。
模型本身使用EMF創建和維護的,並使用EMF風格的模式來創建和添加元素。對模型的改變將立即反應在運行的應用程序中。
模型元素
Eclipse 4中的模型是一組接口,都以M
作為前置,並公開了很多getter、setter方法。Eclipse 4的模型繼承了上一代Eclipse應用程序平台的最佳實踐。模型化的UI描述了窗體、透視圖、stacks or tiles和part,也吸收了Eclipse 3.4中的命令/處理程序/綁定這個模型。
E4AP提供了MApplicationElement
、MUILable
等抽象元素,以及MWindow
、MPerspective
、MPart
、MMenu
等具體元素。詳細內容可以參考這里。
CSS樣式
E4AP的一個主要改進就是重新思考如何處理應用程序的主題和樣式。它可以對控件、窗體、對話框應用樣式。這一開始可能會感到很陌生。不過UI的層次結構與HTML類似。如SWT窗體或對話框包含一個根容器Shell
,它又包含一些Composite
和Group
元素,每個元素又可以包含CTabFolder
、Text
、Tree
和Table
等。
CSS映射
使用CSS選擇器可以通過type#id.class
這樣的方式來指定元素。從E4AP到SWT的映射如下:
- type對應Java組件類(如
Button
、Composite
等)。 - 元素可以包含很多類。E4AP公開了模型化UI元素的接口類型(如
MPart
、MTrimmedWindow
)及其標簽(通過類的特性(attribute))。類的特性也可以訪問SWT組件的數據值。 - id對應模型化元素的elementId。
這里有CSS屬性與SWT控件方法的映射表。
應用樣式
在CSS文件中,我們使用相關SWT控件的標示符,如下面的CSS文件:
Label {
font: Verdana 8px;
color: black;
}
Composite Label {
color: black;
}
Text {
font: Verdana 8px;
}
Composite Text {
background-color: white;
color: black;
}
SashForm {
background-color: #c1d5ef;
}
.MTrimBar {
background-color: #e3efff #c1d5ef;
color: white;
font: Verdana 8px;
}
Shell {
background-color: #e3efff #c1d5ef 60
}
要讓你的程序使用CSS文件,可以有兩種方式:
- 對產品指定
applicationCSS
文件。 - 使用主題管理器
如果程序樣式固定,就使用第一種方式。打開RCP項目的plugin.xml
文件,選擇extensioni
選項卡,向org.eclipse.core.runtime.products
擴展點添加applicationCSS
屬性。該屬性的值是指向CSS文件的URI,格式約定為platform:/plugin/BundleSymbolicName/path/file
這種格式。例如:
platform:/plugin/com.example.e4.rcp.todo/css/default.css
這樣,我們的程序在一開始就將應用該樣式。
第二種方法要更靈活一些。我們定義一個對於org.eclipse.e4.ui.css.swt.theme
擴展點(定義ID和對CSS文件的指針)的擴展。然后為產品定義cssTheme屬性。主題管理器允許我們在運行時選擇樣式,和注冊新主題。
我們創建org.eclipse.e4.ui.css.swt.theme
擴展點的兩個擴展,如下圖
創建一個css/red.css
文件:
CTabItem, ToolBar, Button, CBanner, CoolBar {
font-size: 9;
background-color: red;
}
對項目添加cssTemplate
參數:
創建下面這個handler將選擇紅色樣式:
import javax.inject.Named;
import org.eclipse.e4.core.di.annotations.Execute;
import org.eclipse.e4.ui.css.swt.theme.IThemeEngine;
import org.eclipse.e4.ui.workbench.IWorkbench;
public class ThemeSwitchHandler {
@Execute
public void switchTheme(IThemeEngine engine) {
engine.setTheme("de.vogella.e4.todo.redtheme", true);
}
}
在應用程序中添加一個調用上面handler的菜單。運行程序,就可以通過菜單選擇紅色主題:
此外,我們還可以指定某個控件的標簽,並在CSS文件中定義這些標簽。我們可以用下面的代碼來設置標簽:
Label label = new Label(parenet, SWT.NONE);
label.setData("org.eclipse.e4.ui.css.id","MyCSSTagForLabel");
CSS文件可以這樣定義該標簽的樣式:
#MyCSSTagForLabel{
color:#blue;
}
依賴注入
Eclipse平台經過10年的發展,仍然存在以下問題:
- 代碼需要頻繁使用全局單例訪問器(如
Platform
、PlatformUI
)或請求較深的依賴鏈(如獲取IStatusLineManager
)。單例服務對RAP和Riena這種應用服務器來說,問題多多。 - 單例與提供程序的消費者緊密耦合,並且禁止重用。
- 不夠動態。
- ……
E4AP使用依賴注入來解決這些問題。客戶端代碼不需要知道如何訪問服務,只需要描述所需的服務,而由平台負責配置適當的服務。它提供了與JSR 330兼容的基於注解的依賴注入框架,與Spring類似。注入器定義在多個插件中:org.eclipse.e4.core.di
、org.eclipse.e4.core.di.extensions
、org.eclipse.e4.ui.di
。
在Eclipse 3.x中,視圖需要通過PlatformUI
單例和part site的狀態行管理器來訪問Eclipse幫助系統:
class MyView extends ViewPart {
public void createPartControl(Composite parent) {
Button button = ...;
PlatformUI.getWorkbench().getHelpSystem().setHelp(button, "com.example.button.help.id");
getViewSite().getActionBars().getStatusLineManager().setMessage("Configuring system...");
}
}
而在E4AP中,part是POJO,應用程序服務是直接注入進來的:
class MyView {
@Inject
public void create(Composite parent, IWorkbenchHelpSystem help) {
Button button = ...;
help.setHelp(button, "com.example.button.help.id");
slm.setMessage("Configuring system...");
}
}
DI的好處有:
- 客戶端可以編寫POJO和所需的服務列表。
- 更利於測試。
而DI也有一些缺點:
- 服務發現:無法使用代碼自動完成功能來找到可用的服務。
- 調試加載失敗的注入項時會很困難。
與依賴注入相關的注釋,詳見這里。
上下文
E4AP使用上下文(IEclipseContext
接口)向應用程序提供公共服務。普通代碼不需要使用或了解上下文。應該在Java類中使用@Inject
注解來接收必要的服務,不應該直接使用IEclipseContext
。
Eclipse 3.x存在以下問題,可以由IEclipseContext
和依賴注入來解決:
- 頻繁使用全局單例訪問器(如
Platform
、PlatformUI
等等) - 生產者與消費者之間的耦合過於緊密,同時很難重用
- 對上下文變化的動態響應不是增量的
IEvaluationContext
包含全局狀態(可根據上下文的改變而變化)- 當前解決方案不是多線程的(計算發生在UI線程)
- 尺寸變化
- 當前解決方案不會因為服務的生命周期短而減小尺寸
- 當前解決方案會因為服務多而增大尺寸
- 包含太多相似的並行樹(控件樹、服務位置樹等)
- 不跟蹤服務的消費者
- 客戶端代碼需要了解Eclipse代碼庫的內部
- 不支持由運行時的其他服務組成的服務查找
E4AP的上下文存儲了可用的服務,並提供了OSGi服務查找。由於不太可能向Eclipse上下文請求可用的服務,並且不建議直接調用上下文中的方法,所以了解能夠注入哪些內容是十分重要的。
以模型為例,它們都是MContext
的實例,包含在自身的上下文中。例如,MWindow
和MPart
都是MContext
,所以可以這樣查詢模型對象的上下文:
// window == mwindow
MWindow window = (MWindow) mwindow.getContext().get(MWindow.class.getName());
// part == mpart
MWindow part = (MPart) mpart.getContext().get(MPart.class.getName());
這些模型對象都存在於上下文中,所以可以直接將它們注入到客戶端代碼中:
public class AccountsPart {
@Inject
private MPart part;
@Inject
private MWindow window;
void setDirty(boolean dirty) {
part.setDirty(dirty);
}
}
為了鼓勵重用,你應該注入所需的最小公分母。例如,如果只關注讓part變dirty,只需要:
public class AccountsPart {
@Inject
private MDirtyable dirtyable;
void setDirty(boolean dirty) {
dirtyable.setDirty(dirty);
}
}
這樣其他非MPart
的MDirtyable
實現也可以復用你的代碼。這導致了一個非常有趣的現象,即一個模型接口的上層接口也會添加到上下文中。例如:
public class AccountsPart {
@Inject
private MDirtyable dirtyable;
@Inject
private MUILabel label;
@Inject
private MContext context;
@Inject
private MPart part;
}
所有的字段都是相同的MPart
實例。
關於上下文的詳細內容,請參考這里。
事件模型
Eclipse 4使用了一個發布/訂閱事件模型的全局事件總線(global event bus)。《E4中的事件處理》描述了其原理。全局事件總線實現在OSGi事件引起之上,可使用org.eclipse.e4.core.services.events.IEventBroker
訪問。
IEventBroker
提供了一些方法,可以訂閱、取消訂閱總線上的事件,以及向總線發布事件。
獲取IEventBroker
可以通過EclipseContext
來獲取IEventBroker
的一個實例:
…
private IEclipseContext eclipseContext;
…
IEventBroker eventBroker = eclipseContext.get(IEventBroker.class);
或通過依賴注入:
@Inject
IEventBroker eventBroker;
發布事件
向全局事件總線發布事件十分簡單,只需調用一下兩個方法之一:
IEvent Broker.post(String topic, Object data) // synchronous delivery
IEventBroker.send(String topic, Object data) // asynchronous delivery
例如:
...
foo.Bar payload = getPayload();
boolean wasDispatchedSuccessfully = eventBroker.send(TOPIC_STRING, payload);
如果send
或post
命令的payload
是普通Java對象,將把它作為包含IEventBroker.DATA
鍵的屬性附加到OSGi事件上。如果payload
是Dictionary
或Map
,所有的值都將作為包含相應鍵的屬性進行添加。
響應事件
聲明和相應事件包含兩種方法:依賴注入和通過IEventBroker
訂閱。
Eclipse推薦在任何時候都使用依賴注入來注冊和相應事件。它的代碼更少,更容易閱讀和維護,包含較少的匿名內部類。(但實際上E4代碼庫並沒有使用這種方法來訂閱事件。)
@Inject @Optional
void closeHandler(@UIEventTopic(''TOPIC_STRING'') foo.Bar payload) {
// Useful work that has access to payload. The instance of foo.Bar that the event poster placed on the global event bus with the topic ''TOPIC_STRING''
}
應該將事件處理方法定義為私有的,這樣可以更清晰,也能避免直接調用。依賴注入機制可以注入私有字段和方法。(目前定義成私有方法會有bug,可以定義為包級私有。)
如果你的類沒有使用依賴注入,則只能用IEventBroker
來訂閱:
IEventBroker eventBroker;
…
void addSubscribers() {
eventBroker.subscribe(TOPIC_STRING, closeHandler);
…
}
void removeSubscribers() {
eventBroker.unsubscribe(closeHandler);
…
}
…
private org.osgi.service.event.EventHandler closeHandler = new EventHandler() {
public void handleEvent(Event event) {
// Useful work that has access
foo.Bar payload = (foo.Bar) event.getProperty(IEventBroker.DATA);
}