zoukankan      html  css  js  c++  java
  • Tomcat 核心组件 Connector

    Tomcat

    Connector是Tomcat的连接器,其主要任务是负责处理浏览器发送过来的请求,并创建一个Request和Response的对象用于和浏览器交换数据,然后产生一个线程用于处理请求,Connector会把Request和Response对象传递给该线程,该线程的具体的处理过程是Container容器的事了。执行过程分为以下几个步骤:

    1. 实例化Connector,构造一个Connector对象。
    2. 调用Connector的initIntenal方法,初始化Connetor。
    3. 调用ProtocolHanlder的init方法,完成ProtocolHanlder的初始化。这个过程包括了创建线程池并创建一个线程处理浏览器请求。
    4. 调用Connector的startIntenal方法,启动Connector。
    5. 调用ProtocolHandler的start方法,启动Protocolhanlder。
    6. 调用MapperListener的start方法,启动监听器程序。

    Connect执行过程的时序图:
    Connect执行过程

    注意:由于Tomcat还支持AJP协议,但为了简化,我画的这个序列图是基于Http协议的,这也是我们在Web开发中接触最多的协议了。

    在深入Connector之前我们先看看Connector类的结构:
    Connector

    Connector1

    既然是处理浏览器请求,那么需要支持http协议,在Tomcat中有两种协议处理器:HTTP/1.1与AJP/1.3协议处理器。在server.xml中已经指明tomcat所支持的两种协议:

        <Connector port="8080" protocol="HTTP/1.1"
                   connectionTimeout="20000"
                   redirectPort="8443" />
        <Connector port="8009" protocol="AJP/1.3" redirectPort="8443" />

    在tomcat中是怎么样分别处理这两种协议的呢,我们可以ProtocolHanlder类中找到答案:
    ProtocolHanlder

    图中被选中的就是Tomcat默认使用协议处理器,其实现过程与Java标准Socket编程是一样的,在tomcat中可以使用Connetor类的setProtocol方法,看看源码就知道了:

        public void setProtocol(String protocol) {
            if (AprLifecycleListener.isAprAvailable()) {
                if ("HTTP/1.1".equals(protocol)) {
                    setProtocolHandlerClassName
                        ("org.apache.coyote.http11.Http11AprProtocol");
                } else if ("AJP/1.3".equals(protocol)) {
                    setProtocolHandlerClassName
                        ("org.apache.coyote.ajp.AjpAprProtocol");
                } else if (protocol != null) {
                    setProtocolHandlerClassName(protocol);
                } else {
                    setProtocolHandlerClassName
                        ("org.apache.coyote.http11.Http11AprProtocol");
                }
            } else {
                if ("HTTP/1.1".equals(protocol)) {
                    setProtocolHandlerClassName
                        ("org.apache.coyote.http11.Http11Protocol");
                } else if ("AJP/1.3".equals(protocol)) {
                    setProtocolHandlerClassName
                        ("org.apache.coyote.ajp.AjpProtocol");
                } else if (protocol != null) {
                    setProtocolHandlerClassName(protocol);
                }
            }
        }

    从第2个if子句的最后一个else可以知道tomcat默认使用的是http1.1协议。
    我们再看看Connector的初始化过程:

        @Override
        protected void initInternal() throws LifecycleException {
            super.initInternal();
            // Initialize adapter
            adapter = new CoyoteAdapter(this);
            protocolHandler.setAdapter(adapter);
            // Make sure parseBodyMethodsSet has a default
            if( null == parseBodyMethodsSet ) {
                setParseBodyMethods(getParseBodyMethods());
            }
            if (protocolHandler.isAprRequired() &&
                    !AprLifecycleListener.isAprAvailable()) {
                throw new LifecycleException(
                        sm.getString("coyoteConnector.protocolHandlerNoApr",
                                getProtocolHandlerClassName()));
            }
            try {
                protocolHandler.init();
            } catch (Exception e) {
                throw new LifecycleException
                    (sm.getString
                     ("coyoteConnector.protocolHandlerInitializationFailed"), e);
            }
            // Initialize mapper listener
            mapperListener.init();
        }

    从这段代码中可以看到:首先调用父类org.apache.catalina.util.LifecycleMBeanBase的初始化方法,然后创建一个Adapter,然后设置protocolHanlder(协议处理器)的Adapter,同时判断传过来的请求的请求方法(比如get或者post),如果没有指明请求方法,默认使用post处理,然后调用protocolHanlder的初始化方法,最后调用mapperListener的初始化方法,而mapperListener的初始化方法调用的是org.apache.catalina.util.LifecycleBase的init方法,我们重点关注protocolHanlder的初始化方法,具体是实现在AbstractProtocol抽象类中,其直接子类有AbstractAjpProtocol和AbstractHttp11Protocol,分别对应的是两种不同的处理协议,所以协议处理器的初始化方法是在其子抽象类(实现ProtocolHanlder接口的抽象类)来实现的,这里看看AbstractHttp11Protocol的初始化方法:

        @Override
        public void init() throws Exception {
            if (getLog().isInfoEnabled())
                getLog().info(sm.getString("abstractProtocolHandler.init",
                        getName()));
            if (oname == null) {
                // Component not pre-registered so register it
                oname = createObjectName();
                if (oname != null) {
                    Registry.getRegistry(null, null).registerComponent(this, oname,
                        null);
                }
            }
            if (this.domain != null) {
                try {
                    tpOname = new ObjectName(domain + ":" +
                            "type=ThreadPool,name=" + getName());
                    Registry.getRegistry(null, null).registerComponent(endpoint,
                            tpOname, null);
                } catch (Exception e) {
                    getLog().error(sm.getString(
                            "abstractProtocolHandler.mbeanRegistrationFailed",
                            tpOname, getName()), e);
                }
                rgOname=new ObjectName(domain +
                        ":type=GlobalRequestProcessor,name=" + getName());
                Registry.getRegistry(null, null).registerComponent(
                        getHandler().getGlobal(), rgOname, null );
            }
            String endpointName = getName();
            endpoint.setName(endpointName.substring(1, endpointName.length()-1));
            try {
                endpoint.init();
            } catch (Exception ex) {
                getLog().error(sm.getString("abstractProtocolHandler.initError",
                        getName()), ex);
                throw ex;
            }
        }

    打断点调试可以知道oname的值是Tomcat:type=ProtocolHandler,port=auto-1,address=”127.0.0.1”,tpOname是Tomcat:type=ProtocolHandler,port=auto-1,address=”127.0.0.1”,rOname是Tomcat:type=GlobalRequestProcessor,name=”http-bio-127.0.0.1-auto-1”,我们重点关注endpoint的init方法,主要完成以下几个过程:

    1. 设置线程接收数和最大连接数
    2. 创建线程池,启动监听的线程监听用户请求
    3. 启动一个线程处理请求

    初始化完成Connector就可以启动了,启动阶段调用startInternal方法:

        @Override
        protected void startInternal() throws LifecycleException {
            // Validate settings before starting
            if (getPort() < 0) {
                throw new LifecycleException(sm.getString(
                        "coyoteConnector.invalidPort", Integer.valueOf(getPort())));
            }
            setState(LifecycleState.STARTING);
            try {
                protocolHandler.start();
            } catch (Exception e) {
                String errPrefix = "";
                if(this.service != null) {
                    errPrefix += "service.getName(): "" + this.service.getName() + ""; ";
                }
                throw new LifecycleException
                    (errPrefix + " " + sm.getString
                     ("coyoteConnector.protocolHandlerStartFailed"), e);
            }
            mapperListener.start();
        }

    可以看出Connector调用protocolHandler.start()方法,继续看看这个方法的源码:

        @Override
        public void start() throws Exception {
            if (getLog().isInfoEnabled())
                getLog().info(sm.getString("abstractProtocolHandler.start",
                        getName()));
            try {
                endpoint.start();
            } catch (Exception ex) {
                getLog().error(sm.getString("abstractProtocolHandler.startError",
                        getName()), ex);
                throw ex;
            }
        }

    这个方法又调用了endpoint.start()方法:

        public final void start() throws Exception {
            if (bindState == BindState.UNBOUND) {
                bind();
                bindState = BindState.BOUND_ON_START;
            }
            startInternal();
        }

    然后又调用了org.apache.tomcat.util.net.AbstractEndpoint.startInternal()方法:

        @Override
        public void startInternal() throws Exception {
            if (!running) {
                running = true;
                paused = false;
                // Create worker collection
                if (getExecutor() == null) {
                    createExecutor();
                }
                initializeConnectionLatch();
                startAcceptorThreads();
                // Start async timeout thread
                Thread timeoutThread = new Thread(new AsyncTimeout(),
                        getName() + "-AsyncTimeout");
                timeoutThread.setPriority(threadPriority);
                timeoutThread.setDaemon(true);
                timeoutThread.start();
            }
        }
    1. 设置线程接收数和最大连接数
    2. 创建线程池,启动监听的线程监听用户请求
    3. 启动一个线程处理异步请求

    这个异步线程是如何执行的呢?

        /**
         * Async timeout thread
         */
        protected class AsyncTimeout implements Runnable {
            /**
             * The background thread that checks async requests and fires the
             * timeout if there has been no activity.
             */
            @Override
            public void run() {
                // Loop until we receive a shutdown command
                while (running) {
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        // Ignore
                    }
                    long now = System.currentTimeMillis();
                    Iterator<SocketWrapper<Socket>> sockets =
                        waitingRequests.iterator();
                    while (sockets.hasNext()) {
                        SocketWrapper<Socket> socket = sockets.next();
                        long access = socket.getLastAccess();
                        if (socket.getTimeout() > 0 &&
                                (now-access)>socket.getTimeout()) {
                            processSocketAsync(socket,SocketStatus.TIMEOUT);
                        }
                    }
                    // Loop if endpoint is paused
                    while (paused && running) {
                        try {
                            Thread.sleep(1000);
                        } catch (InterruptedException e) {
                            // Ignore
                        }
                    }
                }
            }
        }
    
        //processSocket
        public boolean processSocketAsync(SocketWrapper<Socket> socket,
                SocketStatus status) {
            try {
                synchronized (socket) {
                    if (waitingRequests.remove(socket)) {
                        SocketProcessor proc = new SocketProcessor(socket,status);
                        ClassLoader loader = Thread.currentThread().getContextClassLoader();
                        try {
                            //threads should not be created by the webapp classloader
                            if (Constants.IS_SECURITY_ENABLED) {
                                PrivilegedAction<Void> pa = new PrivilegedSetTccl(
                                        getClass().getClassLoader());
                                AccessController.doPrivileged(pa);
                            } else {
                                Thread.currentThread().setContextClassLoader(
                                        getClass().getClassLoader());
                            }
                            // During shutdown, executor may be null - avoid NPE
                            if (!running) {
                                return false;
                            }
                            getExecutor().execute(proc);
                            //TODO gotta catch RejectedExecutionException and properly handle it
                        } finally {
                            if (Constants.IS_SECURITY_ENABLED) {
                                PrivilegedAction<Void> pa = new PrivilegedSetTccl(loader);
                                AccessController.doPrivileged(pa);
                            } else {
                                Thread.currentThread().setContextClassLoader(loader);
                            }
                        }
                    }
                }
            } catch (Throwable t) {
                ExceptionUtils.handleThrowable(t);
                // This means we got an OOM or similar creating a thread, or that
                // the pool and its queue are full
                log.error(sm.getString("endpoint.process.fail"), t);
                return false;
            }
            return true;
        }

    org.apache.tomcat.util.net.JIoEndpoint.SocketProcessor的职责是把具体的请求处理过程委派给org.apache.tomcat.util.net.JIoEndpoint.Handler,然后根据handler返回的不同SocketState,来决定是否关闭连接或者进行下一轮处理。

    public void run() {
                boolean launch = false;
                synchronized (socket) {
                    try {
                        SocketState state = SocketState.OPEN;
                        try {
                            // SSL handshake
                            serverSocketFactory.handshake(socket.getSocket());
                        } catch (Throwable t) {
                            ExceptionUtils.handleThrowable(t);
                            if (log.isDebugEnabled()) {
                                log.debug(sm.getString("endpoint.err.handshake"), t);
                            }
                            // Tell to close the socket
                            state = SocketState.CLOSED;
                        }
                        if ((state != SocketState.CLOSED)) {
                            if (status == null) {
                                state = handler.process(socket, SocketStatus.OPEN_READ);
                            } else {
                                state = handler.process(socket,status);
                            }
                        }
                        if (state == SocketState.CLOSED) {
                            // Close socket
                            if (log.isTraceEnabled()) {
                                log.trace("Closing socket:"+socket);
                            }
                            countDownConnection();
                            try {
                                socket.getSocket().close();
                            } catch (IOException e) {
                                // Ignore
                            }
                        } else if (state == SocketState.OPEN ||
                                state == SocketState.UPGRADING ||
                                state == SocketState.UPGRADING_TOMCAT  ||
                                state == SocketState.UPGRADED){
                            socket.setKeptAlive(true);
                            socket.access();
                            launch = true;
                        } else if (state == SocketState.LONG) {
                            socket.access();
                            waitingRequests.add(socket);
                        }
                    } finally {
                        if (launch) {
                            try {
                                getExecutor().execute(new SocketProcessor(socket, SocketStatus.OPEN_READ));
                            } catch (RejectedExecutionException x) {
                                log.warn("Socket reprocessing request was rejected for:"+socket,x);
                                try {
                                    //unable to handle connection at this time
                                    handler.process(socket, SocketStatus.DISCONNECT);
                                } finally {
                                    countDownConnection();
                                }
                            } catch (NullPointerException npe) {
                                if (running) {
                                    log.error(sm.getString("endpoint.launch.fail"),
                                            npe);
                                }
                            }
                        }
                    }
                }
                socket = null;
                // Finish up this request
            }
        }

    其中的process方法主要完成对request的解析,包括请求头、请求行和请求体 :

        //process method of org.apache.coyote.http11.AbstractHttp11Processor<S>.HttpProcessor extends org.apache.coyote.http11.AbstractHttp11Processor<S>
        @Override
        public SocketState process(SocketWrapper<S> socketWrapper)
            throws IOException {
            RequestInfo rp = request.getRequestProcessor();
            rp.setStage(org.apache.coyote.Constants.STAGE_PARSE);
            // Setting up the I/O
            setSocketWrapper(socketWrapper);
            getInputBuffer().init(socketWrapper, endpoint);
            getOutputBuffer().init(socketWrapper, endpoint);
            // Flags
            error = false;
            keepAlive = true;
            comet = false;
            openSocket = false;
            sendfileInProgress = false;
            readComplete = true;
            if (endpoint.getUsePolling()) {
                keptAlive = false;
            } else {
                keptAlive = socketWrapper.isKeptAlive();
            }
            if (disableKeepAlive()) {
                socketWrapper.setKeepAliveLeft(0);
            }
            while (!error && keepAlive && !comet && !isAsync() &&
                    upgradeInbound == null &&
                    httpUpgradeHandler == null && !endpoint.isPaused()) {
                // Parsing the request header
                try {
                    setRequestLineReadTimeout();
                    if (!getInputBuffer().parseRequestLine(keptAlive)) {
                        if (handleIncompleteRequestLineRead()) {
                            break;
                        }
                    }
                    if (endpoint.isPaused()) {
                        // 503 - Service unavailable
                        response.setStatus(503);
                        error = true;
                    } else {
                        // Make sure that connectors that are non-blocking during
                        // header processing (NIO) only set the start time the first
                        // time a request is processed.
                        if (request.getStartTime() < 0) {
                            request.setStartTime(System.currentTimeMillis());
                        }
                        keptAlive = true;
                        // Set this every time in case limit has been changed via JMX
                        request.getMimeHeaders().setLimit(endpoint.getMaxHeaderCount());
                        // Currently only NIO will ever return false here
                        if (!getInputBuffer().parseHeaders()) {
                            // We've read part of the request, don't recycle it
                            // instead associate it with the socket
                            openSocket = true;
                            readComplete = false;
                            break;
                        }
                        if (!disableUploadTimeout) {
                            setSocketTimeout(connectionUploadTimeout);
                        }
                    }
                } catch (IOException e) {
                    if (getLog().isDebugEnabled()) {
                        getLog().debug(
                                sm.getString("http11processor.header.parse"), e);
                    }
                    error = true;
                    break;
                } catch (Throwable t) {
                    ExceptionUtils.handleThrowable(t);
                    UserDataHelper.Mode logMode = userDataHelper.getNextMode();
                    if (logMode != null) {
                        String message = sm.getString(
                                "http11processor.header.parse");
                        switch (logMode) {
                            case INFO_THEN_DEBUG:
                                message += sm.getString(
                                        "http11processor.fallToDebug");
                                //$FALL-THROUGH$
                            case INFO:
                                getLog().info(message);
                                break;
                            case DEBUG:
                                getLog().debug(message);
                        }
                    }
                    // 400 - Bad Request
                    response.setStatus(400);
                    adapter.log(request, response, 0);
                    error = true;
                }
                if (!error) {
                    // Setting up filters, and parse some request headers
                    rp.setStage(org.apache.coyote.Constants.STAGE_PREPARE);
                    try {
                        prepareRequest();
                    } catch (Throwable t) {
                        ExceptionUtils.handleThrowable(t);
                        if (getLog().isDebugEnabled()) {
                            getLog().debug(sm.getString(
                                    "http11processor.request.prepare"), t);
                        }
                        // 400 - Internal Server Error
                        response.setStatus(400);
                        adapter.log(request, response, 0);
                        error = true;
                    }
                }
                if (maxKeepAliveRequests == 1) {
                    keepAlive = false;
                } else if (maxKeepAliveRequests > 0 &&
                        socketWrapper.decrementKeepAlive() <= 0) {
                    keepAlive = false;
                }
                // Process the request in the adapter
                if (!error) {
                    try {
                        rp.setStage(org.apache.coyote.Constants.STAGE_SERVICE);
                        adapter.service(request, response);
    
                        if(keepAlive && !error) { // Avoid checking twice.
                            error = response.getErrorException() != null ||
                                    (!isAsync() &&
                                    statusDropsConnection(response.getStatus()));
                        }
                        setCometTimeouts(socketWrapper);
                    } catch (InterruptedIOException e) {
                        error = true;
                    } catch (HeadersTooLargeException e) {
                        error = true;
                        // The response should not have been committed but check it
                        // anyway to be safe
                        if (!response.isCommitted()) {
                            response.reset();
                            response.setStatus(500);
                            response.setHeader("Connection", "close");
                        }
                    } catch (Throwable t) {
                        ExceptionUtils.handleThrowable(t);
                        getLog().error(sm.getString(
                                "http11processor.request.process"), t);
                        // 500 - Internal Server Error
                        response.setStatus(500);
                        adapter.log(request, response, 0);
                        error = true;
                    }
                }
                // Finish the handling of the request
                rp.setStage(org.apache.coyote.Constants.STAGE_ENDINPUT);
                if (!isAsync() && !comet) {
                    if (error) {
    
                        getInputBuffer().setSwallowInput(false);
                    }
                    if (response.getStatus() < 200 || response.getStatus() > 299) {
                        if (expectation) {
                            // Client sent Expect: 100-continue but received a
                            // non-2xx response. Disable keep-alive (if enabled) to
                            // ensure the connection is closed. Some clients may
                            // still send the body, some may send the next request.
                            // No way to differentiate, so close the connection to
                            // force the client to send the next request.
                            getInputBuffer().setSwallowInput(false);
                            keepAlive = false;
                        }
                    }
                    endRequest();
                }
                rp.setStage(org.apache.coyote.Constants.STAGE_ENDOUTPUT);
                // If there was an error, make sure the request is counted as
                // and error, and update the statistics counter
                if (error) {
                    response.setStatus(500);
                }
                request.updateCounters();
                if (!isAsync() && !comet || error) {
                    getInputBuffer().nextRequest();
                    getOutputBuffer().nextRequest();
                }
                if (!disableUploadTimeout) {
                    if(endpoint.getSoTimeout() > 0) {
                        setSocketTimeout(endpoint.getSoTimeout());
                    } else {
                        setSocketTimeout(0);
                    }
                }
                rp.setStage(org.apache.coyote.Constants.STAGE_KEEPALIVE);
                if (breakKeepAliveLoop(socketWrapper)) {
                    break;
                }
            }
            rp.setStage(org.apache.coyote.Constants.STAGE_ENDED);
            }
        }

    首先在Http11Processor的process方法里,会先从socket里读取http请求数据,并解析请求头,构造Request对象和Response对象,然后调用Adapter.service()方法。Adapter.service()完成请求行以及请求体的解析,并把解析出来的信息封装到Request和Response对象中,Adapter(确切说是org.apache.catalina.connector.CoyoteAdapter)是connector和container的桥梁,经过这一步,请求就从connector传递到container里了,Adapter.service()方法之后便将封装了Request以及Response对象的Socket传给Container容器了。
    要注意的是:最先处理请求的Request是org.apache.coyote.Request类型,这是一个Tomcat中一个轻量级对象,完成基本的请求处理后很容易被JVM回收,那为什么不直接交给Connector.Request对象处理呢?由于后者是Servlet容器真正传递的对象其完成的职责比前者复杂,这里使用org.apache.coyote.Request主要减轻后者的任务负担,出于性能考虑才这么设计。
    具体service方法清单如下:

    @Override
    public void service(org.apache.coyote.Request req,
                          org.apache.coyote.Response res)
          throws Exception {
          Request request = (Request) req.getNote(ADAPTER_NOTES);
          Response response = (Response) res.getNote(ADAPTER_NOTES);
          if (request == null) {
              // Create objects
              request = connector.createRequest();
              request.setCoyoteRequest(req);
              response = connector.createResponse();
              response.setCoyoteResponse(res);
              // Link objects
              request.setResponse(response);
              response.setRequest(request);
              // Set as notes
              req.setNote(ADAPTER_NOTES, request);
              res.setNote(ADAPTER_NOTES, response);
              // Set query string encoding
              req.getParameters().setQueryStringEncoding
                  (connector.getURIEncoding());
          }
          if (connector.getXpoweredBy()) {
              response.addHeader("X-Powered-By", POWERED_BY);
          }
          boolean comet = false;
          boolean async = false;
          try {
     // Parse and set Catalina and configuration //specific
              // request parameters
              req.getRequestProcessor().setWorkerThreadName(Thread.currentThread().getName());
              //postParseRequest方法把CoyoteRequest转换为Connector.Request对象
              //后一类型的对象才是在Tomcat容器流转时真正传递的对象
              boolean postParseSuccess = postParseRequest(req, request, res, response);
              if (postParseSuccess) {
                  //check valves if we support async
                  request.setAsyncSupported(connector.getService().getContainer().getPipeline().isAsyncSupported());
        // 调用Container容器的invoke方法,把请求交给Container容器
                  connector.getService().getContainer().getPipeline().getFirst().invoke(request, response);
                  if (request.isComet()) {
                      if (!response.isClosed() && !response.isError()) {
                          if (request.getAvailable() || (request.getContentLength() > 0 && (!request.isParametersParsed()))) {
                              // Invoke a read event right away if there are available bytes
                              if (event(req, res, SocketStatus.OPEN_READ)) {
                                  comet = true;
                                  res.action(ActionCode.COMET_BEGIN, null);
                              }
                          } else {
                              comet = true;
                              res.action(ActionCode.COMET_BEGIN, null);
                          }
                      } else {
                          // Clear the filter chain, as otherwise it will not be reset elsewhere
                          // since this is a Comet request
                          request.setFilterChain(null);
                      }
                  }
              }
              AsyncContextImpl asyncConImpl = (AsyncContextImpl)request.getAsyncContext();
              if (asyncConImpl != null) {
                  async = true;
              } else if (!comet) {
                  request.finishRequest();
                  response.finishResponse();
                  if (postParseSuccess &&
                          request.getMappingData().context != null) {
                      ((Context) request.getMappingData().context).logAccess(
                              request, response,
                              System.currentTimeMillis() - req.getStartTime(),
                              false);
                  }
                  req.action(ActionCode.POST_REQUEST , null);
              }
          } catch (IOException e) {
              // Ignore
          } finally {
             //ignore
          }
      }

    从connector.getService().getContainer().getPipeline().getFirst().invoke(request, response);这句代码中可以知道下一步的处理需要交给Container容器了。

    经过上面一系列复杂的操作流程,Tomcat的Connector已经完成了protocol.start()方法,返回Connector的startIntenal方法,还有一个步骤要完成就是mapperListener.start()的方法了,整个执行过程比较简单,有两步:

    1. 执行Connector的startIntenal方法
    2. 执行MapperListener的startIntenal方法
       @Override
        public void startInternal() throws LifecycleException {
            setState(LifecycleState.STARTING);
            findDefaultHost();
            Engine engine = (Engine) connector.getService().getContainer();
            addListeners(engine);
            Container[] conHosts = engine.findChildren();
            for (Container conHost : conHosts) {
                Host host = (Host) conHost;
                if (!LifecycleState.NEW.equals(host.getState())) {
                    // Registering the host will register the context and wrappers
                    registerHost(host);
                }
            }
        }

    首先注册已初始化的组件,然后为这些组件添加监听器,最后添加容器之间的映射关系。



    原文博主地址:rhwayfunn

  • 相关阅读:
    JQuery框架中使用blockUI制作自定义的漂亮的网页提示框
    PHP树形菜单一次展开一个子项目,可以记录打开的项目,刷新后不变
    第一次面试
    东软的校园招聘笔试
    fscommand
    从 ActionScript 中调用外部代码
    GCC 参数详解
    flash build找不到调试版plashplayer的解决办法
    C# winform与 flash as 的交互通讯
    LLVM 与 Clang 介绍
  • 原文地址:https://www.cnblogs.com/aixing/p/13327570.html
Copyright © 2011-2022 走看看