??xml version="1.0" encoding="utf-8" standalone="yes"?>
W五版sdk下地址
http://www.forum.nokia.com/info/sw.nokia.com/id/ec866fab-4b76-49f6-b5a5-af0631419e9c/S60_All_in_One_SDKs.html
l大家说下怎么一步一步的扑ֈ下蝲地址Q?br />
1Q进入诺Z论坛Q?br />
http://www.forum.nokia.com
2Q菜单:I want to =>Develop Applications=>Java™ technology=>Download all-in-one S60 SDKs
q样一步一步就q入了sdk的下载页面?br />
psQ每一步的链接地址都是比较隐蔽的,不太好找Q这p看你有没有耐心了?/span>
3Q在Download version for: 下拉框里选择你想要的版本可以啦?br />
有三个版本选择QNokia N97 (569 MB)Q?th Edition (622 MB)Q?rd Edition, FP2 v1.1 (455 MB)
psQ想找资料,q是推荐直接d方去找?/span>
W一、二、三版下载地址
http://www.forum.nokia.com/info/sw.nokia.com/id/4a7149a5-95a5-4726-913a-3c6f21eb65a5/S60-SDK-0616-3.0-mr.html
]]>
应对之道Q?br />
老是不熟悉也不是办法呀Q还要是有解决的办法?br />
1Q常用的sqlQ各U关联用法?br />
2Q常用函?br />
3Q技?br />
4Q存储过E,包,触发器等用户
0Q前a
我们知道了tomcat的整体框架了Q?也明白了里面都有些什么组Ӟ 以及各个lg是干什么用的了?/p>
http://www.csdn.net/Develop/read_article.asp?id=27225
我想Q接下来我们应该M解一?tomcat 是如何处理jsp和servleth的?/p>
1. 我们以一个具体的例子Q来跟踪TOMCATQ看看它是如何把Request一层一层地递交l下一个容器,q最后交lWrapper来处理的?/p>
以http://localhost:8080/web/login.jspZ?/p>
Q以下例子,都是以tomcat4 源码为参考)
q篇心得主要分ؓ3个部分: 前期Q?中期Q?和末期?/p>
前期Q讲解了在浏览器里面输入一个URLQ是怎么被tomcat抓住的?/p>
中期Q讲解了被tomcat抓住后,又是怎么在各个容器里面穿梭, 最后到达最后的处理地点?/p>
末期Q讲解到达最后的处理地点后,又是怎么具体处理的?/p>
2?前期 Request的born.
在这里我先简单讲一下requestq个东西?/p>
我们先看着q个URLQhttp://localhost:8080/web/login.jsp 它是动用?080端口来进行socket通讯的?/p>
我们知道, 通过
InputStream in = socket.getInputStream() ?/p>
OutputStream out = socket.getOutputStream()
可以实现消息的来来往往了?/p>
但是如果把Streaml应用层看,昄操作h不方ѝ?
所以,在tomcat 的Connector里面Q?socket被封装成了Request和Responseq两个对象?/p>
我们可以单地把Request看成发到服务器来的数据Q把Response看成惛_出服务器的数据?/p>
但是q样又有其他问题了啊Q?Requestq个对象是把socket装h了, 但是他提供的又东西太多了?/p>
诸如Request.getAuthorization(), Request.getSocket()?像Authorizationq种东西开发h员拿来基本上用不太着Q而像socketq种东西Q暴露给开发h员又有潜在的危险?而且啊, 在Servlet Specification里面标准的通信cLServletRequest和HttpServletRequestQ而非q个RequestcR?So, So, So. Tomcat必须得捣持捣持Request才行?最后tomcat选择了用捣持模式(应该叫适配器模式)来解册个问题。它把org.apache.catalina.Request 捣持成了 org.apache.coyote.tomcat4.CoyoteRequest?而CoyoteRequest又实CServletRequest和HttpServletRequest q两U接口?q样提供给开发h员需要且刚刚需要的Ҏ了?/p>
ok, 让我们在 tomcat的顶层容?- StandardEngin 的invoke()Ҏq里讄一个断点, 然后讉K
http://localhost:8080/web/login.jspQ?我们来看看在前期都会路过哪些地方Q?/p>
1. run(): 536, java.lang.Thread, Thread.java
CurrentThread
2. run():666, org.apache.tomcat.util.threads.ThreadPool$ControlRunnable, ThreadPool.java
ThreadPool
3. runIt():589, org.apache.tomcat.util.net.TcpWorkerThread, PoolTcpEndpoint.java
ThreadWorker
4. processConnection(): 549
org.apache.coyote.http11.Http11Protocol$Http11ConnectionHandler, Http11Protocol.java
http protocol parser
5. Process(): 781, org.apache.coyote.http11.Http11Processor, Http11Processor.java
http request processor
6. service(): 193, org.apache.coyote.tomcat4.CoyoteAdapter,CoyoteAdapter.java
adapter
7. invoke(): 995, org.apache.catalina.core.ContainerBase, ContainerBase.java
StandardEngin
1. ȝE?/p>
2. 启动U程?
3. 调出U程池里面空闲的工作U程?/p>
4. ?080端口传过来由httpd协议装的数据,解析成Request和Response对象?/p>
5. 使用Http11Processor来处理request
6. 在Http11Processor里面Q?又会call CoyoteAdapter来进行适配处理Q把Request适配成实CServletRequest和HttpServletRequest接口的CoyoteRequest.
7. Cq里Q前期的L拔皮工作基本上搞定Q可以交lStandardEngin 做核心的处理工作了?/p>
3. 中期?在各个容器间的穿梭?/p>
Request在各个容器里面的I梭大致是这样一U方式:
每个容器里面都有一个管道(piplineQ, 专门用来传送Request用的?/p>
道里面又有好几个阀门(valveQ, 专门用来qoRequest用的?/p>
在管道的低部通常都会放上一个默认的阀们?q个阀们至会做一件事情,是把Request交给子容器?/p>
让我们来惌一下:
当一个Requestq入一个容器后Q?它就在管道里面流动,波罗~ 波罗~ 波罗~ 地穿q各个阀门。在到最后一个阀门的时候,吧唧~ 那个该死的阀门就把它扔给了子容器?然后又开?波罗~ 波罗~ 波罗~ ... 吧唧~.... 波罗~ 波罗~ 波罗~ ....吧唧~....
是通过q种方式Q?Request 走完了所有的容器。( 感觉有点像消化系l,最后一个地Ҏ点像那里~ Q?/p>
OKQ?让我们具体看看都有些什么容器, 各个容器里面又都有些什么阀门,q些阀们都Ҏ们的Request做了些什么吧Q?/p>
3.1 StandardEngin 的pipeline里面攄是:StandardEnginValve
在这里,VALVE做了三g事:
1. 验证传递过来的request是不是httpservletRequest.
2 验证传递过来的 request 是否携带了host header信息.
3 选择相应的hostd理它。(一般我们都只有一个host:localhostQ也是127.0.0.1Q?/p>
Cq个地方Q我们的request已l完成了在Enginq个部分的历史命,通向前途未卜的下一站: host了?/p>
3.2 StandardHost 的pipline里面攄是: StandardHostValve
1. 验证传递过来的request是不是httpservletRequest.
2. ҎRequest来确定哪个Context来处理?/p>
Context其实是webappQ比如http://localhost:8080/web/login.jsp
q里web是Context|!
3. 既然定了是哪个Context了,那么应该把那个Context的classloader付给当前U程了?/p>
Thread.currentThread().setContextClassLoader(context.getLoader().getClassLoader());
q样request只看得见指定的context下面的classes啊, jar啊这些,而看不见tomcat本n的类Q什么Engin啊, Valve啊。不然还得了啊!
4. 既然requestCq里了,看来用户是准备访问webq个web app了,咋们得更C下这个用Lsession不是Q?Ok , qmanager更新一下用Lsession信息
5. 交给具体的Context 容器ȝl处理Request.
6. Context处理完毕了,把classloaderq回来?/p>
3.3 StandardContext 的pipline里面攄是: StandardContextValve
1. 验证传递过来的request是不是httpservletRequest.
2. 如果request意图不轨Q想要访?meta-inf, /web-infq些目录下的东西Q呵呵,没有用D!
3. q个时候就会根据Request到底是ServletQ还是jspQ还是静态资源来军_到底用哪UWrapper来处理这个Reqeust了?/p>
4. 一旦决定了到底用哪UWrapperQOKQ交l那个Wrapper处理?/p>
4. 末期?不同的需求是怎么处理?
StandardWrapper
之前对Wrapper没有做过讲解Q其实它是这样一U东ѝ?/p>
我们在处理Request的时候,可以分成3U?/p>
处理静态的Q?org.apache.catalina.servlets.DefaultServlet
处理jsp的:org.apache.jasper.servlet.JspServlet
处理servlet的:org.apache.catalina.servlets.InvokerServlet
不同的requestqq?U不同的servletd理?/p>
Wrapper是对它们的一U简单的装Q有了Wrapper后,我们可以轻村֜拦截每次的Request。也可以Ҏ地调用servlet的init()和destroy()ҎQ?便于理嘛!
具体情况是这么滴Q?/p>
如果request是找jsp文gQStandardWrapper里面׃装一个org.apache.jasper.servlet.JspServletd理它?/p>
如果request是找 静态资?QStandardWrapper里面׃装一个org.apache.jasper.servlet.DefaultServlet d理它?/p>
如果request是找servlet QStandardWrapper里面׃装一个org.apache.jasper.servlet.InvokerServlet d理它?/p>
StandardWrapper同样也是容器Q既然是容器Q?那么里面一定留了一个管道给requestȝQ管道低部肯定也有一个阀??)Q用来做最后一道拦截工?
在这最底部的阀门里Q其实就主要做了两g?
一是启动过滤器Q让request在N个过滤器里面{一通,如果OKQ?那就PASS?否则p到其他地方去了?/p>
二是servlet.service((HttpServletRequest) request,(HttpServletResponse) response); q个Ҏ.
如果?JspServletQ?那么先把jsp文g~译成servlet_xxx, 再invoke servlet_xxx的servie()Ҏ?/p>
如果?DefaultServletQ?q接找到静态资源,取出内容Q?发送出厅R?/p>
如果?InvokerServletQ?p用那个具体的servlet的service()Ҏ?/p>
ok! 完毕?/p>
?: StandardWrapper 里面的阀门是最后一道关口了?如果q个阀门欲意把request交给StandardWrapper 的子容器处理?对不P 在设计考虑的时候, Wrapperp考虑成最末的一个容器, 压根儿就不会lWrapperd子容器的ZQ?如果是要调用addChild(), 立马抛出IllegalArgumentExceptionQ?/p>
参考:
<http://jakarta.apache.org/tomcat/>
<http://www.onjava.com/pub/a/onjava/2003/05/14/java_webserver.html>
众所周知Tomcat是一个免费的开放源码的Serlvet容器Q它是Apache基金会的Jakarta目中的一个核心项目,也是sun公司官方推荐的servlet和jsp容器Q同时它q获得过多种荣誉。servlet和jsp的最新规范都可以在tomcat的新版本中得到实现。Tomcath轻量U和灉|嵌入到应用系l中的优点,所以得Cq泛的应用。在Tomcat的发展中QSun?999q六月宣布参与Jakarta目的Tomcat servlet容器和Jsp引擎的开发,使得Tomcat?.x?.x版之间系l设计上发生了比较大的变化。Tomcat的其他信息我׃多说了。有兴趣的朋友可以访问http://jakarta.apache.org/ 的官方网站得到更多的信息?/p>
因ؓ工作的原因,我改写了Tomcat的一些代码,所以我_略的研I了一下Tomcat3.3和Tomcat4.0的源码,深深地被q个开放Y件的设计和实现吸引,感觉到这个Y件中有许多值得我们学习和借鉴的地斏V我把自q理解介绍l大家算是抛砖引玉,不和偏差还望大家批评指正。下面就来让我们看看从Tomcat那里我们可以得到什么?/p>
Tomcat的设计和实现处处体现着设计模式的思想Q它的基本流E是首先通过解析xml格式的配|文Ӟ获得pȝ的配|和应用信息Q然后加载定制的lg模块提供各种pȝ服务。系l的各部分功能都是通过可以配置的组件模块来实现的。Tomcat实现中像ObserverQFacadeQAdapterQSingleton{多U设计模型在Tomcat的源码中随处可见Qؓ我们提供了一个很好的学习设计模式的^台。我主要介绍一下Tomcat中程序流E控制所采用的设计模式,q是一个程序运行的框架。前面提到由于Sun公司的参与,Tomcat虽然基本的流E没有变化,但是Tomcat3.3和Tomcat4.0版本之间在概念上q是有很大地不同的。Tomcat3.3整体上是模块化的设计Q而Tomcat4.0可以看作是采用面向组件技术进行设计的。组件是比模块更高的一个层ơ。我们可以通过比较它们之间的不同来了解实现一个服务器软g可以采用的设计模式和实现方式?/p>
2Q?Tomcat3.3的基本结构设?/span>
Tomcat3.3采用的是一U模块化的链状的控制l构Q它的主要设计模式有Q?/p>
Chain of responsibilityQ责任链Q?/strong>
作ؓ一个基于请求响应模式的服务器,在Tomcat3.3中采用一U链状处理的控制模式。请求在链上的各个环节上传递,在Q一环节上可以存在若q个"监听?处理它。这样做的目的是避免h的发送者和接受者之间的直接耦合Q从而可以ؓ其他的对象提供了参与处理h的机会。采用这个方式不但可以通过"监听?实现pȝ功能Q而且可以通过d新的"监听?对象实现pȝ功能的扩展?/p>
InterceptorQ监听器Q?/strong>
"监听?是一个过M用的名称Q它可以看作 "模块(module)"的同义词。它是Tomcat功能模块构徏和扩展的方式。Tomcat3.3的大部分功能都是通过"监听?实现的。在Tomcat中提供了一U简单的钩子QHookQ机Ӟ监听器对钩子中感兴趣的事件进行注册,事g发生后通过钩子唤醒已注册的"监听?对象Q?监听?对象对Tomcat内核事gq行处理。这些模块都是围l着"责Q??{略"的模式进行设计。通过"监听?你可以监听各U特D事Ӟq而控制处理请求的各个步骤---解析Q认证,授权Q会话,响应提交Q缓冲区提交{等?/p>
StrategyQ策略)
所谓策略是?定义一l规则,按照规则q行对象装Q得他们只在规则内部进行交?。通过{略模式使得Tomcat作ؓ一个开源项目在开攄境下的开发和演变变得更轻松。通过q种模式把复杂的法分成模块然后不同的开发组提供各自的实现。从而实现调用模块的代码和模块的具体实现代码的分别开发。这样可以我们专注于问题的重点Qƈ且减问题之间的依赖性。在Tomcat中大量采用了{略的设计模式,通过q种方式每一U服务都提供了多U的实现Q例如Tomcat中有2Q?U认证模块)Q在代码完成后可以从E_性和性能表现的考虑选择更好的实现。策略模式对开攑ּ环境下的软g开发非常有用?/p>
我们通过化的cd(见图一)和时序图(见图?Q描qC下Tomcat3.3的程序流E控制如何通过监听器和责Q铑֮现?/p>
关于cd的简单说明:
BaseInterceptorQ是所有监听器的基c,描述了基本的模块功能和对各种事g的缺省处理?/p>
ContextManageQ系l的核心控制对象Q进行请求处理和pȝ配置。它l护了全局的属性、web应用的内容和全局模块{多U信息,责Q铄钩子实现也在其中?/p>
PoolTcpConnectorQ一个处理TCPq接的连接器对象Q从BaseInterceporz。它包括一个具体处理socketq接的PoolTcpEndPointcd象?/p>
PoolTcpEndPointQ处理实际的tcpq接。它有一个连接池对象ThreadPool和运行在独立U程中的应用逻辑cTcpWorkThread?/p>
TcpWorkTheadQ处理socketq接事务Q调用接口TcpConnectionHandler中的h处理Ҏ?/p>
Http10InterceptorQ从PoolTcpConnectorzQ实CTcpConnectionHandler接口Q是一个真正的监听器对象。它按照Http1.0的协议标准对tcpq接q行处理Q调用核心对象ContextManage的服务方法?/p>
关于时序图中需要说明的地方Q?/p>
BaseInterceptor ri[];//取得注册对象ri=defaultContainer.getInterceptors(Container.H_postReadRequest);//执行注册对象的对消息的处理方法for( int i=0; i< ri.length; i++ ) { status=ri[i].postReadRequest( req ); ......}
Tomcat3.3的基本程序结构就是采用上面介l的方式设计的。它l我们的设计和开发提供了一个很好的思\Q通过q种模式可以L的实C个事仉动的Z模块化设计的应用E序。各个功能通过模块实现Q通过对责任链上的消息和处理步骤的改动或者添加新的监听器对象可以非常方便的扩展Tomcat的功能。所以这是一个非常好的设计?/p>
2Q?Tomcat4.0的基本结构设?/span>
虽然Tomcat3.x已经实现了一个非常好的设计体p,但是在Sun公司加入后, Tomcat4.0中还是引入了不同的实现方式。主要的区别是Tomcat4.0采用了面向组件的设计方式Q?Tomcat4.0中的功能是由lg提供的,控制程通过lg之间的通讯完成。这不同于Tomcat3.3中的Z模块的链式控制结构?/p>
面向lg的技?CO)是比面向对象的技?OOP)更高一层的抽象Q它融合了面向对象的优点Q加入了安全性和可扩展的模块设计Q可以更好的映射问题域空间。采用面向组件的设计会带来很多好处,可以提高复用性、降低耦合度和通过l装构成pȝ{等。面向组件编E中有许多概念与原来面向对象的编E是不同的,例如Q?/p>
Message(消息)Q定义抽象操作; Method(Ҏ)Q定义具体的操作Q?br /> Interface(接口)Q一l消息的集合Q?Implementation(实现)Q一l方法的集合Q?br /> Module(模块)Q静态的数据l构, Type(cd)Q动态的数据l构?/p>
软glg不同与功能模块,它具有以下特性:
在Java 语言中对面向lg~程的支持是通过JavaBeans模型获得的。JavaBeanlg框架提供了对事g和属性的支持。Tomcat4.0的组件的是通过JavaBean技术实现的。这是它和Tomcat3.3中最大的不同。下面我们来看一下Tomcat4.0是如何通过面向lg~程来实现程序流E控制的?/p>
面向lg~程时设计组件是关键Q从Tomcat4.0中可以看Z要用了以下的设计模式:
Separation of ConcernsQSOCQ?/strong>
设计lg时应该从不同的问题领域,站在不同的观点上分析Q把每一U属性分别考虑。D一个例子FileLoggerlgQ它用于把系l日志信息保存到文gpȝ中。按照这U模式分析,我们从不同的角度看待它:它如何启动服务、停止服务和q行通讯Q它的具体的功能有哪些?别的lg可以发给它哪些消息?Zq些考虑QFileLoggerlg应该实现两种接口QLifecycleQ生存期接口Q和LoggerBaseQ功能接口)?/p>
Inversion of ControlQIOCQ这个模式定义的是,lgL通过外部q行理的。组仉要的信息L来源于外部,实际上组件在生存期的各个阶段都是被创建它的组件管理的。在Tomcat4.0中就是通过q种lg之间的相互控制和调用实现各个功能的?/p>
按照q些模式分析得到的Tomcat4.0中的lg是既有共性又有特性。共性是Lifecycle接口Q特性是不同的功能接口。其中生存期接口处理lg的整个生存期中的各个阶段的事?功能接口提供l其他的lg使用?/p>
具体的功能如何实现我在这里不多介l了Q我主要介绍一下Tomcat4.0中组件的Lifecycle接口的设计。Lifecycle接口实现了组件的生存期管理,控制理和通讯。创Z个Y件组件和创徏一个JavaBean对象一P可以参考JavaBeanq行理解。我通过一个模拟的Lifecycle接口lg的类图来描述?见图?
Ҏ拟的Lifecycle接口lg的类囄说明
通过上面的分析我们可以看到组件成为Tomcat4.0中的核心概念Q系l的功能都是通过lg实现的,lg之间的通讯构成了系l的q行控制机制。我们把Tomcat3.3中模块化的链状控制机制和Tomcat4.0的面向组件的设计q行比较Q就会发现Tomcat4.0在设计思想上Y件组件的概念非常明确。Tomcat4.0和Tomcat3.3最主要的区别就在于此。至于面向对象和面向lg的关pd区别Q我在这里就不介l了Q有兴趣的朋友可以找到很多这斚w的资源?/p>
Tomcat不但为我们提供了设计和实现系l时的新思\Q同时因为它是由lg或者模块构成的Q所以它qؓ我们提供了大量可用的高效软glg。这些组仉可以在我们的E序开发中使用。我单列举一些,需要时可以直接从源码中取得?/p>
通过以上对Tomcat的简单的介绍Q我们可以看出,作ؓ一个开放源码的目QTomcat不但为我们提供了一个应用的q_Q同时它qؓ我们提供了一个学习和研究设计模式、面向组件技术等理论的实践^台?/p>
Tomcat3.3源码和Tomcat4.0源码http://jakarta.apache.org/tomcat/index.html
《设计模式?/p>
TOMCAT源码分析(启动框架)
前言Q?br />
本文是我阅读了TOMCAT源码后的一些心得?主要是讲解TOMCAT的系l框Ӟ 以及启动程。若有错漏之处,敬请批评指教Q?br />
Q?br />
毕竟TOMCAT的框架还是比较复杂的Q?单是从文字上理解Q?是不那么Ҏ掌握TOMCAT的框架的?所以得实践、实c再实践?下蝲一份TOMCAT的源码, 调试通过Q?然后单步跟踪其启动过E?如果有不明白的地方, 再来查阅本文Q?看是否能得到帮助?我相信这h果以及学习速度都会好很多!
1. Tomcat的整体框架结?br />
Tomcat的基本框Ӟ 分ؓ4个层ơ?br />
Top Level Elements:
Server
Service
Connector
HTTP
AJP
Container
Engine
Host
Context
Component
manager
logger
loader
pipeline
valve
...
站在框架的顶层的是Server和Service
Server: 其实是BackGroudE序Q?在Tomcat里面的Server的用处是启动和监听服务端事gQ诸如重启、关闭等命o?在tomcat的标准配|文Ӟserver.xml里面Q?我们可以看到“<Server port="8005" shutdown="SHUTDOWN" debug="0">”q里?SHUTDOWN"是server在监听服务端事g的时候所使用的命令字Q?br />
ServiceQ?在tomcat里面Q?service是指一c问题的解决Ҏ?nbsp; 通常我们会默认用tomcat提供的:Tomcat-Standalone 模式的service?在这U方式下的service既给我们提供解析jsp和servlet的服务, 同时也提供给我们解析静态文本的服务?br />
Connector: Tomcat都是在容器里面处理问题的Q?而容器又到哪里去取得输入信息呢?
Connector是专干q个的?他会把从socket传递过来的数据Q?装成Request, 传递给容器来处理?br />
通常我们会用CUConnector,一U叫http connectoerQ?用来传递http需求的?另一U叫AJPQ?在我们整合apache与tomcat工作的时候, apache与tomcat之间是通过q个协议来互动的?Q说到apache与tomcat的整合工作, 通常我们的目的是Z让apache 获取静态资源, 而让tomcat来解析动态的jsp或者servlet。)
Container: 当http connector把需求传递给的container: Engin的时候, 我们的视U就应该Ud到Containerq个层面来了?br />
在Containerq个层, 我们包含?U容器: Engin, Host, Context.
Engin: 收到service传递过来的需求, 处理后, 结果返回给service( service 是通过 connector q个媒介来和Engin互动?).
Host: Engin收到service传递过来的需求后Q不会自己处理, 而是交给合适的Host来处理?br />
Host在这里就是虚拟主机的意思, 通常我们都只会用一个主机,?#8220;localhost”本地机来处理?
Context: Host接到了从Host传过来的需求后Q?也不会自己处理, 而是交给合适的Context来处理?
比如Q?<http://127.0.0.1:8080/foo/index.jsp>
<http://127.0.1:8080/bar/index.jsp>
前者交lfooq个Context来处理, 后者交lbarq个Context来处理?br />
很明昑Q?context的意思其实就是一个web app的意思?br />
我们通常都会在server.xml里面做这L配置
<Context path="/foo" docBase="D:/project/foo/web" />
q个context容器Q就是用来干我们该干的事儿的地方的?br />
Compenent: 接下来, 我们l箋讲讲component是干什么用的?br />
我们得先理解一下容器和lg的关pR?br />
需求被传递到了容器里面, 在合适的时候, 会传递给下一个容器处理?br />
而容器里面又盛装着各种各样的组Ӟ 我们可以理解为提供各U各L增值服务?br />
manager: 当一个容器里面装了managerlg后,q个容器支持session理了, 事实上在tomcat里面的session理Q?是靠的在context里面装的manager component.
logger: 当一个容器里面装了loggerlg后, q个容器里所发生的事情, p该组件记录下来啦Q?我们通常会在logs/ q个目录下看?catalina_log.time.txt 以及 localhost.time.txt 和localhost_examples_log.time.txt?q就是因为我们分别ؓQengin, host以及context(examples)q三个容器安装了loggerlgQ?q也是默认安装, 又叫做标?Q)
loader: loaderq个lg通常只会l我们的context容器使用Q?loader是用来启动context以及理q个context的classloader用的?br />
pipline: pipeline是这样一个东西, 当一个容器决定了要把从上U传递过来的需求交l子容器的时候, 他就把这个需求放q容器的道(pipeline)里面厅R?而需求傻呼呼得在道里面动的时候, ׃被管道里面的各个阀门拦截下来?比如道里面放了两个阀门?W一个阀门叫?#8220;access_allow_vavle”Q?也就是说需求流q来的时候,它会看这个需求是哪个IPq来的, 如果q个IP已经在黑名单里面了, sure, 杀Q?W二个阀门叫?#8220;defaul_access_valve”它会做例行的查, 如果通过的话QOKQ?把需求传递给当前容器的子容器?是通过q种方式Q?需求就在各个容器里面传递,动Q?最后抵辄的地的了?br />
valve: 是上面所说的阀门啦?br />
Tomcat里面大概是q么些东西, 我们可以单地q么理解tomcat的框Ӟ它是一U自上而下Q?容器里又包含子容器的q样一U结构?br />
2. Tomcat的启动流E?br />
q篇文章是讲tomcat怎么启动的,既然我们大体上了解了TOMCAT的框架结构了Q?那么我们可以望文生意地就猜到tomcat的启动, 会先启动父容器,然后逐个启动里面的子容器?启动每一个容器的时候, 都会启动安插在他w上的组件?当所有的lg启动完毕Q?所有的容器启动完毕的时候, tomcat本n也就启动完毕了?br />
理成章圎ͼ 我们同样可以猜到Q?tomcat的启动会分成两大部分Q?W一步是装配工作?W二步是启动工作?
装配工作是为父容器装上子容器, 为各个容器安插进lg的工作?q个地方我们会用到digester模式Q?至于digester模式什么, 有什么用Q?怎么工作? 请参?<http://software.ccidnet.com/pub/article/c322_a31671_p2.html>
启动工作是在装配工作之后Q?一旦装配成功了Q?我们只需要点燃最上面的一根导U, 整个tomcat׃被激zv来?q就好比我们要开一辆已l装配好了的汽R的时候一P我们只要把钥匙插q钥匙孔Q一拧,汽R的引擎就会发动v来,I׃开hQ?安全装置׃生效Q?如此一来,汽R整个发动v来了。(q个q程实和TOMCAT的启动过E不谋而和Q?让我们不得不怀?TOMCAT的设计者是在GE做JAVA开发的Q?br />
2.1 一些有意思的名称Q?br />
Catalina
Tomcat
Bootstrap
Engin
Host
Context
他们的意思很有意思:
Catalina: q程轰炸?br />
Tomcat: 熊猫轰炸?-- 轰炸机的一U(q让我想起了让国人引以ؓ豪的熊猫手机Q是不是英文可以叫做tomcat??? Q?又让我想起了另一则广告: 波导-手机中的战斗机、L?客机中的战斗?Q?br />
Bootstap: 引导
Engin: 发动?br />
Host: LQ领?br />
Context: 内容Q?目标Q?上下?br />
... 在许多许多年后, C人类已经灭绝?后现代生物发Cq些单词零落零落在一块?一个自以ؓ聪明的家伙把q些东西译出来了:
在地勤h员的引导(bootstrap)下, 一架蘪炸架(catalina)腄跃vQ?q看是熊猫蘪炸机(tomcat)Q?q看q是熊猫轰炸机! 凭借着优秀的发动机技?engin)Q?q架熊猫轰炸机飞临了敌国的领土上I?host)Q?对准目标(context)投下了毁天灭地的核弹_波~ C生物p么隔屁了~
lg所qͼ q又不得不让惛_GE是不是也参与了军事设备的生呢?
反对帝国主义! 反对霸权主义! 和^万岁Q?自由万岁Q?br />
2.2 历史是那么惊h的相| tomcat的启动就是从org.apache.catalina.startup.Bootstrapq个cL然启动的Q?br />
在Bootstrap里做了两件事Q?br />
1. 指定?U类型classloader:
commonLoader: common/classes、common/lib、common/endorsed
catalinaLoader: server/classes、server/lib、commonLoader
sharedLoaderQ?nbsp; shared/classes、shared/lib、commonLoader
2. 引导Catalina的启动?br />
用Reflection技术调用org.apache.catalina.startup.Catalina的processҎQ?q传递参数过厅R?br />
2.3 Catalina.java
Catalina完成了几个重要的dQ?br />
1. 使用Digester技术装配tomcat各个容器与组件?br />
1.1 装配工作的主要内Ҏ安装各个大g?比如server下有什么样的servcie?Host会容U_个context?Context都会使用到哪些组件等{?
1.2 同时呢, 在装配工作这一步, q完成了mbeans的配|工作?在这里,我简单地但不十分_地描qC下mbean是什么,q什么用的?br />
我们自己生成的对象, 自己理Q?天经CQ?但是如果我们创徏了对象了Q?惌别h来管Q?怎么办呢Q?我想臛_得告诉别人我们都有什么, 以及通过什么方法可以找?nbsp; 吧! JMX技术给我们提供了一U手Dc?JMX里面主要?U东ѝMbean, agent, connector.
MbeanQ?用来映射我们的对象。也许mbean是我们创徏的对象, 也许不是Q?但有了它Q?可以引用到我们的对象了?br />
Agent: 通过它, 可以找到mbean了?br />
Connector: q接Agent的方式?可以是http的, 也可以是rmi的,q可以直接通过socket?br />
发生在tomcat 装配q程中的事情: GlobalResourcesLifecycleListener cȝ初始化会被触发:
protected static Registry registry = MBeanUtils.createRegistry(); 会运?br />
MBeanUtils.createRegistry() 会依?org/apache/catalina/mbeans/mbeans-descriptors.xmlq个配置文g创徏 mbeans. Ok, 外界有了条途径讉Ktomcat中的各个lg了。(有点像后门儿Q?br />
2. 为top level 的server 做初始化工作?实际上就是做通常会配|给service的两条connector.(http, ajp)
3. 从serverq个容器开始启动, 点燃整个tomcat.
4. 为server做一个hookE序Q?当server shutdown的时候, 关闭tomcat的各个容器用?br />
5. 监听8005端口Q?如果发?SHUTDOWN"Q默认培植下字符Ԍq来Q?关闭8005serverSocket?br />
2.4 启动各个容器
1. Server
触发Server容器启动?before_start)Q?启动?start)Q?启动?after_start)3个事Ӟ q运行相应的事g处理器?br />
启动Server的子容器QServcie.
2. Service
启动Service的子容器QEngin
启动Connector
3. Engin
CEnginq个层次Q以及以下别的容器Q?Tomcat׃用了比较一致的启动方式了?br />
首先Q?nbsp; q行各个容器自己Ҏ一些Q?br />
随后Q?nbsp; 触发启动前事?br />
立即Q?nbsp; 讄标签Q就表示该容器已l启?br />
接着Q?nbsp; 启动容器中的各个lgQ?loader, logger, manager{等
再接着Q启动mappinglg。(?Q?br />
紧跟着Q启动子容器?br />
接下来,启动该容器的道(pipline)
然后Q?nbsp; 触发启动中事?br />
最后, 触发启动后事件?br />
Engin大致会这么做Q?Host大致也会q么做, Context大致q是会这么做?那么很显然地Q?我们需要在q里使用C码复用的技术?tomcat在处理这个问题的时候, 漂亮C用了抽象cL处理?ContainerBase. 最后得这部分完成复杂功能的代码显得干净利落Q?q练爽快Q?实在是o得叹止, l细品来Q?直觉如n佳珍Q?另h齉K留香Q?留恋往q啊Q?br />
Engin的触发启动前事g里, 会激zȝ定在Engin上的唯一一个ListenerQEnginConfig?br />
q个EnginConfigcd本上没有做什么事情, 是把EnginConfig的调试别设|ؓ和Engin相当?另外是输出几行文本Q?表示Engin已经配置完毕Q?q没有做什么实质性的工作?br />
?: mappinglg的用处是Q?当一个需求将要从父容器传递到子容器的时候, 而父容器又有多个子容器的话, 那么应该选择哪个子容器来处理需求呢Q?q个由mapping lg来定夺?br />
4. Host
同Engin一P 也是调用ContainerBase里面的start()ҎQ?不过之前做了些自个儿的Q?是往Hostq个容器的通道QpiplineQ里面, 安装了一个叫?br />
“org.apache.catalina.valves.ErrorReportValve”的阀门?br />
q个阀门的用处是这LQ?nbsp; 需求在被Engin传递给Host后, 会l传递给Context做具体的处理?q里需求其实就是作为参C递的Request, Response?所以在context把需求处理完后, 通常会改动response?而这个org.apache.catalina.valves.ErrorReportValve的作用就是检察response是否包含错误Q?如果有就做相应的处理?br />
5. Context
Cq里Q?q于轮Ctomcat启动中真正的重头戏,启动Context了?br />
StandardContext.start() q个启动Context容器的方法被StandardHost调用.
5.1 webappResources 该context所指向的具体目?br />
5.2 安装defaultContex, DefaultContext 是默认Context?如果我们在一个Host下面安装了DefaultContextQ而且defaultContext里面又安装了一个数据库q接池资源的话?那么其他所有的在该Host下的Context, 都可以直接用这个数据库q接池, 而不用格外做配置了?br />
5.3 指定Loader. 通常用默认的org.apache.catalina.loader.WebappLoaderq个cR?nbsp; Loader是用来指定q个context会用到哪些类啊, 哪些jar包啊q些什么的?br />
5.4 指定 Manager. 通常使用默认的org.apache.catalina.session. StandardManager ?Manager是用来管理session的?br />
其实session的管理也很好实现?以一U简单的session理Z?当需求传递过来的时候, 在Request对象里面有一个sessionId 属性?OKQ?得到q个sessionId后, 我们可以把它作为map的keyQ而value我们可以攄一个HashMap. HashMap里边儿, 再放我们x的东ѝ?br />
5.5 postWorkDirectory (). Tomcat下面有一个work目录?我们把时文仉扔在那儿厅R?q个步骤是在那里创Z个目录?一般说来会?CATALINA_HOME%/work/Standalone\localhost\ q个地方生成一个目录?br />
5.6 Binding thread。到了这里, 应该发?class Loader 互换了?之前是看得见tomcat下面所有的class和lib. 接下来需要看得见当前context下的class?所以要讄contextClassLoader, 同时q要把旧的ClassLoader记录下来Q因Z后还要用的?br />
5.7 启动 Loader. 指定q个Context具体要用哪些classesQ?用到哪些jar文g?如果reloadable讄成了true, ׃启动一个线E来监视classes的变化, 如果有变化就重新启动Context?br />
5.8 启动logger
5.9 触发安装在它w上的一个监听器?br />
lifecycle.fireLifecycleEvent(START_EVENT, null);
作ؓ监听器之一QContextConfig会被启动. ContextConfig是用来配置web.xml的?比如q个Context有多ServletQ?又有多少FilterQ?是在这里给Context装上ȝ?br />
5.9.1 defaultConfig. 每个context都得配置 tomcat/conf/web.xml q个文g?br />
5.9.2 applicationConfig 配置自己?WEB-INF/web.xml 文g
5.9.3 validateSecurityRoles 权限验证?通常我们在访?admin 或?manager的时候,需要用戯么是admin的要么是manager的, 才能讉K?而且我们q可以限刉些资源可以访问, 而哪些不能?都是在这里实现的?br />
5.9.4 tldScan: 扫描一下, 需要用到哪些标{?tag lab)
5.10 启动 manager
5.11 postWelcomeFiles() 我们通常会用到的3个启动文件的名称Q?br />
index.html、index.htm、index.jsp p默认地绑在了q个context?br />
5.12 listenerStart 配置listener
5.13 filterStart 配置 filter
5.14 启动带有<load-on-startup>1</load-on-startup>的Servlet.
序是从到大: 1,2,3… 最后是0
默认情况下, 臛_会启动如?个的Servlet:
org.apache.catalina.servlets.DefaultServlet
处理静态资源的Servlet. 什么图片啊Q?html啊, css啊, js啊都找他
org.apache.catalina.servlets.InvokerServlet
处理没有做Servlet Mapping的那些Servlet.
org.apache.jasper.servlet.JspServlet
处理JSP文g?
5.15 标识context已经启动完毕?br />
C多少个步骤啊Q?Contextȝ是启动完毕喽?br />
OK! 走到了这里, 每个容器以及lg都启动完毕?Tomcatl于不辞辛劳Cؓ人民服务了!
3. 参考文献:
<http://jakarta.apache.org/tomcat/>
<http://www.onjava.com/pub/a/onjava/2003/05/14/java_webserver.html>
4. 后记
q篇文章是讲解tomcat启动框架的,q有文章是讲解TOMCAT里面的消息处理流E的l节的?文章内容已经写好了, 现在正在整理阶段?怿很快可以做出来Q?大家共同研究共同q步?br />
q篇文章是独自分析TOMCAT源码所写的Q?所以一定有地方是带有个Z观色彩, 隑օ会有片面之处。若有不当之处敬h评指教,q样不仅可以使刚开始研ITOMCAT的兄弟们走弯\Q?我也可以学到东西?br />
email: sojan_java@yahoo.com.cn
我用OO技术第一ơ设计Y件的时候,犯了一个设计者所能犯的所有错误。那是一个来自国外的外包目Q外方负责功能设计,我们公司负责E序设计、编码和试?
W一个重要的错误是,我没有认真的把设计说明书看明白。功能点设计实有一些问题,按照他们的设计,一个重要的程是无法实现的。于是我在没有与投资Ҏ通的情况下,擅自改动了设计,把一个原本在Linuxpȝ上开发的模块改到了Windowspȝ上。结果流E确实是实现了,但是很不q,Ҏ不符合他们的需要,比v原先的设计差的更多。在询问了这个流E的设计意图之后Q我也清楚了q一炏V对方的工程师承认了错误Q但是问题是Q?#8220;Z么不早说啊,我们都跟领导讲过了品的构架Q也保证了交货时间了Q现在怎么去说啊?”。他们设计的是一个苹果,而我造了一个桔子出来。最后和工程师商议的l果是:先把桔子Ҏ设计书上的苹果,按时交货Q然后再悄悄的改成他们真正需要的香蕉。的q时候距M货的旉已经不三天了,于是我每天加班工作到天明Q把代码逐行抽出来,用gcc~译调试。好在大部分都是体力z,没有什么技术含量,即在深夜大脑半休眠的情况下仍然可以接着qӀ?
目中出现的另外一个错误是Q我对工作量的估计非常的不准。在W一个阶D늚时候,按照功能设计说明书中的一个流E,我做了一个示例,用上了投资方规定的所有的技术。当我打开览器,看到面上出C数据库里?#8220;TomQJerryQ王帅”Q就愉快的跑到走廊上d怺一口新鲜空气,然后乐观的认为:设计书都已经写好了,CZ也做出来了,剩下的事情肯定就象砍瓜切菜一样了。不是把大家召集v来讲讲设计书Q看看示例,然后扑上d工,然后大功告成。我为每个画面分配的~码工作量是三个工作日。结果却是,他们的设计ƈ不完,我的理解也ƈ不正,大家的思想也ƈ不一致。于是我天天召集开会,朝o夕改Q不断返工。最后算了一下,实际上写完一个画面用的时间在十个工作日以上。编码占用了太多的时_试在匆忙中草草了事Q质?#8230;…能掩盖的问题也就只好掩盖一下了Q性能更是无暇֏了?
q有一个方面的问题是出在技术上的,q方面是我本文要说的重点。按照投资方的方案,pȝ的主体部分需要用J2EE框架Q选择的中间g是免费的JBoss。再加上Tomcat作ؓWeb服务器,Struts作ؓ表示层的框架。他们对于这些东西的使用都是有明目的,但是我ƈ不了解这些技术。新手第一ơ进行OO设计Q加上过多的新式技术,于是出现了一大堆的问题。公司原本安排了一个牛人对我进行指|他熟悉OO设计Qƈ且熟悉这些开源框Ӟ曄读Tomcat和Struts源代码。可是他实太忙Q能指导我的旉非常有限?
投资方发来设计书以后Q很快就z来了两个工E师对这个说明书q行讲解。这是一个功能设计说明书Q包括一个数据库设计说明书,和一个功能点设计说明。功能点说明里面叙述了每一个工作流E,画面设计和数据流E。两位工E师向我们简单的说明了品的构想Q然后花了一个多星期的时间十分详l的说明了他们的设计Q包括数据表里每一个字D늚含义Q画面上每一个控件的业务意义。除了这些功能性的需求以外,他们q有一些技术上的要求?
Z减少客户的拥有成本,他们不想品绑定在特定的数据库和操作系l上Qƈ且希望用免费的q_。于是他们选择了Java作ؓ开发语aQƈ且用了一pd免费的^台。选用的中间g是JBossQ用Entity Bean作ؓ数据库访问的方式。我们对Entity Bean的效率不攑ֿQ因为猜他q用了大量的反射技术。在l过一D|间的技术调查之后,我决定不采用Entity BeanQ而是自己写出一大堆的Value ObjectQ每个Value Object对应一个数据库表,Value Object里面只有一些setter和getterҎQ只保存数据Q不做Q何事情。Value Object的属性与数据库里面的字段一一对应。与每个Value Object对应Q做一个数据表的GatewayQ负责把数据从数据库里面查出来塞到这些Value Object里面Q也负责把Value Object里面的数据塞回数据库?
按照q样的设计,需要ؓ每一个数据表写一个Gateway和一个Value ObjectQ这个数量是比较庞大的。因此我们做了一个自动生成代码的工具Q到数据库里面遍历每一个数据表Q然后遍历表里面的每一个字D,把这些代码自动生成出来?
q等于自己实C一个ORM的机制。当时我们做q些事情的时候,ORMq是一个很陌生的名词,Hibernateq样的ORM框架q没听说q。接着我们q是需要解决系l在多种数据库上q行的问题。Gateway是用JDBCq接数据库的Q用SQL查询和修Ҏ据的。于是问题就是:要解决不同数据库之间SQL的微差别。我是这样干的:我做了一个SqlParser接口Q这个接口的作用是把ANSI SQL格式的查询语句{化成各种数据库的查询语句。当然我没必要做的很全面Q只要支持我在项目中用到的查询方式和数据cd够了。然后再开发几个具体的Parser来{换不同的数据库SQL格式?
到这个时候,数据库里面的数据成功转化成了E序里面的对象。非常好Q按道理_剩下的OO之\p理成章了。但是,很不q,我不知道该怎样用这些Value ObjectQ接下来我就怀着困惑的心情把q程式的代码嫁接在这个OO的基上了?
我ؓ每一个画面设计出了一个Session BeanQ在q个Session Bean里面装了画面所兌的一切业务流E,让这个Session Bean调用一大堆Value Object开始干zR在Session Bean和页面之_我没有让他们直接调用Q因为据公司的牛Q?#8220;面直接调用业务代码不好Q耦合性太强?#8221;q句话没错,但是我对“业务代码”的理解实在有问题Q于是就生生的造出一个Helper来,L在页面和Session Bean中间Q充当了一个传声筒的角艌Ӏ?
于是在开发中出C下面q副景象Q每当设计发生变_我就要修Ҏ据库的设计,用代码生成工具重新生成Value ObjectQ然后重C改Session Bean里面的业务流E,按照新的参数和返回g改Helper的代码,最后修攚w面的调用代码Q修攚w面样式?
实际情况比我现在说v来复杂的多。比如Value Object的修改,E序规模来大以后Q我Z避免出现内存的大量占用和效率的下降,不得不把一些数据库查询的逻辑写到了Gateway和Value Object里面Q于是在发生变更的时候,我还要手工修改代码生成工L成的Gateway和Value Object。这Ll护十分ȝQ这使我困惑OO到底有什么好处。我在这个项目中用OO方式解决了很多问题,而这些问题都是由OO本n造成的?
另一个比较大的问题出在Struts上。投资方为系l设计了很灵zȝ界面Q界面上的所有元素都是可以配|出来,包括位置、数据来源、读写属性。ƈ且操作员的权限可以精到每一个查看、修改的动作Q可以控制每一个控件的d操作。于是他们希望用Struts。Struts框架的每一个Action恰好对应一个操作,只需要自己定义Action和权限角色的关系Q就可以实现行ؓ的权限控制。但是我错误的理解了Struts的用法,我ؓ每一个页面设计了一个ActionQ而不是ؓ每一个行计一个ActionQ这h本就无法做到他们惌的权限控制方式。他们很快发C我的问题Q于是发来了一个说明书Q向我介lStruts的正用方式。说明书打印出来厚厚的一本,我翻了一天,l于知道了错在什么地斏V但是一大半画面已经生米煮成熟饭Q再加上我的Session Bean里面的流E又是按画面来封装的Q于是只能改造小部分能改造的画面Q权限问题另扑֊法解决了?
下面是q个pȝ的全貌,场面看上去还是蔚为壮观的Q?
pȝl历q数ơ较大的修改Q这个框架不但没有减d更的压力Q反而得变更困隑֊大了。到后来Q因Z务流E的变更的越来越复杂Q现有流E无法修改,只得用一些十分曲折的方式来实玎ͼq行效率来低。由于结构过于复杂,Ҏ没有办法q行性能上的优化。ؓ了^衡效率的延缓Q不得不把越来越多的Value Object攑֜了内存中~存hQ这又造成了内存占用的急剧增加。到后期调试E序的时候,服务器经常出?#8220;Out of memory”异常Q各cd象庞大繁多,pȝ~译部v一ơ需?0多分钟。投资方原先是希望我们用JUnit来进行单元测试,但是q样的流E代码测试v来困N重,要花费太多的旉和h手,也只得作|。此外他们设计的很多功能其实都没有实玎ͼq且g以后也很隑ֆ实现了。设计中预想的很多优U特点在这h架中一一消失Q大家无奈的接受一个失望的局面?
在我d公司两年以后Q这个系l仍然在持箋开发中。新的模块不断的dQ框架上不断d新的功能炏V有一ơ遇C然在公司工作的同事,他们_“q是原来那个框架Q前台加上一个个的JSPQ然后后台加上一个个的Value ObjectQ中间的Session Bean装来多的业务流E?#8221;
我的W一个OOpȝ的设计,表面上用了OO技术,实际上分析设计还是过E主导的方式。设计的时候过多、过早、过深入的考虑了需要做哪些画面Q画面上应该有哪些功能点Q功能点的数据流E。再加上一个复杂的OO框架Q名目繁多的对象Q不仅无法做到快速的开发,灉|的适应需求的变化Q反而ɾpȝ变得更加复杂Q功能修Ҏ加的ȝ了?
在面条式代码的时代,很多人用汇编代码写出了一个个优秀的程序。他们利用一些工P或者共同遵守一些特别的规约Q采用一致的变量命名方式Q规范的代码注释Q可以一个庞大的开发团队运行的井井有条。h如果有了先进的思想Q工具在q些人的手中可以发挥出越时代的能量。而我设计的第一个OOpȝQ恰好是一个相反的例子?
实际上,面向对象的最独特之处Q在于他分析需求的方式。按照这L方式Q不要过分的U缠于程序的画面、操作的q程Q数据的程Q而是要更加深入的探烦需求中的一些重要概c下面,我们通过一个实例看一看,怎样L住需求中的这些重要概念,q且q用OOҎ把他融合到程序设计中。也看看OO技术是如何帮助开发h员控制程序的复杂度,让大家工作的更加单、高效?
我们来看看一个通信公司的̎务系l的开发情c最开始,开发h员找到电信公司的职员询问需求的情况。电信公司的职员是这栯的:
“账务pȝ主要做这样几件事情:每个?日凌晨按照用户用情는成̎单,然后用预存冲销q个账单。还要受理用L~费Q缴费后可以自动冲销Ơ费的̎单,Ơ费用户~清费用之后要发指oC换上Q开启他的服务。费用缴清以后可以打印发,发票是下面q个样子?#8221;
l过一番调查,开发h员设计了下面几个主要的流E:
1?̎Q根据一个月内用L消费情况生成账单Q?
2?销账:冲销用户账户上的余额和̎单;
3?~费Q用户向自己的̎户上~费Q缴清欠费后打印发票?
弄清了流E,接着p计用L面来实现q样的流E。下面是其中一个数据查询界面,分ؓ两个部分Q上半部分是~费信息Q记录了用户的缴费历Ԍ下半部分是̎单信息,昄账单的费用和销账情c?
界面上的数据一眼看h很复杂,其实l合̎、缴贏V销账的程讲解一下,是比较容易理解的。下面简单说明一下?
~费的时候,在缴费信息上d一条记录,记录下缴贚w额。然后查找有没有Ơ费的̎单,如果有就做销账。冲抉|费的金额记录?#8220;Ơ费金额”的位|。如果欠Ҏ间较长,p滞U金Q记录在“滞纳?#8221;的位|上。冲销Ơ费以后Q剩余的金额记录?#8220;预存?#8221;的位|上?#8220;其他费用”q个位置是预留的Q目前没有作用?
每个月出账的时候,在̎单信息里面加上一条记录,记录下̎单的应收和优惠,q两部分相减是账单的总金额。然后检查一下̎户上有没有余额,如果有就做销账。销账的时候,预存Ƒֆ销的部分记录在“预存划拨”的位|,如果不以冲抉|费,账单暂时处?#8220;未缴”状态。等Cơ缴费的时候,冲销的金额再记录?#8220;C?#8221;的位|。等到所有费用缴清了Q̎单状态变?#8220;已缴”?
销账的程p栯合在~费和出账的q程中?
看v来一切成功搞定了Q最重要的几个流E很明确了,剩下的事情无疑就像砍瓜切菜一栗无非是l着q几个流E,设计出其他更多的程。现在有个小问题Q打印发的时候,发票的右侧需要有上次l余、本ơ实~、本ơ话贏V本ơ结余这几个金额?
上次l余Q上个月账单销账后剩下来的金额Q这个容易理解;
本次l余Q当前的账单销账后剩下的金额,q个也不难;
本次话费Q这是̎单的费用Q还是最后一ơ完全销账时的缴费,应该用哪一个呢Q?
本次~费Q这个和本次话费有什么区别,他在哪里出来?
带着问题Q开发者去问电信公司的职员。开发者把他们设计的界面指点给用户看,向他说明了自q设计的这几个程Q同时也说出了自q疑问。用h有直接回{这个疑问,却提Z另一个问题:
“~费打发这个流Eƈ不Lq样的,~费以后不一定立刻要打印发票的。我们的用户可以在银行、超市这L地方~话费,几个月以后才来到我们q里打印发票。ƈ且缴费的旉和销账的旉可以相距很长的,可以先缴U一W话费,后面几个月的账单都用q笔钱销账;也可以几个月都不~费Q然后缴U一W费用冲销q几个̎单。你们设计的q个界面不能很好的体现用L~费和消Ҏ况,很难看出来某一ơ缴Ҏ在什么时候用完的。必MW一ơ、或者最后一ơ缴费余额推这个历Ԍ太麻烦了。还有,‘预存划拨’?#8216;C?#8217;q两个概忉|们以前从来没有见q,对用戯释v来肯定是很麻烦的?#8221;
开发h员^静了一下自己沮丧(或愤怒)的心情,仔细想一惻Iq样的设计确实很不合理。如果一个会计记L账本来,他肯定会被老板开除的?
看v来流E要改,比先前设计的更加灉|Q界面也要改。就好像原先盖好的一栋房子忽然被捅了几个H窿Q变得四处透风了。还有,那四个数值到底应该怎样计算出来呢?我们先到走廊上去呼吸两口新鲜I气Q然后再回来x吧?
现在Q让我们先忘记这几个变化多端的流E,׃Ҏ间想一x基本的几个概念吧。系l里面最显而易见的一个概忉|什么呢Q没错,是̎PAccountQ。̎户可以缴费和消费。每个月消费的情冉|记录在一个̎单(BillQ里面的。̎户和账单之间是一对多的关pR此外,账户q有另一个重要的相关的概念:~费QDepositQ。̎户和~费之间也是一对多的关pR在我们刚才的设计中Q这些对象是q样的:
q个设计看来有些问题Q用了一些用户闻所未闻的概念(预存划拨Q新交款Q。ƈ且他分离了缴费和消费Q表面上很清楚,实际上账单的查询变得困难了。在实现一些功能的时候确实比较简单(比如~费和销账)Q但是另一些功能变得很困难Q比如打印发)。问题到底在什么地方呢Q?
涉及到̎务的行业有很多,最Ҏ惛_的也许就是银行了。从银行w上Q我们是不是可以学到什么呢Q下面是一个银行的存折Q这是一个委托收Ƅ账号。用户在账户上定期存钱,然后他的消费会自动从q里扣除。这个情景和我们需要实现的需求很怼。可以观察一下这个存折,存入和支取都是记录在同一列上的,在支出或者存入的右侧记录当时的结余?
有两ơ̎户上的金额被扣除?Q这时候金额已l被全部扣除了,但是消费q没有完全冲销。等到再ơ存入以后,会l支取。这U记账的方式是最基本的流水̎Q每一条存入和支出都要记录Z条̎目(EntryQ。程序的设计应该是这P
q个l构看上d刚才那个g没有什么不同,其实差别是很大的。上面的那个Deposit只是~费记录Q这里的Entry是̎目,包括~费、扣贏V滞U金……所有的费用。销账扣费的q程不应该记录在账单中,而是应该以̎目的形式记录下来。Account的代码片D如下:
Entry有很多种cdQ存入、支取、滞U金、赠送费Q,可以考虑可以为每一U类型创Z个子c,像q样Q?
搞成父子关系看v来很复杂、麻烦,q且目前也看不出这些类型作为Entry的子cL哪些好处。所以我们决定不q样做,只是单的把这几种cd作ؓEntry的一个属性。Entry的代码片D如下:
Entry是一个枚丄型,代码如下Q?br />
下面的界面显C的是刚才那个账户的̎目。要昄q个界面只需要调用Account的GetEntreesҎQ得到所有的账目Q然后按旉序昄出来。这个界面上的消Ҏ况就明确多了Q用户很Ҏ弄明白某个缴Ҏ在哪几个月䆾被消Ҏ的?
q且Q发上的那几个一直搞不明白的数g有了{案。比?005q?月䆾的发,我们先看?005q?月䆾销账的所有̎目(W六行、第八行Q,q两ơ一共扣?3.66元,q个金额是本次消费Q两ơ扣除之间存?00元,q个是本次实缴Q第五行的结余是17.66元,q就是上ơ结余;W八行上的结余是144元,q个是本次l余?
用户查了q个设计Q觉得这L费用昄明确多了。尽一些措辞不W合习惯的业务词汇,但是他们的概念都是符合的。ƈ且上ơ还有一个需求没有说Q有时候需要把多个月䆾的发合在一h印。按照这L账目表达方式Q合q的发票数g比较Ҏ搞清楚了。明了q样的对象关p,实现q个需求其实很Ҏ?
面向对象的设计就是要q样Q不要急于定pȝ需要做哪些功能点和哪些界面Q而是首先要深入的探烦需求中出现的概c在具体的流E不甚清楚的情况下,先把q些概念搞清楚,一个一个的开发出来。然后只要把q些做好的零件拿q来Q千变万化的程其实变得很单了Q一番搭U木式的装配可以比较轻杄实现?
另一个重要的cd也渐渐清晰的现在我们的眼前Q̎单(BillQ。他的代码片D如下:
BillcL两个与滞U金有关的方法,q开发者想C原先忽略的一个流E:计算滞纳金。经q与电信公司的确认,军_每个月进行一ơ计滞U金的工作。开发h员写了一个脚本,先得到系l中所有的Ơ费账单Q然后一一调用他们的CaculatePenaltyҎ。每个月这个脚本执行一ơ,可以完成滞U金的计工作?
Bill对象中有账户的基本属性和各账目的金额和销账的情况Q要打印发票Q只有这些数值是不够的。还要涉及到上次l余、本ơ结余和本次实缴Q这三个数值是需要从账目中查到的。ƈ且发有严格的格式要求,也不需要显C用的l节Q只要显CZU和二的费用类可以了。应该把q些东西另外装成一个类Q发(InvoiceQ:
通信公司后来又提Z新的需求:有些账号和银行签订了托收协议Q每个月通信公司打印些̎L托收单交l银行,银行从个人结̎户上扣除q笔钱,再把一个扣费单交给通信公司。通信公司Ҏq个扣费单冲销用户的欠贏V于是开发h员可以再做一个托收单QDeputyBillQ:
账单中的GetFeeҎ的返回值类型是FeeQFeecd包含了费用的名称、金额和他包含的其他费用。例如下面的情况Q?
我们可以用这L一个类来表C用(FeeQ,一个费用可以包含其他的费用Q他的金额是子费用的金额和。代码片D如下:
现在开发者设计出了这么一堆类Q构成Y件系l的主要零gp么制造出来了。下面要做的是把这些零件串在一Pd现需要的功能。OO设计的重点就是要扑ֈq些零g。就像是设计一辆汽车,仅仅知道油\、电路、传动的各项程是不够的Q重要的是知道造一辆汽车需要先刉哪些零件。要x的把这些零件设计出来不是一件容易的事情Q很有开发者一开始就了解pȝ的需求,设计出合理的对象关系。根本的原因在于领域知识的乏,开发者和用户之间也缺乏必要的交流。很多h在Y件开发的q程中才渐渐意识到原来的设计中存在一些难受的地方Q然后探索下去,才知道了正确的方式,q就是业务知识的一个突破。不q的是,当这个突破到来的时候,E序员经常是已经忙得热火朝天Q快把代码写完了。要把一切恢复到正常的轨道上Q需要勇气,旉Q有q见的领D,也需要有q气?/p>