设为首页 收藏本站
查看: 635|回复: 0

[经验分享] tomcat 组件研究一

[复制链接]

尚未签到

发表于 2017-12-25 20:11:17 | 显示全部楼层 |阅读模式
  作为java 开发者,从开始学习java 便知道tomcat 这个容器了,但是一直却没有怎么研究过它的内部结构,以前对tomcat的认识也仅仅局限在那几个常用的目录放什么东西,那几个常用的配置文件应该写说明内容,却很少研究其内部的组件以及启动过程,另外,去网上找相关的资料博客,也发现不是很多很全面,所以这几天特意了解了下tomcat 的内部工作的原理,简单总结了tomcat比较核心的一些组件,仅供学习交流,今天这篇博客主要是研究下tomcat 的大体组件有什么以及它们的启动过程,后面会继续总结tomcat 处理请求的过程。下面是本篇博客的题纲:
  1、tomcat 主要组件
  2、tomcat动过程
  3、从tomcat 中得到的编程启迪
  一、tomcat主要组件
  下面,先简单介绍下tomcat的主要组件,让各位读者初步认识tomcat的组织结构。
  一直觉得java的抽象建模能力超6,而在研究优秀开源框架的时候,我们也会感到作者将这种语言的建模能力发挥到了极致,tomcat 的组织结构正是这样一个非常生动的例子,它非常巧妙地地将服务器处理的过程抽象成一个个类。
  先简单粗略说说常规web应用客户请求与服务器响应的整个过程吧:客户发起request-->服务器收到request-->服务器调用服务应用-->业务处理-->返回结果。在这个过程,tomcat把各个涉及到的实体抽象为一个个对象,下图是tomcat 的大概组织结构图:
DSC0000.png

  tomcat内部是如何抽象类的呢?由上面的图片大概知道,tomcat 把处理请求的的过程分别抽象为如下的类(接口):server -->对应容器本身,代表一个tomcat容器;service-->服务,代表容器下可以提供的服务,service 可以简单理解为独立的一个提供服务的项目,tomcat 支持同时运行多个服务,所以一个server 可以有多个service;Connector 和Container 共同构成Service的核心组件;Connector是tomcat的对外连接器,主要负责处理连接相关,它实现了http协议,把数据封装好为request 对象和response 对象;而Container 是管理容器,它主要负责容器tomcat内部各种servlet。
  上面的图片说的是大多tomcat的核心组件,他们主要负责处理客户端请求并返回结果,姑且称他们为工作组件;另外,还有一些组件,他们主要负责管理这些工作组件的创建、销毁等管理工作,姑且称他们为控制组件,这些组件在tomcat里面主要有以下三个:前面提到的代表容器本身的server,server控制了所有工作组件的启动、停止工作;的Catalina以及Catalina的一个适配器Bootstrap,Catalina 主要是用来启动tomcat 的server,它是tomcat的总开关,而Bootstrap又控制着Catalina,即用户点击startup的启动程序时,实际上是调用Bootstrap来启动tomcat的,至于控制类组件中为什么要这么蛋疼硬是分出这么多类(接口)来,后面会说到。
  总的来说,tomcat 中组件他们之间的控制关系(注意,下图说是控制关系,不是调用关系或者继承关系)大概如下,总的来说有这样一种关系,底层的组件的开关由上一层控制。
DSC0001.png

  大概了解了tomcat的组织结构之后,下面总结下tomcat的启动/初始化(销毁)机制即tomcat 管理组件生命周期的主要方法。
  二、tomcat如何管理组件生命周期
  下面总结下tomcat是如何管理内部组件的声明周期的,主要分以下部分进行总结:一是三个核心控制类的组件如何控制启动;二是tomcat控制声明周期的核心接口LifeCycle的讲解,下面我就开车了,各位赶紧上车了。
  1、BootStrap 的启动过程。
  bootStrap 相当于一个Adaptor 即适配器,它通过调用Catalina 来进行tomcat 容器的启动,其实,Bootstrap 中的main方法就是整个容器的执行入口处,它的源码也不难看懂,如下(代码太长折叠了):

DSC0002.gif DSC0003.gif   

public static void main(String args[]) {  

if (daemon == null) {// Don't set daemon until init() has completed  Bootstrap bootstrap = new Bootstrap();
  try {
  bootstrap.init();
  } catch (Throwable t) {
  handleThrowable(t);
  t.printStackTrace();
  return;
  }
  daemon = bootstrap;
  } else {
  // When running as a service the call to stop will be on a new
  // thread so make sure the correct>  // a range of>  
            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")) {
  daemon.setAwait(true);
  daemon.load(args);
  daemon.start();
  } 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);
  }
  

  }
  


