Echo2框架源码分析
Echo2是一个基于Ajax技术的组件式web框架 ,其代码实现中的主要概念有:
1. WebContainerServlet基类。
a. Echo2的每个应用界面都通过对应的容器servlet来进行访问。
b. 每个Echo2应用都需要继承WebContainerServlet基类,并实现newApplicationInstance()函数,在这个函数中创建Echo2应用程序实例。
c. 在目前Echo2的实现当中,webcontainer功能实现的一部分被剥离到webrender包中,例如WebContainerServlet从WebRenderServlet继承。
2. Service接口。
interface Service{
String getId();
int getVersion();
void service(nextapp.echo2.webrender.Connection connection);
}
a. WebContainerServlet对外提供的服务被分解为一个个通用的Service对象。由service对象实现web调用模型与Echo2组件模型之间的连接,并将Echo2组件模型转换为html界面描述。Echo2框架中的其他对象都与网络无关。
b. 每个可调用的service对象都需要在WebContainerServlet中的静态(全局)的ServiceRegistry中注册或者在基于每个应用界面的ContainerInstance中注册。
c. service采用singleton模式,无状态。
d. 缺省提供的服务有:
Echo2核心服务,同步客户端与服务器端的状态。这个服务对象会调用各个Component对应的Peer对象来完成操作。
serviceId=Echo.Synchronize ==> ContainerSynchronizeService
serviceId=Echo.Expired ==> SessionExpiredService
返回Echo2客户端的引擎脚本ClientEngine.js。在Echo2框架中每一个js文件也对应于一个service对象,便于前台ajax装载。
serviceId=Echo.ClientEngine ==> JavaScriptService
Echo2的初始化界面
serviceId=Echo.Default ==> WindowHtmlService
创建一个新的Echo2应用界面
serviceId=Echo.NewInstance ==> NewInstanceService
serviceId=Echo.AsyncMonitor ==> AsyncMonitorService
返回Echo2自带调试器的页面Debug.html
serviceId=Echo.Debug ==> StaticTextService
处理过程:
Echo2的基本web访问格式为 container_servlet_url?serviceId=[registered service id]&other_parameters
在container servlet中,处理过程非常简单。
Service service = WebContainerServlet.getServiceRegistry().get(serviceId);
if (service == null && userInstance != null) {
service = userInstance.getServiceRegistry().get(id);
}
service.service(new Connection(this,request,response));
3. ContainerInstance类。
在NewInstanceService服务中被创建,用来存储所有与某个Echo2应用相关的数据。
ContainerInstance的主要成员变量有:
String applicationUri; Echo2应用程序对应的servlet的url,例如/myapp
ApplicationInstance applicationInstance; Echo2应用程序实例对象
HttpSession session; ContainerInstance被保存在session中。
4. ApplicationInstance基类。
每个Echo应用界面都继承这个基类,并在init()函数中构造缺省主窗口。
ApplicationInstance的主要成员变量有
Window defaultWindow; 唯一的主窗口
WeakReference focusedComponent; 当前焦点所在组件
UpdateManager updateManager; 记录一次调用过程中对Echo2界面结构所造成的更新。
这些更新将会以xml格式传输到浏览器端进行界面同步。
List modalComponents; 进入modal状态的组件
ApplicationInstance对象在NewInstanceService服务中被创建。它与ContainerInstance的关系是:与网络无关的部分被抽象出来成为applicationInstance。
5. Component基类
每一个界面组件在服务器端都对应于一个Component对象。
Component的主要成员变量有:
String id;
String renderId; // 加上前缀c_之后对应于生成的html中元素的id, 它与Component.id没有直接关系。
List children;
Component parent;
ApplicationInstance applicationInstance; // 组件在此applicationInstance中注册。
MutableStyle localStyle; // 保存该Component的各种属性
EventListenerList listenerList; // 注册的事件响应函数
我们在创建Component对象之后对其属性进行任何修改都会调用 applicationInstance.notifyComponentPropertyChange()函数,从而这些改变将被收集到applicationInstance.updateManager对象中。在一次调用完毕之后,ContainerSynchronizeService将会根据这些改变将对应的界面变化返回给浏览器端的ClientEngine。
6. ComponentSynchronizePeer接口。
Peer具体实现浏览器端与服务器端Component的状态同步。它基本的功能是根据Component的状态信息生成html。(Echo2中并不直接输出html文本,而是不断向一个DOM文档中追加节点,最后再序列化为文本)。
Peer可以选择实现ActionProcessor接口,以响应用户输入事件,例如按下按钮。
Peer对象采用singleton模式,需要在全局的SynchronizePeerFactory中注册。ContainerSynchronizeService 运行时将会根据Component的Class映射到对应的Peer, 如果不存在,则递归的查找父类对应的Peer。
7. ClientEngine.js
Echo2前台通过ClientEngine这个Ajax引擎与服务器端交互。
一个具体的访问过程如下:
前台访问url : /myapp
webContainerServlet.doGet(request,response)
webContainerServlet.process(request,response)
NewInstanceService.service(connection)
applicationInstance := webContainerServlet.newApplicationInstance()
session.setAttribute("Echo2UserInstance:/myapp", applicationInstance)
applicationInstance.doInit
window := applicationInstance.init()
applicationInstance.setStyleSheet();
applicationInstance.doValidation()
WindowHtmlService.serivce(connection)
render applicationInstance.defaultWindow
至此生成 前台Echo2启动页面
<body onload="EchoClientEngine.init('/myapp');">
<form style="padding:0px;margin:0px;" action="#" id="c_0" onsubmit="return false;" />
</body>
接着前台js中
EchoClientEngine.init(baseServerUri)
将客户端程序版本等信息打包为EchoClientMessage之后通过xmlhttp以post方式发送到/myapp?serviceId=Echo.Synchronize
<messagepart processor="EchoClientAnalyzer"><property type="text" name="navigatorAppName"
value="Netscape"/>...</messagepart>
服务器端处理:
ContainerSynchronizeService.service
this.renderInit(clientMessage,serverMessage);
this.processClientMessage(clientMessage);
ContentPane content = applicationInstance.getDefaultWindow().getContent());
componentUpdate = new ServerComponentUpdate(content);
syncPeer = SynchronizePeerFactory.getPeerForComponent(content);
syncPeer.renderAdd(serverMessage,componentUpdate, targetId,content);
serverMessage.render(response.getWriter());
客户端接收到消息内容大致为
<messagepart processor="EchoDomUpdate"><domadd parentid="c_0">...</domadd>
</messagepart>
用户点击前台按钮之后,ClientEngine向服务器发送消息/myapp?serviceId=Echo.Synchronize
<clientmessage><messagepart processor="EchoAction">
<action componentid="c_8" name="click"/>
</messagepart></clientmessage>
服务器端处理:
ContainerSynchronizeService.service
this.renderUpdate(clientMessage, serverMessage);
this.processClientMessage(clientMessage);
actionProcessor.process(containerInstance, clientMessage.actionElement);
syncPeer = SynchronizePeerFactory.getPeerForComponent(actionElement.componentId);
((ActionProcessor)syncPeer).process(containerInstance,component,actionElement);
applicationInstance.updateManager.processClientUpdates();
actionComponent.processInput();
getButtonModel().fireActionPerformed();
AbstractButton.fireActionPerformed(); 触发在Component中注册的事件监W人S听Xw B器
this.processServerUpdates(); 根据updateManager收集到的更新信息生成更新消息。
for each update
updatedCompenent.peer.renderUpdate();
serverMessage.render(response.getWriter());279