美文网首页程序员
【Ovirt 笔记】前端 GWT 使用分析与整理

【Ovirt 笔记】前端 GWT 使用分析与整理

作者: 58bc06151329 | 来源:发表于2018-02-06 12:49 被阅读657次

文前说明

作为码农中的一员,需要不断的学习,我工作之余将一些分析总结和学习笔记写成博客与大家一起交流,也希望采用这种方式记录自己的学习之旅。

本文仅供学习交流使用,侵权必删。
不用于商业目的,转载请注明出处。

分析整理的版本为 Ovirt 3.4.5 版本。

Ovirt 前端采用了 Google 的 GWT 的框架 Gwtp,前端代码很大一部门是 Java 代码,最后编译成 js 在客户端的浏览器中运行。

Guice 的使用

  • 使用 Maven,添加依赖项
<dependency>
    <groupId>com.google.inject</groupId>
    <artifactId>guice</artifactId>
    <version>4.1.0</version>
</dependency>
  • Gwtp 里用到了 Guice,负责管理对象的实例化。
  • Guice 是 Google 开发的一个轻量级,基于 Java5(主要运用泛型与注释特性)的依赖注入框架(IOC)。Guice 非常小而且快。Guice 是类型安全的,它能够对构造函数,属性,方法(包含任意个参数的任意方法,而不仅仅是 Setter 方法)进行注入。Guice 采用 Java 加注解的方式进行托管对象的配置,充分利用 IDE 编译器的类型安全检查功能和自动重构功能,使得配置的更改也是类型安全的。
  • Guice 有两种模式
    • 基于 Module
    • @Inject 注解

基于 Module 模式

  • Module 的实现类将会在 configure() 方法中进行配置,指定某个基类/接口对应具体的实现类。
  • 再通过 @Inject 来注解属性/构造函数,自动根据配置进行实例化。
binder.bind(BaseClass.class).to(ImplClass.class);
  • 在链接绑定中,作用域是应用于绑定源上,而不是应用了绑定目标上。

  • 默认情况下,Guice 每次调用都会返回一个新的实例,这种行为可以通过作用域进行配置,作用域允许复用对象实例。

  • 在一个应用服务的生命周期中的作用域

    • 单例 @Singleton
    • 会话 @SessionScoped
    • 请求 @RequestScoped
  • 饿汉式单例可以很快揭示初始化问题,并确保最终用户获得一致的,直观的体验。懒汉式单例保证了一个快速的编辑-完成-启动的开发周期,用不同的配置方式可以区分策略的不同场景的使用。

配置方式 PRODUCTION DEVELOPMENT
.asEagerSingleton() eager eager
.in(Singleton.class) eager lazy
.in(Scopes.SINGLETON) eager lazy
@Singleton eager* lazy
  • *号表示仅仅有已知类型才会马上创建单例对象。
  • 假设一个对象是用状态的,它的状态就非常明显了。每一个应用使用则是 @Singleton,每一个请求使用则是 @RequestScoped。
  • 个对象是无状态的而且创建的代价非常小。就没有必要配置作用域了。Guice 每次都创建一个新的对象。
  • 单例模式不能提供多个对象,特别是在使用了依赖注入之后。尽管单例模式降低了对象的创建、使垃圾回收推后。但单例对象的初始化须要进行同步。
  • 单例对象最适用于
    • 有状态对象,假设配置对象或者计数器
    • 要花非常大的代价去创建或者查找
    • 捆绑了资源的对象。比如数据库连接池。

@Inject 注解模式

  • 作为属性比直白的类,直接 new 就可以了,可以采用 @Inject 进行注解构造函数的方式,来进行自动实例化构造参数。

使用 @Provides 注解增加灵活性

  • Guice 会自动发现具有 @Provides 注释的 Module 中的所有方法。根据返回类型(对象),在请求某个返回类型(对象)时,Guice 会进行计算,它应该调用 Provider 方法来提供返回类型(对象)。

