CXF发布Webservice服务,出现异常,请帮忙分析一下

Posted on

CXF发布Webservice服务,出现异常,请帮忙分析一下 - Java - Java EE

#

#

<<>> CSDN-CSDN社区-Java-Java EE

  • 管理菜单

  • 生成帖子

  • 置顶
  • 推荐
  • 取消推荐
  • 锁定
  • 解锁
  • 移动
  • 编辑
  • 删除
  • 帖子加分
  • 帖子高亮
  • 取消高亮
  • 结 帖
  • 发 帖
  • 回 复

    收藏 不显示删除回复显示所有回复显示星级回复显示得分回复 CXF发布Webservice服务,出现异常,请帮忙分析一下[问题点数:50分,结帖人:ma_liang]

  • ma_liang
  • (黑马) *
  • 等 级:
  • 结帖率:93.33%
  • 楼主发表于:2008-10-12 16:13:23 使用Cxf发布WebServic出现如下异常,请帮忙分析一下 信息: Creating Service {http://service.webservice.client.api.workflow.cvicse.com/}WsClient from class com.cvicse.workflow.api.client.webservice.service.WsClient Exception in thread "main" java.lang.AbstractMethodError: org.apache.xerces.dom.DocumentImpl.getInputEncoding()Ljava/lang/String; at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source) at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source) at java.lang.reflect.Method.invoke(Unknown Source) at org.apache.ws.commons.schema.utils.DOMUtil.getInputEncoding(DOMUtil.java:602) at org.apache.ws.commons.schema.SchemaBuilder.build(SchemaBuilder.java:84) at org.apache.ws.commons.schema.XmlSchemaCollection.read(XmlSchemaCollection.java:424) at org.apache.ws.commons.schema.XmlSchemaCollection.read(XmlSchemaCollection.java:418) at org.apache.cxf.common.xmlschema.SchemaCollection.read(SchemaCollection.java:142) at org.apache.cxf.databinding.AbstractDataBinding.addSchemaDocument(AbstractDataBinding.java:93) at org.apache.cxf.jaxb.JAXBDataBinding.initialize(JAXBDataBinding.java:336) at org.apache.cxf.service.factory.ReflectionServiceFactoryBean.buildServiceFromClass(ReflectionServiceFactoryBean.java:354) at org.apache.cxf.jaxws.support.JaxWsServiceFactoryBean.buildServiceFromClass(JaxWsServiceFactoryBean.java:514) at org.apache.cxf.service.factory.ReflectionServiceFactoryBean.initializeServiceModel(ReflectionServiceFactoryBean.java:405) at org.apache.cxf.service.factory.ReflectionServiceFactoryBean.create(ReflectionServiceFactoryBean.java:188) at org.apache.cxf.jaxws.support.JaxWsServiceFactoryBean.create(JaxWsServiceFactoryBean.java:164) at org.apache.cxf.frontend.AbstractWSDLBasedEndpointFactory.createEndpoint(AbstractWSDLBasedEndpointFactory.java:100) at org.apache.cxf.frontend.ServerFactoryBean.create(ServerFactoryBean.java:116) at org.apache.cxf.jaxws.JaxWsServerFactoryBean.create(JaxWsServerFactoryBean.java:168) at org.apache.cxf.jaxws.EndpointImpl.getServer(EndpointImpl.java:336) at org.apache.cxf.jaxws.EndpointImpl.doPublish(EndpointImpl.java:251) at org.apache.cxf.jaxws.EndpointImpl.publish(EndpointImpl.java:201) at org.apache.cxf.jaxws.spi.ProviderImpl.createAndPublishEndpoint(ProviderImpl.java:84) at javax.xml.ws.Endpoint.publish(Endpoint.java:47) at com.cvicse.workflow.WorkflowWebService.(WorkflowWebService.java:13) at com.cvicse.workflow.WorkflowWebService.main(WorkflowWebService.java:18) * 对我有用[0]
  • 丢个板砖[0]
  • 引用
  • 举报
  • 管理
  • TOP 回复次数:9 * accp206用户头像
  • accp206
  • (与勋章仅一步之遥,遗憾……没太) *
  • 等 级:
  • /#1楼 得分:10回复于:2008-10-12 18:45:58 你接口中的某些方法,其参数或返回值类型是否有String数组的? 如果有的话,消除这些类型的参数,用别的方式代替。比如String数组,可换成一个用“,”号分隔的字符串传递。 在开发WebService程序时,尽量不要使用复杂数据类型。 * 对我有用[0]
  • 丢个板砖[0]
  • 引用
  • 举报
  • 管理
  • TOP * ma_liang用户头像
  • ma_liang
  • (黑马) *
  • 等 级:
  • /#2楼 得分:0回复于:2008-10-12 21:17:36 接口中的方法里没有数组类型,不过返回值和参数里有接口类型的,我已经将它增加了适配器,如果不用适配器的话发布服务的时候会报JAXB绑定的错误,但是适配了所有后发布服务,报上面的错误,让人不解 * 对我有用[0]
  • 丢个板砖[0]
  • 引用
  • 举报
  • 管理
  • TOP * teamlet用户头像
  • teamlet
  • (teamlet) *
  • 等 级:
  • /#3楼 得分:10回复于:2008-10-13 11:23:08 看看classpath路径下有没有 xercesImpl-2.8.1.jar ,如果没有加上试试。 这是由于获取字符集的时候,你所使用的 jar 包中的org.apache.xerces.dom.DocumentImpl没有实现getInputEncoding()这个方法。 * 对我有用[0]
  • 丢个板砖[0]
  • 引用
  • 举报
  • 管理
  • TOP * teamlet用户头像
  • teamlet
  • (teamlet) *
  • 等 级:
  • /#4楼 得分:10回复于:2008-10-13 12:55:22 报错后,字符集被设置为 UTF-8 * 对我有用[0]
  • 丢个板砖[0]
  • 引用
  • 举报
  • 管理
  • TOP * ma_liang用户头像
  • ma_liang
  • (黑马) *
  • 等 级:
  • /#5楼 得分:0回复于:2008-10-14 08:38:30 我把项目构件路径中的 xercesImpl-2.8.1.jar 去掉了,反而发布成功了,为什么? * 对我有用[0]
  • 丢个板砖[0]
  • 引用
  • 举报
  • 管理
  • TOP * teamlet用户头像
  • teamlet
  • (teamlet) *
  • 等 级:
  • /#6楼 得分:10回复于:2008-10-14 10:56:15 更正一下 org.apache.xerces.dom.DocumentImpl.getInputEncoding()在其父类CoreDocumentImpl中实现了。 在DOMUtil.java中,通过如下语句调用 getInputEncoding 方法: Method m = Document.class.getMethod("getInputEncoding", new Class[]{}); Document是一个接口,声明了 getInputEncoding 方法。 DocumentImpl继承了CoreDocumentImpl,CoreDocumentImpl实现了Document接口。 在通过Document接口调用DocumentImpl的时候,没有找到getInputEncoding 方法, 因此提示java.lang.AbstractMethodError 你的cxf是哪个版本的?在你的环境下使用的是哪个版本的 xercesImpl?是否存在多个不同的xercesImpl? * 对我有用[0]
  • 丢个板砖[0]
  • 引用
  • 举报
  • 管理
  • TOP * ma_liang用户头像
  • ma_liang
  • (黑马) *
  • 等 级:
  • /#7楼 得分:0回复于:2008-10-15 08:32:12 我的Cxf是用的2.1.2版本,xercesImpl.jar只有一个,在构建路径里去掉就可以正常发布服务,实际上它在原来的项目里伴有重要的角色,去掉之后是可以发布服务了,但是以前的功能有些就不支持了 * 对我有用[0]
  • 丢个板砖[0]
  • 引用
  • 举报
  • 管理
  • TOP * ma_liang用户头像
  • ma_liang
  • (黑马) *
  • 等 级:
  • /#8楼 得分:0回复于:2008-10-15 09:02:58 我还有一个疑问是在用Cxf发布服务时,怎么会调用这个类的方法,为什么去掉之后就不去找这个方法了? * 对我有用[0]
  • 丢个板砖[0]
  • 引用
  • 举报
  • 管理
  • TOP * teamlet用户头像
  • teamlet
  • (teamlet) *
  • 等 级:
  • /#9楼 得分:10回复于:2008-10-15 10:57:07 1、在根据Class生成XMLSchema的时候,当涉及到如下的namespace就会调用这个方法: http://www.w3.org/2005/02/addressing/wsdl http://www.w3.org/2005/08/addressing http://schemas.xmlsoap.org/ws/2005/02/rm http://www.w3.org/2005/05/xmlmime 每个namespache对应的xsd在classpath中: "http://www.w3.org/2005/02/addressing/wsdl","classpath:/schemas/wsdl/ws-addr-wsdl.xsd"); "http://www.w3.org/2005/08/addressing", "classpath:/schemas/wsdl/ws-addr.xsd"); "http://schemas.xmlsoap.org/ws/2005/02/rm", "classpath:/schemas/wsdl/wsrm.xsd"); "http://www.w3.org/2005/05/xmlmime", "classpath:/schemas/wsdl/ws-addr.xsd"); 2、javax.xml.validation.SchemaFactory 是sun提供的一个抽象类,声明了一些抽象的方法; 通过在classpath中查找同名的配置文件可以获得具体的实现。 在xercesImpl.jar中有一个配置文件javax.xml.validation.SchemaFactory,声明使用xercesImpl中的类。 当你去除这个jar的时候,这个配置无法读取,就不去找这个方法了! 但是你的环境中还可以找到其他的 javax.xml.validation.SchemaFactory 文件,绑定的是其他的实现。 如果这些实现只能解析特定的schema ,那么它不能解析的schema声明的功能就无法使用了。 * 对我有用[0]
  • 丢个板砖[0]
  • 引用
  • 举报
  • 管理
  • TOP

  • 管理菜单

  • 生成帖子

  • 置顶
  • 推荐
  • 取消推荐
  • 锁定
  • 解锁
  • 移动
  • 编辑
  • 删除
  • 帖子加分
  • 帖子高亮
  • 取消高亮
  • 结 帖
  • 发 帖
  • 回 复 相关问题 [求助大家]关于Web Service调用时出现"no jbi part element"的问题

[关闭] [关闭]

Java Service Wrapper

Posted on

Java Service Wrapper - Logging Configuration Properties

World MapEnglishJapanese (Nihongo)German (Deutsch)

Please or

  • Java Service Wrapper Logo
  • Tanuki Software Ltd Logo

Java Service Wrapper is the easiest way to make your product more reliable.

Logging Configuration Properties # Logging Configuration Properties Logging properties are used to configure how where the Java Service Wrapper logs internal output as well as JVM output which would normally show up in the console used to launch a Java application. Wrapper Logging properties do not in any way control nor replace Logging tools within the Java application.

[General/Common]

[Console]

[Command]

[Log File]

[Log-Dialog]

[Syslog]

Tomcat源码分析

Posted on

Tomcat源码分析

Tomcat源码分析

TOMCAT源码分析(启动框架) 前言: 本文是我阅读了TOMCAT源码后的一些心得。 主要是讲解TOMCAT的系统框架, 以及启动流程。若有错漏之处,敬请批评指教! 建议: 毕竟TOMCAT的框架还是比较复杂的, 单是从文字上理解, 是不那么容易掌握TOMCAT的框架的。 所以得实践、实践、再实践。 建议下载一份TOMCAT的源码, 调试通过, 然后单步跟踪其启动过程。 如果有不明白的地方, 再来查阅本文, 看是否能得到帮助。 我相信这样效果以及学习速度都会好很多!

  1. Tomcat的整体框架结构 Tomcat的基本框架, 分为4个层次。 Top Level Elements: Server Service
    Connector HTTP AJP Container Engine Host Context Component
    manager logger loader pipeline valve

      ...
    

    站在框架的顶层的是Server和Service Server: 其实就是BackGroud程序, 在Tomcat里面的Server的用处是启动和监听服务端事件(诸如重启、关闭等命令。 在tomcat的标准配置文件:server.xml里面, 我们可以看到“;”这里的"SHUTDOWN"就是server在监听服务端事件的时候所使用的命令字) Service: 在tomcat里面,service是指一类问题的解决方案。 通常我们会默认使用tomcat提供的:Tomcat-Standalone 模式的service。 在这种方式下的service既给我们提供解析jsp和servlet的服务, 同时也提供给我们解析静态文本的服务。

    Connector: Tomcat都是在容器里面处理问题的, 而容器又到哪里去取得输入信息呢? Connector就是专干这个的。 他会把从socket传递过来的数据, 封装成Request, 传递给容器来处理。 通常我们会用到两种Connector,一种叫http connectoer, 用来传递http需求的。 另一种叫AJP, 在我们整合apache与tomcat工作的时候,apache与tomcat之间就是通过这个协议来互动的。 (说到apache与tomcat的整合工作, 通常我们的目的是为了让apache 获取静态资源, 而让tomcat来解析动态的jsp或者servlet。) Container: 当http connector把需求传递给顶级的container: Engin的时候, 我们的视线就应该移动到Container这个层面来了。 在Container这个层, 我们包含了3种容器:Engin, Host, Context. Engin: 收到service传递过来的需求, 处理后, 将结果返回给service( service 是通过connector 这个媒介来和Engin互动的). Host: Engin收到service传递过来的需求后,不会自己处理, 而是交给合适的Host来处理。 Host在这里就是虚拟主机的意思, 通常我们都只会使用一个主机,既“localhost”本地机来处理。 Context: Host接到了从Host传过来的需求后, 也不会自己处理, 而是交给合适的Context来处理。 比如:http://127.0.0.1:8080/foo/index.jsp;

      <http://127.0.1:8080/bar/index.jsp>;
    

    前者交给foo这个Context来处理, 后者交给bar这个Context来处理。 很明显吧!context的意思其实就是一个web app的意思。 我们通常都会在server.xml里面做这样的配置 ; 这个context容器,就是用来干我们该干的事儿的地方的。

    Compenent: 接下来, 我们继续讲讲component是干什么用的。 我们得先理解一下容器和组件的关系。 需求被传递到了容器里面, 在合适的时候, 会传递给下一个容器处理。 而容器里面又盛装着各种各样的组件, 我们可以理解为提供各种各样的增值服务。 manager: 当一个容器里面装了manager组件后,这个容器就支持session管理了, 事实上在tomcat里面的session管理, 就是靠的在context里面装的manager component. logger: 当一个容器里面装了logger组件后, 这个容器里所发生的事情, 就被该组件记录下来啦! 我们通常会在logs/ 这个目录下看见catalina_log.time.txt 以及localhost.time.txt 和localhost_examples_log.time.txt。 这就是因为我们分别为:engin, host以及context(examples)这三个容器安装了logger组件, 这也是默认安装, 又叫做标配 :) loader: loader这个组件通常只会给我们的context容器使用,loader是用来启动context以及管理这个context的classloader用的。 pipline: pipeline是这样一个东西, 当一个容器决定了要把从上级传递过来的需求交给子容器的时候, 他就把这个需求放进容器的管道(pipeline)里面去。 而需求傻呼呼得在管道里面流动的时候, 就会被管道里面的各个阀门拦截下来。 比如管道里面放了两个阀门。 第一个阀门叫做“access_allow_vavle”, 也就是说需求流过来的时候,它会看这个需求是哪个IP过来的, 如果这个IP已经在黑名单里面了,sure, 杀! 第二个阀门叫做“defaul_access_valve”它会做例行的检查, 如果通过的话,OK, 把需求传递给当前容器的子容器。 就是通过这种方式, 需求就在各个容器里面传递,流动, 最后抵达目的地的了。 valve: 就是上面所说的阀门啦。 Tomcat里面大概就是这么些东西, 我们可以简单地这么理解tomcat的框架,它是一种自上而下, 容器里又包含子容器的这样一种结构。

  2. Tomcat的启动流程 这篇文章是讲tomcat怎么启动的,既然我们大体上了解了TOMCAT的框架结构了, 那么我们可以望文生意地就猜到tomcat的启动, 会先启动父容器,然后逐个启动里面的子容器。 启动每一个容器的时候, 都会启动安插在他身上的组件。 当所有的组件启动完毕, 所有的容器启动完毕的时候,tomcat本身也就启动完毕了。 顺理成章地, 我们同样可以猜到,tomcat的启动会分成两大部分, 第一步是装配工作。 第二步是启动工作。 装配工作就是为父容器装上子容器, 为各个容器安插进组件的工作。 这个地方我们会用到digester模式, 至于digester模式什么, 有什么用, 怎么工作的. 请参考http://software.ccidnet.com/pub/article/c322_a31671_p2.html; 启动工作是在装配工作之后, 一旦装配成功了, 我们就只需要点燃最上面的一根导线, 整个tomcat就会被激活起来。 这就好比我们要开一辆已经装配好了的汽车的时候一样,我们只要把钥匙插进钥匙孔,一拧,汽车的引擎就会发动起来,空调就会开起来, 安全装置就会生效, 如此一来,汽车整个就发动起来了。(这个过程确实和TOMCAT的启动过程不谋而和, 让我们不得不怀疑TOMCAT的设计者是在GE做JAVA开发的)。 2.1 一些有意思的名称: Catalina Tomcat Bootstrap Engin Host Context 他们的意思很有意思: Catalina: 远程轰炸机 Tomcat: 熊猫轰炸机-- 轰炸机的一种(这让我想起了让国人引以为豪的熊猫手机,是不是英文可以叫做tomcat??? , 又让我想起了另一则广告: 波导-手机中的战斗机、波音-客机中的战斗机 ) Bootstap: 引导 Engin: 发动机 Host: 主机,领土 Context: 内容, 目标, 上下文

    ... 在许多许多年后, 现代人类已经灭绝。 后现代生物发现了这些单词零落零落在一块。 一个自以为聪明的家伙把这些东西翻译出来了: 在地勤人员的引导(bootstrap)下, 一架轰炸架(catalina)腾空跃起, 远看是熊猫轰炸机(tomcat), 近看还是熊猫轰炸机! 凭借着优秀的发动机技术(engin), 这架熊猫轰炸机飞临了敌国的领土上空(host), 对准目标(context)投下了毁天灭地的核弹头,波~ 现代生物就这么隔屁了~

    综上所述, 这又不得不让人联想到GE是不是也参与了军事设备的生产呢? 反对美帝国主义! 反对美霸权主义! 和平万岁! 自由万岁!