View Code  总的来说,Bootstrap  main方法启动时,会新建一个Bootstrap对象,然后调用该对象的init方法,这个方法会获取Catalina 的ClassLoder,然后利用反射进行Catalina 对象的创建,创建完对象之后,便会通过传进来的args[]的参数,调用Catalina对象进行tomcat的start 或者stop等操作。
  由代码可以看到,start 操作时,Bootstrap 会调用Catalina对象的三个方法:setAwait 、load、以及start方法,Catalina的三个方法解释如下:setAwait的方法的设置使得Catalina 在启动Server的时候,Server一直在一个循环中执行wait操作等待请求的到来,load则是加载tomcat启动必须的数据(例如配置文件等等),最后start 方法则是正真调用Server的启动方法。
  下面我们再看看Catalina又是如何调用Server的。
  2、Catalina的启动过程
  上面总结Bootstrap 启动过程式,有提到Catalina的三个方法:setAwait 、load、以及start,那么,这三个方法在启动的时候,又是如何工作的呢?首先看下setAwait的源码是怎么工作的,如下:
  

public void setAwait(boolean b) {  await
= b;  }
  

  好吧,其实它就是设置一个标记量,而这个标记量,主要用在start方法中,我们看在start 方法中,await 有什么用,下面是start 的部分代码:
  

if (await) {  await();
  stop();
  }
  

  这段代码是在start方法最后的,所以可以知道,setAwait 的作用就是使得执行完start 方法之后,调用本身的await 方法,而查看下面的源码可知,await 方法的作用就是调用server(getServer 返回server对象)的await 方法。而其实在server中,await方法的作用就是在tomcat 启动完成之后,处于一种等待请求状态。
  

public void await() {  

  getServer().await();
  

  }
  

  然后,我们再看load方法又做了些什么工作,还是自己看源码(代码可能有点长,所以我折起来了):


  

public void load() {  

long t1 = System.nanoTime();  

  initDirs();
  

// Before digester - it may be needed  

  initNaming();
  

  // Create and execute our Digester
  Digester digester = createStartDigester();
  

  InputSource inputSource = null;
  InputStream inputStream = null;
  File file = null;
  try {
  try {
  file = configFile();
  inputStream = new FileInputStream(file);
  inputSource = new InputSource(file.toURI().toURL().toString());
  } catch (Exception e) {
  if (log.isDebugEnabled()) {
  log.debug(sm.getString("catalina.configFail", file), e);
  }
  }
  if (inputStream == null) {
  try {
  inputStream = getClass().getClassLoader()
  .getResourceAsStream(getConfigFile());
  inputSource = new InputSource
  (getClass().getClassLoader()
  .getResource(getConfigFile()).toString());
  } catch (Exception e) {
  if (log.isDebugEnabled()) {
  log.debug(sm.getString("catalina.configFail",
  getConfigFile()), e);
  }
  }
  }
  

  // This should be included in catalina.jar
  //>  if( inputStream==null ) {
  try {
  inputStream = getClass().getClassLoader()
  .getResourceAsStream("server-embed.xml");
  inputSource = new InputSource
  (getClass().getClassLoader()
  .getResource("server-embed.xml").toString());
  } catch (Exception e) {
  if (log.isDebugEnabled()) {
  log.debug(sm.getString("catalina.configFail",
  "server-embed.xml"), e);
  }
  }
  }
  

  

  if (inputStream == null || inputSource == null) {
  if  (file == null) {
  log.warn(sm.getString("catalina.configFail",
  getConfigFile() + "] or [server-embed.xml]"));
  } else {
  log.warn(sm.getString("catalina.configFail",
  file.getAbsolutePath()));
  if (file.exists() && !file.canRead()) {
  log.warn("Permissions incorrect, read permission is not allowed on the file.");
  }
  }
  return;
  }
  

  try {
  inputSource.setByteStream(inputStream);
  digester.push(this);
  digester.parse(inputSource);
  } catch (SAXParseException spe) {
  log.warn("Catalina.start using " + getConfigFile() + ": " +
  spe.getMessage());
  return;
  } catch (Exception e) {
  log.warn("Catalina.start using " + getConfigFile() + ": " , e);
  return;
  }
  } finally {
  if (inputStream != null) {
  try {
  inputStream.close();
  } catch (IOException e) {
  // Ignore
  
                }
  }
  }
  

  getServer().setCatalina(this);
  

  // Stream redirection
  
        initStreams();
  

  // Start the new server
  try {
  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("Catalina.start", e);
  }
  

  }
  

  long t2 = System.nanoTime();
  if(log.isInfoEnabled()) {
  log.info("Initialization processed in " + ((t2 - t1) / 1000000) + " ms");
  }
  

  }
  


View Code  上面代码,我们可以看到,load 其实就是分为以下几个步骤:首先读取各种启动所需要的配置文件(context.xml/server.xml等),读取完之后,创建server对象,当创建server 完成之后,会调用server 的init 方法进行容器的进一步初始化工作,至于init 方法,后面总结server 的时候再详细总结。
  最后的start 方法,其实就是开启应用了,下面是start 方法的源码,由于太长,也折起来了,需要查看请自己展开:


  

