82
Exodus2 Exodus2 大局观 大局观 内容纲要: 1 Exodus2 的历史 2 WebX 大局观 3 Exodus2 的部署 4 ,数据访问层 hongjiang 2009.5.16

Exodus2 大局观

Embed Size (px)

Citation preview

Page 1: Exodus2 大局观

Exodus2 Exodus2 大局观大局观内容纲要:

1 , Exodus2 的历史2 , WebX 大局观 3 , Exodus2 的部署4 ,数据访问层

hongjiang2009.5.16

Page 2: Exodus2 大局观

1, Exodus21, Exodus2 的历史的历史

Exodus2Exodus2 代号名称的由来:代号名称的由来:

不知道大家注意过没有,不知道大家注意过没有, exodusexodus 和和 ezra(BOPSezra(BOPS 的代号的代号 )) 都是都是

出自圣经,容易联想到这两个产品的设计者可能是基督教信仰出自圣经,容易联想到这两个产品的设计者可能是基督教信仰

者或者深谙圣经。者或者深谙圣经。

校长: 校长:

ExodusExodus 是《圣经》出埃及记那一章。摩西带领犹太人走出埃是《圣经》出埃及记那一章。摩西带领犹太人走出埃

及是一段非常艰辛的过程。早些年及是一段非常艰辛的过程。早些年 aliali 曾经比较崇拜国外的技曾经比较崇拜国外的技

术,雇佣硅谷的一些工程师开发过一些东西,后来证明并不好术,雇佣硅谷的一些工程师开发过一些东西,后来证明并不好

用,后由宝宝带领开发出一套新的框架。这个过程是有些类似用,后由宝宝带领开发出一套新的框架。这个过程是有些类似

于出埃及的过程的。于出埃及的过程的。 exodus2exodus2 是是 exodusexodus 后来的重构版本。后来的重构版本。

Page 3: Exodus2 大局观

Exodus2Exodus2 的历史的历史

即使一些工作即使一些工作 22 ,, 33 年以上的同事,谈及年以上的同事,谈及Exodus2Exodus2 的时候,也会说“水很深”。越的时候,也会说“水很深”。越深入的了解,发现它的确有很多模糊或者深入的了解,发现它的确有很多模糊或者困惑的地方可能是历史遗留原因,我们需困惑的地方可能是历史遗留原因,我们需要弄清楚,避免一些错误。要弄清楚,避免一些错误。

这个框架不仅这个框架不仅 aliali 在用,淘宝,支付宝也在在用,淘宝,支付宝也在用,有些因素可能是其他公司有特殊的需用,有些因素可能是其他公司有特殊的需求,使得框架有所妥协而违背原先的设计求,使得框架有所妥协而违背原先的设计。。

Page 4: Exodus2 大局观

2. WebX2. WebX 大局观大局观

2.12.1 ,, SpringSpring 框架框架

2.22.2 ,, ServiceService 框架框架

2.32.3 ,, WebXWebX 的表现层的表现层

2.42.4 ,, WebXWebX 的业务层的业务层

2.52.5 ,表现层和业务层的交互,表现层和业务层的交互 ::CommandDispatcher—CommandDispatcher— 忧愁河上的桥忧愁河上的桥

Page 5: Exodus2 大局观

2.1 Spring 2.1 Spring 框架框架

核心:核心: DIDI 和和 AOPAOP

Page 6: Exodus2 大局观

IoC容器 AOP框架

DAO JMS事务 远程 JNDI

Spring应用框架

J2EE应用程序

基于POJO简单开发

消除重复代码

支持企业服务

Spring 框架布局

Page 7: Exodus2 大局观

SpringSpring 容器:容器:

SpringSpring 容器有多种实现,分两类容器有多种实现,分两类 :: 1: BeanFactory1: BeanFactory 2: ApplicationContext 2: ApplicationContext (( 扩展了扩展了 BeanFactory,BeanFactory, 提供了更多功能,提供了更多功能,

绝大部分应用都使用绝大部分应用都使用

ApplicationContext)ApplicationContext)

Page 8: Exodus2 大局观

依赖注入:依赖注入:

依赖注入是依赖注入是 SpringSpring 运用反向控制运用反向控制

原则解决配置管理和对象关系管理原则解决配置管理和对象关系管理

的手段。的手段。

依赖注入的方式:

1: setter 方式 2: 构造器方式

3: 方法注入,抽象方法

Page 9: Exodus2 大局观

对对 AOPAOP 的支持,的支持,

用代理类包裹切面用代理类包裹切面

SpringSpring 基于代理实现对基于代理实现对 AOPAOP 的支持的支持通过通过 J2SE J2SE dynamic proxiesdynamic proxies ,可以代理任意,可以代理任意 Java Java 接口。接口。

通过通过 CGLIBCGLIB字节码生成,可以代理字节码生成,可以代理 JavaJava 类。类。

SpringSpring 的代理机制可以支持由的代理机制可以支持由 SpringSpring 容器创建的容器创建的beanbean ,无法支持应用中自行,无法支持应用中自行 newnew 出来的出来的 beanbean 。。

SpringSpring只支持方法连接点。只支持方法连接点。

调用者

代理

目标

Page 10: Exodus2 大局观

BeanBean如何和如何和 SpringSpring 容器交容器交

互互

33 个个 WareWare接口:《骇客帝国》中的红药丸接口:《骇客帝国》中的红药丸

BeanNameAwareBeanNameAware : 了解: 了解 BeanBean 的名称的名称

BeanFactoryAware: BeanFactoryAware: 了解容器了解容器

ApplicationContextAware: ApplicationContextAware: 了解容器 了解容器

Page 11: Exodus2 大局观

2.2 Service FrameWork2.2 Service FrameWork

和和 SpringSpring 有些相似的一套框架,有些相似的一套框架,

用来管理用来管理 ServiceService 对象,负责其生命周期,根对象,负责其生命周期,根据配置来装载据配置来装载 ServiceService ,可以确保,可以确保 ServiceService之间之间

