一.什么是Tomcat?
1.tomcat是一个由Java编写的程序,启动tomcat,其实就是启动BootStrap 中的main方法(任何java程序的启动入口都是main方法);
2.tomcat是一个web服务器软件,用于部署web包/jar包,让客户端能够访问部署在tomcat中的web包/jar包中的静态资源及动态资源;
3.我们常说的servlet技术,filter技术, IO模型(NIO和BIO), 应用层协议(HTTP/HTTPS),都是在tomcat中实现的;
4.关键类:Servlet、ServletContent、ServletConfig、Cookie、Session、Filter
二.Tomcat的目录
三.为什么说Tomcat是个容器?
Tomcat 本质上就是一款 Servlet 容器;不同的Servlet 处理不同的请求;
Tomcat 本质上就是一款 Servlet 容器, 因此Catalina 才是 Tomcat 的核心 , 其他模块
都是为Catalina 提供支撑的。 比如 : 通过Coyote 模块提供链接通信,Jasper 模块提供
JSP引擎,Naming 提供JNDI 服务,Juli 提供日志服务。
Catalina 各个组件的职责:
server.xml中的配置也可以体现server下的层级结构:
1个server代表1个tomcat服务,一般server只有1个;
1个server中可以配置多个service;
1个service中可配置多个Connector连接器;
1个service中可配置多个容器Container;
Container包括:是Engine、Host、Context和Wrapper。这4种容器不是平
行关系,而是父子关系。, Tomcat通过一种分层的架构,使得Servlet容器具有很好的灵活性。
四.Tomcat 启动流程
步骤 :
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 对象, 用于解析 server.xml,解析出Server对象。
6)在调用server的init方法,然后一级一级往下调用init方法,对各个组件进行初始化。
其中在Connector初始化的时候会根据server.xml配置的 Connector标签中的 protocol属性,生成ProtocolHandler协议处理器,ProtocolHandler具体实现类有Http11NioProtocol、Http11Nio2Protocol、AjpNioProtocol等,这些具体实现类的构造方法中回去创建AbstractEndpoint的实现类,AbstractEndpoint类的具体实现有NioEndpoint、Nio2Endpoint等;
<Connector port="8080" protocol="HTTP/1.1"
connectionTimeout="20000"
redirectPort="8443"
maxParameterCount="1000"
/>
7) init完之后,会调用 Catalina的start方法,来一级级启动各个组件,启动到ProtocolHandler中的endpoint 时,会去创建线程池,开启socket的监听,时刻准备接收客户的请求;
五.Tomcat 支持的的IO模型和应用层协议
Tomcat 支持的IO模型(在 8.0 之前 , Tomcat 默认采用的I/O方式为 BIO,自8.5/9.0 版本起,Tomcat 移除了 对 BIO 的支持):
Tomcat支持的应用层协议 :
六.Tomcat中的Jasper 简介
原理总结:
Jsp文件其实就是一个servlet,用户在访问XXX.jsp资源时,会映射到web.xml中配置的JspServlet,通过JspServlet找到对应的.jsp文件进行编译,编译好之后将请求交给其处理,servle会对每一行的静态内容(HTML) , 调用 out.write 输出。所以反馈给用户看到的其实是一个HTML页面;
Jasper 即Jsp引擎,对于基于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文件。
JSP 编译方式:
1.运行时编译
Tomcat 并不会在启动Web应用的时候自动编译JSP文件, 而是在客户端第一次请求时,
才编译需要访问的JSP文件。
2.编译过程:
Tomcat 在默认的web.xml 中配置了一个org.apache.jasper.servlet.JspServlet,用于处
理所有的.jsp 或 .jspx 结尾的请求,该Servlet 实现即是运行时编译的入口。
3.编译结果:
1) 如果在 tomcat/conf/web.xml 中配置了参数scratchdir , 则jsp编译后的结果,就会
存储在该目录下 。
2) 如果没有配置该选项, 则会将编译后的结果,存储在Tomcat安装目录下的
work/Catalina(Engine名称)/localhost(Host名称)/Context名称 。 假设项目名称为
jsp_demo
4.预编译:
除了运行时编译,我们还可以直接在Web应用启动时, 一次性将Web应用中的所有的JSP
页面一次性编译完成。在这种情况下,Web应用运行过程中,便可以不必再进行实时编
译,而是直接调用JSP页面对应的Servlet 完成请求处理, 从而提升系统性能。
Tomcat 提供了一个Shell程序JspC,用于支持JSP预编译,而且在Tomcat的安装目录下提
供了一个 catalina-tasks.xml 文件声明了Tomcat 支持的Ant任务, 因此,我们很容易使
用 Ant 来执行JSP 预编译 。(要想使用这种方式,必须得确保在此之前已经下载并安装
了Apache Ant)。
七.Tomcat支持HTTPS
1) 生成秘钥库文件。
keytool ‐genkey ‐alias tomcat ‐keyalg RSA ‐keystore tomcatkey.keystore
输入对应的密钥库密码, 秘钥密码等信息之后,会在当前文件夹中出现一个秘钥库文件:tomcatkey.keystore
2) 将秘钥库文件 tomcatkey.keystore 复制到tomcat/conf 目录下。
3) 配置tomcat/conf/server.xml
<Connector port="8443"
protocol="org.apache.coyote.http11.Http11NioProtocol"
maxThreads="150" schema="https" secure="true" SSLEnabled="true">
<SSLHostConfig certificateVerification="false">
<Certificate
certificateKeystoreFile="D:/DevelopProgramFile/apache‐tomcat‐8.5.42‐
windows‐x64/apache‐tomcat‐8.5.42/conf/tomcatkey.keystore"
certificateKeystorePassword="itcast" type="RSA" />
</SSLHostConfig>
</Connector>
4)访问Tomcat ,使用https协议。
八.Tomcat 性能优化
1.JVM内存参数:
找到Tomcat根目录下的bin目录,设置catalina.sh文件中JAVA_OPTS变量即可
2.Tomcat性能调优:
找到Tomcat根目录下的conf目录,修改server.xml文件的内容
<Connector port="8080" protocol="HTTP/1.1"
connectionTimeout="20000"
redirectPort="8443"
maxParameterCount="1000"
/>
九.Tomcat是如何启动SSM项目的(spring及springmvc的bean加载我们都知道是在项目启动中就完成的)?
tomcat在一启动时会去加载web.xml
<!-- spring容器一加载就读取的配置文件-->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:applicationcontext.xml,classpath:spring-security.xml</param-value>
</context-param>
<!-- LOG4J2的配置文件 -->
<context-param>
<param-name>log4jConfigLocation</param-name>
<param-value>classpath:log4j2.xml</param-value>
</context-param>
<!-- 一个小时检查一次日志配置文件 -->
<context-param>
<param-name>log4jRefreshInterval</param-name>
<param-value>3600000</param-value>
</context-param>
<!--ContextLoaderListener 实现 ServletContextListener接口
ServletContextListener监听ServletContext对象创建与销毁
ServletContext对象创建时机?
服务器启动创建
ContextLoaderListener在服务器启动时就执行内部的初始化方法
在该初始化方法内部做什么?spring容器的创建 创建容器时 加载spring的配置文件
-->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
1.tomcat先启动spring容器
tomcat在启动ServletContext容器的时候会发布ServletContextEvent事件。web.xml中设置的监听器ContextLoaderListener实现了ServletContextListener接口,对ServletContext启动监听,在initWebApplicationContext方法中:
1.会生成web容器(XmlWebApplicationContext),并调用refresh()方法,根据web.xml中配置的contextConfigLocation中配置的spring的配置文件扫描包路径,加载bean(SpringIOC容器完成启动);
2.通过ServletContext获取上面配置的contextConfigLocation参数将其赋值给web容器对象;
3.将创建web容器对象,赋值给servletContext对象的 ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE属性中@11
public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
if (servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null) {
throw new IllegalStateException(
"Cannot initialize context because there is already a root application context present - " +
"check whether you have multiple ContextLoader* definitions in your web.xml!");
}
servletContext.log("Initializing Spring root WebApplicationContext");
Log logger = LogFactory.getLog(ContextLoader.class);
if (logger.isInfoEnabled()) {
logger.info("Root WebApplicationContext: initialization started");
}
long startTime = System.currentTimeMillis();
try {
//创建web容器XmlWebApplicationContext
if (this.context == null) {
this.context = createWebApplicationContext(servletContext);
}
if (this.context instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
if (!cwac.isActive()) {
if (cwac.getParent() == null) {
ApplicationContext parent = loadParentContext(servletContext);
cwac.setParent(parent);
}
//执行web容器的refresh()方法
configureAndRefreshWebApplicationContext(cwac, servletContext);
}
}
//将web容器对象,赋值给servletContext对象的 ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE属性中
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);
ClassLoader ccl = Thread.currentThread().getContextClassLoader();
if (ccl == ContextLoader.class.getClassLoader()) {
currentContext = this.context;
}
else if (ccl != null) {
currentContextPerThread.put(ccl, this.context);
}
if (logger.isInfoEnabled()) {
long elapsedTime = System.currentTimeMillis() - startTime;
logger.info("Root WebApplicationContext initialized in " + elapsedTime + " ms");
}
return this.context;
}
catch (RuntimeException | Error ex) {
logger.error("Context initialization failed", ex);
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex);
throw ex;
}
}
2.tomcat是如何启动SpringMVC的?
SpringIOC容器启动之后,再启动SpringMVC容器;
tomcat配置文件web.xml中的配置了DispatcherServlet,Tomcat中默认情况下只在请求匹配到Servlet后才会初始化Servlet(也就是项目启动后第一次进行请求的时候,如果想让项目启动时就初始化Servlet,则配置load-on-startup属性为1),初始化时会调DispatcherServlet的父类HttpServletBean中的init()方法,init()方法中就实现了SpringMVC的整个启动流程。下面看下源码:
web.xml配置:
<!--配置DispatcherServlet 核心其实就是一个servlet 重点: springmvc的前端控制器-->
<servlet>
<servlet-name>SpringMVCCore</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!-- 配置springmvc的配置文件 如果不配置 默认加载/WRB_INF/servlet名称—serlvet.xml-->
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:SpringMVC.xml</param-value>
</init-param>
<!-- 配置 servlet 的对象的创建时间点:应用加载时创建。
取值只能是非 0 正整数,表示启动顺序 -->
<load-on-startup>1</load-on-startup>
</servlet>
在HttpServletBean中实现了init()方法->initServletBean()->org.springframework.web.servlet.FrameworkServlet#initServletBean:
if (!this.refreshEventReceived) {
// Either the context is not a ConfigurableApplicationContext with refresh
// support or the context injected at construction time had already been
// refreshed -> trigger initial onRefresh manually here.
synchronized (this.onRefreshMonitor) {
onRefresh(wac);
}
}
在SpringMVC容器初始化的时候,如果有springIOC容器,会将SpringIOC容器设置为mvc的父容器:
springIOC容器的获取是从ServletContext对象的ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE属性中获取,详见@11处
if (wac == null) {
// No context instance is defined for this servlet -> create a local one
wac = createWebApplicationContext(rootContext);
}
正是因为tomcat是先启动spring容器在启动springmvc容器的,所以springmvc容器中的controller实例再初始化时所依赖的service已经存在。