Tomcat专题-----Tomcat源码嵌入式Tomcat
Posted 阿里-马云的学习笔记
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Tomcat专题-----Tomcat源码嵌入式Tomcat相关的知识,希望对你有一定的参考价值。
Tomcat顶层结构
Server:服务器的意思,代表整个tomcat服务 器,一个tomcat只有一个Server;
Service:Server中的一个逻辑功能层, 一个 Server可以包含多个Service;
Connector:称作连接器,是Service的核心组 件之一,一个Service可以有多个Connector, 主要是连接客户端请求;
Container:Service的另一个核心组件,按照 层级有Engine,Host,Context,Wrapper四种, 一个Service只有一个Engine,其主要作用是执 行业务逻辑;
Jasper:JSP引擎;
Session:会话管理;
Server
Server是Tomcat最顶层的容器,代表着整个服务器
Tomcat中其标准实现: org.apache.catalina.core.StandardServer类
StandardServer继承结构类图如下图: 除了实现Server接口还需要继承Lifecycle
这里要强调一点,整个Tomcat中的设计方式,我们讲的都是一个抽象,抽象在源码中都是接口,具体的实现一般都是StandardXXXX之类的。
Lifecycle这个概念重点讲下:像Tomcat这么大的系统,必要要对生命周期进行统一的管理,所以基本上大部分的组件都会去继承这个接口,Lifecycle把所有的启动、停止、关闭等都组织在了一起。
补充点:MBeanRegistration这个类是完成JMX的功能,就是我们俗称的监控管理功能,之前我们讲的使用jconsole查看Tomcat也就是通过JMX玩的。
好处:生命周期统一接口Lifecycle把所有的启 动、停止、关闭等都放在一起统一管理
Service
Service:一个Tomcat包含多个Service
Tomcat中其标准实现: org.apache.catalina.core.StandardServic类
StandardService继承结构类图如下图: 除了实现Service接口还需要继承Lifecycle
好处:生命周期统一接口Lifecycle把所有的启 动、停止、关闭等都放在一起统一管理
Service拓展出Tomcat原型
Service中请求监听和请求处理分开为两个模块: Connector负责处理请求监听; Container负责处理请求处理;
一个Service可以有多个Connector,但只能有 一个Container。
任何容器都有启动start()和关闭stop()方法。
Connector解析
Connector使用ProtocolHandler来处理请求的。
ProtocolHandler由包含了三个部件: Endpoint、Processor、Adapter。
Endpoint用来处理底层Socket的网络连接。 Processor用于将Endpoint接收到的Socket封装 成Request。 Adapter充当适配器,用于将Request转换为 ServletRequest交给Container进行具体的处理。
Container解析
Engine:引擎、只有一个 定义了一个名为Catalina的Engine
Host:站点、虚拟主机 一个Engine包含多个Host的设计,使得 一个服务器实例可以承担多个域名的服 务,是很灵活的设计
Context:一个应用 默认配置下webapps下的每个目录都是 一个应用
Wrapper:一个Servlet
在理解下面的4个容器之间的关系:
Tomcat启动流程
Tomcat启动两大流程:init流程和start流程
1、init流程:分别在Bootstrap、Catalina、StandardServer、StandardService 的init方法加入断点和输出日志
2、start流程:分别在Bootstrap、Catalina、StandardServer、StandardService 的start方法加入断点和输出日志
Lifecycle与模板方法模式
模板方法就是为多种类似业务提供一个算法执行的统一框 架,把这些业务中共同的部分抽取出来进行具体实现,而 某些业务中特定的需求推迟到子类中进行重写实现
案例:
Tomcat的启动过程中Catalina调用StandardService中的start() 方法,但是StandardService自身没有start()方法!
分析:
原来StandardService继承了抽象类LifecycleBase,它有start() 并且它在此方法中调用了一个未实现的抽象方法startInternal() ,Catalina调用StandardService中的start()最后会调用至 startInternal()
优点:
这种模式使得StandardService等这些类抽出一个共同的start() 在LifecycleBase中进行实现(方便统一生命周期管理)。如果它 需进行特殊的业务处理的话可以在startInternal()中处理
嵌入式Tomcat
嵌入式Tomcat: 非传统的部署方式,将Tomcat嵌入到主程序中进行运行。
优点: 灵活部署、任意指定位置、通过复杂的条件判断。
发展趋势: Springboot默认集成的是Tomcat容器。
Maven中Springboot引入Tomcat:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-tomcat</artifactId> <scope>provided</scope> </dependency>
Maven集成Tomcat插件
Tomcat 7 Maven插件:tomcat7-maven-plugin
<dependency> <groupId>org.apache.tomcat.maven</groupId> <artifactId>tomcat7-maven-plugin</artifactId> <version>2.2</version> </dependency>
插件运行 选择pom.xml文件,击右键——>选择 Run As——> Maven build 在Goals框加加入以下命令: tomcat7:run
Maven集成Tomcat 插件启动Tomcat是常用的Tomcat嵌入式启动
pom.xml:
<?xml version="1.0" encoding="UTF-8"?> <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.0</modelVersion> <groupId>tomcat-maven</groupId> <artifactId>tomcat-maven</artifactId> <version>0.0.1-SNAPSHOT</version> <name>tomcat-maven</name> <!-- FIXME change it to the project\'s website --> <url>http://www.example.com</url> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <maven.compiler.source>1.7</maven.compiler.source> <maven.compiler.target>1.7</maven.compiler.target> </properties> <dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.11</version> <scope>test</scope> </dependency> <!-- 引入Maven的Tomcat --> <dependency> <groupId>org.apache.tomcat.maven</groupId> <artifactId>tomcat7-maven-plugin</artifactId> <version>2.2</version> </dependency> </dependencies> <build> <pluginManagement><!-- lock down plugins versions to avoid using Maven defaults (may be moved to parent pom) --> <plugins> </plugins> </pluginManagement> </build> </project>
Maven集成Tomcat插件启动分析
分析它的启动
Tomcat7RunnerCli是引导类
@SuppressWarnings( "static-access" ) public class Tomcat7RunnerCli { public static void main( String[] args ) throws Exception { . . . // here we go tomcat7Runner.run(); } }
进一步分析
Tomcat7RunnerCli主要依靠Tomcat7Runner。重要代码如下:
public class Tomcat7Runner { public void run() throws Exception { // start with a server.xml if ( serverXmlPath != null || useServerXml() ) { container = new Catalina(); container.setUseNaming( this.enableNaming() ); if ( serverXmlPath != null && new File( serverXmlPath ).exists() ) { container.setConfig( serverXmlPath ); } else { container.setConfig( new File( extractDirectory, "conf/server.xml" ).getAbsolutePath() ); } container.start(); } else { tomcat = new Tomcat() { public Context addWebapp( Host host, String url, String name, String path ) { Context ctx = new StandardContext(); ctx.setName( name ); ctx.setPath( url ); ctx.setDocBase( path ); ContextConfig ctxCfg = new ContextConfig(); ctx.addLifecycleListener( ctxCfg ); ctxCfg.setDefaultWebXml( new File( extractDirectory, "conf/web.xml" ).getAbsolutePath() ); if ( host == null ) { getHost().addChild( ctx ); } else { host.addChild( ctx ); } return ctx; } }; tomcat.getHost().setAppBase( new File( extractDirectory, "webapps" ).getAbsolutePath() ); String connectorHttpProtocol = runtimeProperties.getProperty( HTTP_PROTOCOL_KEY ); if ( httpProtocol != null && httpProtocol.trim().length() > 0 ) { connectorHttpProtocol = httpProtocol; } debugMessage( "use connectorHttpProtocol:" + connectorHttpProtocol ); if ( httpPort > 0 ) { Connector connector = new Connector( connectorHttpProtocol ); connector.setPort( httpPort ); if ( httpsPort > 0 ) { connector.setRedirectPort( httpsPort ); } connector.setURIEncoding( uriEncoding ); tomcat.getService().addConnector( connector ); tomcat.setConnector( connector ); } // add a default acces log valve AccessLogValve alv = new AccessLogValve(); alv.setDirectory( new File( extractDirectory, "logs" ).getAbsolutePath() ); alv.setPattern( runtimeProperties.getProperty( Tomcat7Runner.ACCESS_LOG_VALVE_FORMAT_KEY ) ); tomcat.getHost().getPipeline().addValve( alv ); // create https connector if ( httpsPort > 0 ) { Connector httpsConnector = new Connector( connectorHttpProtocol ); httpsConnector.setPort( httpsPort ); httpsConnector.setSecure( true ); httpsConnector.setProperty( "SSLEnabled", "true" ); httpsConnector.setProperty( "sslProtocol", "TLS" ); httpsConnector.setURIEncoding( uriEncoding ); . . . tomcat.getService().addConnector( httpsConnector ); if ( httpPort <= 0 ) { tomcat.setConnector( httpsConnector ); } } tomcat.start(); Runtime.getRuntime().addShutdownHook( new TomcatShutdownHook() ); } waitIndefinitely(); } }
分析结论
原来嵌入式启动就是调用了Tomcat的API来实现的
Tomcat API接口
实现嵌入式Tomcat的基础:
Tomcat本身提供了外部可以调用的API
Tomcat类:
1.位置:org.apache.catalina.startup.Tomcat
2.该类是public的。
3.该类有Server、Service、Engine、Connector、Host等属性。
4.该类有init()、start()、stop()、destroy()等方法。
分析结论: Tomcat类是外部调用的入口
手写Tomcat思路分析
分析
Tomcat单独启动时的流程
结论
使用Tomcat的API来实现:
1.新建一个Tomcat对象
2.设置Tomcat的端口号
3.设置Context目录
4.添加Servlet容器
5.调用Tomcat对象Start()
6.强制Tomcat等待
手写嵌入式Tomcat-----开发流程
1.准备好一个简单的Servlet项目
2.新建一个手写嵌入式Tomcat工程
3.Tomcat工程中使用一个类完成手写嵌入式Tomcat的功能
4.调用该类的main方法执行
5.效果演示和分析
代码:
1、代码结构
2、pom.xml
<?xml version="1.0" encoding="UTF-8"?> <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.0</modelVersion> <groupId>tomcat-maven</groupId> <artifactId>tomcat-maven</artifactId> <version>0.0.1-SNAPSHOT</version> <name>tomcat-maven</name> <!-- FIXME change it to the project\'s website --> <url>http://www.example.com</url> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <maven.compiler.source>1.7</maven.compiler.source> <maven.compiler.target>1.7</maven.compiler.target> </properties> <dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.11</version> <scope>test</scope> </dependency> <!-- 引入Maven的Tomcat --> <dependency> <groupId>org.apache.tomcat.maven</groupId> <artifactId>tomcat7-maven-plugin</artifactId> <version>2.2</version> </dependency> </dependencies> <build> <pluginManagement><!-- lock down plugins versions to avoid using Maven defaults (may be moved to parent pom) --> <plugins> </plugins> </pluginManagement> </build> </project>
3、servlet
public class DemoServlet extends HttpServlet { private static final long serialVersionUID = 1L; @Override public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { response.setContentType("text/html"); response.setCharacterEncoding("UTF-8"); PrintWriter out = response.getWriter(); out.println("<!DOCTYPE html><html>"); out.println("<head>"); out.println("<meta charset=\\"UTF-8\\" />"); out.println("<title></title>"); out.println("</head>"); out.println("<body bgcolor=\\"white\\">"); out.println("<h1>阿里-马云 V5!</h1>"); out.println("</body>"); out.println("</html>"); } }
4、EmbeddedTomcatServer-----服务器主类
public class EmbeddedTomcatServer { public static void main(String[] args) throws Exception{ //把目录的绝对的路径获取到 String classpath = System.getProperty("user.dir"); System.out.println(classpath); //D:\\workspace-tomcat\\tomcat-maven //我们new一个Tomcat Tomcat tomcat = new Tomcat(); //设置Tomcat的端口tomcat.setPort(9091)。两种写法都可以设置端口 Connector connector = tomcat.getConnector(); connector.setPort(9091); //设置Host Host host = tomcat.getHost(); //我们会根据xml配置文件来 host.setName("localhost"); host.setAppBase("webapps"); //前面的那个步骤只是把Tomcat起起来了,但是没啥东西 //要把class加载进来,把启动的工程加入进来了 Context context =tomcat.addContext(host, "/", classpath); if(context instanceof StandardContext){ StandardContext standardContext = (StandardContext) context; standardContext.setDefaultContextXml("E:/utils/Tomcat/apache-tomcat-8.0.53/conf/web.xml"); //我们要把Servlet设置进去 Wrapper wrapper = tomcat.addServlet("/", "DemoServlet", new DemoServlet()); wrapper.addMapping("/king"); } //Tomcat跑起来 tomcat.start(); //强制Tomcat server等待,避免main线程执行结束后关闭 tomcat.getServer().await(); } }
5、运行主类,启动成功。
浏览器中访问:http://localhost:9091/king
以上是关于Tomcat专题-----Tomcat源码嵌入式Tomcat的主要内容,如果未能解决你的问题,请参考以下文章
SpringBoot 源码解析 ----- Spring Boot的核心能力 - 内置Servlet容器源码分析(Tomcat)