的耦合是松散的的耦合是松散的

Page 12: Exodus2 大局观

ServiceService 是什么?是什么?

没什么特别的,就是一些对象没什么特别的,就是一些对象 (( 实实现了现了 ServiceService接口而已接口而已 )) ,不过这,不过这

些对象在些对象在 WebXWebX中都是一些比较通中都是一些比较通用的服务或者工具。用的服务或者工具。

Page 13: Exodus2 大局观

WebxWebx正常运行所必须的正常运行所必须的 ServiceService

Velocity 模板引擎,用来渲染 velocity写成的页面模板。需要 TemplateService 的支持。

VelocityService

用来上传图片和文件的 service 。UploadService

自动生成 URI 的 service 。URIBrokerService

线程空间的 singleton 服务,方便 service存取 request scope 的对象。ThreadContextService

用来管理 template engine ,渲染页面的 service 。TemplateService

Webx 核心 service之一,用来保存每一个 HTTP请求的状态。需要 PoolService和 UploadService 的支持。

RunDataService

用来装载资源的 Resource loader service 。ResourceLoaderService

Pull model service ,自动创建一定作用域的对象,而不需要应用程序干预( push)。

PullService

缓存以减少对象创建的开销。PoolService

用来创建 pipeline 的 service 。PipelineService

装载 turbine module 的 service ,例如: screen、 action、 control等。ModuleLoaderService

将一个名字映射到另一个名字的 service 。MappingService

JSP 模板引擎,用来渲染 jsp写成的页面模板。需要 TemplateService 的支持。JSPService

读取并验证用户 submit 的 form 表单的 service 。FormService

Page 14: Exodus2 大局观

Service概念 (摘自《 Exodus2 代码分析初步》 )

一个服务 (Service) 可以有多个服务实例

一个实例 (Instance) 可以包含多个服务,并有一个实例上下文(InstanceContext)

同一个实例的所有服务共享该实例上下文

在 Webx中:每个 WebxComponent 都是一个实例

每个 WebxComponent 都由一个 webx.xml 配置其中的服务

每个 WebxComponent 都继承缺省实例的配置,缺省实例由 webx-default.xml 和webx-turbine-default.xml 的来合并定义

Page 15: Exodus2 大局观

类图结构类图结构

Page 16: Exodus2 大局观

ServiceService 容器结构容器结构

Page 17: Exodus2 大局观

假设我们有假设我们有 22 个个 carcar 。。我们为全局配置了我们为全局配置了 A,BA,B 两个服两个服

务;为务;为 car1car1 配置了配置了 CC 服务服务

;为;为 car2car2 配置了配置了 DD 服务。服务。

ServiceManagerServiceManager初始化后初始化后

容器中会是怎样?容器中会是怎样?

Page 18: Exodus2 大局观

是这样么是这样么 ??

Car1 Car2

A B

C D

Page 19: Exodus2 大局观

注意:注意: ServiceInstanceServiceInstance 是对是对

ServiceService 的包装的包装

ServiceInstance Service

ServiceInstanceContext

ServiceConfig

Page 20: Exodus2 大局观

它有一个将未申明它有一个将未申明 ServiceService补全的过程。是这样补全的过程。是这样

??

ServiceInstancA ServiceInstanceB ServiceInstanceC ServiceInstanceD

ServiceInstancA

ServiceInstanceB

ServiceInstanceC

ServiceInstanceD

ServiceInstancA

ServiceInstanceB

ServiceInstanceC

ServiceInstanceD

Page 21: Exodus2 大局观

// 1. // 1. 如果是如果是 main-instancemain-instance ,并且指定了,并且指定了 classnameclassname ,则实例化,则实例化 main-instancemain-instance 。。 // 2. // 2. 如果是如果是 main-instancemain-instance ,并且未指定,并且未指定 classnameclassname ,则返回,则返回 nullnull 。。

// 3. // 3. 如果是如果是 sub-instancesub-instance ,并且指定了,并且指定了 classnameclassname ,那么:,那么: // 3.1 // 3.1 如果是如果是 MultiInstanceMultiInstance ,如果和,如果和 main-instancemain-instance 的类相同,则同的类相同,则同 4.34.3 。。 // // 否则报错。(这样做是为了兼容性好,特别是当一个原来不是否则报错。(这样做是为了兼容性好,特别是当一个原来不是

MultiInstanceMultiInstance 的类转换成的类转换成 MISMIS 时)时) // 3.2 // 3.2 如果如果 main-instancemain-instance 不存在,则实例化不存在,则实例化 service instanceservice instance 。。 // 3.3 // 3.3 如果如果 main-instancemain-instance存在,但不是存在,但不是 MultiInstanceMultiInstance ,则实例化,则实例化 service service instanceinstance 。。 // 3.4 // 3.4 如果如果 main-instancemain-instance存在,而且是存在,而且是 MultiInstanceMultiInstance ,则报错。,则报错。 // 4. // 4. 如果是如果是 sub-instancesub-instance ,并且未指定,并且未指定 classnameclassname ,那么:,那么: // 4.1 // 4.1 如果如果 main-instancemain-instance 不存在,则返回不存在,则返回 nullnull // 4.2 // 4.2 如果如果 main-instancemain-instance存在,但不是存在,但不是 MultiInstanceMultiInstance ,则用,则用 main-instancemain-instance 的的类实例化类实例化 service instanceservice instance 。。 // 4.3 // 4.3 如果如果 main-instancemain-instance存在,而且是存在,而且是 MultiInstanceMultiInstance ,则调用,则调用 main-main-instanceinstance 的的 getInstancegetInstance 方法。方法。

ServiceInstance 是对 Service 的包装。那么从ServiceInstance中如何获取 Service 对象?不同的ServiceInstance 会共享 Service么?看一下代码中的注释:

Page 22: Exodus2 大局观

假设我们的假设我们的 AA 服务实现了服务实现了 MultiInstanceMultiInstance接口,接口, BB 服务服务

