本文重点介绍下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 的绑定都可以在该类中找到,
// VirtualMachinebindPresenter(MainTabVirtualMachinePresenter.class, MainTabVirtualMachinePresenter.ViewDef.class, MainTabVirtualMachineView.class, MainTabVirtualMachinePresenter.ProxyDef.class);
vm的MainTab 的 view与Presenter绑定,view的构造 方法中,有个参数modelProvider,所有的mainTab的构造方法中的这个参数 都实现 了MainTabModelProvider 这个类。
@Inject public MainTabVirtualMachineView(MainModelProvidermodelProvider, 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 MainModelProvidergetVmListProvider(EventBus eventBus, Provider defaultConfirmPopupProvider, final Provider assignTagsPopupProvider, final Provider makeTemplatePopupProvider, final Provider maintainProvider, final Provider runOncePopupProvider, final Provider changeCDPopupProvider, final Provider exportPopupProvider, final Provider createSnapshotPopupProvider, final Provider migratePopupProvider, final Provider newVmPopupProvider, final Provider newVmListPopupProvider, final Provider addPermissionsPopupProvider, final Provider addBackpermissionsPopupProvider, final Provider guidePopupProvider, final Provider removeConfirmPopupProvider, final Provider vmRemoveConfirmPopupProvider, final Provider reportWindowProvider, final Provider consolePopupProvider, final Provider vncWindoProvider) { return new MainTabModelProvider (eventBus, defaultConfirmPopupProvider, VmListModel.class) { @Override public AbstractModelBoundPopupPresenterWidget 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 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 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(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(ListselectedItems) { 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; ListstorageDomains = (List ) returnValue; List filteredStorageDomains = new ArrayList (); 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.ArrayListlist = new java.util.ArrayList (); 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, ProviderdefaultConfirmPopupProvider) { this.eventBus = eventBus; // Configure UiCommon dialog handler this.popupHandler = new ModelBoundPopupHandler (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. ** 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) 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. */void revealPopup(final T model, final AbstractModelBoundPopupPresenterWidget 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等用户的行为触发的 事件 等。