当前位置:   article > 正文

Tomcat源码分析-启动分析(二) Catalina初始化_tomcat源码分析 如何从engin到host 初始化

tomcat源码分析 如何从engin到host 初始化

Server初始化

StandardServer是由Catalina进行init初始化的,调用的是LifecycleBase父类的init方法,而StandardServer继承至LifecycleMBeanBase,重写了initInternal方法。关于这块的知识,请参考上一篇Lifecycle的博客

StandardServer初始化的时序图如下所示,为了表述清楚,我这里把LifecycleBase、LifecycleMBeanBase拆开了,实际上是同一个StandardServer实例对象,存在继承关系

由上图可以很清晰地看到,StandardServer的初始化过程,先由父类LifecycleBase改变当前的state值并发出事件通知,那么这个时候StandardServer的子容器StandardService内部的state是否会发生改变呢,是否会发出事件通知呢? 当然是不会的,因为这个state值不是LifecycleBase的静态成员变量,StandardServer只能改变自己的值,而StandardService只有在被StandardServer调用init初始化的时候才会改变,二者拥有独立的状态。考虑到有其它线程可能会改变StandardServer的state值,比如利用jmx执行init操作,因此要考虑并发问题,所以LifecycleBase#init()使用了synchronized锁,并且state是volatile修饰的。