没有。没有。

InstanceA InstanceB InstanceCA B null

A

B

C

null

A

B

null

D

InstanceDnull

Page 23: Exodus2 大局观

说明:可以参考说明:可以参考 DefaultServiceManagerDefaultServiceManager 的的 initMappinginitMapping函数,最后函数,最后

一段逻辑。一段逻辑。

Exdodus2Exdodus2中一共配置了中一共配置了 1212 个个 carcar ,, 2222 个个 ServiceService 。经过。经过

initMappinginitMapping 后,后, servicesservices 这个这个 MapMap中一共有中一共有 286286 个个 entryentry

而实现而实现 MultiInstanceMultiInstance接口的只有:接口的只有:

BootstrapResourceLoaderService,BootstrapResourceLoaderService,DefaultIPSeekerService, DefaultRunDataService, DefaultIPSeekerService, DefaultRunDataService, DefaultViewCacheService, DefaultViewCacheService, 这这 44 个个 ServiceService ,也就是说大部分,也就是说大部分

ServiceService每个每个 carcar 都会再创建自己的实例。这样在全局定义那么多都会再创建自己的实例。这样在全局定义那么多

ServiceService干嘛?比如全局的干嘛?比如全局的 PullServicePullService ,, TemplateServiceTemplateService 有什么作有什么作

用?用?

另外 : 在 webx-turbine-default.xml 中对 sub-instance定义了 FreeMarkerService ,会用到么?

Page 24: Exodus2 大局观

配置级联的配置级联的 SpringSpring 容器容器

我们说过,每个我们说过,每个 carcar里的里的 serviceservice 都是一份独立的都是一份独立的实例。同样,作为一个普通的实例。同样,作为一个普通的 ServiceService ,每个,每个 carcar中都会创建一份自己的中都会创建一份自己的 SpringSpring 容器。假如我有容器。假如我有1010 个个 carcar ,就有,就有 1010 个各不相干的个各不相干的 SpringSpring 容器。容器。

Page 25: Exodus2 大局观

但是,有一些但是,有一些 BeansBeans 是不能够这样做的,否则会占用不必要的是不能够这样做的,否则会占用不必要的

系统资源。例如,和系统资源。例如,和 cachecache 有关的有关的 beansbeans 。怎么解决这个问题。怎么解决这个问题

呢?有办法!我们可以把呢?有办法!我们可以把 SpringSpring 容器级联起来,使父容器被所容器级联起来,使父容器被所

有的有的 carcar共享。改进方案如图:共享。改进方案如图:

Page 26: Exodus2 大局观

要配置这种级联的要配置这种级联的 SpringSpring 容器非常简单:容器非常简单:

WARWAR └─ └─ WEB-INFWEB-INF

│ │webx.xmlwebx.xml - - 在此配置共享的在此配置共享的父容器父容器

│ │ ├─ ├─ car1car1

│ │webx.xmlwebx.xml - - 在此配置派生的在此配置派生的子容器子容器

└─ └─ car2car2 webx.xmlwebx.xml - - 在此配置派生的在此配置派生的子容器子容器

配置文件的写法完全同前例。这样,当查找一个配置文件的写法完全同前例。这样,当查找一个 beanbean 时时,, SpringSpring 会首先在子容器中查找,如果找不到,再到父会首先在子容器中查找,如果找不到,再到父容器里查找。你可以自由地决定,哪些容器里查找。你可以自由地决定,哪些 beansbeans 需要被共享需要被共享

,哪些,哪些 beansbeans 需要被某个需要被某个 carcar独享。独享。

Page 27: Exodus2 大局观

看看看看 ServiceService接口定义的接口定义的 getParentServicegetParentService 方法:方法:

对于对于 sub-instancesub-instance ,取得其,取得其 main-instancemain-instance ,否则返回,否则返回 nullnull 。。

注意:和注意:和 ServiceContext.getServiceServiceContext.getService 方法不同,即使方法不同,即使 parent instanceparent instance初始化失败,初始化失败, getParentServicegetParentService 也不会抛出异常,而是将也不会抛出异常,而是将 parent parent instanceinstance初始化失败的信息记录在初始化失败的信息记录在 loglog中。 中。

ServiceContext(ServiceContext(全局全局 ServiceService 容器容器 ))中:中:

getService(svcName)getService(svcName)getService(svcName, instanceName)getService(svcName, instanceName)注释中说:如果注释中说:如果 serviceservice 不存在,或初始化失败抛出不存在,或初始化失败抛出

ServiceInstantiationExceptionServiceInstantiationException但实际代码并未声明但实际代码并未声明 throws ServiceInstantiationExceptionthrows ServiceInstantiationExceptionto fix: to fix: 代码中的注释应该也修改一下。代码中的注释应该也修改一下。

Page 28: Exodus2 大局观

ServiceService 框架和框架和 SpringSpring 框架的异同框架的异同

1: Spring1: Spring 是个更通用的容器,容器装载的是个更通用的容器,容器装载的 BeanBean可 以是任何类型对象。而可 以是任何类型对象。而 ServiceService 容器中装载容器中装载的是的是 ServiceService 对象。对象。

2: Spring2: Spring 容器中的对象可以不必知道容器,而容器中的对象可以不必知道容器,而ServiceService 容器中容器中 serviceservice 对象是紧密和容器耦合的对象是紧密和容器耦合的。。

Page 29: Exodus2 大局观

ServiceService 框架究竟有没有实现“依框架究竟有没有实现“依

赖注入”赖注入”

《《 ServiceService 框架指南》中说框架指南》中说 ServiceService 框架不支持“注入”,而框架不支持“注入”,而 ModuleModule中却中却用到了注入。用到了注入。

宝宝的答复:宝宝的答复:

ServiceService 框架不支持注入。但是应工程师要求,在框架不支持注入。但是应工程师要求,在screen/action/controlscreen/action/control等部等部