public void start() {  

if (getServer() == null) {  load();
  }
  

if (getServer() == null) {  log.fatal(
"Cannot start server. Server instance is not configured.");return;  }
  

long t1 = System.nanoTime();  

// Start the new server  try {
  getServer().start();
  } catch (LifecycleException e) {
  log.fatal(sm.getString("catalina.serverStartFail"), e);
  try {
  getServer().destroy();
  } catch (LifecycleException e1) {
  log.debug("destroy() failed for failed Server ", e1);
  }
  return;
  }
  

  long t2 = System.nanoTime();
  if(log.isInfoEnabled()) {
  log.info("Server startup in " + ((t2 - t1) / 1000000) + " ms");
  }
  

  // Register shutdown hook
  if (useShutdownHook) {
  if (shutdownHook == null) {
  shutdownHook = new CatalinaShutdownHook();
  }
  Runtime.getRuntime().addShutdownHook(shutdownHook);
  

  // If JULI is being used, disable JULI's shutdown hook since
  // shutdown hooks run in parallel and log messages may be lost
  // if JULI's hook completes before the CatalinaShutdownHook()
  LogManager logManager = LogManager.getLogManager();
  if (logManager instanceof>  ((ClassLoaderLogManager) logManager).setUseShutdownHook(
  false);
  }
  }
  

  if (await) {
  await();
  stop();
  }
  }
  


View Code  start 方法其实主要的步骤就是调用server 的start方法进行容器的开启,相信读者看源码也不难理解,这里不累赘说了。
  3、server 的启动过程
  server 是 tomcat 控制类组件的核心组件,它控制着tomcat service 的启动和关闭,从而达到控制容器开关的目的。server 实际上以一个接口,在tomcat 中,默认实现了这个接口的类是 StandardServer ,在这个server 中,关于启动阶段,主要有两个方法:initInternal 和startInternal ,两个方法都是分别调用所有service 的init方法和start  方法,具体可以查看下面的源代码:
  这个是StandrfServer 的initInternal 的源码:


  

protected void initInternal() throws LifecycleException {super.initInternal();  

// Register global String cache// Note>
// present in the JVM (may happen when embedding) then the same cache// will be registered under multiple names  onameStringCache = register(new StringCache(), "type=StringCache");
  

  // Register the MBeanFactory
  MBeanFactory factory = new MBeanFactory();
  factory.setContainer(this);
  onameMBeanFactory = register(factory, "type=MBeanFactory");
  // Register the naming resources
  
        globalNamingResources.init();
  // Populate the extension validator with JARs from common and shared
  //>  if (getCatalina() != null) {
  ClassLoader cl = getCatalina().getParentClassLoader();
  // Walk the>  // This will add the shared (if present) and common>  while (cl != null && cl !=>  if (cl instanceof URLClassLoader) {
  URL[] urls = ((URLClassLoader) cl).getURLs();
  for (URL url : urls) {
  if (url.getProtocol().equals("file")) {
  try {
  File f = new File (url.toURI());
  if (f.isFile() &&
  f.getName().endsWith(".jar")) {
  ExtensionValidator.addSystemResource(f);
  }
  } catch (URISyntaxException e) {
  // Ignore
  } catch (IOException e) {
  // Ignore
  
                            }
  }
  }
  }
  cl = cl.getParent();
  }
  }
  // Initialize our defined Services
  for (int i = 0; i < services.length; i++) {
  services.init();
  }
  }
  


View Code  可以看到,在源码里面,initInternal 依次调用了 Service 的init方法,而startInternal 也是类似的,不累赘讲了,具体有兴趣的读者可以展开下面的源码查看:


  

protected void startInternal() throws LifecycleException {  

  fireLifecycleEvent(CONFIGURE_START_EVENT,
null);  setState(LifecycleState.STARTING);
  

  globalNamingResources.start();
// Start our defined Services  synchronized (servicesLock) {
  for (int i = 0; i < services.length; i++) {
  services.start();
  }
  }
  }
  


View Code  startInternal  和 initInternal的方法都较为简单,下面重点研究下Server 的await 方法,也是上面被Catalina 调用的方法,具体请先看一下await 的源码(代码过长先折叠起来了):


  

