到官网下载需要的Tomcat版本的压缩包即可
注意:Tomcat 9 对应的JRE必须>=JRE 8;Tomcat 8 7同理
然后解压到指定的文件夹;目录名为:“apache-tomcat-[version]”
(WINDOWS和Linux)一样的效果
配置JRE环境变量,变量名为:CATALINA_HOME(required)和CATALINA_BASE(optional)
前者必须,后者可选
CATALINA_HOME:tomcat根目录的路径
CATALINA_BASE:用于Advanced Configuration - Multiple Tomcat Instances 多个Tomcat实例
==================================================
Advanced Configuration - Multiple Tomcat Instances
==================================================
In many circumstances, it is desirable to have a single copy of a Tomcat
binary distribution shared among multiple users on the same server. To make
this possible, you can set the CATALINA_BASE environment variable to the
directory that contains the files for your 'personal' Tomcat instance.
When running with a separate CATALINA_HOME and CATALINA_BASE, the files
and directories are split as following:
In CATALINA_BASE:
* bin - Only the following files:
* setenv.sh (*nix) or setenv.bat (Windows),
* tomcat-juli.jar
The setenv scripts were described above. The tomcat-juli library
is documented in the Logging chapter in the User Guide.
* conf - Server configuration files (including server.xml)
* lib - Libraries and classes, as explained below
* logs - Log and output files
* webapps - Automatically loaded web applications
* work - Temporary working directories for web applications
* temp - Directory used by the JVM for temporary files (java.io.tmpdir)
In CATALINA_HOME:
* bin - Startup and shutdown scripts
The following files will be used only if they are absent in
CATALINA_BASE/bin:
setenv.sh (*nix), setenv.bat (Windows), tomcat-juli.jar
* lib - Libraries and classes, as explained below
* endorsed - Libraries that override standard "Endorsed Standards"
libraries provided by JRE. See Classloading documentation
in the User Guide for details.
This is only supported for Java <= 8.
By default this "endorsed" directory is absent.
Tomcat官方说:CATALINA_HOME相当于Tomcat的安装目录,而CATALINA_BASE则是Tomcat的工作目录
所以CATALINA_BASE的存在就是为了实现多个Tomcat实例可以同时运行(而不采取copy多个Tomcat的副本的方法)
每个Tomcat实例的私有文件夹:(conf配置文件,logs日志,webapps自己的app目录,work自己的临时工作区间,temp存放JVM的临时文件)
找到Tomcat的安装目录,然后把Tomcat的私有工作目录全部拷贝到新的(每个Tomcat实例文件夹下)
私有工作目录如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0RoI3Mzj-1570847392385)(D:\my_heart_note\tomcat\笔记\Tomcat.assets\1569894952277.png)]
拷贝到Tomcat实例文件夹下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RDZepeP4-1570847392387)(D:\my_heart_note\tomcat\笔记\Tomcat.assets\1569894978489.png)]
创建bin目录:bin目录下创建新的startup.bat启动脚本,和把tomcat-juli.jar拷贝过来
startup.bat简易写法如下:(主要是配置CATALINA_HOME和CATALINA_BASE)
set "TITLE=Tomcat9-1"
cd..
set "CATALINA_BASE=%cd%"
set "CATALINA_HOME=D:\my_heart_note\tomcat\apache-tomcat-9.0.26"
set "EXECUTABLE=%CATALINA_HOME%\bin\catalina.bat"
CALL %EXECUTABLE% start
set "TITLE=Tomcat9-2"
cd..
set "CATALINA_BASE=%cd%"
set "CATALINA_HOME=D:\my_heart_note\tomcat\apache-tomcat-9.0.26"
set "EXECUTABLE=%CATALINA_HOME%\bin\catalina.bat"
CALL %EXECUTABLE% start
TITLE是设置Tomcat窗口名字
还要修改端口号:三个端口号(Shutdown的,connector的,AJP的)
引发了窗口的乱码问题:因为我们窗口名字默认为Tomcat,但是到了我们的多个Tomcat实例的时候,窗口名字就不默认了。所以手动设置TITLE;
然后我想要实现的是修改Tomcat窗口的字符集。而不是修改Tomcat打印日志的字符集,去适应Windows的gbk;而是适应我们的UTF-8
所以决定修改Windows对于某个窗口的字符集。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-B8Qnm7RC-1570847392388)(D:\my_heart_note\tomcat\笔记\Tomcat.assets\1569895509349.png)]
十进制修改为65001
左侧Console下面的项为窗口名字,如:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZzSc1SDT-1570847392389)(D:\my_heart_note\tomcat\笔记\Tomcat.assets\1569895545382.png)]
效果:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-A8xGEAMv-1570847392391)(D:\my_heart_note\tomcat\笔记\Tomcat.assets\1569895773026.png)]
厉害吧!
上面的效果是我修改了每个Tomcat实例中的webapp里的ROOT文件夹里的index.jsp首页的title
原理是:
在每个Tomcat实例中的conf配置文件中,web.xml配置了一些关于应用处理的信息(Servlet相关配置)
<servlet>
<servlet-name>defaultservlet-name>
<servlet-class>org.apache.catalina.servlets.DefaultServletservlet-class>
<init-param>
<param-name>debugparam-name>
<param-value>0param-value>
init-param>
<init-param>
<param-name>listingsparam-name>
<param-value>falseparam-value>
init-param>
<load-on-startup>1load-on-startup>
servlet>
<servlet>
<servlet-name>jspservlet-name>
<servlet-class>org.apache.jasper.servlet.JspServletservlet-class>
<init-param>
<param-name>forkparam-name>
<param-value>falseparam-value>
init-param>
<init-param>
<param-name>xpoweredByparam-name>
<param-value>falseparam-value>
init-param>
<load-on-startup>3load-on-startup>
servlet>
mapping
<servlet-mapping>
<servlet-name>defaultservlet-name>
<url-pattern>/url-pattern>
servlet-mapping>
<servlet-mapping>
<servlet-name>jspservlet-name>
<url-pattern>*.jspurl-pattern>
<url-pattern>*.jspxurl-pattern>
servlet-mapping>
<welcome-file-list>
<welcome-file>index.htmlwelcome-file>
<welcome-file>index.htmwelcome-file>
<welcome-file>index.jspwelcome-file>
welcome-file-list>
意思是如果没有精准匹配到的Servlet,就会走DefaultServlet,就会调用到welcome-file-list中的网页
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OklaP7Fh-1570847392393)(D:\my_heart_note\tomcat\笔记\Tomcat.assets\1569909095997.png)]
<Server port="9005" shutdown="SHUTDOWN">
<Listener className="org.apache.catalina.startup.VersionLoggerListener" />
//提供的一种服务Service:名字为Catalina
<Service name="Catalina">
//这个服务下有多个连接器Connector,可以连接这个服务
<Connector port="9090" protocol="HTTP/1.1"
connectionTimeout="20000"
redirectPort="8443" />
<Connector port="9009" protocol="AJP/1.3" redirectPort="8443" />
//这个Service的底层由Engine支持,Engine里由主机或者虚拟主机构成
(提供应用webapp支持)
<Engine name="Catalina" defaultHost="localhost">
<Realm className="org.apache.catalina.realm.LockOutRealm">
<Realm className="org.apache.catalina.realm.UserDatabaseRealm"
resourceName="UserDatabase"/>
Realm>
//Host主机(或者在这里配置虚拟主机也可以,name为主机名,appBase为webapp目录的路径
<Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs"
prefix="localhost_access_log" suffix=".txt"
pattern="%h %l %u %t "%r" %s %b" />
Host>
Engine>
Service>
Server>
虚拟目录:映射到别的文件夹中
比如我们想127.0.0.1/blog/index 和 127.0.0.1/sky_chou/index
表现是:两个webapp目录blog和sky_chou,然后webapp里有各自的app应用
而不是把所有的app都部署在同一个webapp下(同时还能实现修改域名的功能)
原理是把域名请求 映射到 docBase配置的映射路径下。
(缺点:要重启服务器)
在HOST标签里,添加 pom文件 注意:Tomcat 9 里面的JDTCompiler源码里有CompilerOptions.VERSION_1_9这种高于1.9版本的,如果没有安装这些版本的话,识别不了 Tomcat9中有部分代码使用 在 参考链接:https://blog.csdn.net/linxdcn/article/details/72811928 如何发现编码是哪种乱码所导致的呢? [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-S4OBaPFd-1570847392405)(D:\my_heart_note\tomcat\笔记\Tomcat.assets\1570003119787.png)] [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uDI0VcEm-1570847392406)(D:\my_heart_note\tomcat\笔记\Tomcat.assets\1570003125879.png)] 一步步debug的过程找寻原因: [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HOmcuZVo-1570847392407)(D:\my_heart_note\tomcat\笔记\Tomcat.assets\1570002765099.png)] 找到这个国际化配置文件 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TNKIMBcw-1570847392408)(D:\my_heart_note\tomcat\笔记\Tomcat.assets\1570002802066.png)] 继续debug下去,sm.getString()底层调用的ResourceBundle实现类去读取的Properties文件 我们看下面的文档注释(知道ResourceBundle只会按ISO8859-1去读取,要想读取非ISO8859-1编码的内容,需要以UNICODE形式去填写properties文件。 效果如下: [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-T70Jpttl-1570847392410)(D:\my_heart_note\tomcat\笔记\Tomcat.assets\1570002943894.png)] 客户端->服务端(在浏览器输入url) 用的是HTTP协议 所以在传输层先通过TCP协议与服务器建立三次握手 连接完成后,客户端再封装请求数据为HTTP报文格式 然后从高层往底层封装,传输到服务器那边去。 服务器拆包,解析HTTP报文。 然后服务端调用服务器软件程序去处理这个HTTP请求,(比如Tomcat,调用不同的Servlet去处理请求) 然后把响应数据封装成HTTP报文,再传回去。 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YQfaHx7A-1570847392412)(D:\my_heart_note\tomcat\笔记\Tomcat.assets\1570004188277.png)] 为了解耦,HTTP服务器并不直接调用Servlet去处理每个用户请求。而是交给了更为专业的Servlet容器(Tomcat)去处理请求。 首先,把用户请求的HTTP报文,解析封装成一个ServletRequest对象,然后传给Servlet容器,根据这个对象里的url,通过事先在(web.xml或者注解上定义的映射关系),找到对应的Servlet进行处理。(这里利用了反射技术,动态地创建相应的Servlet) 把ServletRequest传给Servlet之后,Servlet再调用核心方法Service()进行逻辑的处理,根据不同的请求格式,如POST,GET分别处理(doPost(),doGet() ) 然后再把响应信息封装到servletResponse对象中,最后通过HTTP服务的处理,加上响应头,封装成HTTP报文传回给客户端。 从宏观上来看,且从数据流转的角度来看,Tomcat最外面的两个核心组件就是Connector连接器和Container容器 Connector:负责处理Socket请求,然后把请求数据封装成Request对象;还把Response中的响应数据封装成HTTP报文返回给客户端。(相当于HTTP服务器,不处理具体的逻辑业务,只负责转发和接收) Container容器(Servlet容器):主要是拿到Connector传过来的Request对象,通过解析里面的url, 找到对应的Servlet进行处理。先检查这个Servlet是否已经加载(如果加载过了,就直接用),没有加载过的话,就通过反射进行创建);所以从这里可以看得出来Servlet是单例的。 然后Servlet调用Service()方法进行处理,把响应数据封装成Response对象传回给Connector (中间还要经过catalina容器) (Catalina容器接收的是ServletRequest对象) (所以在Connector组件和Catalina之间还有好多组件) [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TLRrZZWo-1570847392413)(D:\my_heart_note\tomcat\笔记\Tomcat.assets\1570005479858.png)] 解耦! 应用层协议 传输层 “阻塞”与"非阻塞"与"同步"与“异步"不能简单的从字面理解,提供一个从分布式系统角度的回答。 而异步则是相反,调用*在发出之后,这个调用就直接返回了,所以没有返回结果。换句话说,当一个异步过程调用发出后,调用者不会立刻得到结果。而是在调用*发出后,被调用者通过状态、通知来通知调用者,或通过回调函数处理这个调用。 典型的异步编程模型比如Node.js 举个通俗的例子: \2. 阻塞与非阻塞 阻塞调用是指调用结果返回之前,当前线程会被挂起。调用线程只有在得到结果之后才会返回。 还是上面的例子, 如果是关心阻塞 IO/ 异步 IO, 参考 Unix Network Programming View Book – 还是2014年写的以解释概念为主,主要是同步异步 阻塞和非阻塞会被用在不同层面上,可能会有不准确的地方,并没有针对 阻塞 IO/ 异步 IO 等进行讨论,大家可以后续看看这两个回答: 怎样理解阻塞非阻塞与同步异步的区别? 怎样理解阻塞非阻塞与同步异步的区别? [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sGXMV2JV-1570847392414)(D:\my_heart_note\tomcat\笔记\Tomcat.assets\1570009028902.png)] Endpoint:Coyote的通信断点,是具体Socket接收和发送处理器,是对传输层的抽象,因此,Endpoint是用来实现TCP/IP协议的。 Tomcat没有Endpoint接口,而是定义了AbstractEndpoint,里面定义了两个内部类:Acceptor和SocketProcessor Acceptor用来监听Socket请求,SocketProcessor用来处理接收到的Socket请求,它实现了Runnable接口, 在run()方法里调用协议处理组件Processor进行处理。为了提高处理能力,SocketProcessor被提交到线程池执行,这个线程池叫Executor。Tomcat扩展了原生的Java线程池。java.concurrent包下的。 这是抽象类SocketProcessorBase 这是NIOEndpoint的内部类SocketProcessor Processor:Coyote协议处理接口,如果说Endpoint是用来实现TCP/IP协议的,那么Processor是专门用来实现HTTP协议的。Processor接收来自Endpoint的Socket,读取字节流(按照HTTP协议)解析成Tomcat Request和Response对象,并通过Adapter提交到容器进行处理。 ProcessorHandler:Coyote协议接口,通过Endpoint和Processor,实现对具体协议的处理能力。 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xuHQSHZq-1570847392415)(D:\my_heart_note\tomcat\笔记\Tomcat.assets\1570093202977.png)] Tomcat按照协议和I/O提供了4个抽象类 和 6个实现类 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-G5Zj7J1G-1570847392416)(D:\my_heart_note\tomcat\笔记\Tomcat.assets\1570087837941.png)] 我们在server.xml中要在连接器Connector中指定具体的协议,起码要写协议名protocol="" Adater:适配器的应用 Connector里的方法:createRequest和createResponse(创建了两个低级的Request和Response) 最终返回的 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JWIYXTiX-1570847392417)(D:\my_heart_note\tomcat\笔记\Tomcat.assets\1570088616988.png)] (上面用的是Tomat7的源码结构,其实差不多)这个Mapper数据结构:有Host[]主机数组,每个主机内部类里有 ContextsList应用列表。每个Context里有Wrapper [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-01G1IF0N-1570847392419)(D:\my_heart_note\tomcat\笔记\Tomcat.assets\1570258949302.png)] 在StandardService的initInternal()方法中初始化了这个mapperListener(),而这个mapperListener里面就有Mapper组件 最后在CoyoteAdaptor生成request的时候,就把已经在每个request中拥有一堆与这个request相映射的容器信息,比如Host,Context,Wrapper等等。都在mappingData里 (所以映射是Tomcat启动的时候就根据xml配置文件完成一系列容器的创建和映射对象的初始化。) 所以当请求来临时,可以按部就班的跟着pipeline走就能达到对应的servlet。 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TfmoKqCf-1570847392420)(D:\my_heart_note\tomcat\笔记\Tomcat.assets\1570061700582.png)] Catalina负责管理Server,Server就是整个服务器。一个Server里包含着多个Service,(默认的Service是Catalina Service) 每个Service(表示对外提供的服务),里面可以包含多个Connector(对外的连接器) (实现是Coyote实现) 和一个容器组件(Container) Container又含有四大基本容器类:Engine,Host,Context,Wrapper 有很多Listener 一个Service包含着多个Connector,共享着单一的容器Container. 因为Service本身不是容器Container,所以有关的配置要在Engine里面编写(一个Service对应一个Engine) 不知道上面说的Values是什么的话:看https://www.iteye.com/blog/gearever-1536022 主要是容器里的一些Value,在catalina包下,有四大容器类:StandardEngine,StandardHost,StandardContext,StandardWrapper 这四大容器类会在Tomcat的启动流程中,一 一 初始化,并进行信息的传递以及start() 四大容器类有着默认的Value实现: 可以自定义定制每个容器的Value,用于容器间有序的通讯 在每个容器类里有着pipeline和Value模块。必不可缺! 看一下Tomcat 包的大体结构 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ffnAYMSV-1570847392421)(D:\my_heart_note\tomcat\笔记\Tomcat.assets\1570063332465.png)] 主要的是:coyote组件,catalina容器,jasper引擎 实现了Host接口的StandardHost ContainerBase基类定义了Pipeline成员。 举例:StandardHostValue Value的作用如下图: 当在server.xml文件中配置了一个定制化valve时,会调用pipeline对象的addValve方法,将valve以链表方式组织起来,看一下代码; 为容器定制Value valve按照容器作用域的配置顺序来组织valve,每个valve都设置了指向下一个valve的next引用。同时,每个容器缺省的标准valve都存在于valve链表尾端,这就意味着,在每个pipeline中,缺省的标准valve都是按顺序,最后被调用。 消息流 图中显示的是各个容器默认的valve之间的实际调用情况。从StandardEngineValve开始,一直到StandardWrapperValve,完成整个消息处理过程。 从图中可以看出,engine有四大组件: Engine接口的方法:(与Service的绑定,默认的Host,以及集群相关的JvmRoute配置) [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vXAA4rTm-1570847392425)(D:\my_heart_note\tomcat\笔记\Tomcat.assets\1570065906793.png)] StandardHost主要是对Context的预处理,比如下面的关于部署deploy的问题 (如war包的自动部署) 又比如app目录的设置(或者采用默认的) Context是wrapper容器的集合 StandardContext Manager: 它主要是应用的session管理模块。其主要功能是session的创建,session的维护,session的持久化(persistence),以及跨context的session的管理等。Manager模块可以定制化,tomcat也给出了一个标准实现; manager模块是必须要有的,可以在server.xml中配置,如果没有配置的话,会在程序里生成一个manager对象。 Mapper简单介绍 注意到,上图有两个Mapper。一个是Connector里的Mapper,一个是StandardContext里的Mapper对象 对于mapper对象,可以抽象的理解成一个map结构,其key是某个访问资源,例如/*.do,那么其value就是封装了处理这个资源TestServlet的某个wrapper对象。当访问/*.do资源时,TestServlet就会在mapper对象中定位到。这里需要特别说明的是,通过这个mapper对象定位特定的wrapper对象的方式,只有一种情况,那就是在servlet或jsp中通过forward方式访问资源时用到。例如, Mapper对象在tomcat中存在于两个地方(注意,不是说只有两个mapper对象存在), 其一,是每个context容器对象中,它只记录了此context内部的访问资源与相对应的wrapper子容器的映射; 其二,是connector模块中,这是tomcat全局的变量,它记录了一个完整的映射对应关系,即根据访问的完整URL如何定位到哪个host下的哪个context的哪个wrapper容器。 这样,通过上面说的forward方式访问资源会用到第一种mapper,除此之外,其他的任何方式,都是通过第二种方式的mapper定位到wrapper来处理的。也就是说,forward是服务器内部的重定向,不需要经过网络接口,因此只需要通过内存中的处理就能完成。这也就是常说的forward与sendRedirect方式重定向区别的根本所在。 StandardContext的Mapper:使用场景是 这种转发的情况下:用于在Context里的mapper中查找url与其对应的Servlet(封装的Wrapper对象)的映射 看源码: StandardContext Connector里的Request 的getRequestDispatcher(String path) ApplicationContext(实现了ServletContext) getRequestDispatcher()方法 ApplicationContext内部类: Wrapper的基本功能已经说了。那么再说一个wrapper比较重要的概念。严格的说,并不是每一个访问资源对应一个wrapper对象。而是每一种访问资源对应一个wrapper对象。其大致可分为三种: 处理jsp的一个wrapper:例如访问的所有jsp文件,它包含了一个tomcat的实现处理jsp的缺省servlet: 它主要实现了对jsp的编译等操作 需要注意的是,前两种wrapper分别是一个,主要是其对应的是DefaultServlet及JspServlet。这两个servlet是在tomcat的全局conf目录下的web.xml中配置的,当app启动时,加载到内存中。 主要说说servlet对象与servlet stack对象。这两个对象在wrapper容器中只存在其中之一,也就是说只有其中一个不为空。 当以servlet对象存在时,说明此servlet是支持多线程并发访问的,也就是说不存在线程同步的过程,此wrapper容器中只包含一个servlet对象(这是我们常用的模式); 当以servlet stack对象存在时,说明servlet是不支持多线程并发访问的,每个servlet对象任一时刻只有一个线程可以调用,这样servlet stack实现的就是个简易的线程池,此wrapper容器中只包含一组servlet对象,它的基本原型是worker thread模式实现的。(废弃) (提供的并发方式并不安全) Wrapper接口下的方法:(可以看得出是处理Servlet的) 时序图: [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NBixZigZ-1570847392432)(D:\my_heart_note\tomcat\笔记\Tomcat.assets\1570085332850.png)] startup.bat里 catalina.bat中的核心部分: 粗略介绍Tomcat启动流程 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JtQFO3xE-1570847392433)(D:\my_heart_note\tomcat\笔记\Tomcat.assets\1570088784843.png)] Lifecycle接口:定义了每个组件的生命周期(几乎所有的Tomcat组件都实现了这个接口) 方法: [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tB8u11ti-1570847392435)(D:\my_heart_note\tomcat\笔记\Tomcat.assets\1570089043548.png)] 继承图: [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-veaWrB5a-1570847392436)(D:\my_heart_note\tomcat\笔记\Tomcat.assets\1570089442444.png)] Bootstrap的main()方法:Tomcat的入口 Bootstrap的init()方法: bootstrap的load()方法 我个人找了下源码:看了一下Catalina的server是怎么创建的 Catalina里创建了digester对象,这是用来解析XML的 从这里就可以看出,是先通过解析XML配置文件,来反射的创建StandardServer,注入到Catalina中 然后Catalina在load()过程中,初始化了下一个子组件: Catalina#load() 解析一下这里的精华:每个组件(这里是server)实现了LifeCycle接口。LifeCycle有个实现类LifeCycleBase LifeCycleBase#init()方法 在这里:StandardServer继承了LifeCycleBase类,所以要重写这个抽象方法initInternal 下一步:到Service.initInternal() LifeCycleBase是一套模板,实现了LifeCycle接口的一系列生命周期方法;但是把一些具体的差异化实现改为抽象方法;让子类实现。并根据多态调用。 下一步:Connector()#initInternal()方法 下一步:protocolHandler#initInternal()方法 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-T07mTI4y-1570847392438)(D:\my_heart_note\tomcat\笔记\Tomcat.assets\1570093505540.png)] 实现类:AbstractHttp11Protocol:HTTP1.1版本的 我们来看看Endpoint到底是不是和上面说的一样:是与Socket相关的 进入后:又是AbstractEndpoint的模板方法bind() 点击左边的绿色按钮,可以看到哪个类实现了这个抽象方法 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-L4EvCDVL-1570847392439)(D:\my_heart_note\tomcat\笔记\Tomcat.assets\1570093854377.png)] 我们选择NioEndpoint(默认) 进入initServerSocket() 回到Bootstrap#main()方法里的start()方法 start()也有startInternal()模板方法设计模式 ?的,我自己偷懒,没去看StandardContext的startInternal()方法 在Context接口下有一个方法:获取所有的应用生命周期监听器 然后在StandardContext里有个listenerStart()方法:启动所有的监听器 而这个linstenerStart()方法又是在哪里被调用的呢? 答案是:在StandardContext的startInternal()里 一直按着顺序,调用子组件的start()方法 一直到最后endpoint()的start() Acceptor类(Tomcat8 是NioEndpoint的内部类) 预备工作:首先先写一个最简单的Servlet,然后通过IDEA生成class文件,测试运行成功。 然后把out路径下的war包 copy到tomcat源码工程下的home文件夹里的webapp下, 然后就可以通过编译tomcat源码,在启动Tomcat的时候,访问编写好的Servlet。 然后debug debug流程: [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mtHNcBsY-1570847392440)(D:\my_heart_note\tomcat\笔记\Tomcat.assets\1570244886481.png)] 主要是通过pipeline + valve来进行责任链的传递,最终找到wrapper,然后经过过滤器链,最后抵达Servlet NioEndpoint启动start();并且创建acceptor线程(接收Socket请求,封装为SocketWrapper),以及开启poller threads线程(完成对selectionKey的处理) [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WjZCnyCC-1570847392441)(D:\my_heart_note\tomcat\笔记\Tomcat.assets\1570250105452.png)] 还在里面创建线程池 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2s507mOV-1570847392441)(D:\my_heart_note\tomcat\笔记\Tomcat.assets\1570250229071.png)] 创建accpetor线程和Poller线程 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KKeGyUZ5-1570847392443)(D:\my_heart_note\tomcat\笔记\Tomcat.assets\1570250274134.png)] 1.1 下面我们来看看Acceptor线程的run方法 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-z4GoLt9B-1570847392444)(D:\my_heart_note\tomcat\笔记\Tomcat.assets\1570251006485.png)] 调用NioEndpoint的accept()方法,阻塞等待Socket请求 获取到Socket请求之后,由Acceptor配置configure这个Socket (通过NioEndpoint的setSocketOptions方法) [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-w3XNZS9O-1570847392445)(D:\my_heart_note\tomcat\笔记\Tomcat.assets\1570251148202.png)] 下面我们看看NioEndpoint的setSocketOptions(SocketChannel)方法 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AamutVf1-1570847392445)(D:\my_heart_note\tomcat\笔记\Tomcat.assets\1570251238663.png)] 添加Channel进NioChannels中(是个同步的栈SynchronizedStack) [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jmggXTWu-1570847392448)(D:\my_heart_note\tomcat\笔记\Tomcat.assets\1570251345624.png)] 把SocketChannel封装成NioSocketWrapper (wrapper中含SocketChannel和endpoint) [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-I10UWvKH-1570847392449)(D:\my_heart_note\tomcat\笔记\Tomcat.assets\1570251384133.png)] 并且把channel注册进poller中的selector里 (注册监听事件为Read),并且创建PollerEvent(为Register),addEvent() [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TUiwfypW-1570847392450)(D:\my_heart_note\tomcat\笔记\Tomcat.assets\1570251531821.png)] 先看看Wrapper里面是什么东东(居然有NioSelectorPool,NioChannel的同步栈,对应的Poller) 也就是一个wrapper对应着一个poller,然后还含有一个栈的niochannel通道(因为一个selector上有多个channel) [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lYIPcMd2-1570847392452)(D:\my_heart_note\tomcat\笔记\Tomcat.assets\1570253051467.png)] 看看PollerEvent 也是个线程, (封装了NioChannel和channel的监听事件) [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QJcMVxZ6-1570847392452)(D:\my_heart_note\tomcat\笔记\Tomcat.assets\1570252518986.png)] 再分析下PollerEvent的run()方法 (上面我们说过在配置SocketChannel的时候,我们就把这个比通过poller.register方法注册到pollerEvent线程中去了,并且设置为监听Register事件) 这里的run方法就是判断如果是register状态的channel,那么就注册到其selector中去,并且监听Read事件, 最后还要加上attachment【SocketWrapper】(附加在SelectionKey上面的东西)->后面poller线程在处理key时会把这个比取出来 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-H41rBnsM-1570847392454)(D:\my_heart_note\tomcat\笔记\Tomcat.assets\1570252658708.png)] PollerEvent#run()方法中还会监听不是Register的事件,那么就会设置key的感兴趣状态为Read(有点懵逼,没看懂,反正就是修改了key附件中Wrapper里面的感兴趣事件interestOps)或者关闭掉这些key [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AaKLY5hw-1570847392456)(D:\my_heart_note\tomcat\笔记\Tomcat.assets\1570253235319.png)] 至此:新连接进来的Socket配置完成 接下来:应该分析poller线程了 分析下Poller的run()方法 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-H3yu2POJ-1570847392456)(D:\my_heart_note\tomcat\笔记\Tomcat.assets\1570252028086.png)] 总之就是设置一个hasEvents的(判断有无事件的状态标志位)—poller维护着一个同步的事件队列 然后selector.select()熟悉的老朋友了,反正就是用来轮询我们注册在这个selector上的SocketChannel的状态(监听) [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vocKVAgB-1570847392457)(D:\my_heart_note\tomcat\笔记\Tomcat.assets\1570253388930.png)] 然后下一步的重头戏来了,进入processKey(sk,socketWrapper);也就是拿着SeletionKey和SocketWrapper进去做处理 ProcessKey (就是Nio那一套东西,分逻辑处理读事件,写事件) [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xLL2AyPp-1570847392458)(D:\my_heart_note\tomcat\笔记\Tomcat.assets\1570253810634.png)] 怎么处理呢?交给Executor去处理这个任务(Executor是在我们上面创建NioEndpoint的时候就已经创建了) [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8A2JbEUa-1570847392460)(D:\my_heart_note\tomcat\笔记\Tomcat.assets\1570254008315.png)] execute(this)这个this是个什么东西,这个this是(上一步socketWrapper.readOperation这个操作状态线程) [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZT8fMJVE-1570847392461)(D:\my_heart_note\tomcat\笔记\Tomcat.assets\1570254133353.png)] 如图,这只是个抽象类,里面的run方法是空的,那么实现类当然是:Nio啦 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PsA5aeMr-1570847392462)(D:\my_heart_note\tomcat\笔记\Tomcat.assets\1570254320526.png)] 注意到:上面处理读事件的时候,大概率会进入到这个处理中processSocket()这里面去 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lZO1sXlC-1570847392463)(D:\my_heart_note\tomcat\笔记\Tomcat.assets\1570254570574.png)] 进到下一个组件Processor中去进一步处理(我们之前分析,有SocketProcessor和ProcessorHandler两个Processor)一个负责TCP阶段的,一个负责Http 在进入另一个方法:AbstractEndpointprocessKey()中,也会提交给Executor()去执行任务(这次的任务线程是SocketProcessorBase) 然后进入SocketProcessorBase的实现类SocketProcessor(NioEndpoint的内部类) [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-I8j8Lfpw-1570847392464)(D:\my_heart_note\tomcat\笔记\Tomcat.assets\1570254903658.png)] 看吧,是TLS的握手协议,说明SocketProcessor处理的是TCP连接。(安全层) [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-87dMyrya-1570847392465)(D:\my_heart_note\tomcat\笔记\Tomcat.assets\1570254776226.png)] 下一步调用Handler去处理 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9PEc0Uqz-1570847392466)(D:\my_heart_note\tomcat\笔记\Tomcat.assets\1570255223333.png)] 进入AbstractProtocol里的process()方法 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mxTrZDyo-1570847392467)(D:\my_heart_note\tomcat\笔记\Tomcat.assets\1570255353183.png)] 这个connections是Map 通过Socket拿到这个Socket的Processor 然后进入processor的process()方法 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aXCH2Wvt-1570847392469)(D:\my_heart_note\tomcat\笔记\Tomcat.assets\1570255538905.png)] 进入里面的process方法 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ScW7WMI3-1570847392470)(D:\my_heart_note\tomcat\笔记\Tomcat.assets\1570255591417.png)] 看好:这个service进入之后 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oe5SxMki-1570847392471)(D:\my_heart_note\tomcat\笔记\Tomcat.assets\1570255702442.png)] 是这个抽象类的实现类:都是实现了Processor接口的,HTTP1.1协议,选择Http11Processor进行处理 (至此,我们从TCP处理完后,进入Http处理) [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PxUKiD1f-1570847392472)(D:\my_heart_note\tomcat\笔记\Tomcat.assets\1570255653586.png)] 在Http11Processor的service()中处理SocketWrapper里的Http报文 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vYiJi4Ta-1570847392473)(D:\my_heart_note\tomcat\笔记\Tomcat.assets\1570255878208.png)] 在Adapter适配器中处理request和response (request和response都是Http11Processor处理器解析Http数据后,创建的两个核心对象) [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pfDjRRMx-1570847392474)(D:\my_heart_note\tomcat\笔记\Tomcat.assets\1570256148914.png)] 进入Adapter()的service()是个抽象方法: [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6JSuU6UG-1570847392475)(D:\my_heart_note\tomcat\笔记\Tomcat.assets\1570256243357.png)] 看清楚了,是唯一的适配器实现类,那就是Coyote协议的Adapter适配器 CoyoteAdapter调用connecton.getService().getContainer() catalina容器,然后通过pipeline+valve机制去进行 一条容器链上的valve处理器的执行 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZpdAJ3CL-1570847392477)(D:\my_heart_note\tomcat\笔记\Tomcat.assets\1570256319215.png)] Service.getConnector()与Service直接对接的容器就只有一个Engine [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5yNKaxx1-1570847392477)(D:\my_heart_note\tomcat\笔记\Tomcat.assets\1570256454587.png)] 看下pipeline的官方解释 (我们在上面启动流程源码中解析过这个pipeline和valve,每个容器自己有个pipeline,然后这个pipeline自己设置该容器的valve) 下面是Pipeline类的getBasic()方法,说明pipeline里有这个容器的valve(按容器作用域严格区分) [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-drjwTk8W-1570847392479)(D:\my_heart_note\tomcat\笔记\Tomcat.assets\1570256543101.png)] [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TOVOBs8K-1570847392480)(D:\my_heart_note\tomcat\笔记\Tomcat.assets\1570256481916.png)] 看下Valve接口的实现类 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QxxbG6FW-1570847392481)(D:\my_heart_note\tomcat\笔记\Tomcat.assets\1570256645950.png)] 主要看下面几个:都是标准的容器Valve实现类 一个Pipeline里可以有多个Valve(每个容器有自己的标准Valve实现,也可以在server.xml中自己配置) [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oOigvM5h-1570847392482)(D:\my_heart_note\tomcat\笔记\Tomcat.assets\1570256760112.png)] 最后回到上面的valve.invoke()处理方法 进入到的是StandardEngineValve#invoke() 很简单的逻辑:就是通过request拿到下一个子容器host,然后再调用host.pipeline的valve,执行其invoke()方法 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4pDQ1e2y-1570847392483)(D:\my_heart_note\tomcat\笔记\Tomcat.assets\1570256852492.png)] 然后进入的是StandardContext#invoke()方法(不允许直接访问Web-INF和META-INF里的资源) [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JXVpLRII-1570847392485)(D:\my_heart_note\tomcat\笔记\Tomcat.assets\1570256919478.png)] 差点分析少了一步很关键的,因为到了context这里,下一步就是wrapper了(而wrapper已经和指定的Servlet绑定在一起了),那么就需要在context这个组件中选择合适的servlet去处理这个request请求 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wEyW5oa5-1570847392486)(D:\my_heart_note\tomcat\笔记\Tomcat.assets\1570257702227.png)] 在StandardWrapper#invoke()方法里:调用了FilterChain#doFilter()执行过滤器过滤,让我们一起来看下吧。 所谓责任链设计模式: 无非就是给定一组任务,要逐个按顺序的完成这些任务。只有把所有的任务都完成了,才能做下面的逻辑。 否则提早退出。(通过 在chain调用类里维护一个index索引,这个用来判断任务完成的数量) Tomcat的源码,filterChain.doFilter()的调用是在StandardWrapperValve#invoke()方法里面(也就是在Servlet执行之前)。 按照您的if(index == size){return}的话是不能说明实际的拦截效果的。 更合理的应该是按照源码的 if(pos < n){ //xxxxx filter.doFilter() 如果在这里递归回来了,就要退出(说明有的filter还没有执行,就退出了) return; } //而只有所有的filter执行完,pos==n,才能到达这里 执行Servlet#service(req,resp) (Mapper组件开始发挥作用) 在CoyoteAdaptor中的postParseRequest方法 postParseRequest方法的执行步骤如下: 参考链接:https://www.cnblogs.com/jiaan-geng/p/4894832.html 最后,进入到最后一个子容器:Wrapper(封装Servlet的东西) [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qTBbytjU-1570847392487)(D:\my_heart_note\tomcat\笔记\Tomcat.assets\1570257038966.png)] 进入StandardWrapper#invoke()方法 然后通过wrapper.allocate()尝试获取servlet [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tCYvpwbh-1570847392489)(D:\my_heart_note\tomcat\笔记\Tomcat.assets\1570257147362.png)] [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-27YEKK24-1570847392490)(D:\my_heart_note\tomcat\笔记\Tomcat.assets\1570257302915.png)] 说明了Servlet在这种模式下,一般来说都属于单例的(返回the same instance) [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VXM2k3yp-1570847392491)(D:\my_heart_note\tomcat\笔记\Tomcat.assets\1570257370316.png)] DoubleCheck实现的单例方法,创建Servlet,然后initServlet(instance)初始化 接下来,我们回到allocate()返回后Servlet后,然后经过filterChain去完成指定的过滤 最终到达Servlet的service方法 然后调用实现类的doPost()或doGet() [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SGM4HaU7-1570847392492)(D:\my_heart_note\tomcat\笔记\Tomcat.assets\1570496907062.png)] [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-J9q7DnDx-1570847392493)(D:\my_heart_note\tomcat\笔记\Tomcat.assets\1570281441128.png)] 略(普通的登陆页面即可),成功后跳转聊天室页面 (关于跳转到chat页面的,可以做个拦截器,或者叫过滤器,优化一下) 前端就用JS,创建WebSocket对象,对服务器发起WebSocket请求(ws://) 然后写几个关于WebSocket的生命周期函数:如onOpen()的处理函数 新建一个class:主要是处理WebSocket请求的类ChatSocket,继承javax.websocket.Endpoint类 (或者直接用@ServerEndpoint注解) 记录几个成员: Session session (这个是这次WebSocket请求的session会话),主要用于提升 这个变量的作用域 (重写的onOpen()方法中就有,不用担心这个) static Map 这样就相当于每个人都能知道别的会话信息,从而能够实现广播功能。 (静态的,共享) 当前登录的用户数(原子整形类) (主要方法:通过定好的几个注解把,@xxxx之类的) 然后写几个生命周期的处理方法:拿到服务端的数据,然后渲染到页面上 执行流程: 登陆用户数+1 获取此次WebSocket连接会话的 用户的HttpSession(注意:不是Endpoint里的session,session直接从参数里拿就可以了) HttpSession需要另外创建一个类getHttpSessionConfigurator,然后继承ServerEndpointConfig.Configurator(内部类) 用于getHttpSession的 重写他的modifyHandshake(ServerEndpointConfig config, request, response)方法 然后通过这个session = (HttpSession)request.getHttpSession(); 强转后拿到HttpSession 然后存放进这个ServerEndpointConfig config中的userProperties(这是个Map)中 config.getUserProperties().put(HttpSession.class.getName(),httpSession); 反正就是建立key-value记录下这个httpSession, 然后我们就可以在ChatSocket类中通过 重写那个onOpen(Session session, EndpointConfig config)方法。 哈哈,这个config就是我们刚刚的那个类getHttpSessionConfigurator中就存进config里的userProperties中了。 我们直接拿就可以了 (HttpSession)config.getUserProperties().get(HttpSession.class.getName()); 通过这个key就可以拿到Endpoint Socket连接内部处理时 预存的HttpSession了 记录进onlineUsers 映射表中 先判断httpSession里的username是否为空。 然后onlineUsers.put(httpSession,this) 广播信息(因为是登陆,业务要做到每个人登陆,都要发送一条广播:xxx登陆了) 拿到当前聊天室所有的用户 通过遍历map的keySet,然后拿到每个用户username,然后组装成一个String, 让前端去组装(形成好友列表的信息:也就是可选择的聊天对象) 组装信息(形式为JSON格式) {“data”:“当前登录的所有用户,用,分割”,“toName”:"",“fromName”:"",type:“user”} 这里可以封装一个工具类,用于组装服务端的响应数据 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cFiloZYB-1570847392495)(D:\my_heart_note\tomcat\笔记\Tomcat.assets\1570284523356.png)] DATA:发送数据 Type:发送类型(单播,还是广播) From_Name:发送方名字 TO_Name:接收方名字 以一个Map的形式组装起来,然后转换为JSON字符串: 形成messgae 广播信息(告知大家爷来啦!) 遍历onlineUsers这个map,然后拿到他们里面的每个ChatSocket.session.getBasicRemote() 然后就可以发送单行数据sendText(messgae) 在onMessage(String msg,Session session)方法上用上注解 解析前端传过来的数据msg (这里规定为前后端都是传JSON数据) —用JSON.parseObject(msg,Map.class) 利用fastJson包把封装过来的参数对象,解析为Map映射格式 组装信息(利用自己写的MessageUtil),封装成前后端规定好的 JSON字符串格式(也是Map转换过来的) 然后通过判断接收方 msg.toName判断对象是广播还是单播 广播的话,则遍历所有的onlineUsers,然后一一发送这次请求(注意这里,要排除掉发送给自己的,不然会与前端重复渲染) 单播,则判断是否在线,不在线直接退出这次处理。 在线则遍历onlineUsers,然后根据username找到指定的WebSocket,然后直接发送数据 需求1:按照现实的QQ聊天软件,自己发送的消息渲染到右边,别人发给自己的渲染到左边。 需求2:想要实现,每次渲染到聊天框,都是一个组件(容器)这样的效果。 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YIe8oLiK-1570847392496)(D:\my_heart_note\tomcat\笔记\Tomcat.assets\1570416645819.png)] 简单说下 在JQ中想要动态渲染这个,需要创建HTML元素,然后append()进去 (append会导致一堆HTML拼接代码 在JS中) 而Vue好像也没有很好的解决办法,除了用组件 假如我想加个时间显示的需求(每条信息:名字的右边都要显示一下时间) 无非就是加数据的交互上,多了一个时间字段,那么要改的地方就是时间字段, 以及组装信息类 以及界面的数据渲染 JS时间格式化:https://blog.csdn.net/qq_33242126/article/details/79279322 (new Date()添加函数Format原型) 一定要加上onError,不然前端直接关闭会话,会报错 获取HttpSession 目录结构 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zwEtgoti-1570847392497)(D:\my_heart_note\tomcat\笔记\Tomcat.assets\1570847350165.png)]
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0modelVersion>
<groupId>org.apache.tomcatgroupId>
<artifactId>apache-tomcat-9.0-srcartifactId>
<version>1.0-SNAPSHOTversion>
<build>
<finalName>Tomcat9.0finalName>
<sourceDirectory>src/main/javasourceDirectory>
<resources>
<resource>
<directory>src/main/javadirectory>
resource>
resources>
<plugins>
<plugin>
<groupId>org.apache.maven.pluginsgroupId>
<artifactId>maven-compiler-pluginartifactId>
<version>2.3version>
<configuration>
<encoding>UTF-8encoding>
<source>1.8source>
<target>1.8target>
configuration>
plugin>
plugins>
build>
<dependencies>
<dependency>
<groupId>junitgroupId>
<artifactId>junitartifactId>
<version>4.12version>
<scope>testscope>
dependency>
<dependency>
<groupId>org.easymockgroupId>
<artifactId>easymockartifactId>
<version>3.4version>
dependency>
<dependency>
<groupId>antgroupId>
<artifactId>antartifactId>
<version>1.7.0version>
dependency>
<dependency>
<groupId>wsdl4jgroupId>
<artifactId>wsdl4jartifactId>
<version>1.6.2version>
dependency>
<dependency>
<groupId>javax.xmlgroupId>
<artifactId>jaxrpcartifactId>
<version>1.1version>
dependency>
<dependency>
<groupId>org.eclipse.jdt.core.compilergroupId>
<artifactId>ecjartifactId>
<version>4.6.1version>
dependency>
dependencies>
project>
CompilerOptions.VERSION_1_9
来判断java虚拟机版本,里面涉及1.9版本的jvm,我机器上识别不了。java/org/apache/jasper/compiler/JDTCompiler.java
类中把上述代码找出,删除即可,总共有3处。8. 为什么用源码启动Tomcat 9的时候,控制台输出的中文全部都是乱码
* <p>
* The implementation of a {
@code PropertyResourceBundle} subclass must be
* thread-safe if it's simultaneously used by multiple threads. The default
* implementations of the non-abstract methods in this class are thread-safe.
*
* <p>
* <strong>Note:</strong> PropertyResourceBundle can be constructed either
* from an InputStream or a Reader, which represents a property file.
* Constructing a PropertyResourceBundle instance from an InputStream requires
* that the input stream be encoded in ISO-8859-1. In that case, characters
* that cannot be represented in ISO-8859-1 encoding must be represented by Unicode Escapes
* as defined in section 3.3 of
* <cite>The Java™ Language Specification</cite>
* whereas the other constructor which takes a Reader does not have that limitation.
9. Tomcat整体架构
9.1 Http工作原理
9.2 Servlet容器工作流程
9.3 Tomcat整体架构-两大组件
9.4 Connector组件的IO模型
HTTP/1.1
AJP(Apache服务器)
HTTP/2.0
NIO(非阻塞IO)
NIO2.0(异步IO,JDK7.0后支持)
APR(C/C++网络通讯库)
1.同步与异步
同步和异步关注的是消息通信机制 (synchronous communication/ asynchronous communication)
所谓同步,就是在发出一个调用时,在没有得到结果之前,该调用就不返回。但是一旦调用返回,就得到返回值了。
换句话说,就是由调用者主动等待这个调用的结果。
你打电话问书店老板有没有《分布式系统》这本书,如果是同步通信机制,书店老板会说,你稍等,”我查一下",然后开始查啊查,等查好了(可能是5秒,也可能是一天)告诉你结果(返回结果)。
而异步通信机制,书店老板直接告诉你我查一下啊,查好了打电话给你,然后直接挂电话了(不返回结果)。然后查好了,他会主动打电话给你。在这里老板通过“回电”这种方式来回调。
阻塞和非阻塞关注的是程序在等待调用结果(消息,返回值)时的状态.
非阻塞调用指在不能立刻得到结果之前,该调用不会阻塞当前线程。
你打电话问书店老板有没有《分布式系统》这本书,你如果是阻塞式调用,你会一直把自己“挂起”,直到得到这本书有没有的结果,如果是非阻塞式调用,你不管老板有没有告诉你,你自己先一边去玩了, 当然你也要偶尔过几分钟check一下老板有没有返回结果。
在这里阻塞与非阻塞与是否同步异步无关。跟老板通过什么方式回答你结果无关。9.5 Connector重要组件
/**
* Thread used to accept new connections and pass them to worker threads.
*/
protected Acceptor<U> acceptor;
protected abstract SocketProcessorBase<S> createSocketProcessor(
SocketWrapperBase<S> socketWrapper, SocketEvent event);
@Override
public final void run() {
synchronized (socketWrapper) {
if (socketWrapper.isClosed()) {
return;
}
doRun();
}
}
// ---------------------------------------------- SocketProcessor Inner Class
/**
* This class is the equivalent of the Worker, but will simply use in an
* external Executor thread pool.
*/
protected class SocketProcessor extends SocketProcessorBase<NioChannel> {
public SocketProcessor(SocketWrapperBase<NioChannel> socketWrapper, SocketEvent event) {
super(socketWrapper, event);
}
@Override
protected void doRun() {
NioChannel socket = socketWrapper.getSocket();
SelectionKey key = socket.getIOChannel().keyFor(socket.getSocketWrapper().getPoller().getSelector());
Poller poller = NioEndpoint.this.poller;
if (poller == null) {
socketWrapper.close();
return;
}
try {
int handshake = -1;
try {
if (key != null) {
if (socket.isHandshakeComplete()) {
// No TLS handshaking required. Let the handler
// process this socket / event combination.
handshake = 0;
} else if (event == SocketEvent.STOP || event == SocketEvent.DISCONNECT ||
event == SocketEvent.ERROR) {
// Unable to complete the TLS handshake. Treat it as
// if the handshake failed.
handshake = -1;
} else {
handshake = socket.handshake(key.isReadable(), key.isWritable());
// The handshake process reads/writes from/to the
// socket. status may therefore be OPEN_WRITE once
// the handshake completes. However, the handshake
// happens when the socket is opened so the status
// must always be OPEN_READ after it completes. It
// is OK to always set this as it is only used if
// the handshake completes.
event = SocketEvent.OPEN_READ;
}
}
} catch (IOException x) {
handshake = -1;
if (log.isDebugEnabled()) log.debug("Error during SSL handshake",x);
} catch (CancelledKeyException ckx) {
handshake = -1;
}
if (handshake == 0) {
SocketState state = SocketState.OPEN;
// Process the request from this socket
if (event == null) {
state = getHandler().process(socketWrapper, SocketEvent.OPEN_READ);
} else {
state = getHandler().process(socketWrapper, event);
}
if (state == SocketState.CLOSED) {
poller.cancelledKey(key, socketWrapper);
}
} else if (handshake == -1 ) {
getHandler().process(socketWrapper, SocketEvent.CONNECT_FAIL);
poller.cancelledKey(key, socketWrapper);
} else if (handshake == SelectionKey.OP_READ){
socketWrapper.registerReadInterest();
} else if (handshake == SelectionKey.OP_WRITE){
socketWrapper.registerWriteInterest();
}
} catch (CancelledKeyException cx) {
poller.cancelledKey(key, socketWrapper);
} catch (VirtualMachineError vme) {
ExceptionUtils.handleThrowable(vme);
} catch (Throwable t) {
log.error(sm.getString("endpoint.processing.fail"), t);
poller.cancelledKey(key, socketWrapper);
} finally {
socketWrapper = null;
event = null;
//return to cache
if (running && !paused && processorCache != null) {
processorCache.push(this);
}
}
}
}
9.5.1 Protocol协议
<Connector port="8080" protocol="HTTP/1.1"
connectionTimeout="20000"
redirectPort="8443" />
/**
* Create (or allocate) and return a Request object suitable for
* specifying the contents of a Request to the responsible Container.
*
* @return a new Servlet request object
*/
public Request createRequest() {
return new Request(this);
}
/**
* Create (or allocate) and return a Response object suitable for
* receiving the contents of a Response from the responsible Container.
*
* @return a new Servlet response object
*/
public Response createResponse() {
if (protocolHandler instanceof AbstractAjpProtocol<?>) {
int packetSize = ((AbstractAjpProtocol<?>) protocolHandler).getPacketSize();
return new Response(packetSize - org.apache.coyote.ajp.Constants.SEND_HEAD_LEN);
} else {
return new Response();
}
}
9.5.2 Mapper组件(MapperListener的初始化)
/**
* Array containing the virtual hosts definitions.
*/
protected Host[] hosts = new Host[0];
/**
* Default host name.
*/
protected String defaultHostName = null;
/**
* Context associated with this wrapper, used for wrapper mapping.
*/
protected Context context = new Context();
protected static abstract class MapElement {
public String name = null;
public Object object = null;
}
protected static final class Host
extends MapElement {
public ContextList contextList = null;
}
protected static final class ContextList {
public Context[] contexts = new Context[0];
public int nesting = 0;
}
protected static final class Context
extends MapElement {
public String path = null;
public String[] welcomeResources = new String[0];
public javax.naming.Context resources = null;
public Wrapper defaultWrapper = null;
public Wrapper[] exactWrappers = new Wrapper[0];
public Wrapper[] wildcardWrappers = new Wrapper[0];
public Wrapper[] extensionWrappers = new Wrapper[0];
public int nesting = 0;
}
protected static class Wrapper
extends MapElement {
public String path = null;
public boolean jspWildCard = false;
}
// Initialize mapper listener
mapperListener.init();
9.6 Catalina容器结构
10. Tomcat服务器配置
10.1 Server.xml
10.1.1 Server
//这个是版本日志监听器(用于输出版本信息,JVM信息,OS操作系统信息等等)
<Listener className="org.apache.catalina.startup.VersionLoggerListener" />
//ARP库(C/C++)的监听器
<Listener className="org.apache.catalina.core.AprLifecycleListener" SSLEngine="on" />
//避免JRE内存泄漏
<Listener className="org.apache.catalina.core.JreMemoryLeakPreventionListener" />
//全局资源命名的监听器
<Listener className="org.apache.catalina.mbeans.GlobalResourcesLifecycleListener" />
//避免ThreadLocal泄露
<Listener className="org.apache.catalina.core.ThreadLocalLeakPreventionListener" />
10.1.2 Service
<Service name="Catalina">
10.1.x Tomcat中的Value是什么东西
/**
* The Pipeline object with which this Container is associated.
*/
protected final Pipeline pipeline = new StandardPipeline(this);
/**
* Standard implementation of the Host interface. Each
* child container must be a Context implementation to process the
* requests directed to a particular web application.
*
* @author Craig R. McClanahan
* @author Remy Maucherat
*/
public class StandardHost extends ContainerBase implements Host {
private static final Log log = LogFactory.getLog(StandardHost.class);
// ----------------------------------------------------------- Constructors
/**
* Create a new StandardHost component with the default basic Valve.
*/
public StandardHost() {
super();
//初始化设置Value
pipeline.setBasic(new StandardHostValve());
}
/**
这个Value实现了StandardHost容器的基础行为
* Valve that implements the default basic behavior for the
*
StandardHost
container implementation.
*/
//只在处理HTTP请求时有效
final class StandardHostValve extends ValveBase {
}
<Engine name="Catalina" defaultHost="localhost">
<Valve className="MyValve0"/>
<Valve className="MyValve1"/>
<Valve className="MyValve2"/>
……
<Host name="localhost" appBase="webapps">
Host>
Engine>
public void addValve(Valve valve) {
// Validate that we can add this Valve
if (valve instanceof Contained)
((Contained) valve).setContainer(this.container);
// Start the new component if necessary
if (getState().isAvailable()) {
if (valve instanceof Lifecycle) {
try {
((Lifecycle) valve).start();
} catch (LifecycleException e) {
log.error(sm.getString("standardPipeline.valve.start"), e);
}
}
}
// Add this Valve to the set associated with this Pipeline
//(比较特殊的插入链表处理:是尾插,但是确保了basic:也就是StandardXXXValue每个容器的默认Value实现一定要在链表的最末尾)
if (first == null) {
first = valve;
valve.setNext(basic);
} else {
Valve current = first;
while (current != null) {
if (current.getNext() == basic) {
current.setNext(valve);
valve.setNext(basic);
break;
}
current = current.getNext();
}
}
container.fireContainerEvent(Container.ADD_VALVE_EVENT, valve);
}
10.1.3 Engine
这个AJP的Engine是用来做Apache + Tomcat的负载均衡时用到的配置(还需要在Engine标签里配置Cluster集群标签)
参考链接:https://www.cnblogs.com/jdonson/archive/2009/12/02/1615331.html
//一般来说,用这个默认的就好
<Engine name="Catalina" defaultHost="localhost">
10.1.4 Host
/**
* @return the value of the deploy on startup flag. If
true
, it indicates
* that this host's child webapps should be discovered and automatically
* deployed at startup time.
*/
@Override
public boolean getDeployOnStartup() {
return this.deployOnStartup;
}
/**
* The application root for this Host.
*/
private String appBase = "webapps";
10.1.5 Context
@Override
public void setWrapperClass(String wrapperClassName) {
this.wrapperClassName = wrapperClassName;
try {
wrapperClass = Class.forName(wrapperClassName);
if (!StandardWrapper.class.isAssignableFrom(wrapperClass)) {
throw new IllegalArgumentException(
sm.getString("standardContext.invalidWrapperClass",
wrapperClassName));
}
} catch (ClassNotFoundException cnfe) {
throw new IllegalArgumentException(cnfe.getMessage());
}
}
request.getRequestDispatcher(url).forward(request,response)
/**
添加新的Servlet mapping(根据配置的url pattern)
* Add a new servlet mapping, replacing any existing mapping for
* the specified pattern.
*
urlPattern
* @param pattern URL pattern to be mapped
这个name参数:就是ServletName
* @param name Name of the corresponding servlet to execute
* @param jspWildCard true if name identifies the JspServlet
* and pattern contains a wildcard; false otherwise
*
* @exception IllegalArgumentException if the specified servlet name
* is not known to this Context
*/
@Override
public void addServletMappingDecoded(String pattern, String name,
boolean jspWildCard) {
// Validate the proposed mapping
if (findChild(name) == null)
throw new IllegalArgumentException
(sm.getString("standardContext.servletMap.name", name));
//调整一下urlPattern
String adjustedPattern = adjustURLPattern(pattern);
//验证是否合法
if (!validateURLPattern(adjustedPattern))
throw new IllegalArgumentException
(sm.getString("standardContext.servletMap.pattern", adjustedPattern));
// Add this mapping to our registered set
//把这个mapping假如到我们的注册的集合中
//要加锁,否则会由于并发而导致的一个url对应着两个Servlet,这样会引发不可想象的错误
synchronized (servletMappingsLock) {
//看下mapping里有没有这条url的映射
String name2 = servletMappings.get(adjustedPattern);
//有的话,就移除掉旧的
if (name2 != null) {
// Don't allow more than one servlet on the same pattern
Wrapper wrapper = (Wrapper) findChild(name2);
wrapper.removeMapping(adjustedPattern);
}
//然后添加新的
servletMappings.put(adjustedPattern, name);
}
//wrapper里面也有个mapping(是ArrayList
/**
* @return a RequestDispatcher that wraps the resource at the specified
* path, which may be interpreted as relative to the current request path.
*
* @param path Path of the resource to be wrapped
*/
@Override
public RequestDispatcher getRequestDispatcher(String path) {
Context context = getContext();
if (context == null) {
return null;
}
if (path == null) {
return null;
}
int fragmentPos = path.indexOf('#');
if (fragmentPos > -1) {
log.warn(sm.getString("request.fragmentInDispatchPath", path));
path = path.substring(0, fragmentPos);
}
// If the path is already context-relative, just pass it through
if (path.startsWith("/")) {
return context.getServletContext().getRequestDispatcher(path);
}
/*
* Relative to what, exactly?
*
* From the Servlet 4.0 Javadoc:
* - The pathname specified may be relative, although it cannot extend
* outside the current servlet context.
* - If it is relative, it must be relative against the current servlet
*
* From Section 9.1 of the spec:
* - The servlet container uses information in the request object to
* transform the given relative path against the current servlet to a
* complete path.
*
* It is undefined whether the requestURI is used or whether servletPath
* and pathInfo are used. Given that the RequestURI includes the
* contextPath (and extracting that is messy) , using the servletPath and
* pathInfo looks to be the more reasonable choice.
*/
// Convert a request-relative path to a context-relative one
String servletPath = (String) getAttribute(
RequestDispatcher.INCLUDE_SERVLET_PATH);
if (servletPath == null) {
servletPath = getServletPath();
}
// Add the path info, if there is any
String pathInfo = getPathInfo();
String requestPath = null;
if (pathInfo == null) {
requestPath = servletPath;
} else {
requestPath = servletPath + pathInfo;
}
int pos = requestPath.lastIndexOf('/');
String relative = null;
if (context.getDispatchersUseEncodedPaths()) {
if (pos >= 0) {
relative = URLEncoder.DEFAULT.encode(
requestPath.substring(0, pos + 1), StandardCharsets.UTF_8) + path;
} else {
relative = URLEncoder.DEFAULT.encode(requestPath, StandardCharsets.UTF_8) + path;
}
} else {
if (pos >= 0) {
relative = requestPath.substring(0, pos + 1) + path;
} else {
relative = requestPath + path;
}
}
return context.getServletContext().getRequestDispatcher(relative);
}
/**
* Internal class used as thread-local storage when doing path
* mapping during dispatch.
*/
private static final class DispatchData {
public MessageBytes uriMB;
public MappingData mappingData;
public DispatchData() {
uriMB = MessageBytes.newInstance();
CharChunk uriCC = uriMB.getCharChunk();
uriCC.setLimit(-1);
mappingData = new MappingData();
}
}
@Override
public RequestDispatcher getRequestDispatcher(final String path) {
// Validate the path argument
if (path == null) {
return null;
}
if (!path.startsWith("/")) {
throw new IllegalArgumentException(
sm.getString("applicationContext.requestDispatcher.iae", path));
}
// Same processing order as InputBuffer / CoyoteAdapter
// First remove query string
String uri;
String queryString;
int pos = path.indexOf('?');
if (pos >= 0) {
uri = path.substring(0, pos);
queryString = path.substring(pos + 1);
} else {
uri = path;
queryString = null;
}
// Remove path parameters
String uriNoParams = stripPathParams(uri);
// Then normalize
String normalizedUri = RequestUtil.normalize(uriNoParams);
if (normalizedUri == null) {
return null;
}
// Mapping is against the normalized uri
if (getContext().getDispatchersUseEncodedPaths()) {
// Decode
String decodedUri = UDecoder.URLDecode(normalizedUri);
// Security check to catch attempts to encode /../ sequences
normalizedUri = RequestUtil.normalize(decodedUri);
if (!decodedUri.equals(normalizedUri)) {
getContext().getLogger().warn(
sm.getString("applicationContext.illegalDispatchPath", path),
new IllegalArgumentException());
return null;
}
// URI needs to include the context path
uri = URLEncoder.DEFAULT.encode(getContextPath(), StandardCharsets.UTF_8) + uri;
} else {
// uri is passed to the constructor for ApplicationDispatcher and is
// ultimately used as the value for getRequestURI() which returns
// encoded values. Therefore, since the value passed in for path
// was decoded, encode uri here.
uri = URLEncoder.DEFAULT.encode(getContextPath() + uri, StandardCharsets.UTF_8);
}
// Use the thread local URI and mapping data
//ThreadLocal
return new ApplicationDispatcher(wrapper, uri, wrapperPath, pathInfo,
queryString, mapping, null);
//解释了Dispatcher为什么不能跨越Context访问资源,因为是通过本Context来定位到wrapper子容器,去访问资源的。
10.1.6 Wrapper
org.apache.catalina.servlets.DefaultServlet
org.apache.jasper.servlet.JspServlet
<servlet>
<servlet-name>defaultservlet-name>
<servlet-class>org.apache.catalina.servlets.DefaultServletservlet-class>
<init-param>
<param-name>debugparam-name>
<param-value>0param-value>
init-param>
<init-param>
<param-name>listingsparam-name>
<param-value>falseparam-value>
init-param>
<load-on-startup>1load-on-startup>
servlet>
<servlet>
<servlet-name>jspservlet-name>
<servlet-class>org.apache.jasper.servlet.JspServletservlet-class>
<init-param>
<param-name>forkparam-name>
<param-value>falseparam-value>
init-param>
<init-param>
<param-name>xpoweredByparam-name>
<param-value>falseparam-value>
init-param>
<load-on-startup>3load-on-startup>
servlet>
/**
* @return the fully qualified servlet class name for this servlet.
*/
public String getServletClass();
/**
* Set the fully qualified servlet class name for this servlet.
*
* @param servletClass Servlet class name
*/
public void setServletClass(String servletClass);
10.1.7 Connector
10.2 tomcat-users.xml
11. Tomcat的启动流程
set "CURRENT_DIR=%cd%"
## 查看系统变量中是否有CATALINA_HOME(相当于Tomcat安装目录)
if not "%CATALINA_HOME%" == "" goto gotHome
##没有的话,默认设置为本文件夹
set "CATALINA_HOME=%CURRENT_DIR%"
echo %CATALINA_HOME%
if exist "%CATALINA_HOME%\bin\catalina.bat" goto okHome
cd ..
set "CATALINA_HOME=%cd%"
cd "%CURRENT_DIR%"
:gotHome
if exist "%CATALINA_HOME%\bin\catalina.bat" goto okHome
echo %CATALINA_HOME%
echo The CATALINA_HOME environment variable is not defined correctly
echo This environment variable is needed to run this program
goto end
:okHome
##设置可执行文件为catalina.bat
set "EXECUTABLE=%CATALINA_HOME%\bin\catalina.bat"
##省略命令行参数
##执行catalina.bat
call "%EXECUTABLE%" start %CMD_LINE_ARGS%
:end
rem ----- Execute The Requested Command ---------------------------------------
echo Using CATALINA_BASE: "%CATALINA_BASE%"
echo Using CATALINA_HOME: "%CATALINA_HOME%"
echo Using CATALINA_TMPDIR: "%CATALINA_TMPDIR%"
if ""%1"" == ""debug"" goto use_jdk
echo Using JRE_HOME: "%JRE_HOME%"
goto java_dir_displayed
:use_jdk
echo Using JAVA_HOME: "%JAVA_HOME%"
:java_dir_displayed
echo Using CLASSPATH: "%CLASSPATH%"
set _EXECJAVA=%_RUNJAVA%
#设置主类为Bootstrap,从这里开始整个Java程序,启动Tomcat
set MAINCLASS=org.apache.catalina.startup.Bootstrap
12. Tomcat启动流程源码解析
/**
* Main method and entry point when starting Tomcat via the provided
* scripts.
*
* @param args Command line arguments to be processed
*/
public static void main(String args[]) {
synchronized (daemonLock) {
if (daemon == null) {
// Don't set daemon until init() has completed
Bootstrap bootstrap = new Bootstrap();
try {
//初始化bootstrap
bootstrap.init();
} catch (Throwable t) {
handleThrowable(t);
t.printStackTrace();
return;
}
//damon 就是bootstrap了
daemon = bootstrap;
} else {
// When running as a service the call to stop will be on a new
// thread so make sure the correct class loader is used to
// prevent a range of class not found exceptions.
Thread.currentThread().setContextClassLoader(daemon.catalinaLoader);
}
}
try {
String command = "start";
if (args.length > 0) {
command = args[args.length - 1];
}
if (command.equals("startd")) {
args[args.length - 1] = "start";
daemon.load(args);
daemon.start();
} else if (command.equals("stopd")) {
args[args.length - 1] = "stop";
daemon.stop();
} else if (command.equals("start")) {
//看这里:我们的starup.bat里的call %Executable% start命令
//daemon就是bootstrap实例
daemon.setAwait(true);
//load方法--->内部调用catalina的load方法
daemon.load(args);
//start方法
daemon.start();
if (null == daemon.getServer()) {
System.exit(1);
}
} else if (command.equals("stop")) {
daemon.stopServer(args);
} else if (command.equals("configtest")) {
daemon.load(args);
if (null == daemon.getServer()) {
System.exit(1);
}
System.exit(0);
} else {
log.warn("Bootstrap: command \"" + command + "\" does not exist.");
}
} catch (Throwable t) {
// Unwrap the Exception for clearer error reporting
if (t instanceof InvocationTargetException &&
t.getCause() != null) {
t = t.getCause();
}
handleThrowable(t);
t.printStackTrace();
System.exit(1);
}
}
/**
* Initialize daemon.
* @throws Exception Fatal initialization error
*/
public void init() throws Exception {
//初始化类加载器
initClassLoaders();
Thread.currentThread().setContextClassLoader(catalinaLoader);
SecurityClassLoad.securityClassLoad(catalinaLoader);
// Load our startup class and call its process() method
if (log.isDebugEnabled())
log.debug("Loading startup class");
//通过catalinaLoader类加载器来加载Catalina类
Class<?> startupClass =
catalinaLoader.loadClass("org.apache.catalina.startup.Catalina");
//并且通过反射技术,用class对象调用其构造器去创建实例
Object startupInstance = startupClass.getConstructor().newInstance();
// Set the shared extensions class loader
if (log.isDebugEnabled())
log.debug("Setting startup class properties");
//通过反射技术,执行setParentClassLoader方法
//(下面都是与Tomcat独有的类加载器相关的代码)
String methodName = "setParentClassLoader";
Class<?> paramTypes[] = new Class[1];
paramTypes[0] = Class.forName("java.lang.ClassLoader");
Object paramValues[] = new Object[1];
paramValues[0] = sharedLoader;
Method method =
startupInstance.getClass().getMethod(methodName, paramTypes);
method.invoke(startupInstance, paramValues);
//设置startupInstance为catalinaDaemon
catalinaDaemon = startupInstance;
}
/**
* Load daemon.
*/
private void load(String[] arguments) throws Exception {
// Call the load() method
String methodName = "load";
Object param[];
Class<?> paramTypes[];
if (arguments==null || arguments.length==0) {
paramTypes = null;
param = null;
} else {
paramTypes = new Class[1];
paramTypes[0] = arguments.getClass();
param = new Object[1];
param[0] = arguments;
}
Method method =
catalinaDaemon.getClass().getMethod(methodName, paramTypes);
if (log.isDebugEnabled()) {
log.debug("Calling startup class " + method);
}
//param是命令行参数
//这是在调用catalina的load()方法
method.invoke(catalinaDaemon, param);
}
// Configure the actions we will be using
digester.addObjectCreate("Server",
"org.apache.catalina.core.StandardServer",
"className");
// Start the new server
try {
//初始化Server组件
getServer().init(); //多态的调用
} catch (LifecycleException e) {
if (Boolean.getBoolean("org.apache.catalina.startup.EXIT_ON_INIT_FAILURE")) {
throw new java.lang.Error(e);
} else {
log.error(sm.getString("catalina.initError"), e);
}
}
@Override
public final synchronized void init() throws LifecycleException {
if (!state.equals(LifecycleState.NEW)) {
invalidTransition(Lifecycle.BEFORE_INIT_EVENT);
}
try {
setStateInternal(LifecycleState.INITIALIZING, null, false);
//这个方法就是模板方法的精髓(一套模板算法的步骤都是固定的,但是其中具体的逻辑交由子类自己实现)
initInternal();
setStateInternal(LifecycleState.INITIALIZED, null, false);
} catch (Throwable t) {
handleSubClassException(t, "lifecycleBase.initFail", toString());
}
}
//这是个让子类自己去实现的方法(具体逻辑交由子类自己实现)---所以定义为抽象方法
/**
* Sub-classes implement this method to perform any instance initialisation
* required.
*
* @throws LifecycleException If the initialisation fails
*/
protected abstract void initInternal() throws LifecycleException;
/**
* Invoke a pre-startup initialization. This is used to allow connectors
* to bind to restricted ports under Unix operating environments.
*/
@Override
protected void initInternal() throws LifecycleException {
super.initInternal();
//省略。。。。。。。。
// Initialize our defined Services(因为一个Server可以有多个Service)
//初始化下一个子组件:Service
for (int i = 0; i < services.length; i++) {
services[i].init();
}
/**
* Invoke a pre-startup initialization. This is used to allow connectors
* to bind to restricted ports under Unix operating environments.
*/
@Override
protected void initInternal() throws LifecycleException {
super.initInternal();
//初始化Engine
if (engine != null) {
engine.init();
}
// Initialize any Executors
//初始化Executor (注意:这个初始化顺序其实和Server.xml里严格定义的分层结构是密切关联的)
for (Executor executor : findExecutors()) {
if (executor instanceof JmxEnabled) {
((JmxEnabled) executor).setDomain(getDomain());
}
executor.init();
}
// Initialize mapper listener
//这个mapper是 host主机与URL相关联的;跟虚拟目录相关的映射;还有默认主机localhost的配置
mapperListener.init();
// Initialize our defined Connectors
//初始化Connector
synchronized (connectorsLock) {
for (Connector connector : connectors) {
connector.init();
}
}
}
//适配器
adapter = new CoyoteAdapter(this);
//protocolHandler连接器的组件初始化
protocolHandler.init();
//endpoint的初始化
endpoint.init();
public final void init() throws Exception {
if (bindOnInit) {
bindWithCleanup();
bindState = BindState.BOUND_ON_INIT;
}
public abstract void bind() throws Exception;
/**
* Initialize the endpoint.
*/
@Override
public void bind() throws Exception {
initServerSocket();
setStopLatch(new CountDownLatch(1));
// Initialize SSL if needed
initialiseSsl();
selectorPool.open(getName());
}
// Separated out to make it easier for folks that extend NioEndpoint to
// implement custom [server]sockets
protected void initServerSocket() throws Exception {
if (!getUseInheritedChannel()) {
//创建ServerSocketChannel(通过静态方法open())
serverSock = ServerSocketChannel.open();
socketProperties.setProperties(serverSock.socket());
//创建套接字InetSocketAddress (IP + PORT端口)
InetSocketAddress addr = new InetSocketAddress(getAddress(), getPortWithOffset());
//绑定到这个套接字,然后监听
serverSock.socket().bind(addr,getAcceptCount());
} else {
// Retrieve the channel provided by the OS
Channel ic = System.inheritedChannel();
if (ic instanceof ServerSocketChannel) {
serverSock = (ServerSocketChannel) ic;
}
if (serverSock == null) {
throw new IllegalArgumentException(sm.getString("endpoint.init.bind.inherited"));
}
}
serverSock.configureBlocking(true); //mimic APR behavior
}
/**
* Start the Catalina daemon.
* @throws Exception Fatal start error
*/
public void start() throws Exception {
if (catalinaDaemon == null) {
init();
}
//用反射是因为catalinaDaemon是Object类型的(根本就没有指定具体类型)
Method method = catalinaDaemon.getClass().getMethod("start", (Class [])null);
method.invoke(catalinaDaemon, (Object [])null);
}
/**
* Obtain the registered application lifecycle listeners.
*
* @return An array containing the application lifecycle listener instances
* for this web application in the order they were specified in the
* web application deployment descriptor
*/
public Object[] getApplicationLifecycleListeners();
/**
* Configure the set of instantiated application event listeners
* for this Context.
* @return
true
if all listeners wre
* initialized successfully, or false
otherwise.
*/
public boolean listenerStart() {
if (log.isDebugEnabled())
log.debug("Configuring application event listeners");
// Instantiate the required listeners
String listeners[] = findApplicationListeners();
Object results[] = new Object[listeners.length];
boolean ok = true;
for (int i = 0; i < results.length; i++) {
String listener = listeners[i];
results[i] = getInstanceManager().newInstance(listener);
}
// Sort listeners in two arrays
List<Object> eventListeners = new ArrayList<>();
List<Object> lifecycleListeners = new ArrayList<>();
for (int i = 0; i < results.length; i++) {
if ((results[i] instanceof ServletContextAttributeListener)
|| (results[i] instanceof ServletRequestAttributeListener)
|| (results[i] instanceof ServletRequestListener)
|| (results[i] instanceof HttpSessionIdListener)
|| (results[i] instanceof HttpSessionAttributeListener)) {
eventListeners.add(results[i]);
}
if ((results[i] instanceof ServletContextListener)
|| (results[i] instanceof HttpSessionListener)) {
lifecycleListeners.add(results[i]);
}
}
// Listener instances may have been added directly to this Context by
// ServletContextInitializers and other code via the pluggability APIs.
// Put them these listeners after the ones defined in web.xml and/or
// annotations then overwrite the list of instances with the new, full
// list.
for (Object eventListener: getApplicationEventListeners()) {
eventListeners.add(eventListener);
}
setApplicationEventListeners(eventListeners.toArray());
for (Object lifecycleListener: getApplicationLifecycleListeners()) {
lifecycleListeners.add(lifecycleListener);
if (lifecycleListener instanceof ServletContextListener) {
noPluggabilityListeners.add(lifecycleListener);
}
}
setApplicationLifecycleListeners(lifecycleListeners.toArray());
// Send application start events
if (getLogger().isDebugEnabled())
getLogger().debug("Sending application start events");
// Ensure context is not null
getServletContext();
context.setNewServletContextListenerAllowed(false);
Object instances[] = getApplicationLifecycleListeners();
if (instances == null || instances.length == 0) {
return ok;
}
ServletContextEvent event = new ServletContextEvent(getServletContext());
ServletContextEvent tldEvent = null;
if (noPluggabilityListeners.size() > 0) {
noPluggabilityServletContext = new NoPluggabilityServletContext(getServletContext());
tldEvent = new ServletContextEvent(noPluggabilityServletContext);
}
for (int i = 0; i < instances.length; i++) {
if (!(instances[i] instanceof ServletContextListener))
continue;
ServletContextListener listener =
(ServletContextListener) instances[i];
try {
fireContainerEvent("beforeContextInitialized", listener);
if (noPluggabilityListeners.contains(listener)) {
listener.contextInitialized(tldEvent);
} else {
//在这里调用监听器的初始化方法
listener.contextInitialized(event);
}
fireContainerEvent("afterContextInitialized", listener);
} catch (Throwable t) {
ExceptionUtils.handleThrowable(t);
fireContainerEvent("afterContextInitialized", listener);
getLogger().error
(sm.getString("standardContext.listenerStart",
instances[i].getClass().getName()), t);
ok = false;
}
}
return ok;
}
// Configure and call application event listeners
if (ok) {
if (!listenerStart()) {
//这里调用
log.error(sm.getString("standardContext.listenerFail"));
ok = false;
}
}
/**
* Start the NIO endpoint, creating acceptor, poller threads.
*/
@Override
public void startInternal() throws Exception {
if (!running) {
running = true;
paused = false;
if (socketProperties.getProcessorCache() != 0) {
processorCache = new SynchronizedStack<>(SynchronizedStack.DEFAULT_SIZE,
socketProperties.getProcessorCache());
}
if (socketProperties.getEventCache() != 0) {
eventCache = new SynchronizedStack<>(SynchronizedStack.DEFAULT_SIZE,
socketProperties.getEventCache());
}
if (socketProperties.getBufferPool() != 0) {
nioChannels = new SynchronizedStack<>(SynchronizedStack.DEFAULT_SIZE,
socketProperties.getBufferPool());
}
// Create worker collection
if (getExecutor() == null) {
createExecutor();
}
initializeConnectionLatch();
// Start poller thread
poller = new Poller();
Thread pollerThread = new Thread(poller, getName() + "-ClientPoller");
pollerThread.setPriority(threadPriority);
pollerThread.setDaemon(true);
pollerThread.start();
//主要看这句话,开启Acceptor线程;之前说过Endpoint实现类内部还有一个叫Accptor的,
//是用来接收Socket请求的!
startAcceptorThread();
}
}
//注意:Tomcat9和Tomcat8又不一样了,Tomcat8这里创建了一堆Acceptor
protected void startAcceptorThread() {
acceptor = new Acceptor<>(this);
String threadName = getName() + "-Acceptor";
acceptor.setThreadName(threadName);
Thread t = new Thread(acceptor, threadName);
t.setPriority(getAcceptorThreadPriority());
t.setDaemon(getDaemon());
t.start();
}
@Override
public void run() {
int errorDelay = 0;
// Loop until we receive a shutdown command
while (endpoint.isRunning()) {
// Loop if endpoint is paused
while (endpoint.isPaused() && endpoint.isRunning()) {
state = AcceptorState.PAUSED;
try {
Thread.sleep(50);
} catch (InterruptedException e) {
// Ignore
}
}
if (!endpoint.isRunning()) {
break;
}
state = AcceptorState.RUNNING;
try {
//if we have reached max connections, wait
endpoint.countUpOrAwaitConnection();
// Endpoint might have been paused while waiting for latch
// If that is the case, don't accept new connections
if (endpoint.isPaused()) {
continue;
}
U socket = null;
try {
// Accept the next incoming connection from the server
// socket
//就是这句:用来accpept等待Socket请求
socket = endpoint.serverSocketAccept();
} catch (Exception ioe) {
// We didn't get a socket
endpoint.countDownConnection();
if (endpoint.isRunning()) {
// Introduce delay if necessary
errorDelay = handleExceptionWithDelay(errorDelay);
// re-throw
throw ioe;
} else {
break;
}
}
// Successful accept, reset the error delay
errorDelay = 0;
// Configure the socket
if (endpoint.isRunning() && !endpoint.isPaused()) {
// setSocketOptions() will hand the socket off to
// an appropriate processor if successful
if (!endpoint.setSocketOptions(socket)) {
endpoint.closeSocket(socket);
}
} else {
endpoint.destroySocket(socket);
}
} catch (Throwable t) {
ExceptionUtils.handleThrowable(t);
String msg = sm.getString("endpoint.accept.fail");
// APR specific.
// Could push this down but not sure it is worth the trouble.
if (t instanceof Error) {
Error e = (Error) t;
if (e.getError() == 233) {
// Not an error on HP-UX so log as a warning
// so it can be filtered out on that platform
// See bug 50273
log.warn(msg, t);
} else {
log.error(msg, t);
}
} else {
log.error(msg, t);
}
}
}
state = AcceptorState.ENDED;
}
13. Tomcat请求处理流程源码解析
13.1 NioEndpoint
13.1.1 Acceptor
13.1.2 Poller
13.1.3 Processor
13.1.4 Adapter()
13.1.5 容器链 pipeline+valve责任链
FilterChain所采用的责任链设计模式
Tomcat源码之FilterChain
@Override
public final void invoke(Request request, Response response)
throws IOException, ServletException {
requestCount.incrementAndGet();
StandardWrapper wrapper = (StandardWrapper) getContainer();
Servlet servlet = null;
Context context = (Context) wrapper.getParent();
// Create the filter chain for this request
ApplicationFilterChain filterChain =
ApplicationFilterFactory.createFilterChain(request, wrapper, servlet);
// Call the filter chain for this request
// NOTE: This also calls the servlet's service() method
Container container = this.container;
if ((servlet != null) && (filterChain != null)) {
if (request.isAsyncDispatching()) {
request.getAsyncContextInternal().doInternalDispatch();
} else {
//就是在这里:filterChain.doFilter(req,resp)执行
filterChain.doFilter
(request.getRequest(), response.getResponse());
}
}
}
}
}
private void internalDoFilter(ServletRequest request,
ServletResponse response)
throws IOException, ServletException {
// Call the next filter if there is one
if (pos < n) {
ApplicationFilterConfig filterConfig = filters[pos++];
try {
Filter filter = filterConfig.getFilter();
if (request.isAsyncSupported() && "false".equalsIgnoreCase(
filterConfig.getFilterDef().getAsyncSupported())) {
request.setAttribute(Globals.ASYNC_SUPPORTED_ATTR, Boolean.FALSE);
}
if( Globals.IS_SECURITY_ENABLED ) {
final ServletRequest req = request;
final ServletResponse res = response;
Principal principal =
((HttpServletRequest) req).getUserPrincipal();
Object[] args = new Object[]{
req, res, this};
SecurityUtil.doAsPrivilege ("doFilter", filter, classType, args, principal);
} else {
filter.doFilter(request, response, this);
}
} catch (IOException | ServletException | RuntimeException e) {
throw e;
} catch (Throwable e) {
e = ExceptionUtils.unwrapInvocationTargetException(e);
ExceptionUtils.handleThrowable(e);
throw new ServletException(sm.getString("filterChain.filter"), e);
}
return;
}
// We fell off the end of the chain -- call the servlet instance
// Use potentially wrapped request from this point
if ((request instanceof HttpServletRequest) &&
(response instanceof HttpServletResponse) &&
Globals.IS_SECURITY_ENABLED ) {
final ServletRequest req = request;
final ServletResponse res = response;
Principal principal =
((HttpServletRequest) req).getUserPrincipal();
Object[] args = new Object[]{
req, res};
SecurityUtil.doAsPrivilege("service",
servlet,
classTypeUsedInService,
args,
principal);
} else {
//service()被调用
servlet.service(request, response);
}
14. WebSocket
15. 实例驱动-基于WebSocket的多人实时聊天室
15.1 聊天界面
15.2 登陆界面
15.3 登录逻辑
15.4 WebSocket连接建立
15.4.1 前端
15.4.2 后端
15.5 WebSocket onMessage()的处理流程
15.6 关于前端界面的诸多问题
Date.prototype.Format = function(formatStr) {
var str = formatStr;
var Week = ['日','一','二','三','四','五','六'];
str=str.replace(/yyyy|YYYY/,this.getFullYear());
str=str.replace(/yy|YY/,(this.getYear() % 100)>9?(this.getYear() % 100).toString():'0' + (this.getYear() % 100));
str=str.replace(/MM/,this.getMonth()>9?(this.getMonth()+1).toString():'0' + this.getMonth());
str=str.replace(/M/g,this.getMonth()+1);
str=str.replace(/w|W/g,Week[this.getDay()]);
str=str.replace(/dd|DD/,this.getDate()>9?this.getDate().toString():'0' + this.getDate());
str=str.replace(/d|D/g,this.getDate());
str=str.replace(/hh|HH/,this.getHours()>9?this.getHours().toString():'0' + this.getHours());
str=str.replace(/h|H/g,this.getHours());
str=str.replace(/mm/,this.getMinutes()>9?this.getMinutes().toString():'0' + this.getMinutes());
str=str.replace(/m/g,this.getMinutes());
str=str.replace(/ss|SS/,this.getSeconds()>9?this.getSeconds().toString():'0' + this.getSeconds());
str=str.replace(/s|S/g,this.getSeconds());
return str;
};
15.7 WebSocket的关闭onClose()
//关闭连接
@OnClose
public void onClose(Session session, CloseReason closeReason){
//1. 在线人数-1
onlineUserCount.decrementAndGet();
//2.移除map里的东西 (通过迭代器删除)
Iterator<Map.Entry<HttpSession, ChatWebSocket>> iterator = onlineUsers.entrySet().iterator();
while (iterator.hasNext()){
Map.Entry<HttpSession, ChatWebSocket> entry = iterator.next();
if(entry.getKey() == httpSession){
iterator.remove();
break;
}
}
//3. 关闭会话
try {
session.close(closeReason);
} catch (IOException e) {
e.printStackTrace();
}
//4.重新更新好友列表 ()
String names = getOnlineUserNames();
String respMsg = MessageUtil.getRespMsg(MessageUtil.TYPE_USER, "", "", names);
broadcastUsersList(respMsg);
//5. 系统通知好友下线
broadcastOnOrOffLine(OFF_LINE);
}
@OnError
public void onError(Session session, Throwable throwable){
throwable.printStackTrace();
try {
session.close();
broadcastOnOrOffLine(OFF_LINE);
} catch (IOException e) {
e.printStackTrace();
}
}
15.8 WebSocket聊天室完整代码
@ServerEndpoint(value = "/websocket",configurator = getHttpSessionConfigurator.class)
public class ChatWebSocket {
private static final String ON_LINE = "on";
private static final String OFF_LINE = "off";
private HttpSession httpSession;
private Session session;
private static Map<HttpSession,ChatWebSocket> onlineUsers = new HashMap<>();
private static AtomicInteger onlineUserCount = new AtomicInteger(0);
@OnOpen
public void onOpen(Session session, EndpointConfig config){
this.session = session;
//1. 登陆用户数+1
onlineUserCount.incrementAndGet();
//2. 获取HttpSession
HttpSession hSession = (HttpSession) config.getUserProperties().get(HttpSession.class.getName());
this.httpSession = hSession;
//3. 添加进Map中
onlineUsers.put(hSession,this);
//4. 获取好友(可选择的聊天对象)列表 (在线用户列表)
String names = getOnlineUserNames();
//5. 组装响应信息
String respMsg = MessageUtil.getRespMsg(MessageUtil.TYPE_USER, "", "", names);
//测试用:看看这个ChatWebSocket是单例的还是一个会话,就创建一个
System.out.println("当前登陆用户为:"+httpSession.getAttribute("username")+",Endpoint: "+hashCode());
//6. 广播信息 (更新用户列表 + 广播用户上线的消息)
broadcastUsersList(respMsg);
broadcastOnOrOffLine(ON_LINE);
}
@OnMessage
public void onMessage(String msg,Session session){
// 1.先获取信息 (规定前端也是以JSON字符串格式传过来的)
Map<String,String> map = JSON.parseObject(msg, Map.class);
String fromName = map.get("fromName");
String toName = map.get("toName");
String content = map.get("content");
boolean online = false;
//组装信息
String respMsg = MessageUtil.getRespMsgWithTime(MessageUtil.TYPE_MESSAGE, fromName, toName, content);
//2. 判断信息是广播还是单播 (广播对象是broadcast)
if(toName.equals("broadcast")){
//广播
broadcastAllUser(respMsg);
}else{
//单播
//2.2. 先判断对方是否在线
for(Map.Entry<HttpSession,ChatWebSocket> entry:onlineUsers.entrySet()){
if(entry.getKey().getAttribute("username").equals(toName)){
online = true;
break;
}
}
if(!online){
return;
}
//2.3 在线,找出指定用户,发送信息
for(Map.Entry<HttpSession,ChatWebSocket> entry:onlineUsers.entrySet()){
if(entry.getKey().getAttribute("username").equals(toName)){
try {
entry.getValue().session.getBasicRemote().sendText(respMsg);
} catch (IOException e) {
e.printStackTrace();
}
return;
}
}
}
}
//关闭连接
@OnClose
public void onClose(Session session, CloseReason closeReason){
//1. 在线人数-1
onlineUserCount.decrementAndGet();
//2.移除map里的东西 (通过迭代器删除)
Iterator<Map.Entry<HttpSession, ChatWebSocket>> iterator = onlineUsers.entrySet().iterator();
while (iterator.hasNext()){
Map.Entry<HttpSession, ChatWebSocket> entry = iterator.next();
if(entry.getKey() == httpSession){
iterator.remove();
break;
}
}
//3. 关闭会话
try {
session.close(closeReason);
} catch (IOException e) {
e.printStackTrace();
}
//4.重新更新好友列表 ()
String names = getOnlineUserNames();
String respMsg = MessageUtil.getRespMsg(MessageUtil.TYPE_USER, "", "", names);
broadcastUsersList(respMsg);
//5. 系统通知好友下线
broadcastOnOrOffLine(OFF_LINE);
}
@OnError
public void onError(Session session, Throwable throwable){
throwable.printStackTrace();
try {
session.close();
broadcastOnOrOffLine(OFF_LINE);
} catch (IOException e) {
e.printStackTrace();
}
}
//广播 要群发的信息
private void broadcastAllUser(String respMsg){
for(Map.Entry<HttpSession,ChatWebSocket> entry:onlineUsers.entrySet()){
try {
//不发给自己
if(entry.getKey() != httpSession){
entry.getValue().session.getBasicRemote().sendText(respMsg);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
//广播上下线提醒
private void broadcastOnOrOffLine(String lineType){
String state;
if(lineType.equals(ON_LINE)){
state = "已上线";
}else{
state = "已下线";
}
String username = (String) httpSession.getAttribute("username");
for(Map.Entry<HttpSession,ChatWebSocket> entry:onlineUsers.entrySet()){
if(entry.getKey() != httpSession){
//不包括自己
try {
ChatWebSocket socket = entry.getValue();
String respMsg = MessageUtil.getRespMsg(MessageUtil.TYPE_MESSAGE, "", "", "您的好友" + username + state);
socket.session.getBasicRemote().sendText(respMsg);
String curTime = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date());
String onlineTime = MessageUtil.getRespMsg(MessageUtil.TYPE_MESSAGE,username,"","当前时间:"+curTime);
socket.session.getBasicRemote().sendText(onlineTime);
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
//广播好友列表
private void broadcastUsersList(String respMsg) {
if(onlineUsers.size()>0){
for(Map.Entry<HttpSession,ChatWebSocket> entry:onlineUsers.entrySet()){
ChatWebSocket socket = entry.getValue();
try {
socket.session.getBasicRemote().sendText(respMsg);
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
//获取好友列表(不包括自己)
private String getOnlineUserNames() {
List<String> list = new ArrayList<>();
for(HttpSession session:onlineUsers.keySet()){
String username = (String) session.getAttribute("username");
list.add(username);
}
if(list.size()>0){
return String.join(",",list);
}else{
return "";
}
}
}
/**
* 在握手时获取HttpSession,存进ServerEndpointConfig里
*/
public class getHttpSessionConfigurator extends ServerEndpointConfig.Configurator {
@Override
public void modifyHandshake(ServerEndpointConfig config, HandshakeRequest request, HandshakeResponse response) {
HttpSession httpSession = (HttpSession) request.getHttpSession();
config.getUserProperties().put(HttpSession.class.getName(),httpSession);
}
}