件中加入了注入功能,是用件中加入了注入功能,是用 cglibcglib直接实现的,和直接实现的,和 serviceservice 框框架无关。架无关。

另外,我目前在做的工作是,将另外,我目前在做的工作是,将 webxwebx移植到纯移植到纯 springspring 框架框架中,抛弃中,抛弃 serviceservice 框架,框架,

因此注入从此不是问题了。因此注入从此不是问题了。

Page 30: Exodus2 大局观

2.3 WebX2.3 WebX 表现层表现层

1, RunData1, RunData :对:对 Req,Resp,SessionReq,Resp,Session 的封的封

装装

2, Module2, Module :页面编程模块:页面编程模块

3, Pipeline: 3, Pipeline: 逻辑流程逻辑流程

4, 4, 分析一个页面请求的流程分析一个页面请求的流程

Page 31: Exodus2 大局观

RunData: RunData: 简化对简化对 httphttp请求操作请求操作

RunDataRunData 实现了实现了 RequestContextRequestContext接接口。口。

RequestContextRequestContext体系是一个用装饰体系是一个用装饰

模式实现对功能的层层扩展。模式实现对功能的层层扩展。

Page 32: Exodus2 大局观

RequestContextRequestContext 的构建的构建 : : 封装了哪些功能?封装了哪些功能?

看看配置文件,对看看配置文件,对 RunDataServiceRunDataService 的装配:的装配:

构建一个构建一个 ChinaRunData,ChinaRunData, 它通过一个个的工厂类来进行装配它通过一个个的工厂类来进行装配(Decorate)(Decorate) ,负责调用这些工厂的是,负责调用这些工厂的是 RequestContextChainServiceRequestContextChainService这些工厂的设置顺序:这些工厂的设置顺序:

BufferedRequestContextFactory BufferedRequestContextFactory ↓↓

LazyCommitRequestContextFactory LazyCommitRequestContextFactory ↓↓

ParserRequestContextFactory ParserRequestContextFactory ↓↓

SessionRequestContextFactory SessionRequestContextFactory ↓↓

SetLocaleRequestContextFactory SetLocaleRequestContextFactory ↓↓

RewriteRequestContextFactory RewriteRequestContextFactory

Page 33: Exodus2 大局观

RewriteRequestContext

SetLocaleRequestContext

SessionRequestContext

ParserRequestContext

LazyCommitRequestContext

BufferedRequestContext

RequestContextImpl

ChinaRunData: ChinaRunData: 注意包装的顺序注意包装的顺序

Page 34: Exodus2 大局观

HttpSessionHttpSession中的问题:中的问题:

通过通过 BufferedRequestContextBufferedRequestContextLazyCommitRequestContextLazyCommitRequestContext防止了防止了 HttpResponseHttpResponse 的“自动输出”,使的“自动输出”,使得我们可以一直控制得我们可以一直控制 httphttp头,有机会改写头,有机会改写

cookiecookie 信息,实现信息,实现 sessionsession 的存储。的存储。

BufferedRequestContextBufferedRequestContext 是缓冲了整个是缓冲了整个

httphttp 的的 headhead 和和 contentcontent还是只缓冲了还是只缓冲了

headhead ??

如果遇到一个很大的页面会如何?如果遇到一个很大的页面会如何?

Page 35: Exodus2 大局观

WebXWebX中的中的 ModuleModule

WebxWebx中的页面逻辑编程模块。中的页面逻辑编程模块。

Screen, Action,Control,LayoutScreen, Action,Control,Layout 都实现都实现了了 ModuleModule接口。所有的接口。所有的 ModuleModule 都是通都是通

过过 ModuleLoaderServiceModuleLoaderService装载的。装载的。

Page 36: Exodus2 大局观

ModuleLoaderServiceModuleLoaderService 的装载的装载

和依赖注入和依赖注入

装载装载 Module: Module: 将创建各种将创建各种 modulemodule 的任务转交给 的任务转交给 ModuleFactoryModuleFactory 去做。它可以为每一种去做。它可以为每一种 modulemodule(( screen/control/actionscreen/control/action)指定一个或多个)指定一个或多个ModuleFactoryModuleFactory 来创建。来创建。

Module中的依赖注入 : 1: setter 方式。 2 : abstract getter 方式。

注入究竟是怎么实现的?

Page 37: Exodus2 大局观

如果不加特别的配置,那么如果不加特别的配置,那么 DefaultModuleFactoryDefaultModuleFactory已经可以支持将已经可以支持将 ServiceService注入到注入到 modulemodule中。例如在中。例如在 modulemodule中定义下面的中定义下面的 abstract getterabstract getter ::

  

protected abstract URIBrokerService getURIBrokerService();protected abstract URIBrokerService getURIBrokerService();

这句话告诉这句话告诉 DefaultModuleFactoryDefaultModuleFactory :将:将 URIBrokerServiceURIBrokerService 注入到注入到 modulemodule中中去。去。

对于抽象类,通过对于抽象类,通过 CGLIBCGLIB动态生成子类动态生成子类

。。DefaultModuleFactory 的 getModule 在获取 Module 实例时,调用

instantiateModule(String moduleClassName)判断 Module 是否 abstract class,如果是则用 CGLIB 来创建 Module子

类。 ( 非 abstract 则用反射方式 newInstance)随后 instantiateModule函数中,如果module 实例不为 null 则调用

injectDependences函数,加载所创建的 module 实例依赖的对象。 injectDependences 调用 propertySetter.invoke 来注入依赖对象

Page 38: Exodus2 大局观

如果 注入的不是如果 注入的不是 Service, Service, 而是一个而是一个 Bean, Bean, 则需要利用 则需要利用

ReferenceResolver ReferenceResolver 接接口 口

我们实现了我们实现了 SpringSpringReferenceResolver ReferenceResolver ,用来从,用来从 SpringSpring 容器中取得对容器中取得对

象。象。

也就是说也就是说 ModuleModule中注入中注入 ServiceService 对象,对象, WebxWebx 框架直框架直接实现,因为框架本身是一个接实现,因为框架本身是一个 ServiceService 容器,很容易获容器,很容易获