2.2 历史就是那么惊人的相似!tomcat的启动就是从org.apache.catalina.startup.Bootstrap这个类悍然启动的! 在Bootstrap里做了两件事:

  1. 指定了3种类型classloader: commonLoader: common/classes、common/lib、common/endorsed catalinaLoader: server/classes、server/lib、commonLoader sharedLoader: shared/classes、shared/lib、commonLoader
  2. 引导Catalina的启动。 用Reflection技术调用org.apache.catalina.startup.Catalina的process方法, 并传递参数过去。

2.3 Catalina.java Catalina完成了几个重要的任务:

  1. 使用Digester技术装配tomcat各个容器与组件。 1.1 装配工作的主要内容是安装各个大件。 比如server下有什么样的servcie。Host会容纳多少个context。Context都会使用到哪些组件等等。 1.2 同时呢, 在装配工作这一步, 还完成了mbeans的配置工作。 在这里,我简单地但不十分精确地描述一下mbean是什么,干什么用的。
    我们自己生成的对象, 自己管理, 天经地义! 但是如果我们创建了对象了, 想让别人来管, 怎么办呢? 我想至少得告诉别人我们都有什么, 以及通过什么方法可以找到  吧!JMX技术给我们提供了一种手段。JMX里面主要有3种东西。Mbean, agent, connector.
    
    Mbean: 用来映射我们的对象。也许mbean就是我们创建的对象, 也许不是, 但有了它, 就可以引用到我们的对象了。 Agent: 通过它, 就可以找到mbean了。 Connector: 连接Agent的方式。 可以是http的, 也可以是rmi的,还可以直接通过socket。 发生在tomcat 装配过程中的事情: GlobalResourcesLifecycleListener 类的初始化会被触发: protected static Registry registry = MBeanUtils.createRegistry(); 会运行 MBeanUtils.createRegistry() 会依据/org/apache/catalina/mbeans/mbeans- descriptors.xml这个配置文件创建mbeans. Ok, 外界就有了条途径访问tomcat中的各个组件了。(有点像后门儿)
  2. 为top level 的server 做初始化工作。 实际上就是做通常会配置给service的两条connector.(http, ajp)
  3. 从server这个容器开始启动, 点燃整个tomcat.
  4. 为server做一个hook程序, 检测当server shutdown的时候, 关闭tomcat的各个容器用。
  5. 监听8005端口, 如果发送"SHUTDOWN"(默认培植下字符串)过来, 关闭8005serverSocket。 2.4 启动各个容器
  6. Server 触发Server容器启动前(before_start), 启动中(start), 启动后(after_start)3个事件, 并运行相应的事件处理器。 启动Server的子容器:Servcie.
  7. Service 启动Service的子容器:Engin 启动Connector
  8. Engin 到了Engin这个层次,以及以下级别的容器,Tomcat就使用了比较一致的启动方式了。 首先, 运行各个容器自己特有一些任务 随后, 触发启动前事件 立即, 设置标签,就表示该容器已经启动 接着, 启动容器中的各个组件:loader, logger, manager等等 再接着,启动mapping组件。(注1) 紧跟着,启动子容器。 接下来,启动该容器的管道(pipline) 然后, 触发启动中事件 最后, 触发启动后事件。

    Engin大致会这么做,Host大致也会这么做, Context大致还是会这么做。 那么很显然地, 我们需要在这里使用到代码复用的技术。tomcat在处理这个问题的时候, 漂亮地使用了抽象类来处理。ContainerBase. 最后使得这部分完成复杂功能的代码显得干净利落, 干练爽快, 实在是令人觉得叹为观止, 细细品来, 直觉如享佳珍, 另人齿颊留香, 留恋往返啊!

    Engin的触发启动前事件里, 会激活绑定在Engin上的唯一一个Listener:EnginConfig。 这个EnginConfig类基本上没有做什么事情, 就是把EnginConfig的调试级别设置为和Engin相当。 另外就是输出几行文本, 表示Engin已经配置完毕, 并没有做什么实质性的工作。 注1: mapping组件的用处是, 当一个需求将要从父容器传递到子容器的时候, 而父容器又有多个子容器的话, 那么应该选择哪个子容器来处理需求呢? 这个由mapping 组件来定夺。

  9. Host 同Engin一样, 也是调用ContainerBase里面的start()方法, 不过之前做了些自个儿的任务,就是往Host这个容器的通道(pipline)里面, 安装了一个叫做 “org.apache.catalina.valves.ErrorReportValve”的阀门。 这个阀门的用处是这样的: 需求在被Engin传递给Host后, 会继续传递给Context做具体的处理。 这里需求其实就是作为参数传递的Request, Response。 所以在context把需求处理完后, 通常会改动response。 而这个org.apache.catalina.valves.ErrorReportValve的作用就是检察response是否包含错误, 如果有就做相应的处理。

  10. Context 到了这里, 就终于轮到了tomcat启动中真正的重头戏,启动Context了。 StandardContext.start() 这个启动Context容器的方法被StandardHost调用. 5.1 webappResources 该context所指向的具体目录 5.2 安装defaultContex, DefaultContext 就是默认Context。 如果我们在一个Host下面安装了DefaultContext,而且defaultContext里面又安装了一个数据库连接池资源的话。 那么其他所有的在该Host下的Context, 都可以直接使用这个数据库连接池, 而不用格外做配置了。 5.3 指定Loader. 通常用默认的org.apache.catalina.loader.WebappLoader这个类。 Loader就是用来指定这个context会用到哪些类啊, 哪些jar包啊这些什么的。 5.4 指定Manager. 通常使用默认的org.apache.catalina.session. StandardManager 。Manager是用来管理session的。 其实session的管理也很好实现。 以一种简单的session管理为例。 当需求传递过来的时候, 在Request对象里面有一个sessionId 属性。OK, 得到这个sessionId后, 我们就可以把它作为map的key,而value我们可以放置一个HashMap. HashMap里边儿, 再放我们想放的东西。 5.5 postWorkDirectory (). Tomcat下面有一个work目录。 我们把临时文件都扔在那儿去。 这个步骤就是在那里创建一个目录。 一般说来会在%CATALINA_HOME%/work/Standalone\localhost\ 这个地方生成一个目录。 5.6 Binding thread。到了这里, 就应该发生class Loader 互换了。 之前是看得见tomcat下面所有的class和lib. 接下来需要看得见当前context下的class。 所以要设置contextClassLoader, 同时还要把旧的ClassLoader记录下来,因为以后还要用的。 5.7 启动Loader. 指定这个Context具体要使用哪些classes, 用到哪些jar文件。 如果reloadable设置成了true, 就会启动一个线程来监视classes的变化, 如果有变化就重新启动Context。 5.8 启动logger 5.9 触发安装在它身上的一个监听器。 lifecycle.fireLifecycleEvent(START_EVENT, null); 作为监听器之一,ContextConfig会被启动. ContextConfig就是用来配置web.xml的。 比如这个Context有多少Servlet, 又有多少Filter, 就是在这里给Context装上去的。 5.9.1 defaultConfig. 每个context都得配置tomcat/conf/web.xml 这个文件。 5.9.2 applicationConfig 配置自己的WEB-INF/web.xml 文件 5.9.3 validateSecurityRoles 权限验证。 通常我们在访问/admin 或者/manager的时候,需要用户要么是admin的要么是manager的, 才能访问。 而且我们还可以限制那些资源可以访问, 而哪些不能。 都是在这里实现的。 5.9.4 tldScan: 扫描一下, 需要用到哪些标签(tag lab) 5.10 启动manager 5.11 postWelcomeFiles() 我们通常会用到的3个启动文件的名称: index.html、index.htm、index.jsp 就被默认地绑在了这个context上 5.12 listenerStart 配置listener 5.13 filterStart 配置filter 5.14 启动带有;1;的Servlet. 顺序是从小到大:1,2,3… 最后是0 默认情况下, 至少会启动如下3个的Servlet: org.apache.catalina.servlets.DefaultServlet
    处理静态资源的Servlet. 什么图片啊,html啊,css啊,js啊都找他 org.apache.catalina.servlets.InvokerServlet 处理没有做Servlet Mapping的那些Servlet. org.apache.jasper.servlet.JspServlet 处理JSP文件的. 5.15 标识context已经启动完毕。 走了多少个步骤啊,Context总算是启动完毕喽。 OK! 走到了这里, 每个容器以及组件都启动完毕。Tomcat终于不辞辛劳地为人民服务了!
    1. 参考文献: http://jakarta.apache.org/tomcat/; http://www.onjava.com/pub/a/onjava/2003/05/14/java_webserver.html;
  1. 后记 这篇文章是讲解tomcat启动框架的,还有篇文章是讲解TOMCAT里面的消息处理流程的细节的。 文章内容已经写好了, 现在正在整理阶段。 相信很快就可以做出来, 大家共同研究共同进步。 这篇文章是独自分析TOMCAT源码所写的, 所以一定有地方是带有个人主观色彩, 难免会有片面之处。若有不当之处敬请批评指教,这样不仅可以使刚开始研究TOMCAT的兄弟们少走弯路, 我也可以学到东西。

  2. tomcat源码分析(消息处理)