public void await() {// Negative values - don't wait on port - tomcat is embedded or we just don't like ports  if( port == -2 ) {
  // undocumented yet - for embedding apps that are around, alive.
  return;
  }
  if( port==-1 ) {
  try {
  awaitThread = Thread.currentThread();
  while(!stopAwait) {
  try {
  Thread.sleep( 10000 );
  } catch( InterruptedException ex ) {
  // continue and check the flag
  
                    }
  }
  } finally {
  awaitThread = null;
  }
  return;
  }
  

  // Set up a server socket to wait on
  try {
  awaitSocket = new ServerSocket(port, 1,
  InetAddress.getByName(address));
  } catch (IOException e) {
  log.error("StandardServer.await: create[" + address
  + ":" + port
  + "]: ", e);
  return;
  }
  

  try {
  awaitThread = Thread.currentThread();
  

  // Loop waiting for a connection and a valid command
  while (!stopAwait) {
  ServerSocket serverSocket = awaitSocket;
  if (serverSocket == null) {
  break;
  }
  // Wait for the next connection
  Socket socket = null;
  StringBuilder command = new StringBuilder();
  try {
  InputStream stream;
  long acceptStartTime = System.currentTimeMillis();
  try {
  socket = serverSocket.accept();
  socket.setSoTimeout(10 * 1000);  // Ten seconds
  stream = socket.getInputStream();
  } catch (SocketTimeoutException ste) {
  // This should never happen but bug 56684 suggests that
  // it does.
  log.warn(sm.getString("standardServer.accept.timeout",
  Long.valueOf(System.currentTimeMillis() - acceptStartTime)), ste);
  continue;
  } catch (AccessControlException ace) {
  log.warn("StandardServer.accept security exception: "
  + ace.getMessage(), ace);
  continue;
  } catch (IOException e) {
  if (stopAwait) {
  // Wait was aborted with socket.close()
  break;
  }
  log.error("StandardServer.await: accept: ", e);
  break;
  }
  

  // Read a set of characters from the socket
  int expected = 1024; // Cut off to avoid DoS attack
  while (expected < shutdown.length()) {
  if (random == null)
  random = new Random();
  expected += (random.nextInt() % 1024);
  }
  while (expected > 0) {
  int ch = -1;
  try {
  ch = stream.read();
  } catch (IOException e) {
  log.warn("StandardServer.await: read: ", e);
  ch = -1;
  }
  // Control character or EOF (-1) terminates loop
  if (ch < 32 || ch == 127) {
  break;
  }
  command.append((char) ch);
  expected--;
  }
  } finally {
  // Close the socket now that we are done with it
  try {
  if (socket != null) {
  socket.close();
  }
  } catch (IOException e) {
  // Ignore
  
                    }
  }
  

  // Match against our command string
  boolean match = command.toString().equals(shutdown);
  if (match) {
  log.info(sm.getString("standardServer.shutdownViaPort"));
  break;
  } else
  log.warn("StandardServer.await: Invalid command '"
  + command.toString() + "' received");
  }
  } finally {
  ServerSocket serverSocket = awaitSocket;
  awaitThread = null;
  awaitSocket = null;
  

  // Close the server socket and return
  if (serverSocket != null) {
  try {
  serverSocket.close();
  } catch (IOException e) {
  // Ignore
  
                }
  }
  }
  }
  


View Code  这个方法可以看出,该方法的作用就是监听关闭端口,在接受到请求的时候进行持续的关闭操作,如果端口号是-1,代表不能从外部关闭应用,如果是-2 直接退出;而后面的代码是我们很熟悉的Socket 编程,主要是监听对应的关闭端口,如果接受到对应的关闭指令,则会关闭应用。
  4、Service 的启动过程。
  Service 是代表向外提供的服务,它其实也是一个接口,在tomcat中有一个标准实现StandardService,下面我们就看看这个StandardService的启动部分的代码。由上面的server 启动过程可知,Service 的启动过程主要是init方法以及start 方法,但是,如果读者细心阅读StandardService 的源码的话,会发现找不到对应service 的init 方法,只是找到了initInternal 方法,其实这时我们大概就可以猜到:StandardService 应该是有父类的,init 方法应该是在父类中,查看StandardService父类LifecycleBase(StandardService 继承了LifecycleMBeanBase ,LifecycleMBeanBase 继承了LifecycleBase,而init 方法是在LifecycleBase中的)还真发现有这样一个方法,同时,我们可以看到,父类的init 方法还调用了一个方法initInternal ,只是这个initInternal 什么都不干,纯粹是个模板方法,它由子类(也就是这里的StandardService 实现),这个模板方法的手段在很多开源框架里面都可以看到,也是我们研究开源框架要学习的精粹之一。下面粘上LifecycleBase的源码:


  

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) {  ExceptionUtils.handleThrowable(t);
  setStateInternal(LifecycleState.FAILED,
null, false);throw new LifecycleException(  sm.getString(
"lifecycleBase.initFail",toString()), t);  }
  }
  


View Code  所以,由上面的代码可知,我们要研究Service 的启动过程,其实主要就是研究StandardService中的initInternal 以及startInternal 方法即可,下面我们研究下这两个方法到底干了什么。
  首先,我们先看看initInternal的源码:


  

protected void initInternal() throws LifecycleException {  

super.initInternal();if (container != null) {  container.init();
  }
  

// Initialize any Executors  for (Executor executor : findExecutors()) {
  if (executor instanceof LifecycleMBeanBase) {
  ((LifecycleMBeanBase) executor).setDomain(getDomain());
  }
  executor.init();
  }
  

  // Initialize our defined Connectors
  synchronized (connectorsLock) {
  for (Connector connector : connectors) {
  try {
  connector.init();
  } catch (Exception e) {
  String message = sm.getString(
  "standardService.connector.initFailed", connector);
  log.error(message, e);
  

  if (Boolean.getBoolean("org.apache.catalina.startup.EXIT_ON_INIT_FAILURE"))
  throw new LifecycleException(message);
  }
  }
  }
  }
  


