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 : /myappwebContainerServlet.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