톰캣의 구조에

경량 애플리케이션 서버를 개발하기 위해 자바 언어를 기반으로하는 서블릿 컨테이너의 구현으로 톰캣. Tomcat이 응용 프로그램 서버로, 그것은 완전히 오픈 소스가 자바 개발 및 응용 프로그램 배포에 대한 첫 번째 선택이 될 수 있도록 가볍고, 안정적인 성능, 저렴한 비용으로 배포를 가지고 있기 때문에, 거의 모든 자바 웹 개발자를 사용하지만, 한 톰캣의 전체적인 디자인을 이해하고 그것에 대해 생각하고있다?

이 문서는 분석 Tomcat8, 특히 현재 버전 Tomcat8 공식 웹 사이트를 기반으로합니다 최신 변경 사항 (2019년 11월 21일 9시 28분) 버전 v8.5.49

전체 구조

톰캣의 전체 구조는 모듈의 많은 우리가 메인 모듈의 구조를 분석하려고 다음 그림이있다. 어디 서비스, 커넥터, 엔진, 호스트의 주요 분석 컨텍스트, 래퍼. 피할 층이 너무 지저분보고, 그림 n구성 요소를 나타내는 여러 허용 할 수 있습니다.

도 상술이다 : 서버는 서비스 서버에 복수의 서비스가있을 수 있고, 서버 톰캣이다. 각 서비스는 복수의 커넥터 및 서블릿 엔진 엔진 커넥터 기관의 복수에 대응하는 서비스를 가질 수있다. 각각의 엔진은, 여러 도메인 이름이있을 수 있습니다, 웹 호스팅 개념은 호스트를 나타낼 여기에 사용할 수 있습니다. 어플리케이션의 복수의 호스트 콘텍스트에 존재할 수있다. 서버, 서비스, 커넥터, 엔진, 호스트 컨텍스트 래퍼 병렬 관계이며 이들 사이의 관계, 또한 커넥터 엔진은 다른 개재물 관계이다. 동시에, 그들은 또한 포함 라이프 사이클 관리를 제공하는 라이프 사이클 인터페이스 상속 : 초기화 (INIT), (소멸), 정지 (정지) (시작)를 시작 파괴 . 부모 컨테이너가 시작되면, 그것은 아이의 용기를 전화를 시작, 중지는 동일합니다.

위의 그림에, 당신은 엔진, 호스트, 컨텍스트가 래퍼가 컨테이너에서 상속 볼 수 있습니다. 그것은 가지고 backgroundProcess()있으므로 쉽게 비동기 스레드를 생성 할 수 상속 후에있어서, 상기 백그라운드 처리를 비동기. Tomcat7, 우리는 서비스 대신, 엔진 컨테이너를 개최했다 참조하십시오. 엔진이 방법은 현재 버전에서 호출되는 이유입니다 평가 추가 setContainer.

섬기는 사람

톰캣 소스를 제공하고 org.apache.catalina.Server대한 기본 구현 클래스에 대응하는 인터페이스를 org.apache.catalina.core.StandardServer도 이하의 방법을 제공하는 인터페이스.

그림은 서버를 알 수 위의 작업을 수행합니다 서비스, 주소, 포트, 카탈리나 글로벌 네임 스페이스 관리 작업 자원. 초기화 시간 동안 서버, 우리의 server.xml의 데이터 구성을로드합니다.

여기있는 서비스 운영에 관한 addService서비스의 집합을 정의하기 위해 분석을위한 새로운 서비스를 추가합니다 :

// 保存服务的服务集
private Service services[] = new Service[0];

final PropertyChangeSupport support = new PropertyChangeSupport(this);

@Override
public void addService(Service service) {
    // 相互关联
    service.setServer(this);
    
    // 利用同步锁,防止并发访问   来源:https://ytao.top
    synchronized (servicesLock) {
        Service results[] = new Service[services.length + 1];
        // copy 旧的服务到新的数组中
        System.arraycopy(services, 0, results, 0, services.length);
        // 添加新的 service
        results[services.length] = service;
        services = results;
    
        // 如果当前 server 已经启动,那么当前添加的 service 就开始启动
        if (getState().isAvailable()) {
            try {
                service.start();
            } catch (LifecycleException e) {
                // Ignore
            }
        }
        
        // 使用观察者模式,当被监听对象属性值发生变化时通知监听器,remove 是也会调用。
        support.firePropertyChange("service", null, service);
    }

}
复制代码

소스 코드는 서버에 서비스를 추가 한 후, 임의의 서비스를 시작, 볼 수 있지만, 실제로는 또한 서비스 입구를 시작할 수 있습니다.