View Code  由其源码可以看到,其实initInternal 主要做了两件事情:一个是调用Container 的init 方法,另外一个是调用了connectors 的init方法,初始化了所有的connectors类。Container 和Connectors 的作用上面也大概提到了下,两者分别对应的是Connectors 处理外部请求,Container 则是调用Servlet ,是内部业务的入口,更详细的后面再展开了。
  然后,我们再看看startInternal 的源码(其实估计我们不用看源码都大概可以猜到它要干什么了):

  

protected void startInternal() throws LifecycleException {  

if(log.isInfoEnabled())  log.info(sm.getString(
"standardService.start.name", this.name));  setState(LifecycleState.STARTING);
  

// Start our defined Container first  if (container != null) {
  synchronized (container) {
  container.start();
  }
  }
  

  synchronized (executors) {
  for (Executor executor: executors) {
  executor.start();
  }
  }
  

  // Start our defined Connectors second
  synchronized (connectorsLock) {
  for (Connector connector: connectors) {
  try {
  // If it has already failed, don't try and start it
  if (connector.getState() != LifecycleState.FAILED) {
  connector.start();
  }
  } catch (Exception e) {
  log.error(sm.getString(
  "standardService.connector.startFailed",
  connector), e);
  }
  }
  }
  }
  


View Code  没猜错,startInternal 其实也是调用Connectors 和Container 的对应start 方法来启动Connectors 和Container ,但是,我们还发现它调用了Executor的start 方法,其实这个Executor是对应Connectors 中管理线程的线程池,关于Connectors 的工作机制,后面再详细进行讲解。
  总的来说,Service 的启动过程也不复杂,就是对应的调用了Container 和Connectors 的init 方法和start 方法进行初始化和开启动作。
  5、tomcat 如何管理组件的生命周期?
  由上面对几个控制组件(BootStrap,Catalina 以及Server)的启动过程,我们也大概可以知道,tomcat 的启动流程是怎样的了,那么,tomcat 又是如何控制组件的生命周期的呢?例如说,我要知道某个组件的状态或者我想关闭tomcat ,这时候,组件是怎么工作的呢?其实,我们会发现,无论是StandardService 还是StandardServer ,它都有一个共同的父类--LifecycleMBeanBase,而LifecycleMBeanBase又继承了LifecycleBase,LifecycleBase实现了一个接口:Lifecycle,他们之家你的关系如下图(手动画的图可能会丑了点,各位将就看下,当然,也不太规范,因为传说中,实现接口应该是用虚线的但是我在win画图板找了好久没找到虚线就作罢了):
DSC0004.png

  其实tomcat 就是通过LifeCycle 这个接口进行组件声明周期的控制的,下面就研究下LifeCycle这个接口,直接拷贝源码上来了:

  

/*  * Licensed to the Apache Software Foundation (ASF) under one or more
  * contributor license agreements.  See the NOTICE file distributed with
  * this work for additional information regarding copyright ownership.
  * The ASF licenses this file to You under the Apache License, Version 2.0
  * (the "License"); you may not use this file except in compliance with
  * the License.  You may obtain a copy of the License at
  *
  *
http://www.apache.org/licenses/LICENSE-2.0  *
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
  * limitations under the License.
*/  
package org.apache.catalina;
  

  

  
/**
  * Common interface for component life cycle methods.  Catalina components
  * may implement this interface (as well as the appropriate interface(s) for
  * the functionality they support) in order to provide a consistent mechanism
  * to start and stop the component.
  * <br>
  * The valid state transitions for components that support {@link Lifecycle}
  * are:
  * <pre>
  *            start()
  *  -----------------------------
  *  |                           |
  *  | init()                    |
  * NEW -»-- INITIALIZING        |
  * | |           |              |     ------------------«-----------------------
  * | |           |auto          |     |                                        |
  * | |          \|/    start() \|/   \|/     auto          auto         stop() |
  * | |      INITIALIZED --»-- STARTING_PREP --»- STARTING --»- STARTED --»---  |
  * | |         |                                                            |  |
  * | |destroy()|                                                            |  |
  * | --»-----«--    ------------------------«--------------------------------  ^
  * |     |          |                                                          |
  * |     |         \|/          auto                 auto              start() |
  * |     |     STOPPING_PREP ----»---- STOPPING ------»----- STOPPED -----»-----
  * |    \|/                               ^                     |  ^
  * |     |               stop()           |                     |  |
  * |     |       --------------------------                     |  |
  * |     |       |                                              |  |
  * |     |       |    destroy()                       destroy() |  |
  * |     |    FAILED ----»------ DESTROYING ---«-----------------  |
  * |     |                        ^     |                          |
  * |     |     destroy()          |     |auto                      |
  * |     --------»-----------------    \|/                         |
  * |                                 DESTROYED                     |
  * |                                                               |
  * |                            stop()                             |
  * ---»------------------------------»------------------------------
  *
  * Any state can transition to FAILED.
  *
  * Calling start() while a component is in states STARTING_PREP, STARTING or
  * STARTED has no effect.
  *
  * Calling start() while a component is in state NEW will cause init() to be
  * called immediately after the start() method is entered.
  *
  * Calling stop() while a component is in states STOPPING_PREP, STOPPING or
  * STOPPED has no effect.
  *
  * Calling stop() while a component is in state NEW transitions the component
  * to STOPPED. This is typically encountered when a component fails to start and
  * does not start all its sub-components. When the component is stopped, it will
  * try to stop all sub-components - even those it didn't start.
  *
  * Attempting any other transition will throw {@link LifecycleException}.
  *
  * </pre>
  * The {@link LifecycleEvent}s fired during state changes are defined in the
  * methods that trigger the changed. No {@link LifecycleEvent}s are fired if the
  * attempted transition is not valid.
  *
  * @author Craig R. McClanahan
  */
  