取到取到 ServiceService 对象。对象。

而而 ModuleModule中注入中注入 BeanBean 对象,则需要借助对象,则需要借助 SpringSpring 容器容器来获取,非来获取,非 ServiceService 对象都是通过对象都是通过 SpringSpring 容器托管的。容器托管的。

比如比如 CommandDispatherCommandDispather 这样的非这样的非 ServiceService 对象还有对象还有BizBiz 层的层的 AO,BOAO,BO等都是用等都是用 SpringSpring 容器来托管的。容器来托管的。

Page 39: Exodus2 大局观

模板的渲染:模板的渲染:

TemplateServer通过注册一个 Engine 来实现渲染 。渲染的顺序是: Screen->Layout->Control

渲染过程需要 TemplateContext , module 是从 rundata中获取 templateContext 的, rundata中需要通过

PullService 来获取。

TemplateEngineService

JspService

VelocityService FreeMarkerService

TemplateService

Page 40: Exodus2 大局观

Action/ScreenAction/Screen执行顺序执行顺序

Page 41: Exodus2 大局观

WebXWebX中的中的 PipelinePipeline

PipelinePipeline 是表现层逻辑流程的核心。是表现层逻辑流程的核心。

它是一种责任链模式,它是一种责任链模式, (X (X 它的代码实现上并不它的代码实现上并不是是 ) )

把处理从一个把处理从一个 ValveValve流向下一个流向下一个 ValveValve ,主要响,主要响

应应 action moduleaction module 的的 ValveValve 是是

PerformActionValvePerformActionValve 。。可以改变现有的 valve ,例如:改变分析 URL 的方法

可以插入新的 valve ,例如: Session处理、权限验证等。

Page 42: Exodus2 大局观

内部重定向的实现方式:内部重定向的实现方式: RedirectTargetValve RedirectTargetValve

根据返回值 ValveForward进行跳转

processModule(ChooseValve根据条件选择 PerformValve)

RedirectTargetValve

Page 43: Exodus2 大局观

try{try{ tryPipeline: tryPipeline:

}catch(Exception e){}catch(Exception e){ cachePipeline: cachePipeline:

}finally{}finally{ finallyPipeline: finallyPipeline:

}}

TryCacheFinallyValue:

Page 44: Exodus2 大局观

大峰的提问大峰的提问 ::PerformActionValve PerformActionValve 的安全性隐患?的安全性隐患?

Page 45: Exodus2 大局观

金立的补充:金立的补充:

在在 pipeline.xmlpipeline.xml 配置中,第二个配置中,第二个 valve:valve:<valve <valve class=class=“com.alibaba.exodus2.web.common.webx.valve.AuthContextValve”“com.alibaba.exodus2.web.common.webx.valve.AuthContextValve” /> />AuthContextValveAuthContextValve 的验证机制只是对的验证机制只是对 URLURL 而没有对后续的而没有对后续的 actionaction验证,验证,

比如:请求比如:请求 a.htm?action=a.htm?action=xx&event_submit_doXXX&event_submit_doXXXAuthContextAuthContext只验证了只验证了 a.htma.htm ,并没有验证后续的,并没有验证后续的 actionaction而用户有可能篡改后续的而用户有可能篡改后续的 actionaction ,比如:,比如:

a.htm?action=a.htm?action=yy&event_submit_doYYY&event_submit_doYYY

这样就执行到了这样就执行到了 Y ActionY Action里的逻辑。里的逻辑。

Page 46: Exodus2 大局观

分析一次页面请求经历的逻辑分析一次页面请求经历的逻辑

假设我们执行 offer post 并转向成功页面

1: 从 URL 的角度看2: 从 Pipeline 的角度看

Page 47: Exodus2 大局观

从从 URLURL 的角度看:的角度看:

只关注只关注 URLURL 的进和出的进和出

Request根据规则

Url RewriteMappingService映射到物理路径

业务执行模块

Response URIBrokerService构建要跳转的页面

Page 48: Exodus2 大局观

详细的看这个过程详细的看这个过程 ::

1: Webx 的初始化。2: RunData 的构造。3: Servlet把逻辑交给 Pipeline

Page 49: Exodus2 大局观

ServiceService 容器是被容器是被 WebxControllerListenerWebxControllerListener 所初始化所初始化

的,该的,该 ListenerListener 在在 WebWeb 容器初始化时执行。创建好容器初始化时执行。创建好

ServiceService 容器后,将其保存在容器后,将其保存在 ServletContextServletContext中。中。

Servlet请求之前,先被 RundataFilter拦截。它负责调用 RundataService把 Req/Resp/Session等封装成一个 RunData 对象,随后将其 setAttibute为 req 的属性,方便以后通过 req 得到 RunData 。

RunData

ServletContext 经过多重包装的Response

经过多重包装的Request

RequestAttribute

Page 50: Exodus2 大局观

从从 pipelinepipeline 的流程来看的流程来看::

Logging Auth HessianRemote

AnalyzeURL

CheckCSRF可选

GreenChanelTrace

ExpiredUrl

Action

Choose

TBStoreCheckCode

Redirect

Screen

ScreenTemplate

Action

Page 51: Exodus2 大局观

关于关于 PullServiePullServie

实现实现 Pull-MVCPull-MVC 的设计模式 的设计模式

怎么理解怎么理解 Pull-MVC,Pull-MVC, 和和 pushpush 的不同之处?的不同之处?

What this entails is the ability to create an What this entails is the ability to create an object that is able to “pull” the required object that is able to “pull” the required

information out at execution time within the information out at execution time within the template.template.

Page 52: Exodus2 大局观

2.4 WebX2.4 WebX 业务层业务层 (BIZ(BIZ层层 ))

11 :业务层的:业务层的 ServiceService 容器。是如何创建的?容器。是如何创建的?

22 :: AO,BO,DAOAO,BO,DAO 都在都在 SpringSpring 容器中。容器中。