서비스

주요 책임은 조립 함께 커넥터 서비스 및 엔진이다. 목적은 더 나은 확장 성을 가지고, 요청과 요청 프로세스 모니터 디커플링 둘을 분리하는 것입니다. 각 서비스는 서로 독립적이지만 JVM 및 시스템 라이브러리를 공유 할 수 있습니다. 여기에있는 org.apache.catalina.Service인터페이스와 기본 구현 클래스 org.apache.catalina.coreStandardService.

구현 클래스 StandardService, 일차 분석에서 setContaineraddConnector두 가지 방법.


private Engine engine = null;

protected final MapperListener mapperListener = new MapperListener(this);

@Override
public void setContainer(Engine engine) {
    Engine oldEngine = this.engine;
    // 判断当前 Service 是否有关联 Engine
    if (oldEngine != null) {
        // 如果当前 Service 有关联 Engine,就去掉当前关联的 Engine
        oldEngine.setService(null);
    }
    // 如果当前新的 Engine 不为空,那么 Engine 关联当前 Service,这里是个双向关联
    this.engine = engine;
    if (this.engine != null) {
        this.engine.setService(this);
    }
    // 如果当前 Service 启动了,那么就开始启动当前新的 Engine
    if (getState().isAvailable()) {
        if (this.engine != null) {
            try {
                this.engine.start();
            } catch (LifecycleException e) {
                log.error(sm.getString("standardService.engine.startFailed"), e);
            }
        }
        // 重启 MapperListener ,获取一个新的 Engine ,一定是当前入参的 Engine
        try {
            mapperListener.stop();
        } catch (LifecycleException e) {
            log.error(sm.getString("standardService.mapperListener.stopFailed"), e);
        }
        try {
            mapperListener.start();
        } catch (LifecycleException e) {
            log.error(sm.getString("standardService.mapperListener.startFailed"), e);
        }

        // 如果当前 Service 之前有 Engine 关联,那么停止之前的 Engine
        if (oldEngine != null) {
            try {
                oldEngine.stop();
            } catch (LifecycleException e) {
                log.error(sm.getString("standardService.engine.stopFailed"), e);
            }
        }
    }

    // Report this property change to interested listeners
    support.firePropertyChange("container", oldEngine, this.engine);
}

/**
* 实现方式和 StandardServer#addService 类似,不在细述
* 注意,Connector 这里没有像 Engine 一样与 Service 实现双向关联
*/
@Override
public void addConnector(Connector connector) {

    synchronized (connectorsLock) {
        connector.setService(this);
        Connector results[] = new Connector[connectors.length + 1];
        System.arraycopy(connectors, 0, results, 0, connectors.length);
        results[connectors.length] = connector;
        connectors = results;

        if (getState().isAvailable()) {
            try {
                connector.start();
            } catch (LifecycleException e) {
                log.error(sm.getString(
                        "standardService.connector.startFailed",
                        connector), e);
            }
        }

        // Report this property change to interested listeners
        support.firePropertyChange("connector", null, connector);
    }

}
复制代码

커넥터

커넥터는 주로 커넥터를 제공하기 위해 처리 한 후, 엔진에 후 처리 요청을 요청을 수신 클라이언트에 반환. 지원 현재의 프로토콜 버전은 다음과 같습니다 : HTTP, HHTP / 2, AJP, NIO, NIO2 4 월 주요 특징은 다음과 같습니다 :

  • 클라이언트의 요청을 읽을 서버 포트를 모니터링합니다.
  • 확인 프로토콜과 처리 용기에 대응하는 요청.
  • 정보를 처리 한 후 클라이언트에 반환

구성 정보 서버의 server.xml에 해당하는 커넥터 예 :

<Connector port="8080" protocol="HTTP/1.1"
               connectionTimeout="20000"
               redirectPort="8443" />复制代码

듣고 여기에 포트 번호를 구성하여 port프로토콜 처리 지정, protocol뿐만 아니라 재 주소를 redirectPort. 프로토콜 프로세싱 유형은 커넥터의 실시 예에 의해 설정된다 :

public Connector() {
    // 无参构造,下面 setProtocol 中默认使用HTTP/1.1
    this(null);
}

public Connector(String protocol) {
    // 设置当前连接器协议处理类型
    setProtocol(protocol);
    // 实例化协议处理器,并保存到当前 Connector 中
    ProtocolHandler p = null;
    try {
        Class<?> clazz = Class.forName(protocolHandlerClassName);
        p = (ProtocolHandler) clazz.getConstructor().newInstance();
    } catch (Exception e) {
        log.error(sm.getString(
                "coyoteConnector.protocolHandlerInstantiationFailed"), e);
    } finally {
        this.protocolHandler = p;
    }

    if (Globals.STRICT_SERVLET_COMPLIANCE) {
        uriCharset = StandardCharsets.ISO_8859_1;
    } else {
        uriCharset = StandardCharsets.UTF_8;
    }
}