[ZT]TOMCAT源码分析 0:前言 我们知道了tomcat的整体框架了, 也明白了里面都有些什么组件, 以及各个组件是干什么用的了。

http://www.csdn.net/Develop/read_article.[asp](http://www.2cto.com/kf/web/asp/)?id=27225

我想,接下来我们应该去了解一下tomcat 是如何处理jsp和servlet请求的。

  1. 我们以一个具体的例子,来跟踪TOMCAT, 看看它是如何把Request一层一层地递交给下一个容器, 并最后交给Wrapper来处理的。

http://localhost:8080/web/login.jsp为例子

(以下例子, 都是以tomcat4 源码为参考)

这篇心得主要分为3个部分: 前期, 中期, 和末期。

前期:讲解了在浏览器里面输入一个URL,是怎么被tomcat抓住的。

中期:讲解了被tomcat抓住后,又是怎么在各个容器里面穿梭, 最后到达最后的处理地点。

末期:讲解到达最后的处理地点后,又是怎么具体处理的。

2、 前期Request的born.

在这里我先简单讲一下request这个东西。

 我们先看着这个URL:http://localhost:8080/web/login.jsp  它是动用了8080端口来进行socket通讯的。

 我们知道, 通过

   InputStream in = socket.getInputStream() 和

   OutputStream out = socket.getOutputStream()

 就可以实现消息的来来往往了。

 但是如果把Stream给应用层看,显然操作起来不方便。

 所以,在tomcat 的Connector里面,socket被封装成了Request和Response这两个对象。

 我们可以简单地把Request看成管发到服务器来的数据,把Response看成想发出服务器的数据。



 但是这样又有其他问题了啊?Request这个对象是把socket封装起来了, 但是他提供的又东西太多了。

 诸如Request.getAuthorization(), Request.getSocket()。  像Authorization这种东西开发人员拿来基本上用不太着,而像socket这种东西,暴露给开发 人员又有潜在的危险。 而且啊, 在Servlet Specification里面标准的通信类是ServletRequest和HttpServletRequest,而非这个Request类。So, So, So. Tomcat必须得捣持捣持Request才行。 最后tomcat选择了使用捣持模式(应该叫适配器模式)来解决这个问题。它把org.apache.catalina.Request 捣持成了org.apache.coyote.tomcat4.CoyoteRequest。 而CoyoteRequest又实现了ServletRequest和HttpServletRequest 这两种接口。 这样就提供给开发人员需要且刚刚需要的方法了。