public interface Lifecycle {
  

  

  // ----------------------------------------------------- Manifest Constants
  

  

  /**
  * The LifecycleEvent type for the "component after init" event.
  */
  public static final String BEFORE_INIT_EVENT = "before_init";
  

  

  /**
  * The LifecycleEvent type for the "component after init" event.
  */
  public static final String AFTER_INIT_EVENT = "after_init";
  

  

  /**
  * The LifecycleEvent type for the "component start" event.
  */
  public static final String START_EVENT = "start";
  

  

  /**
  * The LifecycleEvent type for the "component before start" event.
  */
  public static final String BEFORE_START_EVENT = "before_start";
  

  

  /**
  * The LifecycleEvent type for the "component after start" event.
  */
  public static final String AFTER_START_EVENT = "after_start";
  

  

  /**
  * The LifecycleEvent type for the "component stop" event.
  */
  public static final String STOP_EVENT = "stop";
  

  

  /**
  * The LifecycleEvent type for the "component before stop" event.
  */
  public static final String BEFORE_STOP_EVENT = "before_stop";
  

  

  /**
  * The LifecycleEvent type for the "component after stop" event.
  */
  public static final String AFTER_STOP_EVENT = "after_stop";
  

  

  /**
  * The LifecycleEvent type for the "component after destroy" event.
  */
  public static final String AFTER_DESTROY_EVENT = "after_destroy";
  

  

  /**
  * The LifecycleEvent type for the "component before destroy" event.
  */
  public static final String BEFORE_DESTROY_EVENT = "before_destroy";
  

  

  /**
  * The LifecycleEvent type for the "periodic" event.
  */
  public static final String PERIODIC_EVENT = "periodic";
  

  

  /**
  * The LifecycleEvent type for the "configure_start" event. Used by those
  * components that use a separate component to perform configuration and
  * need to signal when configuration should be performed - usually after
  * {@link #BEFORE_START_EVENT} and before {@link #START_EVENT}.
  */
  public static final String CONFIGURE_START_EVENT = "configure_start";
  

  

  /**
  * The LifecycleEvent type for the "configure_stop" event. Used by those
  * components that use a separate component to perform configuration and
  * need to signal when de-configuration should be performed - usually after
  * {@link #STOP_EVENT} and before {@link #AFTER_STOP_EVENT}.
  */
  public static final String CONFIGURE_STOP_EVENT = "configure_stop";
  

  

  // --------------------------------------------------------- Public Methods
  

  

  /**
  * Add a LifecycleEvent listener to this component.
  *
  * @param listener The listener to add
  */
  public void addLifecycleListener(LifecycleListener listener);
  

  

  /**
  * Get the life cycle listeners associated with this life cycle. If this
  * component has no listeners registered, a zero-length array is returned.
  */
  public LifecycleListener[] findLifecycleListeners();
  

  

  /**
  * Remove a LifecycleEvent listener from this component.
  *
  * @param listener The listener to remove
  */
  public void removeLifecycleListener(LifecycleListener listener);
  

  

  /**
  * Prepare the component for starting. This method should perform any
  * initialization required post object creation. The following
  * {@link LifecycleEvent}s will be fired in the following order:
  * <ol>
  *   <li>INIT_EVENT: On the successful completion of component
  *                   initialization.</li>
  * </ol>
  *
  * @exception LifecycleException if this component detects a fatal error
  *  that prevents this component from being used
  */
  public void init() throws LifecycleException;
  

