1.基础
1.1 web概念
1).软件架构
1.c/s:客户端/服务器端
2.b/s:浏览器/服务器端
2) .资源分类
1,静态资源:所有用户访问后,得到的结果都是一样的,称为静态资源。静态资源可以直接被浏览
器解析。* 如: html,css, Javascript, jpg
2.动态资源:每个用户访问相同资源后,得到的结果可能不一样,称为动态盗源。动态瓷源被访问后,需要先转换为静志资源,再返回给浏览器,通过浏览器进行解析。*如: servlet/jsp, php, asp.....
3).网络通信三要素
1.IP:电子设备(计算机)在网络中的唯一标识。
2·端口:应用程序在计算机中的唯一标识。0~65536
3.传输协议:规定了数据传输的规则.
基础协议:
Tcp:安全协议,三次握手。速度稍慢
Udp:不安全协议。速度快
1.2常见的web服务器
1.2.1概念
1). 服务器:安装了服务器软件的计算机
2). 服务器软件:接收用户的请求,处理请求,做出响应
3). web服务器软件:接收用户的请求,处理请求,做出响应。
在web服务器软件中,可以部署web项目,让用户通过浏览器来访问这些项目
1.2.2常见web服务器软件
1) . weblogic:oracle公司,大型的JavaEE服务器,支持所有的JavaEE规范,收费的。
2) .webSphere:IBM公司,大型的JavaEe服务器,支持所有的JavaEE规范,收费的。
3).JBOSS:JBoss公司的,大型的JavaEE服务器,支持所有的JavaEE规范,收费的。
4).Tomcat:Apache基金组织,中小型的JavaEE服务器,仅仅支持少量的JavaE规范servlet/jsp.开源的,免费的。
1.3 Tomcat历史
1)Tomcat最初由Sun公司的软件架构师James Duncan Davidson开发,名称为"JavaMebServer".
2) 1999年,在Davidson的帮助下,该项目于1999年于apache软件基金会旗下的Jserv项目合并,并发布第一个版本(3.x),即是现在的Tomcat,该版本实现了servlet2.2和JSP 1.1规范。
3) 2001年,Tomcat发布了4.0版本,作为里程碑式的版本,Tomcat完全重新设计了其架构,并实现了servlet 2.3和JSP1.2规范。
目前Tomcat已经更新到9.0.x版本,但是目前企业中的Tomcat服务器,主流版本还是7.x和8.x ,所以本课程是基于8.5版本进行讲解
1.4 Tomcat安装
1.4.1下载
https://tomcat.apache.org/download-80.cgi
1.4.2安装
将下载的.zip压缩包,解压到系统的目(建议是没有中文不带空格的目录)下即可。
1.5 Tomcat目录结构
Tomcat的主要目录文件如下:
目录目录下文件说明
bin/存放Tomcat的启动、停止等批处理脚本
startup.bat
startup.sh
用于在windows和linux下的启动脚本
shutdown. bat
shutdown.sh
用于在windows和linux下的停止脚本
conf/用于存放Tomcat的相关配置文件
catalina用于存储针对每个虚拟机的context配置
context. xml用于定义所有web应用均需加载的context配置,如果web应用指定了自己的context.xml该文件将被覆盖
catalina.propertiesTomcat的环境变量配置
catalina.policyTomcat运行的安全策略配置
logging.propertiesTomcat的日志配置文件,可以通过该文件修改Tomcat 的日志级别及日志路径等
server. xmlTomcat服务器的核心配置文件
tomcat-users.xml定义Tomcat默认的用户及角色映射信息配置
web.xmlTomcat中所有应用默认的部署描述文件,主要定义了基础Servlet和MIME映射
lib/Tomcat服务器的依赖包
logs/Tomcat默认的日志存放目录
webapps/Tomcat默认的web应用部署目录
work/web应用Jsp代码生成和编译的临时目录
1.6 Tomcat 启动停止
启动
双击bin/startup.bat
停止
双击bin/shutdown.bat
访问
1.7 Tomcat源码
1.7.1下载
地址:https://tomcat.apache.org/download-80.cgi
E apache-tomcat-8.5.42-src.zip
1.7.2运行
1)解压zip压缩包
2)进入解压目录,并创建一个目录,命名为home(可自定义),并将conf,webapps目录移入home目录中
3)在当前目录下创建一个pom.xml文件,引入tomcat的依赖包
4)在idea中导入该工程
5)配置idea的启动类,配置Mainclass ,并配置VM参数。
-Dfile.encoding=UTF-8
-Dcatalina.home=D:/JetBrains/IntelliJ_IDEA/study/apache-tomcat-8.5.50-src/home
-Dcatalina.base=D:/JetBrains/IntelliJ_IDEA/study/apache-tomcat-8.5.50-src/home
-Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager
-Djava.util.logging.config.file=
D:/JetBrains/IntelliJ_IDEA/study/apache-tomcat-8.5.50-src/home/conf/logging.properties
6)启动主方法,运行Tomcat,访问Tomcat
出现上述异常的原因,是我们直接启动org.apache.catalina.startup.Bootstrap的时候没有加载JasperInitializer,从而无法编译JSP。解决办法是在tomcat的源码contextconfig中的configurestart函数中webConfig()函数下面,手动将JSP解析器初始化:
context.addServletContainerInitializer(new JasperInitializer(), null);
7)重启Tomcat
2. Tomcat架构
2.1 Http工作原理
HTTP协议是浏览器与服务器之间的数据传送协议。作为应用层协议, HTTP是基于TCP/IP协议来传递数据的(HTML文件、图片、查询结果等) ,HTTP协议不涉及数据包(Packet)传输,主要规定了客户端和服务器之间的通信格式。
从图上你可以看到,这个过程是:
1)用户通过浏览器进行了一个操作,比如输入网址并回车,或者是点击链接,接着浏览器获取了这个事件。
2)浏览器向服务端发出TCP连接请求。
3)服务程序接受浏览器的连接请求,并经过TCP三次握手建立连接.
4)浏览器将请求数据打包成一个HTTE协议格式的数据包。
5)浏览器将该数据包推入网络,数据包经过网络传输,最终达到端服务程序。
6)服务端程序拿到这个数据包后,同样以HTTP协议格式解包,获取到客户端的意图。
7)得知客户端意图后进行处理,比如提供静态文件或者调用服务端程序获得动态结果。
8)服务器将响应结果(可能是HTML或者图片等)按照HTTP协议格式打包。
9)服务器将响应数据包推入网络,数据包经过网络传输最终达到到浏览器。
10)浏览器拿到数据包后,以HTTP协议的格式解包,然后解析数据,假设这里的数据是HTML。
11)浏览器将HTML文件展示在页面上。
那我们想要探究的Tomcat和Jetty作为一个HTTP服务器,在这个过程中都做了些什么事情呢?主要是接受连接、解析请求数据、处理请求和发送响应这几个步骤。
2.2 Tomcat整体架构
2.2.1 Http服务器请求处理
浏览器发给服务端的是一个HTTP格式的请求,HTTP服务器收到这个请求后,需要调用服务端程序来处理,所谓的服务端程序就是你写的Java类,一般来说不同的请求需要由不同的Java类来处理。
1)图1 ,表示HTTP服务器直接调用具体业务类,它们是紧耦合的。
2)图2 , HTTP服务器不直接调用业务类,而是把请求交给容器来处理,容器通过servlet接口调用业务类。因此servlet接口和Servlet容器的出现,达到了HTTP服务器与业务类解耦的目的。而servlet接口和servlet容器这一整套规范叫作Servlet规范。Tomcat按照servlet规范的要求实现了servlet容器,同时它们也具有HTTP服务器的功能。作为Java程序员,如果我们要实现新的业务功能,只需要实现一个Servlet ,并把它注册到Tomcat(Servlet容器)中,剩下的事情就由Tomcat帮我们处理了。
2.2.2 Servlet容器工作流程
为了解耦,HTTP服务器不直接调用servlet ,而是把请求交给servlet容器来处理,那Servlet容器又是怎么工作的呢?
当客户请求某个资源时, HTTP服务器会用一个ServletRequest对象把客户的请求信息封装起来,然后调用servlet容器的service方法,Servlet容器拿到请求后,根据请求的URL和servlet的映射关系,找到相应的servlet,如果Servlet还没有被加载,就用反射机制创建这个servlet,并调用servlet的init方法来完成初始化,接着调用servlet的service方法来处理请求,把servletResponse对象返回给HTTP服务器,HTTP服务器会把响应发送给客户端。
2.2.3 Tomcat整体架构
我们知道如果要设计一个系统,首先是要了解需求,我们已经了解了Tomcat要实现两个核心功能:
1)处理socket连接,负责网络字节流与Request和Response对象的转化。
2)加载和管理Servlet,以及具体处理Request请求。
因此Tomcat设计了两个核心组件连接器(connector)和容器(container)来分别做这两件事情。连接器负责对外交流,容器负责内部处理。
2.3连接器-Coyote
2.3.1架构介绍
coyote是Tomcat的连接器框架的名称,是Tomcat服务器提供的供客户端访问的外部接口。客户端通过coyote与服务器建立连接、发送请求并接受响应
coyote封装了底层的网络通信(Socket请求及响应处理) ,为catalina容器提供了统一的接口,使catalina容器与具体的请求协议及IO操作方式完全解耦。coyote将socket输入转换封装为Request对象,交由Catalina容器进行处理,处理请求完成后,Catalina通过coyote提供的Response对象将结果写入输出流。
coyote作为独立的模块,只负责具体协议和IO的相关操作, 与Servlet规范实现没有直接关系,因此即便是Request和Response对象也并未实现servlet规范对应的接口,而是在catalina中将他们进一步封装为ServletRequest和ServletResponse
2.3.2 IO模型与协议
在Coyote中,Tomcat支持的多种I/O模型和应用层协议,具体包含哪些IO模型和应用层协议,请看下表:
Tomcat支持的IO模型(自8.5/9.0版本起,Tomcat移除了对BIO的支持) :
Tomcat支持的应用层协议
协议分层
在8.0之前,Tomcat默认采用的I/O方式为BIO ,之后改为NIO,无论NIO,NIO2还是APR,在性能方面均优于以往的BIO, 如果采用APR,甚至可以达到Apache HTTP server的影响性能。
Toncat为了实现支持多种I/O模型和应用层协议,一个容器可能对接多个连接器,就好比一个房间有多个门。但是单独的连接器或者容器都不能对外提供服务,需要把它们组装起来才能工作,组装后这个整体叫作service组件。这里请你注意, Service本身没有做什么重要的事情,只是在连接器和容器外面多包了一层,把它们组装在一起。Tomcat内可能有多个service ,这样的设计也是出于灵活性的考虑。通过在Tomceat中配置多个service,可以实现通过不同的端口号来访问同一台机器上部署的不同应用。
2.3.3连接器组件
连接器中的各个组件的作用如下:
EndPoint
1)EndPoint:coyote通信端点,即通信监听的接口,是具体socket接收和发送处理器,是对传输层的抽象,因此EndPoint用来实现TCP/IP协议的。
2)Tomcat并没有EndPoint接口,而是提供了一个抽象类AbstractEndpoint ,里面定义了两个内部类:Acceptor /ək'septə/ 和SocketProcessor,Acceptor用于监听socket连接请求。socketProcessor用于处理接收到的socket请求,它实现Runnable接口,在Run方法里调用协议处理组件processor进行处理。为了提高处理能力,socketProcessor被提交到线程池来执行。而这个线程池叫作执行器(Executor),我在后面的专栏会详细介绍Tomcat如何扩展原生的Java线程池,
Processor
Processor:Coyote协议处理接口,如果说EndPoint是用来实现TCP/IP协议的,那么Processor用来实现HTTP协议,Processor接收来自EndPoint的socket,读取字节流解析成Tomcat Request和Response对象,并通过Adapter将其提交到容器处理,Processor是对应用层协议的抽象。
ProtocolHandler
ProtocolHandler:coyote协议接口, 通过Endpoint和Processor ,实现针对具体协议的处理能力。Tomca按照协议和I/O提供了6个实现类: AjpNioProtocol , AjpAprProtocol,
AjpNio2Protocol , Http11NioProtocol,Http11Nio2Protocol
,Httpl1AprProtocol,我们在配置tomcat/conf/server.xml时,至少要指定具体的ProtocolHandler ,当然也可以指定协议名称,如: HTTP/1.1 ,如果安装了APR,那么将使用Http11AprProtocol ,否则使用Http11NioProtocol
Adapter
由于协议不同,客户端发过来的请求信息也不尽相同,Tomcat定义了自己的Request类来"存放"这些请求信息。ProtocolHandler接口负责解析请求并生成Tomcat Request类。但是这个Request对象不是标准的servletRequest,也就意味着,不能用Tomcat Request作为参数来调用容器。Tomcat设计者的解决方案是引入coyoteAdapter,这是适配器模式的经典运用,连接器调用coyoteAdapter的Sevice方法,传入的是Tomcat
Request对象,coyoteAdapter负责将Tomcat Request转成ServletRequest ,再调用容器的Service方法。
2.4容器-catalina
Tomcat是一个由一系列可配置的组件构成的web容器,而Catalina是Tomcat的servlet容器。
Catalina是servlet容器实现,包含了之前讲到的所有的容器组件,以及后续章节涉及到的安全、会话、集群、管理等servlet容器架构的各个方面。它通过松耦合的方式集成Coyote ,以完成按照请求协议进行数据读写。同时,它还包括我们的启动入口、Shell程序等。
2.4.1 Catalina 地位
Tomcat的模块分层结构图,如下:
Tomcat本质上就是一款Servlet容器, 因此Catalina才是Tomcat的核心,其他模块都是为Catalina提供支撑的。比如:通过Coyote模块提供链接通信,Jasper/'dʒæspə/ 模块提供JSP引擎,Naming提供JNDI服务,Juli提供日志服务。
2.4.2 catalina结构
Catalina的主要组件结构如下:
Catalina
如上图所示,Catalina负责管理Server ,而Server表示着整个服务器。Server下面有多个服务Service ,每个服务都包含着多个连接器组件Connector(Coyote实现)和一个容器组件Container/kənˈteɪnə(r)/ ,在Tomcat启动的时候,会初始化一个Catalina的实例。
Catalina各个组件的职责:
2.4.3 Container结构
Tomcat设计了4种容器,分别是Engine, Host,Context和Wrapper,这4种容器不是平行关系,而是父子关系。Tomcat通过一种分层的架构,使得Servlet容器具有很好的灵活性。
各个组件的含义:
我们也可以再通过Tomcat的server.xml配置文件来加深对Tomcat容器的理解。Tomcat采用了组件化的设计,它的构成组件都是可配置的,其中最外层的是Server,其他组件按照一定的格式要求配置在这个顶层容器中。
那么,Tomcat是怎么管理这些容器的呢?你会发现这些容器具有父子关系,形成一个树形结构,你可能马上就想到了设计模式中的组合模式。没错,Tomcat就是用组合模式来管理这些容器的。具体实现方法是,所有容器组件都实现了Container接口,因此组合模式可以使得用户对单容器对象和组合容器对象的使用具有一致性。这里单容器对象指的是最底层的wrapper ,组合容器对象指的是上面的context、Host或者Engine。
Container接口中提供了以下方法(截图中只是一部分方法)
在上面的接口看到了getParent,setParent,addChild和removeChild等方法。
Container接口扩展了Lifecycle接口,Lifecycle接口用来统一管理各组件的生命周期。
2.5 Tomcat启动流程
2.5.1 流程
步骤:
1)启动Tomcat ,需要调用bin/startup.bat (在linux目录下,需要调用bin/startup.sh) ,在startup.bat脚本中,调用了catalina.bat.
2)在catalina.bat脚本文件中,调用了Bootstrap中的main方法。
3)在Bootstrap的main方法中调用了init方法,来创建Catalina及初始化类加载器。
4)在Bootstrap的main方法中调用了load方法,在其中又调用了Catalina的load方法。
5)在Catalina的load方法中,需要进行一些初始化的工作,并需要构造Digester对象,用于解析XML.
6)然后在调用后续组件的初始化操作。。。加载Tomcat的配置文件,初始化容器组件,监听对应的端口号,准备接受客户端请求。
2.5.2源码解析
2.5.2.1 Lifecycle
由于所有的组件均存在初始化、启动、停止等生命周期方法,拥有生命周期管理的特性, 所以Tomcat在设计的时候,基于生命周期管理抽象成了一个接口Lifecycle ,而组件Server, Service, Container, Executor, Connector组件,都实现了一个生命周期的接口,从而具有了以下生命周期中的核心方法:
1) init () :初始化组件
2) start () :启动组件
3) stop () :停止组件
4)destroy():销毁组件
2.5.2.2各组件的默认实现
上面我们提到的Server, Service,
Engine, Host. Context都是接口, 下图中罗列了这些接口的默认实现类。当前对于Endpoint组件来说,在Tomcat中没有对应的Endpoint接口,但是有一个抽象类AbstractEndpoint ,其下有三个实现类:NioEndpoint.Nio2Endpoint,AprEndpoint,这三个实现类,分别对应于前面讲解链接器Coyote时,提到的链接器支持的三种10模型:NIO, NIO2 , APR , Tomcat8.5版本中,默认采用的是NioEndpoint。
ProtocolHandler:Coyote协议接口,通过封装Endpoint和Processor ,实现针对具体协议的处理功能。Tomcat按照协议和IO提供了6个实现类。
AJP协议
1) AjpNioProtocol :采用NIO的IO模型。
2) jpNio2Protocol:NIO2的IO模型。
3) AjpAprProtocol :采用APR的IO模型,需要依赖于APR库。
HTTP协议:
1) Http11NioProtocol :采用NIO的IO模型,默认使用的协议(如果服务器没有安装APR)
2) Httpl1Nio2Protocol :采用NIO2的IO模型。
3) Http11aprErotocol :采用APR的IO模型,需要依赖于APR库
2.5.2.3源码入口
目录: org.apache.catalina.startup
MainClass:BootStrap---->main(String[]args)
2.5.3 总结
从启动流程图中以及源码中,我们可以看出Tomcat的启动过程非常标准化,统一按照生命周期管理接口Lifecycle/laɪf'saɪkəl/ 的定义进行启动。首先·调用init()方法进行组件的逐级初始化操作,然后再调用start ()方法进行启动。
每一级的组件除了完成自身的处理外,还要负责调用子组件响应的生命周期管理方法, 组件与组件之间是松耦合的,因为我们可以很容易的通过配置文件进行修改和替换。
2.6 Tomcat请求处理流程
2.6.1请求流程
设计了这么多层次的容器,Tomcat是怎么确定每一个请求应该由哪个Wrapper容器里的Servlet来处理的呢?答案是,Tomcat是用Mapper组件来完成这个任务的。.
Mapper组件的功能就是将用户请求的URL定位到一个Servlet ,它的工作原理是:Mapper组件里保存了Web应用的配置信息,其实就是容器组件与访问路径的映射关系,比如Host容器里配置的域名、Context容器里的Web应用路径,以及Wrapper容器里Servlet映射的路径,你可以想象这些配置信息就是一个多层次的Map。
当一个请求到来时,Mapper组件通过解析请求URL里的域名和路径,再到自己保存的Map里去查找,就能定位到一个Servlet,请你注意,一个请求URL最后只会定位到一个Wrapper容器,也就是一个Servlet。
下面的示意图中, 就描述了当用户请求链接http://www.itStudy.cn/bbs/findAll 之后,是如何找到最终处理业务逻辑的Servlet
那上面这幅图只是描述了根据请求的URL如何查找到需要执行的Servlet ,那么下面我们再来解析一下,从Tomcat的设计架构层面来分析Tomcat的请求处理
步骤如下:
1)Connector组件Endpoint中的Acceptor监听客户端套接字连接并接收Socket。
2) 将连接交给线程池Executor处理,开始执行请求响应任务。
3)Processor组件读取消息报文,解析请求行、请求体、请求头,封装成Request对象。
4)Mapper组件根据请求行的URL值和请求头的Host值匹配由哪个Host容器、Context容器、Wrapper容器处理请求。
5)Coyoteadaptor/ə'dæptə/ 组件负责将Connector组件和Engine容器关联起来,把生成的Request对象和响应对象Response传递到Engine容器中,调用Pipeline/ˈpaɪplaɪn/ 。
6)Engine容器的管道开始处理,管道中包含若干个Valve、每个Valve负责部分处理逻辑。执行完Valve后会执行基础的Valve--standardEnginevalve,负责调用Host容器的Pipeline。
7)Host容器的管道开始处理,流程类似,最后执行Context容器的Pipeline.
8)Context容器的管道开始处理,流程类似,最后执行Wrapper容器的Pipeline.
9) Wrapper容器的管道开始处理,流程类似,最后执行Wrapper容器对应的Servlet对象的处理方法。
2.6.2 请求流程源码分析
在前面所讲解的Tomcat的整体架构中,我们发现Tomcat中的各个组件各司其职,组件之间松耦合,确保了整体架构的可伸缩性和可拓展性,那么在组件内部,如何增强组件的灵活性和拓展性呢?在Toncat中,每个Container组件采用责任链模式(Pipelien即是责任链模式)来完成具体的请求处理。
在Tomcat中定义了Pipeline和Valve两个接口,Pipeline用于构建责任链,Valve代表责任链上的每个处理器。Pipeline中维护了一个基础的Valve,它始终位于Pipeline的末端(最后执行) ,封装了具体的请求处理和输出响应的过程。当然,我们也可以调用addValve ()方法,为Pipeline添加其他的Valve , 后添加的Valve位于基础的Valve之前,并按照添加顺序执行。Pipiline通过获得首个Valve来启动整合链条的执行。
3.Jasper
3.1 Jasper简介
对于基于JSP的web应用来说,我们可以直接在JSP页面中编写Java代码,添加第三方的标签库,以及使用EL表达式。但是无论经过何种形式的处理,最终输出到客户端的都是标准的HTML页面(包含js , css..) ,并不包含任何的Java相关的语法。也就是说,我们可以把Jsp看做是一种运行在服务端的脚本。那么服务器是如何将JSP页面转换为HTML页面的呢?
Jasper模块是Tomcat的JSP核心引擎,我们知道JSP本质上是一个Servlet,Tomcat使用Jasper对Jsp语法进行解析,生成Servlet并生成Class字节码,用户在进行访问Jsp时,会访问Servlet,最终将访问的结果直接响应在浏览器端。另外,在运行的时候, Jasper还会检测JSP文件是否修改,如果修改,则会重新编译JSP文件。
3.2 JSP编译方式
3.2.1运行时编译
Tomcat并不会在启动web应用的时候自动编译JSP文件,而是在客户端第一次请求时,才编需要访问的JSP文件。
创建一个web项目,并编写JSP代码:
如果这行代码报错uri="http://java.sun.com/jsp/jst1/core"
则:在WEB-INF/lib包下需要导入jstl.jar、standard.jar两个jar包
3.2.1.1编译过程
Tomcat在默认的web.xml 中配置了一个org.apache.jasper.servlet.JspServlet,用于处理所有的.jsp或.jspx结尾的请求,该Servlet实现即是运行时编译的入口。
JspServlte处理流程图
3.2.1.2编译结果
1)如果在tomcat/conf/web.xml中配置了参数scratchdir,则jsp编译后的结果,就会存储在该目录下。
2)如果没有配置该选项,则会将编译后的结果,存储在Tomcat安装目录下的work/Catalina(Engine名称)/localhost (Host名称) /Context名称。假设项目名称为jsp_demo-01 ,默认的目录为: work/Catalina/localhost/jsp-demo_01.
3)如果使用的是IDEA开发工具集成Toncat访问web工程中的jsp ,编译后的结果,存放在:
C:\Users\Alienware\.IntelliJIdea2018.3\systemltomcat\project_tomcat\work\catalina\localhost\jsp_demo_01wa
r_exploded\org\apache\jsp
3.2.2 预编译
除了运行时编译,我们还可以直接在web应用启动时,一次性将web应用中的所有的JSP页面一次性编译完成。在这种情况下,web应用运行过程中,便可以不必再进行实时编译,而是直接调用JSP页面对应的Servlet完成请求处理,从而提升系统性能。
Tomcat提供了一个Shell程序Jspc ,用于支持Jsp预编译,而且在Tomcat的安装目录下提供了一个catalina-tasks.xml文件声明了
Tomcat支持的Ant任务, 因此,我们很容易使用Ant来执行JSP预编译。(要想使用这种方式,必须得确保在此之前已经下载并安装了Apache Ant).
3.3 JSP编译原理
由编译后的源码解读,可以分析出以下几点
1)其类名为index_jsp ,继承自org.apache.jasper.runtime.HttpJspBase,该类是Httpservlet的子类,所以jsp本质就是一个Servlet。
2)通过属性_jspx_dependants保存了当前JSP页面依赖的资源,包含引入的外部的JSP页面、导入的标签、标签所在的jar包等,便于后续处理过程中使用(如重新编译检测,因此它以Map形式保存了每个资源的上次修改时间)。
3)通过属性_jspx_imports_packages存放导入的java包,默认导入javax.servlet,
javax.servlet.http,javax.servlet.jsp。
4)通过属性_jspx_impors_classes存放导入的类,通过import指令导入的DateFormat、SimpleDateFormat、Date都会包含在该集合中。_jspx_imports_packages和_jspx_imports_classes 属性主要用于配置EL引擎上下文。
5)请求处理由方法_jspService完成,而在父类HttpJspBase中的service方法通过模板方法模式,调用了子类的_jspService方法。
6)_jspService方法中定义了几个重要的局部变量: pageContext、Session,
application, config, out, page,由于整个页面的输出有_JspService方法完成,因此这些变量和参数会对整个JSP页面生效。这也是我们为什么可以在JSP页面使用这些变量的原因。
7)指定文档类型的指令(page)最终转换为response.setContentType()方法调用。
8)对于每一行的静态内容(HTML)
,调用out.write输出。
9)对于<%...%>中的java代码,将直接转换为Servlet类中的代码。如果在Java代码中嵌入了静态文件,则同样调用 out.write输出。
3.3.2 编译流程
JSP编译过程如下
Compiler编工作主要包含代码生成和编译两部分
代码生成
1)Compiler通过一个PageInfo对象保存JSP页面编译过程中的各种配置,这些配置可能来源于web应用初始化参数,也可能来源于JSP页面的指令配置(如page , include ).
2)调用ParserController解析指令节点,验证其是否合法,同时将配置信息保存到PageInfo中,用于控制代码生成。
3)调用ParserController解析整个页面, 由于JSP 是逐行解析,所以对于每一行会创建一个具体的Node对象。如静态文本 (Templaterext )、Java代码( scriptlet)、定制标签( Custonag )、Include指令( IncludeDirective ) .
4)验证除指令外其他所有节点的合法性,如脚本、定制标签、EL表达式等
5)收集除指令外其他节点的页面配置信息。
6)编译并加载当前JSP页面依赖的标签
7)对于JSP页面的EL表达式,生成对应的映射函数。
8)生成JSP页面对应的Servlet类源代码
编译
代码生成完成后, Compiler还会生成SMAP信息。如果配置生成SMAP信息,Compiler则会在编译阶段将SMAP信息写到class文件中.
在编译阶段,Compiler的两个实现AntCompiler和JDTCompiler分别调用先关框架的API进行源代码编译。
对于AntCompiler来说,构造一个Ant的javac的任务完成编译。
对干JDTCompiler来说调用org.eclinse.idt.internal.comniler.Comniler完成编译。