AOAO每次请求时都是一个新的实例?每次请求时都是一个新的实例?

BOBO 是一些通用逻辑模块,可供多个是一些通用逻辑模块,可供多个 AOAO调用。调用。

Page 53: Exodus2 大局观

用用 SingletonSingleton启动启动 ServiceService容器容器

为了分离为了分离 WEBWEB 层和业务层的对象,我们常常层和业务层的对象,我们常常将将 WEBWEB 层的层的 ServiceService 容器和业务层的容器和业务层的 ServiceService容器彻底分开。这样,容器彻底分开。这样, WEBWEB 层的层的 SpringSpring 容器容器也随之和业务层的也随之和业务层的 SpringSpring 容器彻底分开了。如容器彻底分开了。如

图所示: 图所示:

Page 54: Exodus2 大局观

WebWeb 层和层和 BIZBIZ 层分离层分离Web 层 Service 容器

Biz 层 Service 容器

Car1

Spring 容器

Spring 容器

AO BO

Dispatcher

Car2

Spring 容器

Dispatcher

Page 55: Exodus2 大局观

用用 SingletonSingleton启动启动 ServiceService 容容

器器

上图中的上图中的 CommandDispatherCommandDispather 在初始化中,在初始化中,

会调用会调用 SingletonServiceManagerLoaderSingletonServiceManagerLoader 来来启动一个独立于启动一个独立于 WEBWEB 层的业务层层的业务层 ServiceService 容容

器,再从中取得业务层的器,再从中取得业务层的 SpringSpring 容器。所有容器。所有

业务层的对象,都配置在这个业务层的对象,都配置在这个 SpringSpring 容器中。容器中。

请记住,这是一个请记住,这是一个 SingletonSingleton ,意味着业务,意味着业务

层的层的 SpringSpring 容器只会被初始化一遍。 容器只会被初始化一遍。

Page 56: Exodus2 大局观

1.5 1.5 表现层和业务层的交互表现层和业务层的交互

CommandDispatherCommandDispather使用命令模式,封装了从表现层到业务层使用命令模式,封装了从表现层到业务层的调用,开发人员可以透明的对待它。的调用,开发人员可以透明的对待它。

它有多种实现方式,它有多种实现方式, Exodus2Exodus2 用的那种方用的那种方式?式?

Page 57: Exodus2 大局观

简单方式简单方式 ??

Page 58: Exodus2 大局观

改进方式改进方式 ??

Page 59: Exodus2 大局观

远程方式远程方式 ??

虽然由此设计,实际上我们所有的 web 和 biz 都是部署在同一个 JVM中的。

Page 60: Exodus2 大局观

异步方式异步方式 ??

Page 61: Exodus2 大局观

在确认之前还要弄清另外一个问题在确认之前还要弄清另外一个问题

:《:《 ServiceService 框架指南》中说框架指南》中说在在

webweb 层的层的 springspring 容器中包含容器中包含 bizbiz层的层的 springspring 容器的引用。容器的引用。

如何做到?如何做到?

Page 62: Exodus2 大局观

宝宝《宝宝《 Webx ModulesWebx Modules 的装载以及依赖注入》 的装载以及依赖注入》

中说:中说:

…… ……

然而还有一个问题。在我们的常见应用中,然而还有一个问题。在我们的常见应用中,

webweb 层和层和 bizbiz 业务层是分离的,业务层是分离的, webweb 层的层的

springspring 容器和容器和 bizbiz 层的层的 springspring 容器也是分离容器也是分离

的。上面的配置只能拿到的。上面的配置只能拿到 webweb 层层 springspring 容容器中的内容,不能拿到器中的内容,不能拿到 bizbiz 层层 springspring 容器的容器的

内容,怎么办呢?方法很简单:在内容,怎么办呢?方法很简单:在 webweb 层的层的

springspring 容器中包含容器中包含 bizbiz 层的层的 springspring 容器的引容器的引

用。用。

Page 63: Exodus2 大局观

既然是分层的既然是分层的 (web(web 层和层和 BizBiz 层分别存在各自的层分别存在各自的 ServiceService 容器中容器中 )) 。。WebWeb 层层 springspring 容器 又如何得到 容器 又如何得到 bizbiz 层的层的 SpringSpring 容器引用呢?容器引用呢?

 我看你后边的配置, 我看你后边的配置, webweb 层是通过获取 层是通过获取 BizBiz 层的层的 ServiceService 容器引容器引

用 再去获得用 再去获得 BizBiz 层层 SpringSpring 容器的,容器的,

<beans><beans> <bean id=“ <bean id=“bizBeanFactorybizBeanFactory” ” class=“com.alibaba.service.spring.support.SingletonBeanFactorySeclass=“com.alibaba.service.spring.support.SingletonBeanFactoryServiceBean”>rviceBean”> <property name=“serviceConfig” <property name=“serviceConfig” value=“value=“biz/services.xmlbiz/services.xml”/><!-- ”/><!-- 指向指向 bizbiz 层层 serviceservice 容器 容器 -->--> </bean> </bean></beans></beans>

那样的话,两个那样的话,两个 ServiceService 容器不就完全耦合了吗,容器不就完全耦合了吗,

还要还要 CommandDispatcherCommandDispatcher干嘛。干嘛。

Page 64: Exodus2 大局观

实际在我们的实际在我们的 webx-defaultwebx-default中对中对 sub-instancesub-instance 也存在一个对也存在一个对 bizbiz层层 SpringSpring 容器的引用:容器的引用:

<service name=<service name=“BeanFactoryService”“BeanFactoryService” class=class=“com.alibaba.service.spring.DefaultBeanFactoryService”“com.alibaba.service.spring.DefaultBeanFactoryService”>> <property name= <property name=“bean.descriptors”“bean.descriptors”>> <value>exodus2/bean/biz-delegate.xml</value> <value>exodus2/bean/biz-delegate.xml</value> <value>exodus2/bean/web-tool.xml</value><value>exodus2/bean/web-tool.xml</value> </property> </property> </service> </service>