Ovirt 前端代码分析

  • Ovirt 前端代码分为了 管理门户用户门户 两个部分。
  • 项目中也划分了两个工程 webadminuserportal
  • 两个工程中各自通过 GWT 配置文件进行了 GIN 的配置。
    • webadmin(WebAdmin.gwt.xml
    • userportal(UserPortal.gwt.xml
<!-- webadmin GWTP GIN configuration -->
<set-configuration-property name="gin.ginjector.modules" value="org.ovirt.engine.ui.webadmin.gin.ClientModule" />
<!-- userportal GWTP GIN configuration -->
<set-configuration-property name="gin.ginjector.modules" value="org.ovirt.engine.ui.userportal.gin.ClientModule" />
  • 前端代码采用了 MVP 模式(Model-View-Presenter)。
  • MVP 是从经典的模式 MVC 演变而来,Presenter 负责逻辑的处理,Model 提供数据,View
    负责显示。
  • 在 MVP 里,Presenter 完全把 Model 和 View 进行了分离,主要的程序逻辑在 Presenter 里实现。
  • Presenter 与具体的 View 是没有直接关联的,而是通过定义好的接口进行交互,从而使得在变更 View 时候可以保持 Presenter 的不变,即重用。

管理门户(用户门户基本与管理门户一致)

  • 入口 Module 实现
/**
 * WebAdmin application GIN module.
 */
public class ClientModule extends AbstractGinModule {

    @Override
    protected void configure() {
        install(new SystemModule());
        install(new PresenterModule());
        install(new UiCommonModule());
        install(new PluginModule());
        install(new UtilsModule());
    }

}
  • 初始化 ApplicationInit
    • 初始化 CommonModelManager
      • 实例化 CommonModel
bind(ApplicationInit.class).asEagerSingleton();
protected void beforeUiCommonInitEvent(LoginModel loginModel) {
    CommonModelManager.init(eventBus);
}
public static void init(final EventBus eventBus) {
        commonModel = CommonModel.newInstance();

        commonModel.getSelectedItemChangedEvent().addListener(new IEventListener() {
            @Override
            public void eventRaised(Event ev, Object sender, EventArgs args) {
                MainModelSelectionChangeEvent.fire(eventBus, commonModel.getSelectedItem());
            }
        });

        commonModel.getSignedOutEvent().addListener(new IEventListener() {
            @Override
            public void eventRaised(Event ev, Object sender, EventArgs args) {

                // Clear CommonModel reference after the user signs out,
                // use deferred command to ensure the reference is cleared
                // only after all UiCommon-related processing is over
                Scheduler.get().scheduleDeferred(new ScheduledCommand() {
                    @Override
                    public void execute() {
                        commonModel = null;
                    }
                });
            }
        });
}
  • CommonModel 中定义了所有前端的 Model,将这些 Model 全部进行实例化放到一个列表中。
dataCenterList = new DataCenterListModel();
list.add(dataCenterList);
clusterList = new ClusterListModel();
list.add(clusterList);
hostList = new HostListModel();
list.add(hostList);
storageList = new StorageListModel();
list.add(storageList);
vmList = new VmListModel();
list.add(vmList);
poolList = new PoolListModel();
list.add(poolList);
templateList = new TemplateListModel();
list.add(templateList);
eventList = new EventListModel();
list.add(eventList);
......
  • 加载 PresenterModule
    • PresenterModule 主要维护了 PresenterView 的绑定和关联关系。
  • 在 PresenterModule 中定义了每个选项卡(MainTab 或者 SubTab)、弹出窗口(Popup)的 V、P 关系。
    • 虚拟机(VM)选项卡(MainTab) 为例进行分析,这里建立了虚拟机选项卡类 MainTabVirtualMachineView(V) 与 MainTabVirtualMachinePresenter(P)的关联关系。
    • 指定 MainTabVirtualMachinePresenter.ViewDef 接口的实现类为 MainTabVirtualMachineView,并且是一个延迟加载的单例。
    • 绑定 MainTabVirtualMachinePresenter 是一个延迟加载的单例。
bindPresenter(MainTabVirtualMachinePresenter.class,
MainTabVirtualMachinePresenter.ViewDef.class,
MainTabVirtualMachineView.class,
MainTabVirtualMachinePresenter.ProxyDef.class);
protected <P extends Presenter<?, ?>, V extends View, Proxy_ extends Proxy<P>> void bindPresenter(
            Class<P> presenterImpl, Class<V> view, Class<? extends V> viewImpl,
            Class<Proxy_> proxy) {
        bind(presenterImpl).in(Singleton.class);
        bind(viewImpl).in(Singleton.class);
        bind(proxy).asEagerSingleton();
        bind(view).to(viewImpl);
}
  • 加载 UiCommonModule
    • UiCommonModule 主要维护了 Provider 的绑定关系,这关系到了 Model 的使用与关联。
    • 还是以虚拟机(VM)选项卡(MainTab)为例进行分析,使用类 VirtualMachineModule 定义了虚拟机相关 Provider 的实例化。
    • VirtualMachineModule 中定义了选择虚拟机(VM)选项卡的数据模型提供者。
    • getModelPopup 方法主要是定义了弹出窗口(Popup)的实现。
@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<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().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);
                }
            }
        };
}
  • 通过 @Provides 注解将 MainModelProvider 注入到容器中,最终实现延迟加载单例。
  • MainModelProvider 的构造函数中传递 VmListModel.class,建立了 MainModelProviderVmListModel(M)的关联关系。通过 MainModelProvidergetModel 方法,就能获得 VmListModel
  • 实际就是从之前实例化的 CommonModel 里的列表中获取。
@Override
public M getModel() {
    return UiCommonModelResolver.getMainListModel(getCommonModel(), mainModelClass);
}
public static <M extends SearchableListModel> M getMainListModel(
            CommonModel commonModel, Class<M> mainModelClass) {
        if (commonModel == null) {
            return null;
        }

        for (SearchableListModel list : commonModel.getItems()) {
            if (list != null && list.getClass().equals(mainModelClass)) {
                return (M) list;
            }
        }

        throw new IllegalStateException("Cannot resolve main list model [" + mainModelClass + "]"); //$NON-NLS-1$ //$NON-NLS-2$
}
  • 选择选项卡从容器中获取绑定的 MainTabVirtualMachinePresenter 类,根据 @Inject 注解,注入之前绑定的 View 和 Provider。
    • 构造函数中传递的是 ViewDef 接口,因为绑定中指定了实现类为 MainTabVirtualMachineView,因此从容器中获取直接注入。
    • 构造函数中传递的是 MainModelProvider, 因此从容器中获取直接注入。