  /**
  * Prepare for the beginning of active use of the public methods other than
  * property getters/setters and life cycle methods of this component. This
  * method should be called before any of the public methods other than
  * property getters/setters and life cycle methods of this component are
  * utilized. The following {@link LifecycleEvent}s will be fired in the
  * following order:
  * <ol>
  *   <li>BEFORE_START_EVENT: At the beginning of the method. It is as this
  *                           point the state transitions to
  *                           {@link LifecycleState#STARTING_PREP}.</li>
  *   <li>START_EVENT: During the method once it is safe to call start() for
  *                    any child components. It is at this point that the
  *                    state transitions to {@link LifecycleState#STARTING}
  *                    and that the public methods other than property
  *                    getters/setters and life cycle methods may be
  *                    used.</li>
  *   <li>AFTER_START_EVENT: At the end of the method, immediately before it
  *                          returns. It is at this point that the state
  *                          transitions to {@link LifecycleState#STARTED}.
  *                          </li>
  * </ol>
  *
  * @exception LifecycleException if this component detects a fatal error
  *  that prevents this component from being used
  */
  public void start() throws LifecycleException;
  

  

  /**
  * Gracefully terminate the active use of the public methods other than
  * property getters/setters and life cycle methods of this component. Once
  * the STOP_EVENT is fired, the public methods other than property
  * getters/setters and life cycle methods should not be used. The following
  * {@link LifecycleEvent}s will be fired in the following order:
  * <ol>
  *   <li>BEFORE_STOP_EVENT: At the beginning of the method. It is at this
  *                          point that the state transitions to
  *                          {@link LifecycleState#STOPPING_PREP}.</li>
  *   <li>STOP_EVENT: During the method once it is safe to call stop() for
  *                   any child components. It is at this point that the
  *                   state transitions to {@link LifecycleState#STOPPING}
  *                   and that the public methods other than property
  *                   getters/setters and life cycle methods may no longer be
  *                   used.</li>
  *   <li>AFTER_STOP_EVENT: At the end of the method, immediately before it
  *                         returns. It is at this point that the state
  *                         transitions to {@link LifecycleState#STOPPED}.
  *                         </li>
  * </ol>
  *
  * Note that if transitioning from {@link LifecycleState#FAILED} then the
  * three events above will be fired but the component will transition
  * directly from {@link LifecycleState#FAILED} to
  * {@link LifecycleState#STOPPING}, bypassing
  * {@link LifecycleState#STOPPING_PREP}
  *
  * @exception LifecycleException if this component detects a fatal error
  *  that needs to be reported
  */
  public void stop() throws LifecycleException;
  

  /**
  * Prepare to discard the object. The following {@link LifecycleEvent}s will
  * be fired in the following order:
  * <ol>
  *   <li>DESTROY_EVENT: On the successful completion of component
  *                      destruction.</li>
  * </ol>
  *
  * @exception LifecycleException if this component detects a fatal error
  *  that prevents this component from being used
  */
  public void destroy() throws LifecycleException;
  

  

  /**
  * Obtain the current state of the source component.
  *
  * @return The current state of the source component.
  */
  public LifecycleState getState();
  

  

  /**
  * Obtain a textual representation of the current component state. Useful
  * for JMX.
  */
  public String getStateName();
  

  

  /**
  * Marker interface used to indicate that the instance should only be used
  * once. Calling {@link #stop()} on an instance that supports this interface
  * will automatically call {@link #destroy()} after {@link #stop()}
  * completes.
  */
  public interface SingleUse {
  }
  
}
  