ok, 让我们在tomcat的顶层容器- StandardEngin 的invoke()方法这里设置一个断点, 然后访问

http://localhost:8080/web/login.jsp , 我们来看看在前期都会路过哪些地方:

   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
  1. 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. 主线程

2. 启动线程池.

3. 调出线程池里面空闲的工作线程。

4. 把8080端口传过来由httpd协议封装的数据,解析成Request和Response对象。

5. 使用Http11Processor来处理request

6. 在Http11Processor里面, 又会call CoyoteAdapter来进行适配处理,把Request适配成实现了ServletRequest和HttpServletRequest接口的CoyoteRequest.
  1. 到了这里,前期的去毛拔皮工作就基本上搞定,可以交给StandardEngin 做核心的处理工作了。
  1. 中期。 在各个容器间的穿梭。

    Request在各个容器里面的穿梭大致是这样一种方式:

    每个容器里面都有一个管道(pipline), 专门用来传送Request用的。

    管道里面又有好几个阀门(valve), 专门用来过滤Request用的。

    在管道的低部通常都会放上一个默认的阀们。 这个阀们至少会做一件事情,就是把Request交给子容器。

    让我们来想象一下:

    当一个Request进入一个容器后, 它就在管道里面流动,波罗~ 波罗~ 波罗~ 地穿过各个阀门。在流到最后一个阀门的时候,吧唧~ 那个该死的阀门就把它扔给了子容器。 然后又开始 波罗~ 波罗~ 波罗~ ... 吧唧~.... 波罗~ 波罗~ 波罗~ ....吧唧~....

    就是通过这种方式,Request 走完了所有的容器。( 感觉有点像消化系统,最后一个地方有点像那里~ )

    OK, 让我们具体看看都有些什么容器, 各个容器里面又都有些什么阀门,这些阀们都对我们的Request做了些什么吧:

3.1 StandardEngin 的pipeline里面放的是:StandardEnginValve

在这里,VALVE做了三件事:

  1. 验证传递过来的request是不是httpservletRequest.

2 验证传递过来的request 是否携带了host header信息.

3 选择相应的host去处理它。(一般我们都只有一个host:localhost,也就是127.0.0.1)。

到了这个地方, 我们的request就已经完成了在Engin这个部分的历史使命, 通向前途未卜的下一站:host了。

3.2 StandardHost 的pipline里面放的是:StandardHostValve

  1. 验证传递过来的request是不是httpservletRequest.

  2. 根据Request来确定哪个Context来处理。

Context其实就是webapp, 比如http://localhost:8080/web/login.jsp

这里web就是Context罗!

  1. 既然确定了是哪个Context了,那么就应该把那个Context的classloader付给当前线程了。

    Thread.currentThread().setContextClassLoader(context.getLoader().getClassLoader());

    这样request就只看得见指定的context下面的classes啊,jar啊这些, 而看不见tomcat本身的类, 什么Engin啊,Valve啊。 不然还得了啊!

  2. 既然request到了这里了,看来用户是准备访问web这个web app了,咋们得更新一下这个用户的session不是!Ok , 就由manager更新一下用户的session信息

  3. 交给具体的Context 容器去继续处理Request.

  4. Context处理完毕了,把classloader还回来。

3.3 StandardContext 的pipline里面放的是:StandardContextValve

  1. 验证传递过来的request是不是httpservletRequest.

  2. 如果request意图不轨,想要访问/meta-inf, /web-inf这些目录下的东西,呵呵,没有用D!

  3. 这个时候就会根据Request到底是Servlet,还是jsp,还是静态资源来决定到底用哪种Wrapper来处理这个Reqeust了。

  4. 一旦决定了到底用哪种Wrapper,OK,交给那个Wrapper处理。

  1. 末期。 不同的需求是怎么处理的.

StandardWrapper

之前对Wrapper没有做过讲解,其实它是这样一种东西。

我们在处理Request的时候,可以分成3种。

处理静态的:org.apache.catalina.servlets.DefaultServlet

处理jsp的:org.apache.jasper.servlet.JspServlet

处理servlet的:org.apache.catalina.servlets.InvokerServlet

不同的request就用这3种不同的servlet去处理。

Wrapper就是对它们的一种简单的封装,有了Wrapper后,我们就可以轻松地拦截每次的Request。也可以容易地调用servlet的init()和destroy()方法, 便于管理嘛!

具体情况是这么滴:

如果request是找jsp文件,StandardWrapper里面就会封装一个org.apache.jasper.servlet.JspServlet去处理它。

如果request是找 静态资源 ,StandardWrapper里面就会封装一个org.apache.jasper.servlet.DefaultServlet 去处理它。

如果request是找servlet ,StandardWrapper里面就会封装一个org.apache.jasper.servlet.InvokerServlet 去处理它。

StandardWrapper同样也是容器,既然是容器, 那么里面一定留了一个管道给request去穿,管道低部肯定也有一个阀门(注1),用来做最后一道拦截工作.

在这最底部的阀门里,其实就主要做了两件事:

一是启动过滤器,让request在N个过滤器里面筛一通,如果OK! 那就PASS。 否则就跳到其他地方去了。

二是servlet.service((HttpServletRequest) request,(HttpServletResponse) response); 这个方法.

 如果是JspServlet, 那么先把jsp文件编译成servlet_xxx, 再invoke servlet_xxx的servie()方法。

 如果是DefaultServlet, 就直接找到静态资源,取出内容, 发送出去。

 如果是InvokerServlet, 就调用那个具体的servlet的service()方法。

ok! 完毕。

注1: StandardWrapper 里面的阀门是最后一道关口了。 如果这个阀门欲意把request交给StandardWrapper 的子容器处理。 对不起, 在设计考虑的时候,Wrapper就被考虑成最末的一个容器, 压根儿就不会给Wrapper添加子容器的机会! 如果硬是要调用addChild(), 立马抛出IllegalArgumentException!