@Inject
public MainTabVirtualMachinePresenter(EventBus eventBus, ViewDef view, ProxyDef proxy,
            PlaceManager placeManager, MainModelProvider<VM, VmListModel> modelProvider) {
        super(eventBus, view, proxy, placeManager, modelProvider);
}
  • 同样的方法,实现 Provider 在 View 中的注入。
@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 中使用模型数据,通过 getMainModel 方法获取数据模型。
    • 实际是通过 MainModelProvider 的 getModel 方法获取的 VmListModel
protected M getMainModel() {
    return modelProvider.getModel();
}
  • 将 Provider 在 View 中注入,基类中将 Provider 中的数据模型解析出来。
this.table = createActionTable();

protected SimpleActionTable<T> createActionTable() {
     return new SimpleActionTable<T>(modelProvider, getTableHeaderlessResources(), getTableResources(), ClientGinjectorProvider.getEventBus(), ClientGinjectorProvider.getClientStorage());
}
  • 弹出窗口的实现分析以 新建虚拟机(VM) 为例
    • VmListModel(M)中定义新建虚拟机的 UICommand
    • 可以通过 getNewVmCommand 方法获得。
    • UICommand 构造函数指定了 newVMCommand 与 VmListModel 的关联关系。
private UICommand newVMCommand;

public UICommand getNewVmCommand() {
    return newVMCommand;
}

private void setNewVmCommand(UICommand newVMCommand) {
    this.newVMCommand = newVMCommand;
}

setNewVmCommand(new UICommand("NewVm", this));
public UICommand(String name, ICommandTarget target) {
    this(name, target, false);
}
  • MainTabVirtualMachineView 对应的虚拟机(VM)选项卡(MainTab)界面中定义新建虚拟机按钮。
    • WebAdminButtonDefinition 的定义完成了按钮的指定操作。
    • 点击新建虚拟机(VM)按钮时,执行 resolveCommand 方法,获取新建按钮的 UICommand。
    • 表格的 .addActionButton 方法定义了指定按钮的点击事件注册。
getTable().addActionButton(new WebAdminButtonDefinition<VM>(constants.newVm()) {

       @Override
       protected UICommand resolveCommand() {
            return getMainModel().getNewVmCommand();
       }
});
update();

public void update() {
    // Update command associated with this button definition, this
    // triggers InitializeEvent when command or its property changes
    setCommand(resolveCommand());
}
newActionButton.addClickHandler(new ClickHandler() {
            @Override
            public void onClick(ClickEvent event) {
                if (buttonDef.isImplemented()) {
                    if (buttonDef instanceof UiMenuBarButtonDefinition) {
                        actionPanelPopupPanel.asPopupPanel().addAutoHidePartner(newActionButton.asToggleButton()
                                .getElement());
                        if (newActionButton.asToggleButton().isDown()) {
                            updateContextMenu(actionPanelPopupPanel.getMenuBar(),
                                    ((UiMenuBarButtonDefinition<T>) buttonDef).getSubActions(),
                                    actionPanelPopupPanel.asPopupPanel());
                            actionPanelPopupPanel.asPopupPanel()
                                    .showRelativeToAndFitToScreen(newActionButton.asWidget());
                        } else {
                            actionPanelPopupPanel.asPopupPanel().hide();
                        }
                    } else {
                        buttonDef.onClick(getSelectedItems());
                    }
                } else {
                    new FeatureNotImplementedYetPopup((Widget) event.getSource(),
                            buttonDef.isImplInUserPortal()).show();
                }
            }
 });
  • 事件的执行调用 buttonDef.onClick(getSelectedItems()),执行的是 WebAdminButtonDefinitiononClick 方法。
    • 执行了 UiCommandexecute 方法。
      • 最终执行了 target(VmListModel)的 executeCommand 方法。
public void onClick(List<T> selectedItems) {
    command.execute();
}
public void execute(Object... parameters)
    {
        if (!getIsAvailable() || !getIsExecutionAllowed())
        {
            return;
        }

        if (target != null)
        {
            if (parameters == null || parameters.length == 0) {
                target.executeCommand(this);
            } else {
                target.executeCommand(this, parameters);
            }
        }
 }
  • VmListModelexecuteCommand 方法,根据判断不同的 UiCommand 进行不同的处理。
    - 新建虚拟机(VM),弹出新建虚拟机属性窗口。