LifecycleBase改变state、发出事件通知之后,便会执行StandardServer自身的initInternal,我们来看看这个里面都干嘛了

  1. protected void initInternal() throws LifecycleException {
  2. super.initInternal();
  3. // 往jmx中注册全局的String cache,尽管这个cache是全局听,但是如果在同一个jvm中存在多个Server,
  4. // 那么则会注册多个不同名字的StringCache,这种情况在内嵌的tomcat中可能会出现
  5. onameStringCache = register(new StringCache(), "type=StringCache");
  6. // 注册MBeanFactory,用来管理Server
  7. MBeanFactory factory = new MBeanFactory();
  8. factory.setContainer(this);
  9. onameMBeanFactory = register(factory, "type=MBeanFactory");
  10. // 往jmx中注册全局的NamingResources
  11. globalNamingResources.init();
  12. // Populate the extension validator with JARs from common and shared class loaders
  13. if (getCatalina() != null) {
  14. // 忽略ClassLoader操作
  15. }
  16. // 初始化内部的Service
  17. for (int i = 0; i < services.length; i++) {
  18. services[i].init();
  19. }

1、 先是调用super.initInternal(),把自己注册到jmx
2、 然后注册StringCache和MBeanFactory
3、 初始化NamingResources,就是server.xml中指定的GlobalNamingResources
4、 调用Service子容器的init方法,让Service组件完成初始化,注意:在同一个Server下面,可能存在多个Service组件

Service初始化

StandardService和StandardServer都是继承至LifecycleMBeanBase,因此公共的初始化逻辑都是一样的,这里不做过多介绍,我们直接看下initInternal

  1. protected void initInternal() throws LifecycleException {
  2. // 往jmx中注册自己
  3. super.initInternal();
  4. // 初始化Engine
  5. if (engine != null) {
  6. engine.init();
  7. }
  8. // 存在Executor线程池,则进行初始化,默认是没有的
  9. for (Executor executor : findExecutors()) {
  10. if (executor instanceof JmxEnabled) {
  11. ((JmxEnabled) executor).setDomain(getDomain());
  12. }
  13. executor.init();
  14. }
  15. // 暂时不知道这个MapperListener的作用
  16. mapperListener.init();
  17. // 初始化Connector,而Connector又会对ProtocolHandler进行初始化,开启应用端口的监听
  18. synchronized (connectorsLock) {
  19. for (Connector connector : connectors) {
  20. try {
  21. connector.init();
  22. } catch (Exception e) {
  23. // 省略部分代码,logger and throw exception
  24. }
  25. }
  26. }

1、 首先,往jmx中注册StandardService
2、 初始化Engine,而Engine初始化过程中会去初始化Realm(权限相关的组件)
3、 如果存在Executor线程池,还会进行init操作,这个Excecutor是tomcat的接口,继承至java.util.concurrent.Executor、org.apache.catalina.Lifecycle
4、 初始化Connector连接器,默认有http1.1、ajp连接器,而这个Connector初始化过程,又会对ProtocolHandler进行初始化,开启应用端口的监听,后面会详细分析

Engine初始化

StandardEngine在init阶段,需要获取Realm,这个Realm是干嘛用的?

Realm(域)是用于对单个用户进行身份验证的底层安全领域的只读外观,并标识与这些用户相关联的安全角色。

StandardEngine初始化的代码如下:

  1. @Override
  2. protected void initInternal() throws LifecycleException {
  3. getRealm();
  4. super.initInternal();
  5. }
  6. public Realm getRealm() {
  7. Realm configured = super.getRealm();
  8. if (configured == null) {
  9. configured = new NullRealm();
  10. this.setRealm(configured);
  11. }
  12. return configured;

由前面的类图可知,StandardEngine继承至ContainerBase,而ContainerBase重写了initInternal()方法,用于初始化start、stop线程池,这个线程池有以下特点:
1、 core线程和max是相等的,默认为1
2、 允许core线程在超时未获取到任务时退出线程
3、 线程获取任务的超时时间是10s,也就是说所有的线程(包括core线程),超过10s未获取到任务,那么这个线程就会被销毁

这么做的初衷是什么呢?因为这个线程池只需要在容器启动和停止的时候发挥作用,没必要时时刻刻处理任务队列

ContainerBase的代码如下所示:

  1. // 默认是1个线程
  2. private int startStopThreads = 1;
  3. protected ThreadPoolExecutor startStopExecutor;
  4. @Override
  5. protected void initInternal() throws LifecycleException {
  6. BlockingQueue<Runnable> startStopQueue = new LinkedBlockingQueue<>();
  7. startStopExecutor = new ThreadPoolExecutor(
  8. getStartStopThreadsInternal(),
  9. getStartStopThreadsInternal(), 10, TimeUnit.SECONDS,
  10. startStopQueue,
  11. new StartStopThreadFactory(getName() + "-startStop-"));
  12. // 允许core线程超时未获取任务时退出
  13. startStopExecutor.allowCoreThreadTimeOut(true);
  14. super.initInternal();
  15. }
  16. private int getStartStopThreadsInternal() {
  17. int result = getStartStopThreads();
  18. if (result > 0) {
  19. return result;
  20. }
  21. result = Runtime.getRuntime().availableProcessors() + result;
  22. if (result < 1) {
  23. result = 1;
  24. }
  25. return result;

这个startStopExecutor线程池有什么用呢?

1、 在start的时候,如果发现有子容器,则会把子容器的start操作放在线程池中进行处理
2、 在stop的时候,也会把stop操作放在线程池中处理

在前面的文章中我们介绍了Container组件,StandardEngine作为顶层容器,它的直接子容器是StardandHost,但是对StandardEngine的代码分析,我们并没有发现它会对子容器StardandHost进行初始化操作,StandardEngine不按照套路出牌,而是把初始化过程放在start阶段。个人认为Host、Context、Wrapper这些容器和具体的webapp应用相关联了,初始化过程会更加耗时,因此在start阶段用多线程完成初始化以及start生命周期,否则,像顶层的Server、Service等组件需要等待Host、Context、Wrapper完成初始化才能结束初始化流程,整个初始化过程是具有传递性的

Connector初始化

Connector也是继承至LifecycleMBeanBase,公共的初始化逻辑都是一样的。我们先来看下Connector的默认配置,大部分属性配置都可以在Connector类中找到,tomcat默认开启了HTTP/1.1、AJP/1.3,其实AJP的用处不大,可以去掉

  1. <Connector port="8080" protocol="HTTP/1.1"
  2. connectionTimeout="20000"
  3. redirectPort="8443" />

Connector定义了很多属性,比如port、redirectPort、maxCookieCount、maxPostSize等等,比较有意思的是竟然找不到connectionTimeout的定义,全文搜索后发现使用了属性名映射,估计是为了兼容以前的版本

  1. protected static final HashMap<String,String> replacements = new HashMap<>();
  2. static {
  3. replacements.put("acceptCount", "backlog");
  4. replacements.put("connectionLinger", "soLinger");
  5. replacements.put("connectionTimeout", "soTimeout");
  6. replacements.put("rootFile", "rootfile");
  7. }
  8. public Object getProperty(String name) {
  9. String repl = name;
  10. if (replacements.get(name) != null) {
  11. repl = replacements.get(name);
  12. }
  13. return IntrospectionUtils.getProperty(protocolHandler, repl);
  14. }
  15. public boolean setProperty(String name, String value) {
  16. String repl = name;
  17. if (replacements.get(name) != null) {
  18. repl = replacements.get(name);
  19. }
  20. return IntrospectionUtils.setProperty(protocolHandler, repl, value);

initInternal过程如下所示:
1、 实例化Coyote适配器,这个适配器是用于Coyote的Request、Response与HttpServlet的Request、Response适配的,后续的博客会进行深入分析
2、 为ProtocolHander指定CoyoteAdapter用于处理请求
3、 初始化ProtocolHander,这一部分放在Connector后面进行分析

  1. protected void initInternal() throws LifecycleException {
  2. // 注册jmx
  3. super.initInternal();
  4. // 初始化Coyote适配器,这个适配器是用于Coyote的Request、Response与HttpServlet的Request、Response适配的
  5. adapter = new CoyoteAdapter(this);
  6. // protocolHandler需要指定Adapter用于处理请求
  7. protocolHandler.setAdapter(adapter);
  8. // Make sure parseBodyMethodsSet has a default
  9. if (null == parseBodyMethodsSet) {
  10. setParseBodyMethods(getParseBodyMethods());
  11. }
  12. // apr支持,忽略部分代码......
  13. // 初始化ProtocolHandler,这个init不是Lifecycle定义的init,而是ProtocolHandler接口的init
  14. try {
  15. protocolHandler.init();
  16. } catch (Exception e) {
  17. throw new LifecycleException(
  18. sm.getString("coyoteConnector.protocolHandlerInitializationFailed"), e);
  19. }

ProtocolHandler初始化

接下来,我们分析下HTTP/1.1的ProtocolHandler的初始化过程。首先,我们来认识下ProtocolHandler,它是一个抽象的协议实现,它不同于JNI这样的Jk协议,它是单线程、基于流的协议。ProtocolHandler是一个Cycote连接器实现的主要接口,而Adapter适配器是由一个Coyote Servlet容器实现的主要接口,定义了处理请求的抽象接口。

  1. public interface ProtocolHandler {
  2. public void setAdapter(Adapter adapter);
  3. public Adapter getAdapter();
  4. public Executor getExecutor();
  5. public void init() throws Exception;
  6. public void start() throws Exception;
  7. public void pause() throws Exception;
  8. public void resume() throws Exception;
  9. public void stop() throws Exception;
  10. public void destroy() throws Exception;
  11. // other code......
  12. }
  13. public interface Adapter {
  14. public void service(Request req, Response res) throws Exception;
  15. public boolean prepare(Request req, Response res) throws Exception;
  16. public boolean asyncDispatch(Request req,Response res, SocketEvent status) throws Exception;
  17. public void log(Request req, Response res, long time);
  18. public void checkRecycled(Request req, Response res);
  19. public String getDomain();

ProtocolHandler的子类如下所示,AbstractProtocol是基本的实现,而NIO默认使用的是Http11NioProtocol

调用ProtocolHandler的init进行初始化是调用的AbstractProtocol,首先完成jmx的注册,然后对NioEndpoint进行初始化

  1. public abstract class AbstractProtocol<S> implements ProtocolHandler,
  2. MBeanRegistration {
  3. public void init() throws Exception {
  4. // 完成jmx注册
  5. if (oname == null) {
  6. oname = createObjectName();
  7. if (oname != null) {
  8. Registry.getRegistry(null, null).registerComponent(this, oname, null);
  9. }
  10. }
  11. if (this.domain != null) {
  12. rgOname = new ObjectName(domain + ":type=GlobalRequestProcessor,name=" + getName());
  13. Registry.getRegistry(null, null).registerComponent(
  14. getHandler().getGlobal(), rgOname, null);
  15. }
  16. String endpointName = getName();
  17. endpoint.setName(endpointName.substring(1, endpointName.length()-1));
  18. endpoint.setDomain(domain);
  19. // 初始化endpoint
  20. endpoint.init();
  21. }

NioEndpoint初始化过程,最重要的是完成端口和地址的绑定监听工作,关于网络通信这块的内容将在后面着重介绍

  1. public class NioEndpoint extends AbstractJsseEndpoint<NioChannel> {
  2. public void bind() throws Exception {
  3. // 实例化ServerSocketChannel,并且绑定端口和地址
  4. serverSock = ServerSocketChannel.open();
  5. socketProperties.setProperties(serverSock.socket());
  6. InetSocketAddress addr = (getAddress()!=null?new InetSocketAddress(getAddress(),getPort()):new InetSocketAddress(getPort()));
  7. // 设置最大连接数,原来是在这里设置的
  8. serverSock.socket().bind(addr,getAcceptCount());
  9. serverSock.configureBlocking(true); //mimic APR behavior
  10. // 初始化acceptor、poller线程的数量
  11. // Initialize thread count defaults for acceptor, poller
  12. if (acceptorThreadCount == 0) {
  13. // FIXME: Doesn't seem to work that well with multiple accept threads
  14. acceptorThreadCount = 1;
  15. }
  16. if (pollerThreadCount <= 0) {
  17. pollerThreadCount = 1;
  18. }
  19. setStopLatch(new CountDownLatch(pollerThreadCount));
  20. // 如果有必要的话初始化ssl
  21. initialiseSsl();
  22. // 初始化selector
  23. selectorPool.open();
  24. }
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/煮酒与君饮/article/detail/741621
推荐阅读
相关标签
  

闽ICP备14008679号