本文重点介绍下ovirt 前端 中的代码架构。本人不是前端开发者,但是ovirt的前面采用的google的GWT的框架Gwtp,前端代码很大一部门是java代码,最后编译成js在clinet的browser中运行。但对于java代码的前台逻辑,每个主tab及每个tab下,每个uiCommand 执行后弹出的popupview的逻辑 过程进行分析 。
首先简单介绍下mvp,其是mvc的m与v与mvc中的指代相同,而p与c类似,负责逻辑的处理。与mvc最大的区别在于view不直接使用model,而是通过Presenter进行交互,m与v分离,model更加高效,view只负责UI定制以及用户的交互。接下来分析代码
GWTP 里用到了gin,类似于ioc,管理对象的实例化。通过注解来实例化对象。我们以vm的tab标签页为例,讲解分析。在
在代码PresenterModule 中定义了每个tab、popubview的 M、V、P三者之间的关系,如下 代码绑定了vm tab标签的P与V之间的关系。 所有的mainTab 的绑定都可以在该类中找到,
// VirtualMachine
bindPresenter(MainTabVirtualMachinePresenter.class,
MainTabVirtualMachinePresenter.ViewDef.class,
MainTabVirtualMachineView.class,
MainTabVirtualMachinePresenter.ProxyDef.class);
vm的MainTab 的 view与Presenter绑定,view的构造 方法中,有个参数modelProvider,所有的mainTab的构造方法中的这个参数 都实现 了MainTabModelProvider 这个类。
@Inject
public MainTabVirtualMachineView(MainModelProvider<VM, VmListModel> modelProvider,
ApplicationResources resources, ApplicationConstants constants,
CommonApplicationConstants commonConstants) {
super(modelProvider);
this.commonConstants = commonConstants;
ViewIdHandler.idHandler.generateAndSetIds(this);
initTable(resources, constants);
initWidget(getTable());
}
view可以通过该类 找到对应的ListModel。过程如下,其中第二个参数mainModelClass 就在每个mainTab对庆的实现类中的构造方法中指定,对应vm为VmListModel.class,而第一个参数CommonModel则在其init方法中初始化了所有的ListModel,最终找到实例化的listModel。
UiCommonModelResolver.getMainListModel(getCommonModel(), mainModelClass);
接下来 介绍下 vm tab的MainModelProvider的 实现。 其实现 在 VirtualMachineModule 类中定义,通过getVmListProvider方法,返回了一个MainModelProvider的实现。
@Provides
@Singleton
public MainModelProvider<VM, VmListModel> getVmListProvider(EventBus eventBus,
Provider<DefaultConfirmationPopupPresenterWidget> defaultConfirmPopupProvider,
final Provider<AssignTagsPopupPresenterWidget> assignTagsPopupProvider,
final Provider<VmMakeTemplatePopupPresenterWidget> makeTemplatePopupProvider,
final Provider<VmMaintainPopupPresenterWidget> maintainProvider,
final Provider<VmRunOncePopupPresenterWidget> runOncePopupProvider,
final Provider<VmChangeCDPopupPresenterWidget> changeCDPopupProvider,
final Provider<VmExportPopupPresenterWidget> exportPopupProvider,
final Provider<VmSnapshotCreatePopupPresenterWidget> createSnapshotPopupProvider,
final Provider<VmMigratePopupPresenterWidget> migratePopupProvider,
final Provider<VmPopupPresenterWidget> newVmPopupProvider,
final Provider<VmListPopupPresenterWidget> newVmListPopupProvider,
final Provider<VmAddPermissionsPopupPresenterWidget> addPermissionsPopupProvider,
final Provider<VmAddBackpermissionsPopupPresenterWidget> addBackpermissionsPopupProvider,
final Provider<GuidePopupPresenterWidget> guidePopupProvider,
final Provider<RemoveConfirmationPopupPresenterWidget> removeConfirmPopupProvider,
final Provider<VmRemovePopupPresenterWidget> vmRemoveConfirmPopupProvider,
final Provider<ReportPresenterWidget> reportWindowProvider,
final Provider<ConsolePopupPresenterWidget> consolePopupProvider,
final Provider<VncInfoPopupPresenterWidget> vncWindoProvider) {
return new MainTabModelProvider<VM, VmListModel>(eventBus, defaultConfirmPopupProvider, VmListModel.class) {
@Override
public AbstractModelBoundPopupPresenterWidget<? extends Model, ?> getModelPopup(VmListModel source,
UICommand lastExecutedCommand, Model windowModel) {
if (lastExecutedCommand == getModel().getAssignTagsCommand()) {
return assignTagsPopupProvider.get();
} else if (lastExecutedCommand == getModel().getNewTemplateCommand()) {
return makeTemplatePopupProvider.get();
} else if (lastExecutedCommand == getModel().getMaintainCommand()) {
return maintainProvider.get();
} else if (lastExecutedCommand == getModel().getRunOnceCommand()) {
return runOncePopupProvider.get();
} else if (lastExecutedCommand == getModel().getChangeCdCommand()) {
return changeCDPopupProvider.get();
} else if (lastExecutedCommand == getModel().getExportCommand()) {
return exportPopupProvider.get();
} else if (lastExecutedCommand == getModel().getCreateSnapshotCommand()) {
return createSnapshotPopupProvider.get();
} else if (lastExecutedCommand == getModel().getMigrateCommand()) {
return migratePopupProvider.get();
} else if (lastExecutedCommand == getModel().getNewVmCommand()) {
return newVmPopupProvider.get();
} else if (lastExecutedCommand == getModel().getNewVmListCommand()) {
return newVmListPopupProvider.get();
} else if (lastExecutedCommand == getModel().getAddPermissions()) {
return addPermissionsPopupProvider.get();
} else if (lastExecutedCommand == getModel().getAddBackpermissions()) {
return addBackpermissionsPopupProvider.get();
} else if (lastExecutedCommand == getModel().getEditCommand()) {
return newVmPopupProvider.get();
} else if (lastExecutedCommand == getModel().getGuideCommand()) {
return guidePopupProvider.get();
} else if (windowModel instanceof VncInfoModel) {
return vncWindoProvider.get();
} else if (lastExecutedCommand == getModel().getEditConsoleCommand()) {
return consolePopupProvider.get();
}
else {
return super.getModelPopup(source, lastExecutedCommand, windowModel);
}
}
@Override
public AbstractModelBoundPopupPresenterWidget<? extends ConfirmationModel, ?> getConfirmModelPopup(VmListModel source,
UICommand lastExecutedCommand) {
if (lastExecutedCommand == getModel().getRemoveCommand()) {
return vmRemoveConfirmPopupProvider.get();
}
else if (lastExecutedCommand == getModel().getStopCommand() ||
lastExecutedCommand == getModel().getShutdownCommand()) {
return removeConfirmPopupProvider.get();
} else {
return super.getConfirmModelPopup(source, lastExecutedCommand);
}
}
@Override
protected ModelBoundPresenterWidget<? extends Model> getModelBoundWidget(UICommand lastExecutedCommand) {
if (lastExecutedCommand instanceof ReportCommand) {
return reportWindowProvider.get();
} else {
return super.getModelBoundWidget(lastExecutedCommand);
}
}
};
}
匿名内部类,而返回的这个MainTabModelProvider类实现了一个重要的方法 getModelPopup (该方法会在指定事件触发后), 当前 view就可以通过这个类 的该方法,以及 根据上一次执行的方法,找到 p,从而弹出view。例如执行了exportCommand,则弹出VmExportPopupPresenterWidget 对应的view,其关系同样在PresenterModule 中定义了。
接下来介绍下 MainTabVirtualMachineView ,对应vmTab的 界面定义,其中对export的button定义如下:
getTable().addActionButton(new WebAdminButtonDefinition<VM>(constants.exportVm()) {
@Override
protected UICommand resolveCommand() {
return getMainModel().getExportCommand();
}
});
定义了一个WebAdminButtion ,在父类的构造方法中会执行如下方法,将UiCommand与该button绑定。
@Override
public void update() {
// Update command associated with this button definition, this
// triggers InitializeEvent when command or its property changes
setCommand(resolveCommand());
}
而addActionButtion的方法则定义了 click后执行该uiCommand
@Override
public void onClick(List<T> selectedItems) {
command.execute();
}
上面定义的Buffton中的回调方法resolveCommand中,getMainModel 就是根据上面介绍的provider 得到vmListModel,从而得到export的UiCommand。会在Bufftion构造时update方法中执行。
UICommadn的执行
当某一个按钮click后,根据上面的介绍,最后执行VmlistModel的executeCommand,根据uiCommand的对象,最终执行指定的方法,这里对应的是export方法
protected void export(String title)
{
T selectedEntity = (T) getSelectedItem();
if (selectedEntity == null)
{
return;
}
if (getWindow() != null)
{
return;
}
1) ExportVmModel model = new ExportVmModel();
2) setWindow(model);
model.startProgress(null);
model.setTitle(title);
model.setHelpTag(HelpTag.export_virtual_machine);
model.setHashName("export_virtual_machine"); //$NON-NLS-1$
setupExportModel(model);
AsyncDataProvider.getStorageDomainList(new AsyncQuery(this,
new INewAsyncCallback() {
@Override
public void onSuccess(Object target, Object returnValue) {
VmBaseListModel vmListModel = (VmBaseListModel) target;
List<StorageDomain> storageDomains =
(List<StorageDomain>) returnValue;
List<StorageDomain> filteredStorageDomains =
new ArrayList<StorageDomain>();
for (StorageDomain a : storageDomains)
{
if (a.getStorageDomainType() == StorageDomainType.ImportExport)
{
filteredStorageDomains.add(a);
}
}
vmListModel.postExportGetStorageDomainList(filteredStorageDomains);
}
}), extractStoragePoolIdNullSafe(selectedEntity));
// check, if the VM has a disk which doesn't allow snapshot
sendWarningForNonExportableDisks(selectedEntity);
}
如上,代码里标注的两步,十分关键,第一步定义了ExportVmModel,第二步执行了监听事件,调用注册的listener。
public void setWindow(Model value)
{
if (window != value)
{
window = value;
onPropertyChanged(new PropertyChangedEventArgs("Window")); //$NON-NLS-1$
}
}
@Override
protected void onPropertyChanged(PropertyChangedEventArgs e)
{
super.onPropertyChanged(e);
getPropertyChangedEvent().raise(this, e);
}
public void raise(Object sender, EventArgs e)
{
//Iterate on a new instance of listeners list,
//to enable listener unsubscribe from event
//as a result on event fairing.
java.util.ArrayList<IEventListener> list = new java.util.ArrayList<IEventListener>();
for (IEventListener listener : listeners)
{
list.add(listener);
}
for (IEventListener listener : list)
{
//Update current context.
setContext(contexts.containsKey(listener) ? contexts.get(listener) : null);
listener.eventRaised(this, sender, e);
}
}
但是问题来了,这里的Listener是在哪里定义的呢?
接着分析,每个model 的构造方法中,会定义Event,
setPropertyChangedEvent(new Event(ProvidePropertyChangedEvent.definition));
该事件对应用户的操作。在上面我们介绍过,MainTabModelProvider,在共父类TabModelProvider 的构造 方法中,需要注意的有几点,第一 定义了一个popupHandler,其二定义了UiCommand的初始化models。
public TabModelProvider(EventBus eventBus,
Provider<DefaultConfirmationPopupPresenterWidget> defaultConfirmPopupProvider) {
this.eventBus = eventBus;
// Configure UiCommon dialog handler
this.popupHandler = new ModelBoundPopupHandler<M>(this, eventBus);
this.popupHandler.setDefaultConfirmPopupProvider(defaultConfirmPopupProvider);
// Add handler to be notified when UiCommon models are (re)initialized
eventBus.addHandler(UiCommonInitEvent.getType(), new UiCommonInitHandler() {
@Override
public void onUiCommonInit(UiCommonInitEvent event) {
TabModelProvider.this.onCommonModelChange();
}
});
eventBus.addHandler(CleanupModelEvent.getType(), new CleanupModelEvent.CleanupModelHandler() {
@Override
public void onCleanupModel(CleanupModelEvent event) {
if (hasModel()) {
//Setting eventbus to null will also unregister the handlers.
getModel().setEventBus(null);
}
}
});
}
/**
* Callback fired when the {@link CommonModel} reference changes.
* <p>
* Override this method to register custom listeners on the corresponding model.
*/
protected void onCommonModelChange() {
// Register dialog model property change listener
popupHandler.addDialogModelListener(getModel());
// Register WidgetModel property change listener
getModel().getPropertyChangedEvent().addListener(new IEventListener() {
@Override
public void eventRaised(Event ev, Object sender, EventArgs args) {
String propName = ((PropertyChangedEventArgs) args).propertyName;
if ("WidgetModel".equals(propName)) { //$NON-NLS-1$
modelBoundWidgetChange();
}
}
});
getModel().setEventBus(getEventBus());
}
在上面的法通过pupupHandler 注册了监听者,具体详情如下
/**
* Adds a property change listener to the given source model that handles its dialog models.
*/
public void addDialogModelListener(final M source) {
hideAndClearAllPopups();
source.getPropertyChangedEvent().addListener(new IEventListener() {
@Override
public void eventRaised(Event ev, Object sender, EventArgs args) {
String propName = ((PropertyChangedEventArgs) args).propertyName;
if (windowPropertyNames.contains(propName)) {
handleWindowModelChange(source, windowPopup, false, propName);
} else if (confirmWindowPropertyNames.contains(propName)) {
handleWindowModelChange(source, confirmWindowPopup, true, propName);
}
}
});
}
对应handler的windowProertyNames 如下方法。与setwidows 方法 发出的“windows”相同时,
@Override
public String[] getWindowPropertyNames() {
return new String[] { "Window" }; //$NON-NLS-1$
}
执行handleWindowModelChange(source, windowPopup, false, propName); 执行发下逻辑,显然windowModel为window,也就是在事件的源头,setWindow方法中的参数 model。因为pupupResolver为TabProvdier
void handleWindowModelChange(M source, AbstractModelBoundPopupPresenterWidget<?, ?> popup,
boolean isConfirm, String propertyName) {
Model windowModel = isConfirm ? popupResolver.getConfirmWindowModel(source, propertyName)
: popupResolver.getWindowModel(source, propertyName);
// Reveal new popup
if (windowModel != null && popup == null) {
// 1. Resolve
AbstractModelBoundPopupPresenterWidget<?, ?> newPopup = null;
UICommand lastExecutedCommand = source.getLastExecutedCommand();
if (windowModel instanceof ConfirmationModel) {
// Resolve confirmation popup
newPopup = popupResolver.getConfirmModelPopup(source, lastExecutedCommand);
if (newPopup == null && defaultConfirmPopupProvider != null) {
// Fall back to basic confirmation popup if possible
newPopup = defaultConfirmPopupProvider.get();
}
} else {
// Resolve main popup
newPopup = popupResolver.getModelPopup(source, lastExecutedCommand, windowModel);
}
// 2. Reveal
if (newPopup != null) {
revealAndAssignPopup(windowModel,
(AbstractModelBoundPopupPresenterWidget<Model, ?>) newPopup,
isConfirm);
} else {
// No popup bound to model, need to clear model reference manually
if (isConfirm) {
popupResolver.clearConfirmWindowModel(source, propertyName);
} else {
popupResolver.clearWindowModel(source, propertyName);
}
}
}
// Hide existing popup
else if (windowModel == null && popup != null) {
hideAndClearPopup(popup, isConfirm);
}
}
从上面的代码的执行逻辑,显然 不是confirm model,因此,执行最关健的一步
newPopup = popupResolver.getModelPopup(source, lastExecutedCommand, windowModel);
也就是上面介绍过的,根据lastExecutedCommand,在Model的executeCommand方法中,会设置last。,找到p, 而popupResolver则是 ModelProvider, 其在VirtualMachineModel中定义 。比如 我们创建一个快照,则会返回如下
VmSnapshotCreatePopupPresenterWidget ,在找到P后,上述代码 第二步,也就是如何呈现所对应的view。 就是 revealAndAssignPopup方法,其中第一个参数windowModel 就是 事件原model中的对应的uiCommand执行时,setWindows时的那个新new的 model的M,而第二个参数newPopup则是将要弹出的presenter对应的P。
/**
* Reveals a popup bound to the given model.
*/
<T extends Model> void revealPopup(final T model,
final AbstractModelBoundPopupPresenterWidget<T, ?> popup) {
assert (model != null) : "Popup model must not be null"; //$NON-NLS-1$
// Initialize popup
popup.init(model);
// Add "PROGRESS" property change handler to Window model
model.getPropertyChangedEvent().addListener(new IEventListener() {
@Override
public void eventRaised(Event ev, Object sender, EventArgs args) {
PropertyChangedEventArgs pcArgs = (PropertyChangedEventArgs) args;
if (PropertyChangedEventArgs.Args.PROGRESS.toString().equals(pcArgs.propertyName)) { //$NON-NLS-1$
updatePopupProgress(model, popup);
}
}
});
updatePopupProgress(model, popup);
// Reveal popup
RevealRootPopupContentEvent.fire(eventBus, popup);
}
1)根据model初始化View , 接着会执行 P的 init(T m)方法,在这个方法中就体现了MVP的思想,v与m不直接交互,而是通过P来进行。首先定义通用的property。 会对model的 PropertyChangedEvent 增加 定义的listener,而listener会根据不同的事件参数的propertyName本执行不同的逻辑,每个listener只会对自己感兴趣的property。而解发事件的Action有很多,比如上面得到p的 setWindows(M) 方法,以及涉及到setItems,setSeletectedItmes等,不同的根据不同的model而不同。
2)而后 addFooterButtons方法,或者 model的Command为空时,也就是要等待initialize方法后,在这里注册command的增加事件,删除buttion,重新根据Command定义button,并和Command绑定。并更新 tabInex。 简单来说这一步所做的就是定义ok 与Cancel的 button.
3)接着同样定义Poupup handler 的事件
4) RevealRootPopupContentEvent.fire(eventBus, popup);这一步灰常 关键,定义界面的显示,前后做的一些操作,比如说 P 或者V 中的 onReveal()都是由该事件 引出的,一个生命周期内的方法。
通过SimpleEventBus,发布事件,而后处理事件时,调用event.dispatch(handler);而这个Handler是在Event中定义其类型,在SimpleEventBus中管理一个map,各种handler。根据Event的type找到handler,如上述事件的hanlder类型为 RevealRootPopupContentHandler。
最终 handler.onRevealRootPopupContent(this); 执行 RootPresenter 继承了这几个handler,在其也能看到注册
@Override
protected void onBind() {
super.onBind();
addRegisteredHandler(ResetPresentersEvent.getType(), this);
addRegisteredHandler(RevealRootContentEvent.getType(), this);
addRegisteredHandler(RevealRootLayoutContentEvent.getType(), this);
addRegisteredHandler(RevealRootPopupContentEvent.getType(), this);
addRegisteredHandler(LockInteractionEvent.getType(), this);
}
而对应handler的实现为如下
@Override
public void onRevealRootContent(
final RevealRootContentEvent revealContentEvent) {
getView().setUsingRootLayoutPanel(false);
setInSlot(rootSlot, revealContentEvent.getContent());
}
public void onRevealRootLayoutContent(
final RevealRootLayoutContentEvent revealContentEvent) {
getView().setUsingRootLayoutPanel(true);
setInSlot(rootSlot, revealContentEvent.getContent());
}
@Override
public void onRevealRootPopupContent(
final RevealRootPopupContentEvent revealContentEvent) {
if (revealContentEvent.isCentered()) {
addToPopupSlot(revealContentEvent.getContent());
} else {
addToPopupSlot(revealContentEvent.getContent(), false);
}
}
而在addToPopupSlot 方法中的实现就会调用PresenterWidget,如通过 child.getView().show();internalReveal;onReveal等。
5) // Initialize popup contents from the model
getView().edit(model);
通过P找到V,然后 执行edit(Model) ,根据不同的view,执行不同的逻辑,但大体上都是 弹出框上的 某一个 控件的事件的 定义,或者说 Model的 setItems ,setXXX等用户的行为触发的 事件 等。