文前说明
作为码农中的一员,需要不断的学习,我工作之余将一些分析总结和学习笔记写成博客与大家一起交流,也希望采用这种方式记录自己的学习之旅。
本文仅供学习交流使用,侵权必删。
不用于商业目的,转载请注明出处。
分析整理的版本为 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 前端代码分为了 管理门户 和 用户门户 两个部分。
- 项目中也划分了两个工程 webadmin 和 userportal。
- 两个工程中各自通过 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
- 初始化 CommonModelManager
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 主要维护了 Presenter 和 View 的绑定和关联关系。
- 在 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,建立了 MainModelProvider 与 VmListModel(M)的关联关系。通过 MainModelProvider 的 getModel 方法,就能获得 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()),执行的是 WebAdminButtonDefinition 的 onClick 方法。
- 执行了 UiCommand 的 execute 方法。
- 最终执行了 target(VmListModel)的 executeCommand 方法。
- 执行了 UiCommand 的 execute 方法。
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);
}
}
}
-
VmListModel 的 executeCommand 方法,根据判断不同的 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);
}
-
VirtualMachineModule 的 getVmListProvider 方法,返回 MainTabModelProvider
- ModelBoundPopupHandler 构造函数中建立了事件与 Provider 的关联关系。
- 指定 MainTabModelProvider 为 ModelBoundPopupHandler 的 Resolver。
- 通过 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 执行。
- 返回 VmListModel 的 getWindow 结果。
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();
- 将 RoleListModel 在 CommonModel 中实例化。
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);
}
- MainTabPanelPresenter 在 PresenterModule 中也进行了 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 之前通过 DataCenterModule 的 getDataCenterListProvider 方法绑定给到了 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);
}
- 之前新建弹出窗口的 Provider 在 DataCenterModule 的 getDataCenterListProvider 方法中进行了定义。
- 通过 Provider 方式构建。
Provider<DataCenterPopupPresenterWidget> popupProvider
-
通过 Provider 的 get 方法获得 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,也可以设置是否混淆。
网友评论