/**
* 这个设置再 tomcat9 中被移除,改为必配项
*/
public void setProtocol(String protocol) {

    boolean aprConnector = AprLifecycleListener.isAprAvailable() &&
            AprLifecycleListener.getUseAprConnector();

    // 这里指定了默认协议和 HTTP/1.1 一样
    if ("HTTP/1.1".equals(protocol) || protocol == null) {
        if (aprConnector) {
            setProtocolHandlerClassName("org.apache.coyote.http11.Http11AprProtocol");
        } else {
            setProtocolHandlerClassName("org.apache.coyote.http11.Http11NioProtocol");
        }
    } else if ("AJP/1.3".equals(protocol)) {
        if (aprConnector) {
            setProtocolHandlerClassName("org.apache.coyote.ajp.AjpAprProtocol");
        } else {
            setProtocolHandlerClassName("org.apache.coyote.ajp.AjpNioProtocol");
        }
    } else {
        // 最后如果不是通过指定 HTTP/1.1,AJP/1.3 类型的协议,就通过类名实例化一个协议处理器
        setProtocolHandlerClassName(protocol);
    }
}
复制代码

ProtocolHandler는 다른 구현을 제공하는 다른 요청에 대해 프로토콜 프로세서입니다. 엔진은 요청을 처리에 초기화시에 구현 클래스 AbstractProtocol는 다음 프로세서는 요청을 읽을 전화, 추상 클래스 AbstractEndpoint 요청을받은 후, 모니터 서버 포트에 스레드를 초기화 마지막 호출에 시작하고 있습니다.

엔진

그에 해당하는 엔진 org.apache.catalina.Engine인터페이스와 org.apache.catalina.core.StandardEngine기본 구현 클래스. 엔진 기능, 관련 컨테이너 처리 관계를 비교적 간단하다.

그러나 구현 클래스는 addChild()없습니다 평균 하위 엔진,하지만 호스트 않습니다. 그리고 부모 컨테이너가 없다, setParent제공 운영 할 수있다.

@Override
public void addChild(Container child) {
    // 添加的子容器必须是 Host 
    if (!(child instanceof Host))
        throw new IllegalArgumentException
            (sm.getString("standardEngine.notHost"));
    super.addChild(child);
}

@Override
public void setParent(Container container) {

    throw new IllegalArgumentException
        (sm.getString("standardEngine.notParent"));

}
复制代码

당신은 server.xml에 우리의 데이터를 구성 할 수 있습니다 :

<!-- 配置默认Host,及jvmRoute -->
<Engine name="Catalina" defaultHost="localhost" jvmRoute="jvm1">复制代码

숙주

호스트는 가상 호스트를 나타냅니다. 당신은 demo.ytao.top, dev.ytao.top 우리의 서버에 여러 도메인 이름을 설정할 수 있어야합니다. 그런 다음 우리는 다른 도메인 이름에 대한 요청을 처리하기 위해 두 개의 서로 다른 호스트를 설정합니다. 요청이 도메인 이름 demo.ytao.top을 왔을 때, 다음은 도메인 이름의 호스트의 컨텍스트에서 이동합니다. 우리의 server.xml 구성 파일 그래서 또한 구성을 제공합니다 :

<!-- name 设置的时虚拟主机域名 -->
<Host name="localhost"  appBase="webapps"
            unpackWARs="true" autoDeploy="true">复制代码

문맥

컨텍스트들이 서블릿 런타임 환경을 가지고 여기 와서, 엔진, 호스트 런타임 환경이없는, 관계를 유지하기 위해 주요 컨테이너입니다. 우리를하자 상황은 예를 들어, 우리는 ytao-데모-1과 ytao-데모-2 루트에있는 두 개의 응용 프로그램을, 응용 프로그램으로 이해 될 수있다,이 두 상황이다. 이는 도입 addChild, 서브 탱크 래퍼 첨가 방법 :