@Override
public void executeCommand(UICommand command)
{
    super.executeCommand(command);

    if (command == getNewVmCommand())
    {
        newVm();
    }
......
  • newVm 方法,完成了新建虚拟机属性弹出窗口模型(M)的实例化。
UnitVmModel model = new UnitVmModel(new NewVmModelBehavior());
model.setTitle(ConstantsManager.getInstance().getConstants().newVmTitle());
model.setHelpTag(HelpTag.new_vm);
model.setHashName("new_vm"); //$NON-NLS-1$
model.setIsNew(true);
model.getVmType().setSelectedItem(VmType.Server);
model.setCustomPropertiesKeysList(getCustomPropertiesKeysList());

setWindow(model);
  • 同时之前在 VirtualMachineModule 定义的 getVmListProvider 中也会根据不同的 UiCommand 执行不同的处理。
......
} else if (lastExecutedCommand == getModel().getNewVmCommand()) {
    return newVmPopupProvider.get();
......
  • newVmPopupProvider 作为 getVmListProvider 的参数传入。
    • 采用了自定义 Provider 的方式定义。
Provider<VmPopupPresenterWidget> newVmPopupProvider
  • 通过 newVmPopupProvider 的 get 方法,实例化 VmPopupPresenterWidget
    • 在 PresenterModule 中同样定义了新建虚拟机(VM)属性弹出窗口(Popup)的 V、P 关系。
    • 完成了 View 接口与实现的绑定。
    • 完成了 Presenter 的绑定。
bindPresenterWidget(VmPopupPresenterWidget.class,
VmPopupPresenterWidget.ViewDef.class,
VmPopupView.class);

bind(presenterImpl);
bind(view).to(viewImpl);
  • VmPopupPresenterWidget 通过 @Inject 注解,实现 VmPopupView(V)的注入。
@Inject
public VmPopupPresenterWidget(EventBus eventBus, ViewDef view) {
    super(eventBus, view);
}
  • VirtualMachineModulegetVmListProvider 方法,返回 MainTabModelProvider
    • ModelBoundPopupHandler 构造函数中建立了事件与 Provider 的关联关系。
    • 指定 MainTabModelProviderModelBoundPopupHandlerResolver
    • 通过 addHandler 添加对 UICommand 的加载处理。
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);
                }
            }
        });
}
public ModelBoundPopupHandler(ModelBoundPopupResolver<M> popupResolver, EventBus eventBus) {
        this.popupResolver = popupResolver;
        this.eventBus = eventBus;

        windowPropertyNames.addAll(Arrays.asList(popupResolver.getWindowPropertyNames()));
        confirmWindowPropertyNames.addAll(Arrays.asList(popupResolver.getConfirmWindowPropertyNames()));
}
  • TabModelProvider.this.onCommonModelChange() 添加弹出窗口事件绑定。
    • 这里的 popupHandler 是 MainTabModelProvider
    • getModel() 获得的是 VmListModel,source 也就是 VmListModel
protected void onCommonModelChange() {
        // Register dialog model property change listener
        popupHandler.addDialogModelListener(getModel());
......
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);
                }
            }
        });
}
  • windowPropertyNames 通过 getWindowPropertyNames 获得。
@Override
public String[] getWindowPropertyNames() {
    return new String[] { "Window" }; //$NON-NLS-1$
}
  • Window 属性发生改变调用 handleWindowModelChange 方法。
    • source 是 VmListModel
    • popup 为 null。
    • isConfirm 为 false。
    • propertyName 为 Window
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);
        }
}
  • isConfirm 为 false 执行。
    • 返回 VmListModelgetWindow 结果。
popupResolver.getWindowModel(source, propertyName);
@Override
public Model getWindowModel(M source, String propertyName) {
      return source.getWindow();
}
  • 之前 newVm 方法完成新建虚拟机属性弹出窗口模型(M)的实例化。
    • 通过 setWindow 方法,完成了弹出窗口模型(M)的设置。
UnitVmModel model = new UnitVmModel(new NewVmModelBehavior());
......
setWindow(model);
  • 最终执行弹出窗口的构建与初始化。
newPopup = popupResolver.getModelPopup(source, lastExecutedCommand, windowModel);
<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);
}

场景实现分析

门户界面中页面头添加新的链接窗口实现
  • HeaderView 类中添加 Anchor 定义新链接。
@UiField(provided = true)
@WithElementId
final Anchor aboutLink;
  • AbstractHeaderPresenterWidget.ViewDef 接口中添加新链接的 get 方法。
HasClickHandlers getAboutLink();
  • HeaderPresenterWidget 类的 onBind 方法中注册新链接的点击(onClick)事件。
registerHandler(getView().getAboutLink().addClickHandler(new ClickHandler() {
    @Override
    public void onClick(ClickEvent event) {
          RevealRootPopupContentEvent.fire(HeaderPresenterWidget.this, aboutPopup);
    }
}));
  • 事件响应窗口通过 HeaderPresenterWidget 构造函数注入。
private final AboutPopupPresenterWidget aboutPopup;