参考:

 <http://jakarta.apache.org/tomcat/>;

http://www.onjava.com/pub/a/onjava/2003/05/14/java_webserver.html;

http://www.2cto.com/os/201202/119145.html 来源: [http://blog.csdn.net/haojun186/article/details/7464912](http://blog.csdn.net/haojun186/article/details/7464912)

Tomcat工作原理2

Posted on

Tomcat工作原理2

Tomcat工作原理2

门面设计模式

门面设计模式在 Tomcat 中有多处使用,在 Request 和 Response 对象封装中、Standard Wrapper 到 ServletConfig 封装中、ApplicationContext 到 ServletContext 封装中等都用到了这种设计模式。

门面设计模式的原理

这么多场合都用到了这种设计模式,那这种设计模式究竟能有什么作用呢?顾名思义,就是将一个东西封装成一个门面好与人家更容易进行交流,就像一个国家的外交部一样。

这种设计模式主要用在一个大的系统中有多个子系统组成时,这多个子系统肯定要涉及到相互通信,但是每个子系统又不能将自己的内部数据过多的暴露给其它系统,不然就没有必要划分子系统了。每个子系统都会设计一个门面,把别的系统感兴趣的数据封装起来,通过这个门面来进行访问。这就是门面设计模式存在的意义。

门面设计模式示意图如下: 图 1. 门面示意图 图 1. 门面示意图

Client 只能访问到 Façade 中提供的数据是门面设计模式的关键,至于 Client 如何访问 Façade 和 Subsystem 如何提供 Façade 门面设计模式并没有规定死。

Tomcat 的门面设计模式示例

Tomcat 中门面设计模式使用的很多,因为 Tomcat 中有很多不同组件,每个组件要相互交互数据,用门面模式隔离数据是个很好的方法。

下面是 Request 上使用的门面设计模式: 图 2. Request 的门面设计模式类图 图 2. Request 的门面设计模式类图

从图中可以看出 HttpRequestFacade 类封装了 HttpRequest 接口能够提供数据,通过 HttpRequestFacade 访问到的数据都被代理到 HttpRequest 中,通常被封装的对象都被设为 Private 或者 Protected 访问修饰,以防止在 Façade 中被直接访问。

回页首

观察者设计模式

这种设计模式也是常用的设计方法通常也叫发布 - 订阅模式,也就是事件监听机制,通常在某个事件发生的前后会触发一些操作。

观察者模式的原理

观察者模式原理也很简单,就是你在做事的时候旁边总有一个人在盯着你,当你做的事情是它感兴趣的时候,它就会跟着做另外一些事情。但是盯着你的人必须要到你那去登记,不然你无法通知它。观察者模式通常包含下面这几个角色:

  • Subject 就是抽象主题:它负责管理所有观察者的引用,同时定义主要的事件操作。
  • ConcreteSubject 具体主题:它实现了抽象主题的所有定义的接口,当自己发生变化时,会通知所有观察者。
  • Observer 观察者:监听主题发生变化相应的操作接口。

Tomcat 的观察者模式示例

Tomcat 中观察者模式也有多处使用,前面讲的控制组件生命周期的 Lifecycle 就是这种模式的体现,还有对 Servlet 实例的创建、Session 的管理、Container 等都是同样的原理。下面主要看一下 Lifecycle 的具体实现。

Lifecycle 的观察者模式结构图: 图 3. Lifecycle 的观察者模式结构图 图 3. Lifecycle 的观察者模式结构图

上面的结构图中,LifecycleListener 代表的是抽象观察者,它定义一个 lifecycleEvent 方法,这个方法就是当主题变化时要执行的方法。 ServerLifecycleListener 代表的是具体的观察者,它实现了 LifecycleListener 接口的方法,就是这个具体的观察者具体的实现方式。Lifecycle 接口代表的是抽象主题,它定义了管理观察者的方法和它要所做的其它方法。而 StandardServer 代表的是具体主题,它实现了抽象主题的所有方法。这里 Tomcat 对观察者做了扩展,增加了另外两个类:LifecycleSupport、LifecycleEvent,它们作为辅助类扩展了观察者的功能。LifecycleEvent 使得可以定义事件类别,不同的事件可区别处理,更加灵活。LifecycleSupport 类代理了主题对多观察者的管理,将这个管理抽出来统一实现,以后如果修改只要修改 LifecycleSupport 类就可以了,不需要去修改所有具体主题,因为所有具体主题的对观察者的操作都被代理给 LifecycleSupport 类了。这可以认为是观察者模式的改进版。

LifecycleSupport 调用观察者的方法代码如下: 清单 1. LifecycleSupport 中的 fireLifecycleEvent 方法

public void fireLifecycleEvent(String type, Object data) { LifecycleEvent event = new LifecycleEvent(lifecycle, type, data);

LifecycleListener interested[] = null;
synchronized (listeners) {

    interested = (LifecycleListener[]) listeners.clone();
}

for (int i = 0; i < interested.length; i++)
    interested[i].lifecycleEvent(event);

}

主题是怎么通知观察者呢?看下面代码: 清单 2. 容器中的 start 方法

public void start() throws LifecycleException { lifecycle.fireLifecycleEvent(BEFORE_START_EVENT, null);

lifecycle.fireLifecycleEvent(START_EVENT, null);
started = true;

synchronized (services) {
    for (int i = 0; i < services.length; i++) {

        if (services[i] instanceof Lifecycle)
            ((Lifecycle) services[i]).start();

        }
    }

lifecycle.fireLifecycleEvent(AFTER_START_EVENT, null);

}

回页首

命令设计模式

前面把 Tomcat 中两个核心组件 Connector 和 Container,比作一对夫妻。男的将接受过来的请求以命令的方式交给女主人。对应到 Connector 和 Container,Connector 也是通过命令模式调用 Container 的。

命令模式的原理

命令模式主要作用就是封装命令,把发出命令的责任和执行命令的责任分开。也是一种功能的分工。不同的模块可以对同一个命令做出不同解释。

下面是命令模式通常包含下面几个角色:

  • Client:创建一个命令,并决定接受者
  • Command 命令:命令接口定义一个抽象方法
  • ConcreteCommand:具体命令,负责调用接受者的相应操作
  • Invoker 请求者:负责调用命令对象执行请求
  • Receiver 接受者:负责具体实施和执行一次请求

Tomcat 中的命令模式的示例

Tomcat 中命令模式在 Connector 和 Container 组件之间有体现,Tomcat 作为一个应用服务器,无疑会接受到很多请求,如何分配和执行这些请求是必须的功能。

下面看一下 Tomcat 是如何实现命令模式的,下面是 Tomcat 命令模式的结构图: 图 4. Tomcat 命令模式的结构图 图 4. Tomcat 命令模式的结构图

Connector 作为抽象请求者,HttpConnector 作为具体请求者。HttpProcessor 作为命令。Container 作为命令的抽象接受者,ContainerBase 作为具体的接受者。客户端就是应用服务器 Server 组件了。Server 首先创建命令请求者 HttpConnector 对象,然后创建命令 HttpProcessor 命令对象。再把命令对象交给命令接受者 ContainerBase 容器来处理命令。命令的最终是被 Tomcat 的 Container 执行的。命令可以以队列的方式进来,Container 也可以以不同的方式来处理请求,如 HTTP1.0 协议和 HTTP1.1 的处理方式就会不同。

回页首

责任链模式

Tomcat 中一个最容易发现的设计模式就是责任链模式,这个设计模式也是 Tomcat 中 Container 设计的基础,整个容器的就是通过一个链连接在一起,这个链一直将请求正确的传递给最终处理请求的那个 Servlet。

责任链模式的原理

责任链模式,就是很多对象有每个对象对其下家的引用而连接起来形成一条链,请求在这条链上传递,直到链上的某个对象处理此请求,或者每个对象都可以处理请求,并传给下一家,直到最终链上每个对象都处理完。这样可以不影响客户端而能够在链上增加任意的处理节点。

通常责任链模式包含下面几个角色:

  • Handler(抽象处理者):定义一个处理请求的接口
  • ConcreteHandler(具体处理者):处理请求的具体类,或者传给下家

Tomcat 中责任链模式示例

在 tomcat 中这种设计模式几乎被完整的使用,tomcat 的容器设置就是责任链模式,从 Engine 到 Host 再到 Context 一直到 Wrapper 都是通过一个链传递请求。

Tomcat 中责任链模式的类结构图如下: 图 5. Tomcat 责任链模式的结构图 图 5. Tomcat 责任链模式的结构图

上图基本描述了四个子容器使用责任链模式的类结构图,对应的责任链模式的角色,Container 扮演抽象处理者角色,具体处理者由 StandardEngine 等子容器扮演。与标准的责任链不同的是,这里引入了 Pipeline 和 Valve 接口。他们有什么作用呢?