web-tool.xmlweb-tool.xml里面配置了里面配置了 WebApplicationContextUtilsBeanWebApplicationContextUtilsBean ,里面,里面 settersetter 了了serviceConfig:alibaba-biz-service.xmlserviceConfig:alibaba-biz-service.xml 。该。该 utilBeanutilBean初始化时会通过调用初始化时会通过调用

SingletonServiceManagerLoader.SingletonServiceManagerLoader.getInstancegetInstance(( bootstrapResourceLoaderService, serviceConfig, initAll); bootstrapResourceLoaderService, serviceConfig, initAll);获取获取 bizbiz 层的层的 ServiceService 容器的单实例引用,进而获取容器的单实例引用,进而获取 bizbiz 层的层的 SpringSpring 容器引用。容器引用。

我们就可以通过该我们就可以通过该 UtilBeanUtilBean 来访问来访问 bizbiz 层的层的 springspring 容器。容器。

Page 65: Exodus2 大局观

Exodus2Exodus2 的中实际场景(不完整) 的中实际场景(不完整)

Car1

Spring 容器

Biz 层 spring 容器

A O B O

UtilBean

Car2

Spring 容器

UtilBean

CommandDispatcher

Page 66: Exodus2 大局观

思考:思考:

这样岂不是将这样岂不是将 webweb 层的层的 ServiceService 容器和容器和 BizBiz 层的层的 ServiceService容器耦合在了一起?容器耦合在了一起?

Michael zhou:Michael zhou:Command DispatcherCommand Dispatcher 是可以访问到业务层的代码的,按理说,这应是可以访问到业务层的代码的,按理说,这应

该是唯一的访问该是唯一的访问

业务层业务层 springspring 容器的方法。容器的方法。

你看到我把你看到我把 bizBeanFactorybizBeanFactory 注入到注入到 webweb 层容器中,说实在这种做法层容器中,说实在这种做法

是不太完美的。是不太完美的。

但这也是对实际需求的一种妥协。因为有一些公用对象(非业务层但这也是对实际需求的一种妥协。因为有一些公用对象(非业务层

和数据访问层对和数据访问层对

象,如象,如 AOAO、、 DAODAO等),需要放在一个大家都能访问到的地方。但等),需要放在一个大家都能访问到的地方。但

当你要访问业务对当你要访问业务对

象(象( AOAO、、 DAODAO等)时,仍然应该通过等)时,仍然应该通过 command dispatchercommand dispatcher 来走来走

。。

Page 67: Exodus2 大局观

Exodus2Exodus2中中 CommandDispatcherCommandDispatcher 用哪种方式和用哪种方式和 bizbiz 层通讯?层通讯?

看一下 看一下 biz-delegate.xmlbiz-delegate.xml ::

<bean id=<bean id=""sessionDispatchersessionDispatcher""

class=class="org.springframework.ejb.access."org.springframework.ejb.access.SimpleRemoteStatelessSessionProxyFactoryBeanSimpleRemoteStatelessSessionProxyFactoryBean"">> <property name= <property name="jndiName""jndiName">> <value>ejb/CommandDispatcherSLSB</value> <value>ejb/CommandDispatcherSLSB</value> </property> </property> <property name= <property name="businessInterface""businessInterface">> <value>com.alibaba.biz.command.dispatcher.CommandDispatcher</value> <value>com.alibaba.biz.command.dispatcher.CommandDispatcher</value> </property> </property> </bean> </bean>

<bean id= <bean id=""messageDispatcher"messageDispatcher"

class=class="com.alibaba.biz.command.dispatcher.ejb.mdb."com.alibaba.biz.command.dispatcher.ejb.mdb.CommandDispatcherClientCommandDispatcherClient"">> <property name= <property name="connectionFactory""connectionFactory">> <bean class= <bean class="org.springframework.jndi.JndiObjectFactoryBean""org.springframework.jndi.JndiObjectFactoryBean">> <property name= <property name="jndiName""jndiName">> <value>AlibabaWebxConnectionFactory</value> <value>AlibabaWebxConnectionFactory</value> </property> </property> </bean> </bean> </property> </property> <property name= <property name="destination""destination">> <bean class= <bean class="org.springframework.jndi.JndiObjectFactoryBean""org.springframework.jndi.JndiObjectFactoryBean">> <property name= <property name="jndiName""jndiName">> <value>queue/AlibabaWebxQueue</value> <value>queue/AlibabaWebxQueue</value> </property> </property> </bean> </bean> </property> </property> </bean> </bean>

Page 68: Exodus2 大局观

commandDispatcherSelectorcommandDispatcherSelector 会根据会根据 CommandCommand 的名称来判断使用的名称来判断使用哪个哪个 dispatcherdispatcher 。。

可供选择的可供选择的 dispatcherdispatcher ::1, 1, sessionDispatcher sessionDispatcher 使用使用 ejbejb 方式。默认选择此方式。方式。默认选择此方式。

2, messageDispatcher 2, messageDispatcher 使用消息队列的方式。使用消息队列的方式。

3, commandDispatcherLogic 3, commandDispatcherLogic 直接通过已持有直接通过已持有 bizbiz 层层 springspring 容器的容器的

引用来找到引用来找到 bizbiz 层的层的 AOAO 来执行。顺藤摸瓜。来执行。顺藤摸瓜。

Page 69: Exodus2 大局观

Exodus2Exodus2中的中的 CommandDispatcherCommandDispatcher默认的方式默认的方式

是 远程方式。用是 远程方式。用 EjbEjb 实现远程调用。实现远程调用。

Page 70: Exodus2 大局观

BizBiz 层的层的 CommandDispatcher: CommandDispatcher: 除了除了 webweb 层和层和 bizbiz之间使用之间使用 CommandDispatcher, bizCommandDispatcher, biz 层内部之间也使层内部之间也使