View Code  查看源码,我们可以看到,该接口定义了一组常量,用于 表示tomcat 目前的运行状态,各个运行状态之间的关系其实在注释中有一个非常生动的示意图,就下面这个截图(大神简直用处文本编辑器的新境界有木有!):
DSC0005.png

  OK,具体各个状态的含义我就不累赘了,这张图说明了一切。启动的机制上面我们已经很详细地讨论过了,下面就以stop tomcat这个过程为例,研究下tomcat 如何通过Lifecycle这个接口,控制组件的生命周期。查看源码可以发现,我们提到的核心组件,包括Connectors 和Container 等组件,它都有有实现或者继承(Container是个接口,它继承了LifeCycle 这个接口)LifeCycle 这个接口,当我们从最外层即Bootstrap 停止容器工作的时候,组件之间会依次调用它所管理的组件(例如,Bootstrap 管理着Catalina ,Server 管理着Service)LifeCycle接口的stop 方法,和启动的过程非常类似,当然,实际的关闭过程是非常复杂的,Connectors 要关闭连接,要销毁线程,Container 要销毁业务类以及涉及到的线程等等,所以我们在实际开发中会发现,如果我们强制stop tomcat (例如在eclipse等IDE中直接点击停止或者直接强退eclipse),那么tomcat 的内部资源时不能有效释放的,很多时候IDE出现所谓的Pemgen space 错误或者oracle中常见的锁库现象便有可能是没有正常释放资源造成的(反正我就试过很多次由于强退tomcat 导致oracle 的锁库的现象)。
  废话说了那么多,这里简单总结下tomcat 管理组件生命周期的方法:各个组件都会实现LifeCycle这个接口,而组件之间便是通过这个接口进行组件的生命周期控制的,最顶层的控制类是Bootstrap ,由上而下控制所有组件的停止与开启。
  好了,到这里,我们可以讨论下一开始那个问题了:为什么tomcat 要把Bootstrap 、Catalina 以及Server 这三个控制类独立出来呢?其实,很多时候,我们都会觉得开源框架的组件或者接口划分有点莫名其妙:为什么要多出这部分组件处理?这个接口有什么用?其实造成这些错觉的原因是,我们很多时候都没有考虑扩展性等问题,我们看问题的角度和境界也没有框架作者那么高,就上面这个例子,个人觉得(不一定对啊,欢迎指正交流)大概是这个原因吧:Tomcat 的启动方式应该允许有很多个(只是平常我们接触的可能就那么一种),Bootstrap 是一个适配器,对用户来说,tomcat 启动过程是透明的,我只需要调用Bootstrap 即可,如果Bootstrap 和Catalina 合在一起,那不同的启动方式,肯定要对应不同的Catalina ,而这些启动方式用户无须知道,所以就抽象出Bootstrap 这个适配器进行调用不同的启动方式了;至于Server 和 Catalina 为什么要分开不能合在一起呢?我理解是,其实这也很好理解了,不同的启动方式,启动都是同一个Server ,所以要分开。
  当然,其实理解开源框架的那些抽象类、接口,我们最后一面向对象的思维理解,比方说上面的tomcat 类组织结构中,如果把tomcat 比作一个电器,Bootstrap 是一个总开关,Catalina 是开关到电器之间的控制电路,Server代表这个电器,那么我们就知道为什么不能将三者合在一起了:分开更符合现实人的思维,更符合面向对象的思维,因为,开关,开关和电器之间的电路以及电器本身,三者是完全不同的对象。
  三、研究tomcat 组织结构中得到的一点启迪
  1、模板方法。
  我们通过上面研究可以发现,类之间的继承,父类很多时候会调用一个模板方法,这个模板方法具体实现会由子类决定,这就体现出一个很精粹的思想:总体流程在高层设定,而调用者或者继承者只需要实现某个方法便可以达到某个目的,而这就是框架--框架规定了程序的整个大体架构流程,调用者灵活自己有不同实现  
  当然,模板方法是实现这种效果的常用手段,我们会发现也会利用接口来实现类似的功能,例如spring 中的拦截器,我们只需要实现Interceptor这个接口即可实现拦截功能,这便是类似于模板方法,spring 已经在运行的适当时刻调用Interceptor了,我们只需要专注于我们的Interceptor要干什么就好了,是不是很棒。
  2、面向对象的思维
  刚学java 的时候,觉得,面向过程?面向对象?好像没什么区别嘛,就是一个封装了一下,在一个class 里面,一个就是封装在函数function 里面,还不一样是代码?还不一样地按部就班一步步走?但是,当我们使用了一些开源框架并研究它内部的原理时,你才会正真领悟到面向对象的精粹,才会发现:我靠,还有这种操作!就如上面研究tomcat 的启动过程,我们如果以面向对象的思维去理解,那就好理解了:tomcat 启动,那还不简单,就三个类,一个给客户用的,开关,Bootstrap;一个tomcat 本身,抽象为server;一个就是连接两者的Catalina ,它负责具体去如何关tomcat。
  当然,面向对象的思维强大之处还有很多地方,个人也仅仅了解了皮毛中的皮毛,不足之处,各位大佬指正下吧!
  本来还想写完tomcat 处理请求的过程,不过这篇博客太长了,所以这部分还是放到下一篇博客中讨论吧,欢迎继续关注我的下一篇关于tomcat 处理请求的博客。
  end之前,喊下口号,秋招雄起!所有学生党老铁拿到好offer!

运维网声明 1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com

所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其承担任何法律责任,如涉及侵犯版权等问题,请您及时通知我们,我们将立即处理,联系人Email:kefu@iyunv.com,QQ:1061981298 本贴地址:https://www.yunweiku.com/thread-428019-1-1.html 上篇帖子: Linux Tomcat安装,Linux配置Tomcat,Linux Tomcat修改内存,Linux tomcat修改端口 下篇帖子: Tomcat启动后,web加载顺序
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

扫码加入运维网微信交流群X

扫码加入运维网微信交流群

扫描二维码加入运维网微信交流群,最新一手资源尽在官方微信交流群!快快加入我们吧...

扫描微信二维码查看详情

客服E-mail:kefu@iyunv.com 客服QQ:1061981298


QQ群⑦:运维网交流群⑦ QQ群⑧:运维网交流群⑧ k8s群:运维网kubernetes交流群


提醒:禁止发布任何违反国家法律、法规的言论与图片等内容;本站内容均来自个人观点与网络等信息,非本站认同之观点.


本站大部分资源是网友从网上搜集分享而来,其版权均归原作者及其网站所有,我们尊重他人的合法权益,如有内容侵犯您的合法权益,请及时与我们联系进行核实删除!



合作伙伴: 青云cloud

快速回复 返回顶部 返回列表