@Override
public void addChild(Container child) {

    // Global JspServlet
    Wrapper oldJspServlet = null;

    // 这里添加的子容器只能时 Wrapper
    if (!(child instanceof Wrapper)) {
        throw new IllegalArgumentException
            (sm.getString("standardContext.notWrapper"));
    }

    // 判断子容器 Wrapper 是否为 JspServlet
    boolean isJspServlet = "jsp".equals(child.getName());

    // Allow webapp to override JspServlet inherited from global web.xml.
    if (isJspServlet) {
        oldJspServlet = (Wrapper) findChild("jsp");
        if (oldJspServlet != null) {
            removeChild(oldJspServlet);
        }
    }

    super.addChild(child);

    // 将servlet映射添加到Context组件
    if (isJspServlet && oldJspServlet != null) {
        /*
         * The webapp-specific JspServlet inherits all the mappings
         * specified in the global web.xml, and may add additional ones.
         */
        String[] jspMappings = oldJspServlet.findMappings();
        for (int i=0; jspMappings!=null && i<jspMappings.length; i++) {
            addServletMappingDecoded(jspMappings[i], child.getName());
        }
    }
}复制代码

다음은 각 응용 프로그램의 서블릿 관리 센터입니다.

싸개

래퍼는 용기의 바닥입니다 자체 있기 때문에, 컨테이너의 자식이 아닌, 그것은 서블릿의 전체 수명주기를 가지고, 서블릿의 행정 중심지이다. 다음은 서블릿의 주요 분석로드 :

public synchronized Servlet loadServlet() throws ServletException {

    // 如果已经实例化或者用实例化池,就直接返回
    if (!singleThreadModel && (instance != null))
        return instance;

    PrintStream out = System.out;
    if (swallowOutput) {
        SystemLogHandler.startCapture();
    }

    Servlet servlet;
    try {
        long t1=System.currentTimeMillis();
        // 如果 servlet 类名为空,直接抛出 Servlet 异常
        if (servletClass == null) {
            unavailable(null);
            throw new ServletException
                (sm.getString("standardWrapper.notClass", getName()));
        }

        // 从 Context 中获取 Servlet
        InstanceManager instanceManager = ((StandardContext)getParent()).getInstanceManager();
        try {
            servlet = (Servlet) instanceManager.newInstance(servletClass);
        } catch (ClassCastException e) {
            unavailable(null);
            // Restore the context ClassLoader
            throw new ServletException
                (sm.getString("standardWrapper.notServlet", servletClass), e);
        } catch (Throwable e) {
            e = ExceptionUtils.unwrapInvocationTargetException(e);
            ExceptionUtils.handleThrowable(e);
            unavailable(null);

            // Added extra log statement for Bugzilla 36630:
            // https://bz.apache.org/bugzilla/show_bug.cgi?id=36630
            if(log.isDebugEnabled()) {
                log.debug(sm.getString("standardWrapper.instantiate", servletClass), e);
            }

            // Restore the context ClassLoader
            throw new ServletException
                (sm.getString("standardWrapper.instantiate", servletClass), e);
        }

        // 加载声明了 MultipartConfig 注解的信息
        if (multipartConfigElement == null) {
            MultipartConfig annotation =
                    servlet.getClass().getAnnotation(MultipartConfig.class);
            if (annotation != null) {
                multipartConfigElement =
                        new MultipartConfigElement(annotation);
            }
        }

        // 对 servlet 类型进行检查
        if (servlet instanceof ContainerServlet) {
            ((ContainerServlet) servlet).setWrapper(this);
        }

        classLoadTime=(int) (System.currentTimeMillis() -t1);

        if (servlet instanceof SingleThreadModel) {
            if (instancePool == null) {
                instancePool = new Stack<>();
            }
            singleThreadModel = true;
        }

        // 初始化 servlet
        initServlet(servlet);

        fireContainerEvent("load", this);

        loadTime=System.currentTimeMillis() -t1;
    } finally {
        if (swallowOutput) {
            String log = SystemLogHandler.stopCapture();
            if (log != null && log.length() > 0) {
                if (getServletContext() != null) {
                    getServletContext().log(log);
                } else {
                    out.println(log);
                }
            }
        }
    }
    return servlet;

}复制代码

이 인스턴스화되지 않은 경우 다음로드 서블릿, 서블릿, 그것은로드해야합니다.

지금까지, 대략의 주요 구성 요소 Tomcat8 설명, 톰캣의 전체적인 아키텍처는, 일반적인 이해, 톰캣 소스 코드 리팩토링, 실제로 훨씬 더 가독성이 당신이 분석에서 내부의 일부의 사용을 시도 할 수 있음을 시사 디자인 패턴은, 우리는 실제 인코딩 과정에서, 여전히 특정 의미가있다.


개인 블로그 : ytao.top

내 공개 번호 ytao내 공개 수

추천

출처juejin.im/post/5ddbc7ede51d452339183c32