用了用了 CommandDispatcherCommandDispatcher

比如发邮件的情况,比如发邮件的情况, webweb 层层 dispatcherdispatcher 到到 bizbiz 层的层的 SendMailBOSendMailBO ,它,它

又会通过又会通过 bizbiz 层的层的 dispatcherdispatcher 交给交给 JMSJMSbizbiz 层这时用的层这时用的 dispatcherdispatcher 是异步消息队列的方式。是异步消息队列的方式。

EJB 容器

Biz-Serverice 容器

??? AlibabaWebxQueue

Spring 容器

Web-CommandDispatcher

B O Biz-dipatcher

Page 71: Exodus2 大局观

EJB 容器

Biz-Serverice 容器

EJB AlibabaWebxQueue

Spring 容器

Web-CommandDispatcher(sessionDispatcher)

AO/BO/DAO Biz-dipatcher

EJB(…ejb.slsb.CommandDispatherBean)

messageDispatcher

Page 72: Exodus2 大局观

SLSB SLSB 和 和 MDB MDB 类型的类型的 EJBEJB分别是什么?分别是什么?

com.alibaba.biz.command.dispatcher.ejb.slsb.com.alibaba.biz.command.dispatcher.ejb.slsb. CommandDispatherHome CommandDispatherHome

CommmandDispatherRemote CommmandDispatherRemoteCommandDispatherBeanCommandDispatherBean

com.alibaba.biz.command.dispatcher.ejb.mdb.com.alibaba.biz.command.dispatcher.ejb.mdb. CommandDispatherClient CommandDispatherClientCommandDispatherBeanCommandDispatherBean

为什么为什么 sessionDispathersessionDispather 用到用到 slsb, messageDispatherslsb, messageDispather 用到用到 mdbmdb(slsb(slsb 可用作远程或本地调用,而异步处理只能用可用作远程或本地调用,而异步处理只能用 mdb)mdb)

Page 73: Exodus2 大局观

为什么这么配置?性能问题为什么这么配置?性能问题

► sessionDispatcher sessionDispatcher 配置的是远程方式。配置的是远程方式。

►就一定会走远程方法调用么?还是也有可能走就一定会走远程方法调用么?还是也有可能走 locallocal方式?方式?

►如果走远程方法调用,是通过如果走远程方法调用,是通过 socketsocket 来进行么? 来进行么?

它的端口? 既然我们已经可以直接得到它的端口? 既然我们已经可以直接得到 bizbiz 层对象层对象

的引用;为何这样? 怎么看待这里的效率问题?的引用;为何这样? 怎么看待这里的效率问题?

►如果是,这些是否都是历史遗留原因?还是?如果是,这些是否都是历史遗留原因?还是?

Page 74: Exodus2 大局观

阿甘:阿甘:

11 :虽然配置方式是远程调用,:虽然配置方式是远程调用, JBossJBoss 内部会内部会对其优化,判断是同一对其优化,判断是同一 JVMJVM ,会相当于本地,会相当于本地调用来对待。调用来对待。

33 :可能是历史原因,早期是支付宝有需要将:可能是历史原因,早期是支付宝有需要将webweb 层和层和 bizbiz耦合起来,耦合起来, B2BB2B 也保持了这样也保持了这样的结构。但开发人员使用时应该按照的结构。但开发人员使用时应该按照CommandDispatcherCommandDispatcher 的方式调用,保持这种的方式调用,保持这种风格便于维护。风格便于维护。

Page 75: Exodus2 大局观

Main-instanceMain-instance 和和 sub-instancesub-instance中都配中都配

置了置了 ComamndDispatherComamndDispather 。。Main-instanceMain-instance中的中的 DispatcherDispatcher 有何有何

用意?用意?

阿甘的答复:阿甘的答复: Main-instanceMain-instance中的中的CommandDispatherCommandDispather 是为了某些全局是为了某些全局 ServiceService

所用的,某些全局所用的,某些全局 ServiceService 也会通过也会通过DispatherDispather去访问去访问 BizBiz 层层

宝宝:也可能是后续维护的人员弄错了。应宝宝:也可能是后续维护的人员弄错了。应该一个就够了。该一个就够了。

Page 76: Exodus2 大局观

3, Exodus23, Exodus2 的部署的部署

JBossJBoss 没有使用集群,当服务器比较多没有使用集群,当服务器比较多时,相互之间的监控需要耗费很大性能时,相互之间的监控需要耗费很大性能

Exodus2Exodus2 也没有实现集群。也没有实现集群。

Page 77: Exodus2 大局观

前段负载均衡 F5

Exodus2

Apache

JBoss

Exodus2

Apache

JBoss

Exodus2

。。。

共 64+7台

DataBaseMemcached集群

Page 78: Exodus2 大局观

数据库的部署:双机热备数据库的部署:双机热备

Oracle9.2DB1

Oracle9.2DB2

共享存储设备

心跳检测

DBDB 并没有使用集群并没有使用集群

,放在两台小型机,放在两台小型机

上。上。 DB2DB2 同时还做同时还做

回滚备份回滚备份

Page 79: Exodus2 大局观

Exodus2Exodus2 的数据层的数据层

1, 1, 数据存储方式,数据源。数据存储方式,数据源。

2, 2, 基于基于 iBatisiBatis 的的 DAODAO

Page 80: Exodus2 大局观

数据访问层数据访问层

Memcache Oracle SearchEngine ViewCache

DAL

LDAP已经不再使用

Page 81: Exodus2 大局观

数据库与搜索引擎数据库与搜索引擎

前端负载均衡 F5

Apache

搜索引擎

Apache

搜索引擎 。。。

Dispatcher

Oracle

Build Index每天晚上

Delta Index每 5 分钟

Page 82: Exodus2 大局观

ViewCacheViewCache缓存数据缓存数据

Oracle

SearchEngine

Sync Engine XML文件(已不用 JOS) ViewCacheService

SCP

Exodus2

搜索引擎的数据:提供统计信息,相当于类目的属性