@Inject
public HeaderPresenterWidget(EventBus eventBus,
            ViewDef view,
            CurrentUser user,
            SearchPanelPresenterWidget searchPanel,
            ScrollableTabBarPresenterWidget tabBar,
            AboutPopupPresenterWidget aboutPopup,
            ConfigurePopupPresenterWidget configurePopup,
            ApplicationDynamicMessages dynamicMessages) {
        super(eventBus, view, user, dynamicMessages.applicationDocTitle(), dynamicMessages.guideUrl());
        this.searchPanel = searchPanel;
        this.tabBar = tabBar;
        this.aboutPopup = aboutPopup;
        this.configurePopup = configurePopup;
        this.feedbackLinkLabel = dynamicMessages.feedbackLinkLabel();
        this.dynamicMessages = dynamicMessages;
        this.currentUser = user;
}
  • AboutPopupPresenterWidget 继承弹出窗口基类 AbstractPopupPresenterWidget
    • 弹出窗口的视图绑定通过构造函数注入。
    • 弹出窗口渲染事件可在 onReveal 方法中完成。
@Inject
public AboutPopupPresenterWidget(EventBus eventBus, ViewDef view) {
    super(eventBus, view);
}
  • PresenterModule 中将视图(V)与呈现者(P)绑定。
    • 之前 AboutPopupPresenterWidget 构造函数中的视图接口注入,会通过绑定关系选择注入 AboutPopupView
    • AboutPopupView 视图中完成控件定义等。
      • 具体的布局通过对应的配置文件 AboutPopupView.ui.xml 完成。
bindSingletonPresenterWidget(AboutPopupPresenterWidget.class,
AboutPopupPresenterWidget.ViewDef.class,
AboutPopupView.class);
管理员门户界面中页面头配置链接窗口中添加新的选项卡内容
  • 配置链接窗口对应 ConfigurePopupPresenterWidget(P)和 ConfigurePopupView(V)类。
bindSingletonPresenterWidget(ConfigurePopupPresenterWidget.class,
ConfigurePopupPresenterWidget.ViewDef.class,
ConfigurePopupView.class);
  • ConfigurePopupView 类中添加新的选项卡 DialogTab
@UiField
DialogTab rolesTab;
  • 定义新的选项卡面板 SimplePanel
@UiField
SimplePanel rolesTabPanel;
  • 选项卡与选项卡面板通过 ConfigurePopupView 的配置文件的布局关联。
<t:tab>
    <t:DialogTab ui:field="rolesTab">
        <t:content>
            <g:SimplePanel addStyleNames="{style.panel}" ui:field="rolesTabPanel" />
        </t:content>
    </t:DialogTab>
</t:tab>
  • 选项卡面板中的内容可以单独定义一个视图类(V)实现。
    • 可以直接 new
    • 也可以通过构造函数 @Inject 直接注入。