实际上 Pipeline 和 Valve 是扩展了这个链的功能,使得在链往下传递过程中,能够接受外界的干预。Pipeline 就是连接每个子容器的管子,里面传递的 Request 和 Response 对象好比管子里流的水,而 Valve 就是这个管子上开的一个个小口子,让你有机会能够接触到里面的水,做一些额外的事情。

为了防止水被引出来而不能流到下一个容器中,每一段管子最后总有一个节点保证它一定能流到下一个子容器,所以每个容器都有一个 StandardXXXValve。只要涉及到这种有链式是处理流程这是一个非常值得借鉴的模式。 来源: [http://blog.csdn.net/haojun186/article/details/7467112](http://blog.csdn.net/haojun186/article/details/7467112)

Servlet工作原理

Posted on

Servlet工作原理

Servlet工作原理

从 Servlet 容器说起

要介绍 Servlet 必须要先把 Servlet 容器说清楚,Servlet 与 Servlet 容器的关系有点像枪和子弹的关系,枪是为子弹而生,而子弹又让枪有了杀伤力。虽然它们是彼此依存的,但是又相互独立发展,这一切都是为了适应工业化生产的结果。从技术角度来说是为了解耦,通过标准化接口来相互协作。既然接口是连接 Servlet 与 Servlet 容器的关键,那我们就从它们的接口说起。

前面说了 Servlet 容器作为一个独立发展的标准化产品,目前它的种类很多,但是它们都有自己的市场定位,很难说谁优谁劣,各有特点。例如现在比较流行的 Jetty,在定制化和移动领域有不错的发展,我们这里还是以大家最为熟悉 Tomcat 为例来介绍 Servlet 容器如何管理 Servlet。Tomcat 本身也很复杂,我们只从 Servlet 与 Servlet 容器的接口部分开始介绍,关于 Tomcat 的详细介绍可以参考我的另外一篇文章《 Tomcat 系统架构与模式设计分析》。

Tomcat 的容器等级中,Context 容器是直接管理 Servlet 在容器中的包装类 Wrapper,所以 Context 容器如何运行将直接影响 Servlet 的工作方式。 图 1 . Tomcat 容器模型 图 1 . Tomcat 容器模型

从上图可以看出 Tomcat 的容器分为四个等级,真正管理 Servlet 的容器是 Context 容器,一个 Context 对应一个 Web 工程,在 Tomcat 的配置文件中可以很容易发现这一点,如下: 清单 1 Context 配置参数

下面详细介绍一下 Tomcat 解析 Context 容器的过程,包括如何构建 Servlet 的过程。

Servlet 容器的启动过程

Tomcat7 也开始支持嵌入式功能,增加了一个启动类 org.apache.catalina.startup.Tomcat。创建一个实例对象并调用 start 方法就可以很容易启动 Tomcat,我们还可以通过这个对象来增加和修改 Tomcat 的配置参数,如可以动态增加 Context、Servlet 等。下面我们就利用这个 Tomcat 类来管理新增的一个 Context 容器,我们就选择 Tomcat7 自带的 examples Web 工程,并看看它是如何加到这个 Context 容器中的。 清单 2 . 给 Tomcat 增加一个 Web 工程

Tomcat tomcat = getTomcatInstance(); File appDir = new File(getBuildDirectory(), "webapps/examples");

tomcat.addWebapp(null, "/examples", appDir.getAbsolutePath()); tomcat.start();

ByteChunk res = getUrl("http://localhost:" + getPort() + "/examples/servlets/servlet/HelloWorldExample");

assertTrue(res.toString().indexOf("

Hello World!

") > 0);

清单 1 的代码是创建一个 Tomcat 实例并新增一个 Web 应用,然后启动 Tomcat 并调用其中的一个 HelloWorldExample Servlet,看有没有正确返回预期的数据。

Tomcat 的 addWebapp 方法的代码如下: 清单 3 .Tomcat.addWebapp

public Context addWebapp(Host host, String url, String path) { silence(url);

    Context ctx = new StandardContext();
    ctx.setPath( url );

    ctx.setDocBase(path);
    if (defaultRealm == null) {

        initSimpleAuth();
    }

    ctx.setRealm(defaultRealm);
    ctx.addLifecycleListener(new DefaultWebXmlListener());

    ContextConfig ctxCfg = new ContextConfig();
    ctx.addLifecycleListener(ctxCfg);

    ctxCfg.setDefaultWebXml("org/apache/catalin/startup/NO_DEFAULT_XML");
    if (host == null) {

        getHost().addChild(ctx);
    } else {

        host.addChild(ctx);
    }

    return ctx;

}

前面已经介绍了一个 Web 应用对应一个 Context 容器,也就是 Servlet 运行时的 Servlet 容器,添加一个 Web 应用时将会创建一个 StandardContext 容器,并且给这个 Context 容器设置必要的参数,url 和 path 分别代表这个应用在 Tomcat 中的访问路径和这个应用实际的物理路径,这个两个参数与清单 1 中的两个参数是一致的。其中最重要的一个配置是 ContextConfig,这个类将会负责整个 Web 应用配置的解析工作,后面将会详细介绍。最后将这个 Context 容器加到父容器 Host 中。

接下去将会调用 Tomcat 的 start 方法启动 Tomcat,如果你清楚 Tomcat 的系统架构,你会容易理解 Tomcat 的启动逻辑,Tomcat 的启动逻辑是基于观察者模式设计的,所有的容器都会继承 Lifecycle 接口,它管理者容器的整个生命周期,所有容器的的修改和状态的改变都会由它去通知已经注册的观察者(Listener),关于这个设计模式可以参考《 Tomcat 的系统架构与设计模式,第二部分:设计模式》。Tomcat 启动的时序图可以用图 2 表示。 图 2. Tomcat 主要类的启动时序图(查看大图 图 2. Tomcat 主要类的启动时序图

上图描述了 Tomcat 启动过程中,主要类之间的时序关系,下面我们将会重点关注添加 examples 应用所对应的 StandardContext 容器的启动过程。

当 Context 容器初始化状态设为 init 时,添加在 Contex 容器的 Listener 将会被调用。ContextConfig 继承了 LifecycleListener 接口,它是在调用清单 3 时被加入到 StandardContext 容器中。ContextConfig 类会负责整个 Web 应用的配置文件的解析工作。

ContextConfig 的 init 方法将会主要完成以下工作:

  1. 创建用于解析 xml 配置文件的 contextDigester 对象
  2. 读取默认 context.xml 配置文件,如果存在解析它
  3. 读取默认 Host 配置文件,如果存在解析它
  4. 读取默认 Context 自身的配置文件,如果存在解析它
  5. 设置 Context 的 DocBase

ContextConfig 的 init 方法完成后,Context 容器的会执行 startInternal 方法,这个方法启动逻辑比较复杂,主要包括如下几个部分:

  1. 创建读取资源文件的对象
  2. 创建 ClassLoader 对象
  3. 设置应用的工作目录
  4. 启动相关的辅助类如:logger、realm、resources 等
  5. 修改启动状态,通知感兴趣的观察者(Web 应用的配置)
  6. 子容器的初始化
  7. 获取 ServletContext 并设置必要的参数
  8. 初始化“load on startup”的 Servlet

Web 应用的初始化工作

Web 应用的初始化工作是在 ContextConfig 的 configureStart 方法中实现的,应用的初始化主要是要解析 web.xml 文件,这个文件描述了一个 Web 应用的关键信息,也是一个 Web 应用的入口。

Tomcat 首先会找 globalWebXml 这个文件的搜索路径是在 engine 的工作目录下寻找以下两个文件中的任一个 org/apache/catalin/startup/NO_DEFAULT_XML 或 conf/web.xml。接着会找 hostWebXml 这个文件可能会在 System.getProperty("catalina.base")/conf/${EngineName}/${HostName}/web.xml.default,接着寻找应用的配置文件 examples/WEB-INF/web.xml。web.xml 文件中的各个配置项将会被解析成相应的属性保存在 WebXml 对象中。如果当前应用支持 Servlet3.0,解析还将完成额外 9 项工作,这个额外的 9 项工作主要是为 Servlet3.0 新增的特性,包括 jar 包中的 META-INF/web-fragment.xml 的解析以及对 annotations 的支持。

接下去将会将 WebXml 对象中的属性设置到 Context 容器中,这里包括创建 Servlet 对象、filter、listener 等等。这段代码在 WebXml 的 configureContext 方法中。下面是解析 Servlet 的代码片段: 清单 4. 创建 Wrapper 实例

for (ServletDef servlet : servlets.values()) { Wrapper wrapper = context.createWrapper();

        String jspFile = servlet.getJspFile();
        if (jspFile != null) {

            wrapper.setJspFile(jspFile);
        }

        if (servlet.getLoadOnStartup() != null) {
            wrapper.setLoadOnStartup(servlet.getLoadOnStartup().intValue());

        }
        if (servlet.getEnabled() != null) {

            wrapper.setEnabled(servlet.getEnabled().booleanValue());
        }

        wrapper.setName(servlet.getServletName());
        Map<String,String> params = servlet.getParameterMap();

        for (Entry<String, String> entry : params.entrySet()) {
            wrapper.addInitParameter(entry.getKey(), entry.getValue());

        }
        wrapper.setRunAs(servlet.getRunAs());

        Set<SecurityRoleRef> roleRefs = servlet.getSecurityRoleRefs();
        for (SecurityRoleRef roleRef : roleRefs) {

            wrapper.addSecurityReference(
                    roleRef.getName(), roleRef.getLink());

        }
        wrapper.setServletClass(servlet.getServletClass());

        MultipartDef multipartdef = servlet.getMultipartDef();
        if (multipartdef != null) {

            if (multipartdef.getMaxFileSize() != null &&
                    multipartdef.getMaxRequestSize()!= null &&

                    multipartdef.getFileSizeThreshold() != null) {
                wrapper.setMultipartConfigElement(new

MultipartConfigElement( multipartdef.getLocation(),

                        Long.parseLong(multipartdef.getMaxFileSize()),
                        Long.parseLong(multipartdef.getMaxRequestSize()),

                        Integer.parseInt(
                                multipartdef.getFileSizeThreshold())));

            } else {
                wrapper.setMultipartConfigElement(new

MultipartConfigElement( multipartdef.getLocation()));

            }
        }

        if (servlet.getAsyncSupported() != null) {
            wrapper.setAsyncSupported(

                    servlet.getAsyncSupported().booleanValue());
        }

        context.addChild(wrapper);

}

这段代码清楚的描述了如何将 Servlet 包装成 Context 容器中的 StandardWrapper,这里有个疑问,为什么要将 Servlet 包装成 StandardWrapper 而不直接是 Servlet 对象。这里 StandardWrapper 是 Tomcat 容器中的一部分,它具有容器的特征,而 Servlet 为了一个独立的 web 开发标准,不应该强耦合在 Tomcat 中。

除了将 Servlet 包装成 StandardWrapper 并作为子容器添加到 Context 中,其它的所有 web.xml 属性都被解析到 Context 中,所以说 Context 容器才是真正运行 Servlet 的 Servlet 容器。一个 Web 应用对应一个 Context 容器,容器的配置属性由应用的 web.xml 指定,这样我们就能理解 web.xml 到底起到什么作用了。

回页首

创建 Servlet 实例

前面已经完成了 Servlet 的解析工作,并且被包装成 StandardWrapper 添加在 Context 容器中,但是它仍然不能为我们工作,它还没有被实例化。下面我们将介绍 Servlet 对象是如何创建的,以及如何被初始化的。

创建 Servlet 对象

如果 Servlet 的 load-on-startup 配置项大于 0,那么在 Context 容器启动的时候就会被实例化,前面提到在解析配置文件时会读取默认的 globalWebXml,在 conf 下的 web.xml 文件中定义了一些默认的配置项,其定义了两个 Servlet,分别是:org.apache.catalina.servlets.DefaultServlet 和 org.apache.jasper.servlet.JspServlet 它们的 load-on-startup 分别是 1 和 3,也就是当 Tomcat 启动时这两个 Servlet 就会被启动。

创建 Servlet 实例的方法是从 Wrapper. loadServlet 开始的。loadServlet 方法要完成的就是获取 servletClass 然后把它交给 InstanceManager 去创建一个基于 servletClass.class 的对象。如果这个 Servlet 配置了 jsp-file,那么这个 servletClass 就是 conf/web.xml 中定义的 org.apache.jasper.servlet.JspServlet 了。

创建 Servlet 对象的相关类结构图如下: 图 3. 创建 Servlet 对象的相关类结构 图 3. 创建 Servlet 对象的相关类结构

初始化 Servlet

初始化 Servlet 在 StandardWrapper 的 initServlet 方法中,这个方法很简单就是调用 Servlet 的 init 的方法,同时把包装了 StandardWrapper 对象的 StandardWrapperFacade 作为 ServletConfig 传给 Servlet。Tomcat 容器为何要传 StandardWrapperFacade 给 Servlet 对象将在后面做详细解析。

如果该 Servlet 关联的是一个 jsp 文件,那么前面初始化的就是 JspServlet,接下去会模拟一次简单请求,请求调用这个 jsp 文件,以便编译这个 jsp 文件为 class,并初始化这个 class。

这样 Servlet 对象就初始化完成了,事实上 Servlet 从被 web.xml 中解析到完成初始化,这个过程非常复杂,中间有很多过程,包括各种容器状态的转化引起的监听事件的触发、各种访问权限的控制和一些不可预料的错误发生的判断行为等等。我们这里只抓了一些关键环节进行阐述,试图让大家有个总体脉络。

下面是这个过程的一个完整的时序图,其中也省略了一些细节。 图 4. 初始化 Servlet 的时序图(查看大图 图 4. 初始化 Servlet 的时序图

回页首

Servlet 体系结构

我们知道 Java Web 应用是基于 Servlet 规范运转的,那么 Servlet 本身又是如何运转的呢?为何要设计这样的体系结构。 图 5.Servlet 顶层类关联图 图 5.Servlet 顶层类关联图

从上图可以看出 Servlet 规范就是基于这几个类运转的,与 Servlet 主动关联的是三个类,分别是 ServletConfig、ServletRequest 和 ServletResponse。这三个类都是通过容器传递给 Servlet 的,其中 ServletConfig 是在 Servlet 初始化时就传给 Servlet 了,而后两个是在请求达到时调用 Servlet 时传递过来的。我们很清楚 ServletRequest 和 ServletResponse 在 Servlet 运行的意义,但是 ServletConfig 和 ServletContext 对 Servlet 有何价值?仔细查看 ServletConfig 接口中声明的方法发现,这些方法都是为了获取这个 Servlet 的一些配置属性,而这些配置属性可能在 Servlet 运行时被用到。而 ServletContext 又是干什么的呢? Servlet 的运行模式是一个典型的“握手型的交互式”运行模式。所谓“握手型的交互式”就是两个模块为了交换数据通常都会准备一个交易场景,这个场景一直跟随个这个交易过程直到这个交易完成为止。这个交易场景的初始化是根据这次交易对象指定的参数来定制的,这些指定参数通常就会是一个配置类。所以对号入座,交易场景就由 ServletContext 来描述,而定制的参数集合就由 ServletConfig 来描述。而 ServletRequest 和 ServletResponse 就是要交互的具体对象了,它们通常都是作为运输工具来传递交互结果。

ServletConfig 是在 Servlet init 时由容器传过来的,那么 ServletConfig 到底是个什么对象呢?

下图是 ServletConfig 和 ServletContext 在 Tomcat 容器中的类关系图。 图 6. ServletConfig 在容器中的类关联图 图 6. ServletConfig 在容器中的类关联图

上图可以看出 StandardWrapper 和 StandardWrapperFacade 都实现了 ServletConfig 接口,而 StandardWrapperFacade 是 StandardWrapper 门面类。所以传给 Servlet 的是 StandardWrapperFacade 对象,这个类能够保证从 StandardWrapper 中拿到 ServletConfig 所规定的数据,而又不把 ServletConfig 不关心的数据暴露给 Servlet。

同样 ServletContext 也与 ServletConfig 有类似的结构,Servlet 中能拿到的 ServletContext 的实际对象也是 ApplicationContextFacade 对象。ApplicationContextFacade 同样保证 ServletContex 只能从容器中拿到它该拿的数据,它们都起到对数据的封装作用,它们使用的都是门面设计模式。

通过 ServletContext 可以拿到 Context 容器中一些必要信息,比如应用的工作路径,容器支持的 Servlet 最小版本等。

Servlet 中定义的两个 ServletRequest 和 ServletResponse 它们实际的对象又是什么呢?,我们在创建自己的 Servlet 类时通常使用的都是 HttpServletRequest 和 HttpServletResponse,它们继承了 ServletRequest 和 ServletResponse。为何 Context 容器传过来的 ServletRequest、ServletResponse 可以被转化为 HttpServletRequest 和 HttpServletResponse 呢? 图 7.Request 相关类结构图 图 7.Request 相关类结构图

上图是 Tomcat 创建的 Request 和 Response 的类结构图。Tomcat 一接受到请求首先将会创建 org.apache.coyote.Request 和 org.apache.coyote.Response,这两个类是 Tomcat 内部使用的描述一次请求和相应的信息类它们是一个轻量级的类,它们作用就是在服务器接收到请求后,经过简单解析将这个请求快速的分配给后续线程去处理,所以它们的对象很小,很容易被 JVM 回收。接下去当交给一个用户线程去处理这个请求时又创建 org.apache.catalina.connector. Request 和 org.apache.catalina.connector. Response 对象。这两个对象一直穿越整个 Servlet 容器直到要传给 Servlet,传给 Servlet 的是 Request 和 Response 的门面类 RequestFacade 和 RequestFacade,这里使用门面模式与前面一样都是基于同样的目的——封装容器中的数据。一次请求对应的 Request 和 Response 的类转化如下图所示: 图 8.Request 和 Response 的转变过程 图 8.Request 和 Response 的转变过程

回页首

Servlet 如何工作

我们已经清楚了 Servlet 是如何被加载的、Servlet 是如何被初始化的,以及 Servlet 的体系结构,现在的问题就是它是如何被调用的。

当用户从浏览器向服务器发起一个请求,通常会包含如下信息:http://hostname: port /contextpath/servletpath,hostname 和 port 是用来与服务器建立 TCP 连接,而后面的 URL 才是用来选择服务器中那个子容器服务用户的请求。那服务器是如何根据这个 URL 来达到正确的 Servlet 容器中的呢?

Tomcat7.0 中这件事很容易解决,因为这种映射工作有专门一个类来完成的,这个就是 org.apache.tomcat.util.http.mapper,这个类保存了 Tomcat 的 Container 容器中的所有子容器的信息,当 org.apache.catalina.connector. Request 类在进入 Container 容器之前,mapper 将会根据这次请求的 hostnane 和 contextpath 将 host 和 context 容器设置到 Request 的 mappingData 属性中。所以当 Request 进入 Container 容器之前,它要访问那个子容器这时就已经确定了。 图 9.Request 的 Mapper 类关系图 图 9.Request 的 Mapper 类关系图

可能你有疑问,mapper 中怎么会有容器的完整关系,这要回到图 2 中 19 步 MapperListener 类的初始化过程,下面是 MapperListener 的 init 方法代码 : 清单 5. MapperListener.init

public void init() { findDefaultHost();

    Engine engine = (Engine) connector.getService().getContainer();
    engine.addContainerListener(this);

    Container[] conHosts = engine.findChildren();
    for (Container conHost : conHosts) {

        Host host = (Host) conHost;
        if (!LifecycleState.NEW.equals(host.getState())) {

            host.addLifecycleListener(this);
            registerHost(host);

        }
    }

}

这段代码的作用就是将 MapperListener 类作为一个监听者加到整个 Container 容器中的每个子容器中,这样只要任何一个容器发生变化,MapperListener 都将会被通知,相应的保存容器关系的 MapperListener 的 mapper 属性也会修改。for 循环中就是将 host 及下面的子容器注册到 mapper 中。 图 10.Request 在容器中的路由图 图 10.Request 在容器中的路由图

上图描述了一次 Request 请求是如何达到最终的 Wrapper 容器的,我们现正知道了请求是如何达到正确的 Wrapper 容器,但是请求到达最终的 Servlet 还要完成一些步骤,必须要执行 Filter 链,以及要通知你在 web.xml 中定义的 listener。

接下去就要执行 Servlet 的 service 方法了,通常情况下,我们自己定义的 servlet 并不是直接去实现 javax.servlet.servlet 接口,而是去继承更简单的 HttpServlet 类或者 GenericServlet 类,我们可以有选择的覆盖相应方法去实现我们要完成的工作。

Servlet 的确已经能够帮我们完成所有的工作了,但是现在的 web 应用很少有直接将交互全部页面都用 servlet 来实现,而是采用更加高效的 MVC 框架来实现。这些 MVC 框架基本的原理都是将所有的请求都映射到一个 Servlet,然后去实现 service 方法,这个方法也就是 MVC 框架的入口。

当 Servlet 从 Servlet 容器中移除时,也就表明该 Servlet 的生命周期结束了,这时 Servlet 的 destroy 方法将被调用,做一些扫尾工作。

回页首

Session 与 Cookie

前面我们已经说明了 Servlet 如何被调用,我们基于 Servlet 来构建应用程序,那么我们能从 Servlet 获得哪些数据信息呢?

Servlet 能够给我们提供两部分数据,一个是在 Servlet 初始化时调用 init 方法时设置的 ServletConfig,这个类基本上含有了 Servlet 本身和 Servlet 所运行的 Servlet 容器中的基本信息。根据前面的介绍 ServletConfig 的实际对象是 StandardWrapperFacade,到底能获得哪些容器信息可以看看这类提供了哪些接口。还有一部分数据是由 ServletRequest 类提供,它的实际对象是 RequestFacade,从提供的方法中发现主要是描述这次请求的 HTTP 协议的信息。所以要掌握 Servlet 的工作方式必须要很清楚 HTTP 协议,如果你还不清楚赶紧去找一些参考资料。关于这一块还有一个让很多人迷惑的 Session 与 Cookie。

Session 与 Cookie 不管是对 Java Web 的熟练使用者还是初学者来说都是一个令人头疼的东西。Session 与 Cookie 的作用都是为了保持访问用户与后端服务器的交互状态。它们有各自的优点也有各自的缺陷。然而具有讽刺意味的是它们优点和它们的使用场景又是矛盾的,例如使用 Cookie 来传递信息时,随着 Cookie 个数的增多和访问量的增加,它占用的网络带宽也很大,试想假如 Cookie 占用 200 个字节,如果一天的 PV 有几亿的时候,它要占用多少带宽。所以大访问量的时候希望用 Session,但是 Session 的致命弱点是不容易在多台服务器之间共享,所以这也限制了 Session 的使用。

不管 Session 和 Cookie 有什么不足,我们还是要用它们。下面详细讲一下,Session 如何基于 Cookie 来工作。实际上有三种方式能可以让 Session 正常工作:

  1. 基于 URL Path Parameter,默认就支持
  2. 基于 Cookie,如果你没有修改 Context 容器个 cookies 标识的话,默认也是支持的
  3. 基于 SSL,默认不支持,只有 connector.getAttribute("SSLEnabled") 为 TRUE 时才支持

第一种情况下,当浏览器不支持 Cookie 功能时,浏览器会将用户的 SessionCookieName 重写到用户请求的 URL 参数中,它的传递格式如 /path/Servlet;name=value;name2=value2? Name3=value3,其中“Servlet;”后面的 K-V 对就是要传递的 Path Parameters,服务器会从这个 Path Parameters 中拿到用户配置的 SessionCookieName。关于这个 SessionCookieName,如果你在 web.xml 中配置 session-config 配置项的话,其 cookie-config 下的 name 属性就是这个 SessionCookieName 值,如果你没有配置 session-config 配置项,默认的 SessionCookieName 就是大家熟悉的“JSESSIONID”。接着 Request 根据这个 SessionCookieName 到 Parameters 拿到 Session ID 并设置到 request.setRequestedSessionId 中。

请注意如果客户端也支持 Cookie 的话,Tomcat 仍然会解析 Cookie 中的 Session ID,并会覆盖 URL 中的 Session ID。

如果是第三种情况的话将会根据 javax.servlet.request.ssl_session 属性值设置 Session ID。

有了 Session ID 服务器端就可以创建 HttpSession 对象了,第一次触发是通过 request. getSession() 方法,如果当前的 Session ID 还没有对应的 HttpSession 对象那么就创建一个新的,并将这个对象加到 org.apache.catalina. Manager 的 sessions 容器中保存,Manager 类将管理所有 Session 的生命周期,Session 过期将被回收,服务器关闭,Session 将被序列化到磁盘等。只要这个 HttpSession 对象存在,用户就可以根据 Session ID 来获取到这个对象,也就达到了状态的保持。 图 11.Session 相关类图 图 11.Session 相关类图

上从图中可以看出从 request.getSession 中获取的 HttpSession 对象实际上是 StandardSession 对象的门面对象,这与前面的 Request 和 Servlet 是一样的原理。下图是 Session 工作的时序图: 图 12.Session 工作的时序图(查看大图 图 12.Session 工作的时序图

还有一点与 Session 关联的 Cookie 与其它 Cookie 没有什么不同,这个配置的配置可以通过 web.xml 中的 session-config 配置项来指定。

回页首

Servlet 中的 Listener

整个 Tomcat 服务器中 Listener 使用的非常广泛,它是基于观察者模式设计的,Listener 的设计对开发 Servlet 应用程序提供了一种快捷的手段,能够方便的从另一个纵向维度控制程序和数据。目前 Servlet 中提供了 5 种两类事件的观察者接口,它们分别是:4 个 EventListeners 类型的,ServletContextAttributeListener、ServletRequestAttributeListener、ServletRequestListener、HttpSessionAttributeListener 和 2 个 LifecycleListeners 类型的,ServletContextListener、HttpSessionListener。如下图所示: 图 13.Servlet 中的 Listener(查看大图 图 13.Servlet 中的 Listener

它们基本上涵盖了整个 Servlet 生命周期中,你感兴趣的每种事件。这些 Listener 的实现类可以配置在 web.xml 中的 标签中。当然也可以在应用程序中动态添加 Listener,需要注意的是 ServletContextListener 在容器启动之后就不能再添加新的,因为它所监听的事件已经不会再出现。掌握这些 Listener 的使用,能够让我们的程序设计的更加灵活。 来源: [http://blog.csdn.net/haojun186/article/details/7467100](http://blog.csdn.net/haojun186/article/details/7467100)