美文网首页程序员
【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