zoukankan      html  css  js  c++  java
  • tomcat原理解析(二):整体架构

    一 整体结构

    前面tomcat实现原理(一)里面描述了整个tomcat接受一个http请求的简单处理,这里面我们讲下整个tomcat的架构,以便对整体结构有宏观的了解。tomat里面由很多个容器结合在一起,主要有server,service,context,host,engine,wrapper,connector这7个容器来组装。当然了tomcat里面还有其它容器这里就不一一列举,因为我只看重点的。这7个容器存着父子关系,即可以通过当前容器找自己的父容器和自己的子容器。说到这我画了一个简单的结构图,让这种关系更加直观。如下:

    根据图可以看出server是最外层的一个容器,它里面可以包含了Service容器,Service容器里面又包含了Connector和Engine,Engine容器里面又包含了Host,Host容器包含Context,Context容器包含Wrapper容器。就好比一个人有最外层的躯体,躯体里面有心脏,肺,胃,肾脏等等很多个个功能器件。

    二 组件描述

           这里主要通过tomcat的源码来分析这7个容器是通过什么样的机制来组织在一起的,相互的包含的关系怎么处理。

     

          1.server组件

          server就像一个架子,很多其它子容器都装配在这个架子上。同时这个容器又暴露了一些入口对外提供服务。比如初始化服务,启动服务,停止服务等。源码中的server是一个接口,如下图:

    查看Server接口类它还继承着Lifecycle接口,详情如下图:


    Lifecycle接口控制着各个组件的生命周期,比如接口中的抽象方法init()初始化,start()启动,stop()停止,destory()销毁。 Server的标准实现类为StandardServer,细读StandardServer里面的接口addService,findServices的实现代码,可以看出这Service和 Server有着关联,Server可以有多个Service以数组的形式保存在其中。

     

    [html] view plain copy
    1. public void addService(Service service) {  
    2.   
    3.         service.setServer(this);  
    4.   
    5.         synchronized (services) {  
    6.             Service results[] = new Service[services.length + 1];  
    7.             System.arraycopy(services, 0, results, 0, services.length);  
    8.             results[services.length] = service;  
    9.             services = results;  
    10.   
    11.             if (getState().isAvailable()) {  
    12.                 try {  
    13.                     service.start();  
    14.                 } catch (LifecycleException e) {  
    15.                     // Ignore  
    16.                 }  
    17.             }  
    18.   
    19.             // Report this property change to interested listeners  
    20.             support.firePropertyChange("service", null, service);  
    21.         }  
    22.   
    23.     }<span style="font-family:Microsoft YaHei;font-size:12px;">  
    24. </span>  

     

    1.将设置进来的Service 对象设置父组件为Server

    2.重新构建了一个Service数组,然后将老的Service拷贝到新的数组中,同时追加新的Service组件

    3.根据当前组件的生命状态,判断新加入的组件是否需要启动

        2.Service组件

            service接口它也继承Lifecycle接口。 前面说过了service组件的父组件是server组件,结构如下图:

    它的标准实现是StandardService类,查看接口抽象方法addConnector的具体实现可以看出通过它可以给service容器添加connector容器,这里的connector就主要负责接收浏览器客户端发过来的http请求,仔细看里面的源码可以发现它接到请求时生成了一个socket对象,并将这个socket对象放入了一个线程中,同时线程会存入一个线程池来处理这次响应,并根据请求的详情信息来定位响应资源,响应的资源通过Engine容器给出。它就像浏览器和 Engine容器的桥梁,具体如何响应请求的会通过后面的章节来细说。先看是怎么将connector容器绑定到service中。 如下面代码:

    [html] view plain copy
    1. public void addConnector(Connector connector) {  
    2.         synchronized (connectors) {  
    3.             connector.setService(this);  
    4.             Connector results[] = new Connector[connectors.length + 1];  
    5.             System.arraycopy(connectors, 0, results, 0, connectors.length);  
    6.             results[connectors.length] = connector;  
    7.             connectors = results;  
    8.   
    9.             if (getState().isAvailable()) {  
    10.                 try {  
    11.                     ((Lifecycle) connector).start();  
    12.                 } catch (LifecycleException e) {  
    13.                     log.error("Connector.start", e);  
    14.                 }  
    15.             }  
    16.   
    17.             // Report this property change to interested listeners  
    18.             support.firePropertyChange("connector", null, connector);  
    19.         }  
    20.   
    21.     }  

    1.给当前入去的Connector主键设置父容器service.
    2.根据原有Connector集合的大小加上新加入的容器,从新构建了一个Connector数组。
    3.将旧的数据拷贝到新的容器数组中,再添加新加的容器
    4.然后根据当前容器的生命状态判断是否要启动新加入的容器
    这里存放connector组件为什么用数组而不用List来存放不是太明白。感觉跟server管理service组件操作一样。看完所有的抽象接口是不是感觉有些奇怪,按照前面给的整体架构图怎么没看到给Service添加Engine组件的入口。查看Engine的标准实现类StandardEngine其实是实现了Container接口的,所以这里应该是通过setContainer接口设置进去的,其源码如下:

    [html] view plain copy
    1. public void setContainer(Container container) {  
    2.   
    3.         Container oldContainer = this.container;  
    4.         if ((oldContainer != null) && (oldContainer instanceof Engine))  
    5.             ((Engine) oldContainer).setService(null);  
    6.         this.container = container;  
    7.         if ((this.container != null) && (this.container instanceof Engine))  
    8.             ((Engine) this.container).setService(this);  
    9.         if (getState().isAvailable() && (this.container != null)) {  
    10.             try {  
    11.                 this.container.start();  
    12.             } catch (LifecycleException e) {  
    13.                 // Ignore  
    14.             }  
    15.         }  
    16.         if (getState().isAvailable() && (oldContainer != null)) {  
    17.             try {  
    18.                 oldContainer.stop();  
    19.             } catch (LifecycleException e) {  
    20.                 // Ignore  
    21.             }  
    22.         }  
    23.   
    24.         // Report this property change to interested listeners  
    25.         support.firePropertyChange("container", oldContainer, this.container);  
    26.   
    27.     }  

     

    1.将原有的Container(Engine)对象 的父对象置为空
    2.同时将旧的Container对象覆盖掉,变成新注入的Container对象
    3.同时根据当前组件的生命周期来判断是否将新注入的容器启动

    3.Engine组件
    Engine接口类为该组件的抽象接口,它继承Container接口跟前面描述的两个组件似乎有些不一样,查看源码后发现Container接口也继承了Lifecycle接口。Engine结构如下图:

    它的标准实现是StandardEngine类,查setService接口的具体实现,service组件设置进Engine组件中,让两者之间关联。怎么没有看到添加子组件的入口呢?我们查看StandardEngine实现类中,它继承了ContainerBase类,而这个类实现了Container接口。在StandardEngine类中看到了addChild方法的实现 如下代码:

    [html] view plain copy
    1. @Override  
    2.     public void addChild(Container child) {  
    3.   
    4.         if (!(child instanceof Host))  
    5.             throw new IllegalArgumentException  
    6.                 (sm.getString("standardEngine.notHost"));  
    7.         super.addChild(child);  
    8.   
    9.     }<span style="font-family:Microsoft YaHei;font-size:12px;">  
    10. </span>  

    通过这个方法来为Engine添加子组件

    1.判断当前传入的组件是否是Host类型的,否则抛异常

    2.调用服务类中的addChild方法,同时将child变量传递到父类中处理,我们继续看父类ContainerBase中的代码。

    [html] view plain copy
    1. public void addChild(Container child) {  
    2.         if (Globals.IS_SECURITY_ENABLED) {  
    3.             PrivilegedAction<Voiddp =  
    4.                 new PrivilegedAddChild(child);  
    5.             AccessController.doPrivileged(dp);  
    6.         } else {  
    7.             addChildInternal(child);  
    8.         }  
    9.     }  
    10.   
    11.     private void addChildInternal(Container child) {  
    12.   
    13.         if( log.isDebugEnabled() )  
    14.             log.debug("Add child " + child + " " + this);  
    15.         synchronized(children) {  
    16.             if (children.get(child.getName()) != null)  
    17.                 throw new IllegalArgumentException("addChild:  Child name '" +  
    18.                                                    child.getName() +  
    19.                                                    "' is not unique");  
    20.             child.setParent(this);  // May throw IAE  
    21.             children.put(child.getName(), child);  
    22.   
    23.             // Start child  
    24.             if ((getState().isAvailable() ||  
    25.                     LifecycleState.STARTING_PREP.equals(getState())) &&  
    26.                     startChildren) {  
    27.                 boolean success = false;  
    28.                 try {  
    29.                     child.start();  
    30.                     success = true;  
    31.                 } catch (LifecycleException e) {  
    32.                     log.error("ContainerBase.addChild: start: ", e);  
    33.                     throw new IllegalStateException  
    34.                         ("ContainerBase.addChild: start: " + e);  
    35.                 } finally {  
    36.                     if (!success) {  
    37.                         children.remove(child.getName());  
    38.                     }  
    39.                 }  
    40.             }  
    41.   
    42.             fireContainerEvent(ADD_CHILD_EVENT, child);  
    43.         }  
    44.   
    45.     }  

    最终它调用了addChildInternal方法来处理。

    child.setParent(this);
    children.put(child.getName(), child);
    1.这里传入host子组件的同时,给host子组件设置了父组件
    2.将子组件保存在children变量中,这里的children其实就是个HashMap对象。
    看到这里也就明白了,所有继承ContainerBase类的组件在添加子组件都是依赖父类中的MAP对象来保存的.

    4.Host组件
    Host类为该组件的接口定义,也继承了Container类,它的标准实现是StandardHost类,继承ContainerBase类。host结构如下图所示:

    因为该标准实现类StandardHost也继承了ContainerBase类,所以该组件的子组件添加也依赖父类中的Map来保存,跟Engine组件管理子组件是一样的。每一个HOST相当于一个主机,并负责展开和运行多个部署进去的war包。每个主机下面有多个部署的应用,一个应用就对应着一个Context。通过context将多个应用分隔开。所以host的子组件就是context,在通过ContainerBase类的addChild方法来添加context子容器的同时也给它设置了父容器host对象。

    5.Context容器

    context接口继承了Container,StandardContext类为是Context的标准实现,它也继承了ContainerBase类,所以给改容器注入子容器也是通过containerBase类中的addaddChild来处理的。Context容器代表着一个servlet容器,它为servlet运行提供环境。context管理着里面servlet实例,servlet实例在context中以Wrapper形式存在。那么一个http请求是怎么找到对应的servlet实例的呢?后面章节再来详细描述

     

    6.Wrapper容器

    Wrapper接口类也继承Container类,它的标准实现类是StandardWrapper,也继承了ContainerBase类。因为Wrapper容器是最底层的容器了,所以它不存在子容器。Wrapper 代表一个Servlet,它负责管理一个 Servlet,包括的 Servlet的装载、初始化、执行以及资源回收。

     

     

    三 组件相关类结构

         上面小节描述将各个容器编织到一起,阅读相关容器的代码结构可以整理出如下类的继承关系。从类的关系图可以看出所有的容器都实现了Lifecycle接口,该接口定义了控制着tomcat的初始化,启动,停止,销毁等抽象操作。具体的实现都留给了子类实现。
     
          
     
     
    仔细查看上面的结构图,可以发现LifecycleBase比较陌生,在前面章节没怎么提到过。LifecycleBase类实现了Lifecycle接口,它实现了Lifecycle接口中的init()start()stop()destroy()。我们看看LifecycleBase中的类是怎么实现的,源码如下:
    [java] view plain copy
    1. /* 
    2.  * Licensed to the Apache Software Foundation (ASF) under one or more 
    3.  * contributor license agreements.  See the NOTICE file distributed with 
    4.  * this work for additional information regarding copyright ownership. 
    5.  * The ASF licenses this file to You under the Apache License, Version 2.0 
    6.  * (the "License"); you may not use this file except in compliance with 
    7.  * the License.  You may obtain a copy of the License at 
    8.  *  
    9.  *      http://www.apache.org/licenses/LICENSE-2.0 
    10.  *  
    11.  * Unless required by applicable law or agreed to in writing, software 
    12.  * distributed under the License is distributed on an "AS IS" BASIS, 
    13.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
    14.  * See the License for the specific language governing permissions and 
    15.  * limitations under the License. 
    16.  */  
    17.   
    18. package org.apache.catalina.util;  
    19.   
    20. import org.apache.catalina.Lifecycle;  
    21. import org.apache.catalina.LifecycleException;  
    22. import org.apache.catalina.LifecycleListener;  
    23. import org.apache.catalina.LifecycleState;  
    24. import org.apache.juli.logging.Log;  
    25. import org.apache.juli.logging.LogFactory;  
    26. import org.apache.tomcat.util.res.StringManager;  
    27.   
    28.   
    29. /** 
    30.  * Base implementation of the {@link Lifecycle} interface that implements the 
    31.  * state transition rules for {@link Lifecycle#start()} and 
    32.  * {@link Lifecycle#stop()} 
    33.  */  
    34. public abstract class LifecycleBase implements Lifecycle {  
    35.   
    36.     private static Log log = LogFactory.getLog(LifecycleBase.class);  
    37.       
    38.     private static StringManager sm =  
    39.         StringManager.getManager("org.apache.catalina.util");  
    40.   
    41.   
    42.     /** 
    43.      * Used to handle firing lifecycle events. 
    44.      * TODO: Consider merging LifecycleSupport into this class. 
    45.      */  
    46.     private LifecycleSupport lifecycle = new LifecycleSupport(this);  
    47.   
    48.   
    49.     /** 
    50.      * The current state of the source component. 
    51.      */  
    52.     private volatile LifecycleState state = LifecycleState.NEW;  
    53.   
    54.   
    55.     /** 
    56.      * {@inheritDoc} 
    57.      */  
    58.     @Override  
    59.     public void addLifecycleListener(LifecycleListener listener) {  
    60.         lifecycle.addLifecycleListener(listener);  
    61.     }  
    62.   
    63.   
    64.     /** 
    65.      * {@inheritDoc} 
    66.      */  
    67.     @Override  
    68.     public LifecycleListener[] findLifecycleListeners() {  
    69.         return lifecycle.findLifecycleListeners();  
    70.     }  
    71.   
    72.   
    73.     /** 
    74.      * {@inheritDoc} 
    75.      */  
    76.     @Override  
    77.     public void removeLifecycleListener(LifecycleListener listener) {  
    78.         lifecycle.removeLifecycleListener(listener);  
    79.     }  
    80.   
    81.       
    82.     /** 
    83.      * Allow sub classes to fire {@link Lifecycle} events. 
    84.      *  
    85.      * @param type  Event type 
    86.      * @param data  Data associated with event. 
    87.      */  
    88.     protected void fireLifecycleEvent(String type, Object data) {  
    89.         lifecycle.fireLifecycleEvent(type, data);  
    90.     }  
    91.   
    92.       
    93.     public synchronized final void init() throws LifecycleException {  
    94.         if (!state.equals(LifecycleState.NEW)) {  
    95.             invalidTransition(Lifecycle.INIT_EVENT);  
    96.         }  
    97.        //1.实现初始化的方法,最终是调用了当前类的initInternal方法,发现  
    98. initInternal抽象的  
    99.   
    100.   
    101.         initInternal();  
    102.           
    103.         setState(LifecycleState.INITIALIZED);  
    104.     }  
    105.       
    106.       
    107.     protected abstract void initInternal() throws LifecycleException;  
    108.       
    109.     /** 
    110.      * {@inheritDoc} 
    111.      */  
    112.     @Override  
    113.     public synchronized final void start() throws LifecycleException {  
    114.           
    115.         if (LifecycleState.STARTING_PREP.equals(state) ||  
    116.                 LifecycleState.STARTING.equals(state) ||  
    117.                 LifecycleState.STARTED.equals(state)) {  
    118.               
    119.             if (log.isDebugEnabled()) {  
    120.                 Exception e = new LifecycleException();  
    121.                 log.debug(sm.getString("lifecycleBase.alreadyStarted",  
    122.                         toString()), e);  
    123.             } else if (log.isInfoEnabled()) {  
    124.                 log.info(sm.getString("lifecycleBase.alreadyStarted",  
    125.                         toString()));  
    126.             }  
    127.               
    128.             return;  
    129.         }  
    130.           
    131.         if (state.equals(LifecycleState.NEW)) {  
    132.             init();  
    133.         } else if (!state.equals(LifecycleState.INITIALIZED) &&  
    134.                 !state.equals(LifecycleState.STOPPED)) {  
    135.             invalidTransition(Lifecycle.BEFORE_START_EVENT);  
    136.         }  
    137.   
    138.         setState(LifecycleState.STARTING_PREP);  
    139.   
    140.         try {  
    141.  //2.实现初始化的方法,最终是调用了当前类的startInternal方法,发现startInternal抽象的  
    142.   
    143.             startInternal();  
    144.         } catch (LifecycleException e) {  
    145.             setState(LifecycleState.FAILED);  
    146.             throw e;  
    147.         }  
    148.   
    149.         if (state.equals(LifecycleState.FAILED) ||  
    150.                 state.equals(LifecycleState.MUST_STOP)) {  
    151.             stop();  
    152.         } else {  
    153.             // Shouldn't be necessary but acts as a check that sub-classes are  
    154.             // doing what they are supposed to.  
    155.             if (!state.equals(LifecycleState.STARTING)) {  
    156.                 invalidTransition(Lifecycle.AFTER_START_EVENT);  
    157.             }  
    158.               
    159.             setState(LifecycleState.STARTED);  
    160.         }  
    161.     }  
    162.   
    163.   
    164.     /** 
    165.      * Sub-classes must ensure that: 
    166.      * <ul> 
    167.      * <li>the {@link Lifecycle#START_EVENT} is fired during the execution of 
    168.      *     this method</li> 
    169.      * <li>the state is changed to {@link LifecycleState#STARTING} when the 
    170.      *     {@link Lifecycle#START_EVENT} is fired 
    171.      * </ul> 
    172.      *  
    173.      * If a component fails to start it may either throw a 
    174.      * {@link LifecycleException} which will cause it's parent to fail to start 
    175.      * or it can place itself in the error state in which case {@link #stop()} 
    176.      * will be called on the failed component but the parent component will 
    177.      * continue to start normally. 
    178.      *  
    179.      * @throws LifecycleException 
    180.      */  
    181.     protected abstract void startInternal() throws LifecycleException;  
    182.   
    183.   
    184.     /** 
    185.      * {@inheritDoc} 
    186.      */  
    187.     @Override  
    188.     public synchronized final void stop() throws LifecycleException {  
    189.   
    190.         if (LifecycleState.STOPPING_PREP.equals(state) ||  
    191.                 LifecycleState.STOPPING.equals(state) ||  
    192.                 LifecycleState.STOPPED.equals(state)) {  
    193.   
    194.             if (log.isDebugEnabled()) {  
    195.                 Exception e = new LifecycleException();  
    196.                 log.debug(sm.getString("lifecycleBase.alreadyStopped",  
    197.                         toString()), e);  
    198.             } else if (log.isInfoEnabled()) {  
    199.                 log.info(sm.getString("lifecycleBase.alreadyStopped",  
    200.                         toString()));  
    201.             }  
    202.               
    203.             return;  
    204.         }  
    205.           
    206.         if (state.equals(LifecycleState.NEW)) {  
    207.             state = LifecycleState.STOPPED;  
    208.             return;  
    209.         }  
    210.   
    211.         if (!state.equals(LifecycleState.STARTED) &&  
    212.                 !state.equals(LifecycleState.FAILED) &&  
    213.                 !state.equals(LifecycleState.MUST_STOP)) {  
    214.             invalidTransition(Lifecycle.BEFORE_STOP_EVENT);  
    215.         }  
    216.           
    217.         setState(LifecycleState.STOPPING_PREP);  
    218.  //3.实现初始化的方法,最终是调用了当前类的stopInternal方法,发现stopInternal抽象的  
    219.   
    220.         stopInternal();  
    221.   
    222.         if (state.equals(LifecycleState.MUST_DESTROY)) {  
    223.             // Complete stop process first  
    224.             setState(LifecycleState.STOPPED);  
    225.   
    226.             destroy();  
    227.         } else {  
    228.             // Shouldn't be necessary but acts as a check that sub-classes are doing  
    229.             // what they are supposed to.  
    230.             if (!state.equals(LifecycleState.STOPPING)) {  
    231.                 invalidTransition(Lifecycle.AFTER_STOP_EVENT);  
    232.             }  
    233.   
    234.             setState(LifecycleState.STOPPED);  
    235.         }  
    236.     }  
    237.   
    238.   
    239.     /** 
    240.      * Sub-classes must ensure that: 
    241.      * <ul> 
    242.      * <li>the {@link Lifecycle#STOP_EVENT} is fired during the execution of 
    243.      *     this method</li> 
    244.      * <li>the state is changed to {@link LifecycleState#STOPPING} when the 
    245.      *     {@link Lifecycle#STOP_EVENT} is fired 
    246.      * </ul> 
    247.      *  
    248.      * @throws LifecycleException 
    249.      */  
    250.     protected abstract void stopInternal() throws LifecycleException;  
    251.   
    252.   
    253.     public synchronized final void destroy() throws LifecycleException {  
    254.         if (LifecycleState.DESTROYED.equals(state)) {  
    255.   
    256.             if (log.isDebugEnabled()) {  
    257.                 Exception e = new LifecycleException();  
    258.                 log.debug(sm.getString("lifecycleBase.alreadyDestroyed",  
    259.                         toString()), e);  
    260.             } else if (log.isInfoEnabled()) {  
    261.                 log.info(sm.getString("lifecycleBase.alreadyDestroyed",  
    262.                         toString()));  
    263.             }  
    264.               
    265.             return;  
    266.         }  
    267.           
    268.         if (!state.equals(LifecycleState.STOPPED) &&  
    269.                 !state.equals(LifecycleState.FAILED) &&  
    270.                 !state.equals(LifecycleState.NEW)) {  
    271.             invalidTransition(Lifecycle.DESTROY_EVENT);  
    272.         }  
    273.  //4.实现初始化的方法,最终是调用了当前类的destroyInternal方法,发现destroyInternal抽象的  
    274.   
    275.         destroyInternal();  
    276.           
    277.         setState(LifecycleState.DESTROYED);  
    278.     }  
    279.       
    280.       
    281.     protected abstract void destroyInternal() throws LifecycleException;  
    282.       
    283.     /** 
    284.      * {@inheritDoc} 
    285.      */  
    286.     public LifecycleState getState() {  
    287.         return state;  
    288.     }  
    289.   
    290.   
    291.     /** 
    292.      * Provides a mechanism for sub-classes to update the component state. 
    293.      * Calling this method will automatically fire any associated 
    294.      * {@link Lifecycle} event. 
    295.      *  
    296.      * @param state The new state for this component 
    297.      */  
    298.     protected synchronized void setState(LifecycleState state) {  
    299.         setState(state, null);  
    300.     }  
    301.       
    302.       
    303.     /** 
    304.      * Provides a mechanism for sub-classes to update the component state. 
    305.      * Calling this method will automatically fire any associated 
    306.      * {@link Lifecycle} event. 
    307.      *  
    308.      * @param state The new state for this component 
    309.      * @param data  The data to pass to the associated {@link Lifecycle} event 
    310.      */  
    311.     protected synchronized void setState(LifecycleState state, Object data) {  
    312.           
    313.         if (log.isDebugEnabled()) {  
    314.             log.debug(sm.getString("lifecycleBase.setState", this, state));  
    315.         }  
    316.         this.state = state;  
    317.         String lifecycleEvent = state.getLifecycleEvent();  
    318.         if (lifecycleEvent != null) {  
    319.             fireLifecycleEvent(lifecycleEvent, data);  
    320.         }  
    321.     }  
    322.   
    323.       
    324.     private void invalidTransition(String type) throws LifecycleException {  
    325.         String msg = sm.getString("lifecycleBase.invalidTransition", type,  
    326.                 toString(), state);  
    327.         throw new LifecycleException(msg);  
    328.     }  
    329. }  
    阅读以上LifecycleBase类中的源码,在第1,2,3,4处分别实现了init(),start(),stop(),destroy()等方法。分析其方法体中内容,实现代码最终还是调用了当前类中的initInternal(),startInternal(),stopInternal(),destroyInternal()等方法,而且这些方法还是抽象的。也就是说在执行这些方法时,最终是执行子类的具体实现。这貌似就是设计模式中的抽象模版方法模式哦!
     

    四 总结

     
          阅读到这,大家应该对tomcat的整体架构和各个容器如何交织在一起有了一定的了解。并在第三节我粗略的分析了下容器的初始化,启动,停止,销毁等生命周期的控制,从结构上看使用了抽象模版方法模式,所以掌握23种设计模式还是非常有用的在阅读源码或自己做设计方面 。了解了这些您是否会再思考,这些容器是什么时候或什么事件来触发装配在一起操作的哇!。下一个章节就来说说容器的初始化吧,它会给出前面的答案!
  • 相关阅读:
    SQL Server 使用日志传送
    SQL Server 2008 R2 主从数据库同步
    JavaScript及C# URI编码详解
    sql server日期时间函数
    ASP.NET Core在Azure Kubernetes Service中的部署和管理
    [Nuget]Nuget命令行工具安装
    利用HttpListener创建简单的HTTP服务
    短链接实现
    [ubuntu]中文用户目录路径改英文
    [ubuntu]deb软件源
  • 原文地址:https://www.cnblogs.com/csguo/p/7499435.html
Copyright © 2011-2022 走看看