@Inject
public ConfigurePopupView(
       EventBus eventBus,
       ApplicationResources resources,
       ApplicationConstants constants,
       RoleView roleView,
       ClusterPolicyView clusterPolicyView,
       SystemPermissionView systemPermissionView) {
......
  • RoleView 需继承 Composite 成为一个组件。
    • RoleView 组件的数据模型通过构造函数注入 Provider 的方式实现。
 @Inject
    public RoleView(ApplicationConstants constants,
            RoleModelProvider roleModelProvider,
            RolePermissionModelProvider permissionModelProvider,
            EventBus eventBus, ClientStorage clientStorage) {
        this.roleModelProvider = roleModelProvider;
        this.permissionModelProvider = permissionModelProvider;
        this.eventBus = eventBus;
        this.clientStorage = clientStorage;
......
  • RoleModelProvider 设置为单例所以在 UiCommonModule 中进行了绑定。
// RoleListModel
bind(RoleModelProvider.class).asEagerSingleton();
  • RoleListModelCommonModel 中实例化。
private RoleListModel roleListModel;

roleListModel = new RoleListModel();

public RoleListModel getRoleListModel() {
     return roleListModel;
}
  • RoleModelProvider 可以通过 getModel 方法直接获得。
@Override
public RoleListModel getModel() {
     return getCommonModel().getRoleListModel();
}
  • RoleView 组件中的弹出窗口,通过 RoleModelProvider 的构造函数进行注入。
@Inject
public RoleModelProvider(EventBus eventBus,
            Provider<DefaultConfirmationPopupPresenterWidget> defaultConfirmPopupProvider,
            final Provider<RolePopupPresenterWidget> rolePopupProvider,
            final Provider<RemoveConfirmationPopupPresenterWidget> removeConfirmPopupProvider) {
        super(eventBus, defaultConfirmPopupProvider);
        this.rolePopupProvider = rolePopupProvider;
        this.removeConfirmPopupProvider = removeConfirmPopupProvider;
}
  • Provider<RolePopupPresenterWidget> rolePopupProvider 注入实例化了 rolePopupProvider
    • 通过 get 方法获取 RolePopupPresenterWidget
    • RolePopupPresenterWidget(P)与 RolePopupView(V)在 PresenterModule 中绑定。
    • RolePopupView 完成组件内弹出窗口控件的定义。
    • RolePopupView.ui.xml 配置文件完成组件内弹出窗口的控件布局。
@Override
public AbstractModelBoundPopupPresenterWidget<? extends Model, ?> getModelPopup(RoleListModel source,
            UICommand lastExecutedCommand, Model windowModel) {
        if (lastExecutedCommand.equals(getModel().getNewCommand())
                || lastExecutedCommand.equals(getModel().getEditCommand())
                || lastExecutedCommand.equals(getModel().getCloneCommand())) {
            return rolePopupProvider.get();
        } else {
            return super.getModelPopup(source, lastExecutedCommand, windowModel);
        }
}
bindPresenterWidget(RolePopupPresenterWidget.class,
RolePopupPresenterWidget.ViewDef.class,
RolePopupView.class);
管理员门户界面添加一级(MainTab)选项卡面板
  • 新建 Module 方便管理每一个一级(MainTab)选项卡。
    • UiCommonModule 类中绑定。
install(new DataCenterModule());
  • 与此一级(MainTab)选项卡面板相关的弹出窗口通过 @Provides 注解注入。
@Provides
@Singleton
public MainModelProvider<StoragePool, DataCenterListModel> getDataCenterListProvider(EventBus eventBus,
            Provider<DefaultConfirmationPopupPresenterWidget> defaultConfirmPopupProvider,
            final Provider<DataCenterPopupPresenterWidget> popupProvider,
            final Provider<GuidePopupPresenterWidget> guidePopupProvider,
            final Provider<RemoveConfirmationPopupPresenterWidget> removeConfirmPopupProvider,
            final Provider<RecoveryStoragePopupPresenterWidget> recoveryStorageConfirmPopupProvider,
            final Provider<ReportPresenterWidget> reportWindowProvider,
            final Provider<DataCenterForceRemovePopupPresenterWidget> forceRemovePopupProvider) {
        return new MainTabModelProvider<StoragePool, DataCenterListModel>(eventBus, defaultConfirmPopupProvider, DataCenterListModel.class) {
            @Override
            public AbstractModelBoundPopupPresenterWidget<? extends Model, ?> getModelPopup(DataCenterListModel source,
                    UICommand lastExecutedCommand, Model windowModel) {
                if (lastExecutedCommand == getModel().getNewCommand()
                        || lastExecutedCommand == getModel().getEditCommand()) {
                    return popupProvider.get();
                } else if (lastExecutedCommand == getModel().getGuideCommand()) {
                    return guidePopupProvider.get();
                } else {
                    return super.getModelPopup(source, lastExecutedCommand, windowModel);
                }
            }

            @Override
            public AbstractModelBoundPopupPresenterWidget<? extends ConfirmationModel, ?> getConfirmModelPopup(DataCenterListModel source,
                    UICommand lastExecutedCommand) {
                if (lastExecutedCommand == getModel().getRemoveCommand()) {
                    return removeConfirmPopupProvider.get();
                } else if (lastExecutedCommand == getModel().getForceRemoveCommand()) {
                    return forceRemovePopupProvider.get();
                } else if (lastExecutedCommand == getModel().getRecoveryStorageCommand()) {
                    return recoveryStorageConfirmPopupProvider.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);
                }
            }
        };
}
  • PresenterModule 中将 MainTabDataCenterPresenter(P)和 MainTabDataCenterView(V)绑定。
bindPresenter(MainTabDataCenterPresenter.class,
MainTabDataCenterPresenter.ViewDef.class,
MainTabDataCenterView.class,
MainTabDataCenterPresenter.ProxyDef.class);
  • Presenter 交给代理 ProxyDef 进行监听,@ProxyCodeSplit 实现代码分隔,选择选项卡时延迟加载。
    • @NameToken 定义访问该选项卡的名称令牌。
@ProxyCodeSplit
@NameToken(ApplicationPlaces.dataCenterMainTabPlace)
public interface ProxyDef extends TabContentProxyPlace<MainTabDataCenterPresenter> {
}
  • 选项卡面板通过 @TabInfo 注解绑定选项卡面板内容并且指定显示容器。
@TabInfo(container = MainTabPanelPresenter.class)
static TabData getTabData(ApplicationConstants applicationConstants,
            MainModelProvider<StoragePool, DataCenterListModel> modelProvider) {
        return new ModelBoundTabData(applicationConstants.dataCenterMainTabLabel(), 0, modelProvider);
}
  • ModelBoundTabData 构造函数中指定选项卡卡名、位置索引与内容。
    • 第一个选项卡索引为 0
public ModelBoundTabData(String label, float priority, ModelProvider<? extends EntityModel> modelProvider) {
     this(label, priority, modelProvider, Align.LEFT);
}
  • MainTabPanelPresenterPresenterModule 中也进行了 MainTabPanelView(V)和MainTabPanelPresenter(P) 的绑定。
bindPresenter(MainTabPanelPresenter.class,
MainTabPanelPresenter.ViewDef.class,
MainTabPanelView.class,
MainTabPanelPresenter.ProxyDef.class);
  • MainTabPanelPresenter 中通过 @ContentSlot 注解定义内容插槽。
    • Presenter 代理监听到选择选项卡事件,更新为 Presenter 对应的 MainTabDataCenterView(V)。
@ContentSlot
public static final Type<RevealContentHandler<?>> TYPE_SetTabContent = new Type<RevealContentHandler<?>>();
  • 通过 @RequestTabs@ChangeTab 定义了选项卡显示和选项卡选择处理。
@RequestTabs
public static final Type<RequestTabsHandler> TYPE_RequestTabs = new Type<RequestTabsHandler>();

@ChangeTab
public static final Type<ChangeTabHandler> TYPE_ChangeTab = new Type<ChangeTabHandler>();
  • MainTabDataCenterPresenter 基类中定义了可以插入的插槽接口。
    • 根据不同的名称令牌更新插槽中的内容。
public AbstractMainTabPresenter(EventBus eventBus, V view, P proxy, PlaceManager placeManager, MainModelProvider<T, M> modelProvider) {
     super(eventBus, view, proxy, MainTabPanelPresenter.TYPE_SetTabContent);
     this.placeManager = placeManager;
     this.modelProvider = modelProvider;
}
  • MainTabPanelPresenter 同理插入 MainContentPresenter 的插槽。
@Inject
public MainContentPresenter(EventBus eventBus, ViewDef view, ProxyDef proxy) {
    super(eventBus, view, proxy, MainSectionPresenter.TYPE_SetMainContent);
}
  • MainContentPresenter 中定义了两个插槽,一个为一级选项卡(MainTab),一个为二级选项卡(SubTab)。
@ContentSlot
public static final Type<RevealContentHandler<?>> TYPE_SetMainTabPanelContent = new Type<RevealContentHandler<?>>();

@ContentSlot
public static final Type<RevealContentHandler<?>> TYPE_SetSubTabPanelContent = new Type<RevealContentHandler<?>>();
  • DataCenterListModel 之前通过 DataCenterModulegetDataCenterListProvider 方法绑定给到了 MainTabModelProvider 中。

    • 再通过 MainTabDataCenterPresenter 的构造函数注入建立了关系。
    • MainTabDataCenterView 的构造函数同样注入了 MainTabModelProvider。通过 getMainModel 获取数据模型,从而将数据显示在视图上。
  • DataCenterListModel 继承 SearchableListModel 获取数据模型查询分页等相关属性。

    • getSearchPageNumber 查询页数。
    • getNextSearchPageNumber 下一页页数。
    • getPreviousSearchPageNumber 上一页页数。
    • getTimer 数据刷新定时器。
    • executeCommand(UICommand command) 分页相关操作。
  • MainTabDataCenterPresenter 需要继承 AbstractMainTabWithDetailsPresenter 类。

  • MainTabDataCenterView 需要继承 AbstractMainTabWithDetailsTableView 类。

管理员门户界面添加二级(SubTab)选项卡面板
  • DataCenterSubTabPanelPresenter(P)与 DataCenterSubTabPanelView(V)在 PresenterModule 中进行绑定。
bindPresenter(DataCenterSubTabPanelPresenter.class,
DataCenterSubTabPanelPresenter.ViewDef.class,
DataCenterSubTabPanelView.class,
DataCenterSubTabPanelPresenter.ProxyDef.class);
  • DataCenterSubTabPanelPresenter 基类中定义了可以插入的插槽接口。
public AbstractSubTabPanelPresenter(EventBus eventBus, V view, P proxy,
            Object tabContentSlot,
            Type<RequestTabsHandler> requestTabsEventType,
            Type<ChangeTabHandler> changeTabEventType,
            ScrollableTabBarPresenterWidget tabBar) {
        super(eventBus, view, proxy, tabContentSlot, requestTabsEventType, changeTabEventType,
                MainContentPresenter.TYPE_SetSubTabPanelContent);
        getView().setUiHandlers(tabBar);
        this.tabBar = tabBar;
}
  • DataCenterSubTabPanelPresenter 中定义了插槽、选项卡显示和选项卡选择处理。
@RequestTabs
 public static final Type<RequestTabsHandler> TYPE_RequestTabs = new Type<RequestTabsHandler>();

@ChangeTab
public static final Type<ChangeTabHandler> TYPE_ChangeTab = new Type<ChangeTabHandler>();

@ContentSlot
public static final Type<RevealContentHandler<?>> TYPE_SetTabContent = new Type<RevealContentHandler<?>>();
  • 二级选项卡 SubTabDataCenterClusterPresenter 中定义了可以插入的插槽接口。
@Inject
public SubTabDataCenterClusterPresenter(EventBus eventBus, ViewDef view, ProxyDef proxy,
            PlaceManager placeManager,
            SearchableDetailModelProvider<VDSGroup, DataCenterListModel, DataCenterClusterListModel> modelProvider) {
        super(eventBus, view, proxy, placeManager, modelProvider, DataCenterSubTabPanelPresenter.TYPE_SetTabContent);
}
  • 同样定义了代码分隔和名称令牌。
@ProxyCodeSplit
    @NameToken(ApplicationPlaces.dataCenterClusterSubTabPlace)
    public interface ProxyDef extends TabContentProxyPlace<SubTabDataCenterClusterPresenter> {
}
  • 同样定义了选项卡面板内容,指定了容器。
@TabInfo(container = DataCenterSubTabPanelPresenter.class)
static TabData getTabData(ApplicationConstants applicationConstants, SearchableDetailModelProvider<VDSGroup, DataCenterListModel, DataCenterClusterListModel> modelProvider) {
        return new ModelBoundTabData(applicationConstants.dataCenterClusterSubTabLabel(), 3, modelProvider);
}
  • 二级选项卡内容页面 SubTabDataCenterClusterView(V)与 SubTabDataCenterClusterPresenter 也需要在 PresenterModule 中进行绑定。
    • 界面布局创建方式与一级选项卡设置页面内容方式一致。
  • SubTabDataCenterClusterPresenter 需要继承 AbstractSubTabPresenter 类。
  • SubTabDataCenterClusterView 需要继承 AbstractSubTabTableView 类。
管理员门户界面面板中添加按钮并且弹出窗口
  • DataCenterListModel(M)中,添加 UICommand。
private UICommand privateNewCommand;

public UICommand getNewCommand()
{
    return privateNewCommand;
}
  • 设置 DataCenterListModel 与 UICommand 的关联关系。
setNewCommand(new UICommand("New", this)); //$NON-NLS-1$
  • executeCommand 中定义执行事件。
@Override
public void executeCommand(UICommand command) {
        super.executeCommand(command);

        if (command == getNewCommand())
        {
            newEntity();
        }
......
  • 执行事件中务必设置 setWindow,使得 window 属性不能为空。
    • 通过 setWindow 方法建立 DataCenterListModel 与新建模型 DataCenterModel 的关联关系。
public void newEntity() {
        if (getWindow() != null)
        {
            return;
        }

        DataCenterModel model = new DataCenterModel();
        setWindow(model);
        model.setTitle(ConstantsManager.getInstance().getConstants().newDataCenterTitle());
        model.setHelpTag(HelpTag.new_data_center);
        model.setHashName("new_data_center"); //$NON-NLS-1$
        model.setIsNew(true);

        UICommand tempVar = new UICommand("OnSave", this); //$NON-NLS-1$
        tempVar.setTitle(ConstantsManager.getInstance().getConstants().ok());
        tempVar.setIsDefault(true);
        model.getCommands().add(tempVar);
        UICommand tempVar2 = new UICommand("Cancel", this); //$NON-NLS-1$
        tempVar2.setTitle(ConstantsManager.getInstance().getConstants().cancel());
        tempVar2.setIsCancel(true);
        model.getCommands().add(tempVar2);
}
  • 之前新建弹出窗口的 ProviderDataCenterModulegetDataCenterListProvider 方法中进行了定义。
    • 通过 Provider 方式构建。
Provider<DataCenterPopupPresenterWidget> popupProvider
  • 通过 Providerget 方法获得 DataCenterPopupPresenterWidget

  • DataCenterPopupPresenterWidget(P)与 DataCenterPopupView(V)在 PresenterModule 中绑定。

bindPresenterWidget(DataCenterPopupPresenterWidget.class,
DataCenterPopupPresenterWidget.ViewDef.class,
DataCenterPopupView.class);
  • DataCenterPopupView 完成新建弹出窗口控件的定义,DataCenterPopupView.ui.xml 完成新建弹出窗口控件的布局等。

engine 中 GWT 配置

配置是否混淆

  • engine 中在 webadmin-modules 项目的 pom.xml 文件中,可以配置是否对前端 js 进行混淆。
<configuration>
            <logLevel>INFO</logLevel>
            <style>OBF</style>
            <port>${engine.port.http}</port>
            <noServer>true</noServer>
            <bindAddress>0.0.0.0</bindAddress>
            <gen>${gwtGenDirectory}</gen>
            <extraJvmArgs>${aspectj.agent} ${gwt-plugin.extraJvmArgs} ${gwt.dontPrune}</extraJvmArgs>
            <copyWebapp>true</copyWebapp>
            <strict>true</strict>
            <compileSourcesArtifacts>
              <compileSourcesArtifact>${engine.groupId}:gwt-extension</compileSourcesArtifact>
              <compileSourcesArtifact>${engine.groupId}:uicommonweb</compileSourcesArtifact>
            </compileSourcesArtifacts>
            <!--Why asm is excluded? -->
            <runClasspathExcludes>
              <runClasspathExclude>asm-3.3.jar</runClasspathExclude>
            </runClasspathExcludes>
</configuration>
style 说明
DETAILED 不混淆,输出格式化的 js,同时携带 Java 类中的详细信息。
PRETTY 不混淆,输出格式化的 js。
OBF 混淆

配置编译范围

  • engine 的 Makefile 文件中默认编译所有浏览器和本地语言(国际化)。
BUILD_FLAGS:=$(BUILD_FLAGS) -P all-langs
  • 编译只编英文和 firefox 浏览器。
BUILD_FLAGS:=$(BUILD_FLAGS) -D gwt.locale=en_US -D gwt.userAgent=gecko1_8
  • Makefile 文件中配置 -D gwt.style=DETAILED,也可以设置是否混淆。

相关文章

网友评论

    本文标题:【Ovirt 笔记】前端 GWT 使用分析与整理

    本文链接:https://www.haomeiwen.com/subject/lipdzxtx.html