文前说明
作为码农中的一员,需要不断的学习,我工作之余将一些分析总结和学习笔记写成博客与大家一起交流,也希望采用这种方式记录自己的学习之旅。
本文仅供学习交流使用,侵权必删。
不用于商业目的,转载请注明出处。
分析整理的版本为 Ovirt 3.4.5 版本。
Ovirt 前端采用了 Google 的 GWT 的框架 Gwtp,前端代码很大一部门是 Java 代码,最后编译成 js 在客户端的浏览器中运行。
Guice 的使用
- 使用 Maven,添加依赖项
com.google.inject
guice
4.1.0
- 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)
- 前端代码采用了 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 , V extends View, Proxy_ extends Proxy
> void bindPresenter(
Class
presenterImpl, Class view, Class extends V> viewImpl,
Class 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 getVmListProvider(EventBus eventBus,
Provider defaultConfirmPopupProvider,
final Provider assignTagsPopupProvider,
final Provider makeTemplatePopupProvider,
final Provider maintainProvider,
final Provider runOncePopupProvider,
final Provider changeCDPopupProvider,
final Provider exportPopupProvider,
final Provider createSnapshotPopupProvider,
final Provider migratePopupProvider,
final Provider newVmPopupProvider,
final Provider newVmListPopupProvider,
final Provider addPermissionsPopupProvider,
final Provider guidePopupProvider,
final Provider removeConfirmPopupProvider,
final Provider vmRemoveConfirmPopupProvider,
final Provider reportWindowProvider,
final Provider consolePopupProvider,
final Provider vncWindoProvider) {
return new MainTabModelProvider(eventBus, defaultConfirmPopupProvider, VmListModel.class) {
@Override
public AbstractModelBoundPopupPresenterWidget 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 getMainListModel(
CommonModel commonModel, Class 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 modelProvider) {
super(eventBus, view, proxy, placeManager, modelProvider);
}
- 同样的方法,实现 Provider 在 View 中的注入。
@Inject
public MainTabVirtualMachineView(MainModelProvider 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 createActionTable() {
return new SimpleActionTable(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(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) 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 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 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 defaultConfirmPopupProvider) {
this.eventBus = eventBus;
// Configure UiCommon dialog handler
this.popupHandler = new ModelBoundPopupHandler(this, eventBus);
this.popupHandler.setDefaultConfirmPopupProvider(defaultConfirmPopupProvider);
// Add handler to be notified when UiCommon models are (re)initialized
eventBus.addHandler(UiCommonInitEvent.getType(), new UiCommonInitHandler() {
@Override
public void onUiCommonInit(UiCommonInitEvent event) {
TabModelProvider.this.onCommonModelChange();
}
});
eventBus.addHandler(CleanupModelEvent.getType(), new CleanupModelEvent.CleanupModelHandler() {
@Override
public void onCleanupModel(CleanupModelEvent event) {
if (hasModel()) {
//Setting eventbus to null will also unregister the handlers.
getModel().setEventBus(null);
}
}
});
}
public ModelBoundPopupHandler(ModelBoundPopupResolver 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) 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);
void revealPopup(final T model,
final AbstractModelBoundPopupPresenterWidget popup) {
assert (model != null) : "Popup model must not be null"; //$NON-NLS-1$
// Initialize popup
popup.init(model);
// Add "PROGRESS" property change handler to Window model
model.getPropertyChangedEvent().addListener(new IEventListener() {
@Override
public void eventRaised(Event ev, Object sender, EventArgs args) {
PropertyChangedEventArgs pcArgs = (PropertyChangedEventArgs) args;
if (PropertyChangedEventArgs.Args.PROGRESS.toString().equals(pcArgs.propertyName)) { //$NON-NLS-1$
updatePopupProgress(model, popup);
}
}
});
updatePopupProgress(model, popup);
// Reveal popup
RevealRootPopupContentEvent.fire(eventBus, popup);
}
场景实现分析
门户界面中页面头添加新的链接窗口实现
- 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 的配置文件的布局关联。
- 选项卡面板中的内容可以单独定义一个视图类(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 defaultConfirmPopupProvider,
final Provider rolePopupProvider,
final Provider removeConfirmPopupProvider) {
super(eventBus, defaultConfirmPopupProvider);
this.rolePopupProvider = rolePopupProvider;
this.removeConfirmPopupProvider = removeConfirmPopupProvider;
}
- Provider
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 getDataCenterListProvider(EventBus eventBus,
Provider defaultConfirmPopupProvider,
final Provider popupProvider,
final Provider guidePopupProvider,
final Provider removeConfirmPopupProvider,
final Provider recoveryStorageConfirmPopupProvider,
final Provider reportWindowProvider,
final Provider forceRemovePopupProvider) {
return new MainTabModelProvider(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 {
}
- 选项卡面板通过 @TabInfo 注解绑定选项卡面板内容并且指定显示容器。
@TabInfo(container = MainTabPanelPresenter.class)
static TabData getTabData(ApplicationConstants applicationConstants,
MainModelProvider 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> TYPE_SetTabContent = new Type>();
- 通过 @RequestTabs 和 @ChangeTab 定义了选项卡显示和选项卡选择处理。
@RequestTabs
public static final Type TYPE_RequestTabs = new Type();
@ChangeTab
public static final Type TYPE_ChangeTab = new Type();
- MainTabDataCenterPresenter 基类中定义了可以插入的插槽接口。
- 根据不同的名称令牌更新插槽中的内容。
public AbstractMainTabPresenter(EventBus eventBus, V view, P proxy, PlaceManager placeManager, MainModelProvider 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> TYPE_SetMainTabPanelContent = new Type>();
@ContentSlot
public static final Type> TYPE_SetSubTabPanelContent = new Type>();
-
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 requestTabsEventType,
Type 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 TYPE_RequestTabs = new Type();
@ChangeTab
public static final Type TYPE_ChangeTab = new Type();
@ContentSlot
public static final Type> TYPE_SetTabContent = new Type>();
- 二级选项卡 SubTabDataCenterClusterPresenter 中定义了可以插入的插槽接口。
@Inject
public SubTabDataCenterClusterPresenter(EventBus eventBus, ViewDef view, ProxyDef proxy,
PlaceManager placeManager,
SearchableDetailModelProvider modelProvider) {
super(eventBus, view, proxy, placeManager, modelProvider, DataCenterSubTabPanelPresenter.TYPE_SetTabContent);
}
- 同样定义了代码分隔和名称令牌。
@ProxyCodeSplit
@NameToken(ApplicationPlaces.dataCenterClusterSubTabPlace)
public interface ProxyDef extends TabContentProxyPlace {
}
- 同样定义了选项卡面板内容,指定了容器。
@TabInfo(container = DataCenterSubTabPanelPresenter.class)
static TabData getTabData(ApplicationConstants applicationConstants, SearchableDetailModelProvider 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 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 进行混淆。
INFO
${engine.port.http}
true
0.0.0.0
${gwtGenDirectory}
${aspectj.agent} ${gwt-plugin.extraJvmArgs} ${gwt.dontPrune}
true
true
${engine.groupId}:gwt-extension
${engine.groupId}:uicommonweb
asm-3.3.jar
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,也可以设置是否混淆。