1、spring cloud 父子容器整体关系

由于在spring cloud启动源码加载过程中,没太弄清楚tomcat容器,spring 容器,spring cloud容器整体的加载顺序以及关系,所以想分析下这个流程,先不多说,上图
在这里插入图片描述
容器大致分为三层:

BootStrap Spring 容器:由SpringCloud 监听器创建,用来初始化 SpringCloud 上下文
SpringBoot Spring 容器:由SpringBoot创建,也是项目中常用的Spring容器。
微服务 Spring相关容器:Feign和Ribbon配置类对应的上下文,由配置容器抽象工厂 NamedContextFactory 创建,用于容器隔离。
先说spring cloud容器bootstrap,在容器启动时,会先读取boostrap.xml文件,进行基本配置的加载,随后才会加载application.xml,这其实也说明了bootstrap容器是先于spring boot容器的,本质其实是spring 容器的环境配置是依赖于bootstrap的,bootstrap上下文是主应用程序的父上下文共享一个 Environment,它是任何Spring应用程序的外部属性的来源。要注意的是,bootstrap容器的配置优先级要高于spring boot,这种优先级不但体现于加载顺序,还有就是当俩者都存在某一项配置,boostrap配置是不会被覆盖,即以bootstrap配置为主

废话不多说,先看源码
SpringBoot 在启动时,会触发相关一系列监听器,监听器各司其职,做一些初始化预处理操作。SpringCloud 实现了自己的监听器:BootstrapApplicationListener,来初始化SpringCloud上下文环境。

@Override
	public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) {
		ConfigurableEnvironment environment = event.getEnvironment();
		//如果未开启SpringCloud,直接返回
		if (!environment.getProperty("spring.cloud.bootstrap.enabled", Boolean.class,
				true)) {
			return;
		}
		// don't listen to events in a bootstrap context
		//判断该监听器是否已经执行过,如果执行过,直接返回
		if (environment.getPropertySources().contains(BOOTSTRAP_PROPERTY_SOURCE_NAME)) {
			return;
		}
		//这里返回了一个 Spring 容器
		ConfigurableApplicationContext context = bootstrapServiceContext(environment,
				event.getSpringApplication());
		apply(context, event.getSpringApplication(), environment);
	}

bootstrapServiceContext方法创建了一个 Spring 容器:ConfigurableApplicationContext:

private ConfigurableApplicationContext bootstrapServiceContext(
			ConfigurableEnvironment environment, final SpringApplication application) {
		StandardEnvironment bootstrapEnvironment = new StandardEnvironment();
		MutablePropertySources bootstrapProperties = bootstrapEnvironment
				.getPropertySources();
		for (PropertySource<?> source : bootstrapProperties) {
			bootstrapProperties.remove(source.getName());
		}
		//设置读取 bootstrap 文件
		String configName = environment
				.resolvePlaceholders("${spring.cloud.bootstrap.name:bootstrap}");
		//设置 bootstrap 文件路径
		String configLocation = environment
				.resolvePlaceholders("${spring.cloud.bootstrap.location:}");
		Map<String, Object> bootstrapMap = new HashMap<>();
		bootstrapMap.put("spring.config.name", configName);
		if (StringUtils.hasText(configLocation)) {
			bootstrapMap.put("spring.config.location", configLocation);
		}
		//设置是否已经初始化BootStrap环境
		bootstrapProperties.addFirst(
				new MapPropertySource(BOOTSTRAP_PROPERTY_SOURCE_NAME, bootstrapMap));
		for (PropertySource<?> source : environment.getPropertySources()) {
			bootstrapProperties.addLast(source);
		}
			//......
			//加载BootstrapConfiguration 配置类
			List<String> names = SpringFactoriesLoader
				.loadFactoryNames(BootstrapConfiguration.class, classLoader);
			for (String name : StringUtils.commaDelimitedListToStringArray(
					environment.getProperty("spring.cloud.bootstrap.sources", ""))) {
				names.add(name);
			}
		//创建 Spring 容器
		SpringApplicationBuilder builder = new SpringApplicationBuilder()
				.profiles(environment.getActiveProfiles()).bannerMode(Mode.OFF)
				.environment(bootstrapEnvironment)
				.properties("spring.application.name:" + configName)
				.registerShutdownHook(false)
				.logStartupInfo(false)
				.web(false);
		List<Class<?>> sources = new ArrayList<>();

		builder.sources(sources.toArray(new Class[sources.size()]));
		AnnotationAwareOrderComparator.sort(sources);
		final ConfigurableApplicationContext context = builder.run();
		//创建祖先容器
		addAncestorInitializer(application, context);
		bootstrapProperties.remove(BOOTSTRAP_PROPERTY_SOURCE_NAME);
		mergeDefaultProperties(environment.getPropertySources(), bootstrapProperties);
		return context;
	}

首先,SpringBoot项目是通过SpringApplicationBuilder启动,在上述逻辑中又构建了一个SpringApplicationBuilder对象,再次执行run方法:final ConfigurableApplicationContext context = builder.run();
所以启动流程会执行两遍,只是读取的配置文件和配置类不同。
同样的,当第二次创建SpringApplicationBuilder并启动时,会不会再次出发监听器,然后接着创建SpringApplicationBuilder呢?
肯定不会。否则就是死循环了。上面已经提到了,SpringCloud通过标识符BOOTSTRAP_PROPERTY_SOURCE_NAME来判断。监听器执行之后,会设置该变量对应值,下次启动前如果有值,表明已经执行。

上面有一行关键的代码:addAncestorInitializer(application, context);
ancestor 祖先的意思,来看一下:

private void addAncestorInitializer(SpringApplication application,
			ConfigurableApplicationContext context) {
		boolean installed = false;
		//遍历所有的initializer,判断是否已经存在 祖先initializer
		for (ApplicationContextInitializer<?> initializer : application
				.getInitializers()) {
			if (initializer instanceof AncestorInitializer) {
				installed = true;
				// 如果存在,则设置 bootStrapApplication
				((AncestorInitializer) initializer).setParent(context);
			}
		}
		//如果不存在,则创建。
		if (!installed) {
			application.addInitializers(new AncestorInitializer(context));
		}
	}

在这里插入图片描述
主要是创建 AncestorInitializer 对象,并传入 SpringCloud 创建的 context。也就是祖先容器。
当SpringCloud BootStrap环境初始化完毕后,再次回到SpringBoot初始化流程,SpringBoot会触发所有的initializers,当执行祖先initializer:AncestorInitializer时,将BootStrapApplication容器设为父容器:

private static class AncestorInitializer implements
			ApplicationContextInitializer<ConfigurableApplicationContext>, Ordered {

		private ConfigurableApplicationContext parent;

		public AncestorInitializer(ConfigurableApplicationContext parent) {
			this.parent = parent;
		}
		@Override
		public void initialize(ConfigurableApplicationContext context) {
			//如果已经存在父容器,则直接取出
			while (context.getParent() != null && context.getParent() != context) {
				context = (ConfigurableApplicationContext) context.getParent();
			}
			reorderSources(context.getEnvironment());
			//设置父容器
			new ParentContextApplicationContextInitializer(this.parent)
					.initialize(context);
		}
}

上述方法将设置父容器的逻辑委托给ParentContextApplicationContextInitializer类处理,来看下initialize方法:

public class ParentContextApplicationContextInitializer implements
		ApplicationContextInitializer<ConfigurableApplicationContext>, Ordered {

	private int order = Ordered.HIGHEST_PRECEDENCE;

	private final ApplicationContext parent;

	@Override
	public void initialize(ConfigurableApplicationContext applicationContext) {
		if (applicationContext != this.parent) {
			//设置父容器
			applicationContext.setParent(this.parent);
			//创建监听器,主要用来发布项目中存在父子容器事件
			applicationContext.addApplicationListener(EventPublisher.INSTANCE);
		}
	}
}

SpringBoot Spring容器

SpringBoot创建的 Spring容器是最核心的容器,也是使用最多的Spring容器。
创建的对象会有3种类型,Servlet,Reactive,和默认。
在SpringBoot2.x版本中的创建以及判断如下:

public ConfigurableApplicationContext run(String... args) {
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        ConfigurableApplicationContext context = null;
        Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList();
        this.configureHeadlessProperty();
        SpringApplicationRunListeners listeners = this.getRunListeners(args);
        listeners.starting();

        Collection exceptionReporters;
        try {
            ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
            ConfigurableEnvironment environment = this.prepareEnvironment(listeners, applicationArguments);
            this.configureIgnoreBeanInfo(environment);
            Banner printedBanner = this.printBanner(environment);
            context = this.createApplicationContext();
            exceptionReporters = this.getSpringFactoriesInstances(SpringBootExceptionReporter.class, new Class[]{ConfigurableApplicationContext.class}, context);
            this.prepareContext(context, environment, listeners, applicationArguments, printedBanner);
            this.refreshContext(context);
            ....
}

进入context = this.createApplicationContext();方法

public class SpringApplication {
	//......
	protected ConfigurableApplicationContext createApplicationContext() {
		Class<?> contextClass = this.applicationContextClass;
		if (contextClass == null) {
			try {
				switch (this.webApplicationType) {
				case SERVLET:
					contextClass = Class.forName(DEFAULT_WEB_CONTEXT_CLASS);
					break;
				case REACTIVE:
					contextClass = Class.forName(DEFAULT_REACTIVE_WEB_CONTEXT_CLASS);
					break;
				default:
					contextClass = Class.forName(DEFAULT_CONTEXT_CLASS);
				}
			}
			catch (ClassNotFoundException ex) {
				throw new IllegalStateException(
						"Unable create a default ApplicationContext, "
								+ "please specify an ApplicationContextClass",
						ex);
			}
		}
		return (ConfigurableApplicationContext) BeanUtils.instantiateClass(contextClass);
	}
	//......
}

好的这里进入了容器类型选择,根据webApplicationType来判断创建哪种类型的Servlet,代码中分别对应着Web类型(SERVLET),响应式Web类型(REACTIVE),非Web类型(default),我们建立的是Web类型,所以肯定实例化DEFAULT_SERVLET_WEB_CONTEXT_CLASS指定的类。
也就是AnnotationConfigServletWebServerApplicationContext类,用图来说明下这个类的关系。
在这里插入图片描述
通过这个类图可以知道,这个类继承的是ServletWebServerApplicationContext,这才是真正的主角,而这个类最终继承了AbstractApplicationContext,了解完创建上下文的情况后,我们再来看看刷新上下文。

	//类: SpringApplication
	
	private void refreshContext(ConfigurableApplicationContext context) {
		refresh(context);
		if (this.registerShutdownHook) {
			try {
				context.registerShutdownHook();
			}
			catch (AccessControlException ex) {
				// Not allowed in some environments.
			}
		}
	}
	
	protected void refresh(ApplicationContext applicationContext) {
		Assert.isInstanceOf(AbstractApplicationContext.class, applicationContext);
		((AbstractApplicationContext) applicationContext).refresh();
	}

这里还是直接传递调用本类的refresh(context)方法,最后强转成父类AbstractApplicationContext,调用其refresh()方法,源码如下:

	//类: AbstractApplicationContext
		
	public void refresh() throws BeansException, IllegalStateException {
		synchronized (this.startupShutdownMonitor) {
			// Prepare this context for refreshing.
			prepareRefresh();

			// Tell the subclass to refresh the internal bean factory.
			ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

			// Prepare the bean factory for use in this context.
			prepareBeanFactory(beanFactory);

			try {
				// Allows post-processing of the bean factory in context subclasses.
				postProcessBeanFactory(beanFactory);

				// Invoke factory processors registered as beans in the context.
				invokeBeanFactoryPostProcessors(beanFactory);

				// Register bean processors that intercept bean creation.
				registerBeanPostProcessors(beanFactory);

				// Initialize message source for this context.
				initMessageSource();

				// Initialize event multicaster for this context.
				initApplicationEventMulticaster();

				// Initialize other special beans in specific context subclasses.
				onRefresh();

				// Check for listener beans and register them.
				registerListeners();

				// Instantiate all remaining (non-lazy-init) singletons.
				finishBeanFactoryInitialization(beanFactory);

				// Last step: publish corresponding event.
				finishRefresh();
			}

			catch (BeansException ex) {
				if (logger.isWarnEnabled()) {
					logger.warn("Exception encountered during context initialization - " +
							"cancelling refresh attempt: " + ex);
				}

				// Destroy already created singletons to avoid dangling resources.
				destroyBeans();

				// Reset 'active' flag.
				cancelRefresh(ex);

				// Propagate exception to caller.
				throw ex;
			}

			finally {
				// Reset common introspection caches in Spring's core, since we
				// might not ever need metadata for singleton beans anymore...
				resetCommonCaches();
			}
		}
	}

这里,我们看到onRefresh()方法是调用其子类的实现,根据我们上文的分析,我们这里的子类是ServletWebServerApplicationContext

	//类: ServletWebServerApplicationContext
	
	protected void onRefresh() {
		super.onRefresh();
		try {
			createWebServer();
		}
		catch (Throwable ex) {
			throw new ApplicationContextException("Unable to start web server", ex);
		}
	}

	private void createWebServer() {
		WebServer webServer = this.webServer;
		ServletContext servletContext = getServletContext();
		if (webServer == null && servletContext == null) {
			ServletWebServerFactory factory = getWebServerFactory();
			this.webServer = factory.getWebServer(getSelfInitializer());
		}
		else if (servletContext != null) {
			try {
				getSelfInitializer().onStartup(servletContext);
			}
			catch (ServletException ex) {
				throw new ApplicationContextException("Cannot initialize servlet context",
						ex);
			}
		}
		initPropertySources();
	}

到这里,终于见到了庐山真面目,createWebServer()就是启动web服务,但是还没有真正启动Tomcat,既然webServer是通过ServletWebServerFactory来获取的,那就来看看这个工厂的真面目。
在这里插入图片描述

Tomcat容器

根据上图,我们发现工厂类是一个接口,各个具体服务的实现是由各个子类来完成的,所以,就去看看TomcatServletWebServerFactory.getWebServer()的实现。

	@Override
	public WebServer getWebServer(ServletContextInitializer... initializers) {
		Tomcat tomcat = new Tomcat();
		File baseDir = (this.baseDirectory != null) ? this.baseDirectory
				: createTempDir("tomcat");
		tomcat.setBaseDir(baseDir.getAbsolutePath());
		Connector connector = new Connector(this.protocol);
		tomcat.getService().addConnector(connector);
		customizeConnector(connector);
		tomcat.setConnector(connector);
		tomcat.getHost().setAutoDeploy(false);
		configureEngine(tomcat.getEngine());
		for (Connector additionalConnector : this.additionalTomcatConnectors) {
			tomcat.getService().addConnector(additionalConnector);
		}
		prepareContext(tomcat.getHost(), initializers);
		return getTomcatWebServer(tomcat);
	}

根据上面的代码,我们发现其实主要做两件事,第一件事就是把Connnctor(我们称之为连接器)对象添加到Tomcat中;第二件事就是configureEngine,这连接器我们勉强能理解(不理解后面会述说),那这个Engine是什么呢?查看tomcat.getEngine()的源码:

    public Engine getEngine() {
        Service service = getServer().findServices()[0];
        if (service.getContainer() != null) {
            return service.getContainer();
        }
        Engine engine = new StandardEngine();
        engine.setName( "Tomcat" );
        engine.setDefaultHost(hostname);
        engine.setRealm(createDefaultRealm());
        service.setContainer(engine);
        return engine;
    }

根据上面的源码,我们发现,原来这个Engine是容器,继续跟踪源码,找到Container接口。
在这里插入图片描述
上图中,我们看到了4个子接口,分别是Engine、Host、Context、Wrapper。我们从继承关系上可以知道他们都是容器,那么他们之间的区别是什么呢?那就从它们的注释来分析分析:

/**
 * If used, an Engine is always the top level Container in a Catalina
 * hierarchy. Therefore, the implementation's <code>setParent()</code> method
 * should throw <code>IllegalArgumentException</code>.
 *
 * @author Craig R. McClanahan
 */
public interface Engine extends Container {
	//省略代码
}


/**
 * <p>
 * The parent Container attached to a Host is generally an Engine, but may
 * be some other implementation, or may be omitted if it is not necessary.
 * <p>
 * The child containers attached to a Host are generally implementations
 * of Context (representing an individual servlet context).
 *
 * @author Craig R. McClanahan
 */
public interface Host extends Container {
	//省略代码
}


/**
 * <p>
 * The parent Container attached to a Context is generally a Host, but may
 * be some other implementation, or may be omitted if it is not necessary.
 * <p>
 * The child containers attached to a Context are generally implementations
 * of Wrapper (representing individual servlet definitions).
 * <p>
 *
 * @author Craig R. McClanahan
 */
public interface Context extends Container, ContextBind {
	//省略代码
}


/**
 * <p>
 * The parent Container attached to a Wrapper will generally be an
 * implementation of Context, representing the servlet context (and
 * therefore the web application) within which this servlet executes.
 * <p>
 * Child Containers are not allowed on Wrapper implementations, so the
 * <code>addChild()</code> method should throw an
 * <code>IllegalArgumentException</code>.
 *
 * @author Craig R. McClanahan
 */
public interface Wrapper extends Container {
	//省略代码
}

上面的注释翻译过来就是:Engine是最高级别的容器,其子容器是Host,Host的子容器是Context,Wrapper是Context的子容器,所以这4个容器的关系就是父子关系。也就是:
Engine > Host > Context > Wrapper 接着,再看看Tomcat的源码:

//部分代码
public class Tomcat {

	//设置连接器
    public void setConnector(Connector connector) {
        Service service = getService();
        boolean found = false;
        for (Connector serviceConnector : service.findConnectors()) {
            if (connector == serviceConnector) {
                found = true;
            }
        }
        if (!found) {
            service.addConnector(connector);
        }
    }

	//获取service
    public Service getService() {
        return getServer().findServices()[0];
    }

    //设置Host容器
    public void setHost(Host host) {
        Engine engine = getEngine();
        boolean found = false;
        for (Container engineHost : engine.findChildren()) {
            if (engineHost == host) {
                found = true;
            }
        }
        if (!found) {
            engine.addChild(host);
        }
    }

    //获取Engine容器
    public Engine getEngine() {
        Service service = getServer().findServices()[0];
        if (service.getContainer() != null) {
            return service.getContainer();
        }
        Engine engine = new StandardEngine();
        engine.setName( "Tomcat" );
        engine.setDefaultHost(hostname);
        engine.setRealm(createDefaultRealm());
        service.setContainer(engine);
        return engine;
    }

    //获取server
    public Server getServer() {

        if (server != null) {
            return server;
        }

        System.setProperty("catalina.useNaming", "false");

        server = new StandardServer();

        initBaseDir();

        server.setPort( -1 );

        Service service = new StandardService();
        service.setName("Tomcat");
        server.addService(service);
        return server;
    }

    //添加context容器
    public Context addContext(Host host, String contextPath, String contextName,
            String dir) {
        silence(host, contextName);
        Context ctx = createContext(host, contextPath);
        ctx.setName(contextName);
        ctx.setPath(contextPath);
        ctx.setDocBase(dir);
        ctx.addLifecycleListener(new FixContextListener());

        if (host == null) {
            getHost().addChild(ctx);
        } else {
            host.addChild(ctx);
        }
        return ctx;
    }

    //添加Wrapper容器
    public Context addWebapp(Host host, String contextPath, String docBase) {
        LifecycleListener listener = null;
        try {
            Class<?> clazz = Class.forName(getHost().getConfigClass());
            listener = (LifecycleListener) clazz.getConstructor().newInstance();
        } catch (ReflectiveOperationException e) {
            // Wrap in IAE since we can't easily change the method signature to
            // to throw the specific checked exceptions
            throw new IllegalArgumentException(e);
        }

        return addWebapp(host,  contextPath, docBase, listener);
    }
}

阅读Tomcat的getServer()方法,我们可以知道,Tomcat的最顶层就是Server,也就是Tomcat的实例,一个Tomcat一个Server;通过getEngine()我们可以了解到Server下面是Service,而且是多个,一个Service代表我们部署的一个应用,还可以知道,Engine容器,一个Service只有一个;根据父子关系,通过setHost()源码可知,host容器有多个;同理,我们发现addContext()源码下,Context也是多个;addServlet()表明Wrapper容器也是多个,而且这段代码也暗示了,其实Wrapper和Servlet是一层意思。另外我们根据setConnector源码可以知道,连接器(Connector)是设置在Service下的,而且是可以设置多个连接器(Connector)。
Engine下有多个Host子容器,Host下有多个Context子容器,Context下有多个Wrapper子容器

根据上面的分析,可以总结出Tomcat主要包含了 2 个核心组件:连接器(Connector)和容器(Container),用图表示如下:
微
在这里插入图片描述
一个Tomcat是一个Server,一个Server下有多个Service,也就是我们部署的多个应用,一个应用下有多个连接器(Connector)和一个容器(Container),容器下有多个子容器,关系用图表示如下:
在这里插入图片描述

tomcat最顶层容器是Server,代表着整个服务器,一个Server包含多个Service。从上图可以看除Service主要包括多个Connector和一个Container。Connector用来处理连接相关的事情,并提供Socket到Request和Response相关转化。Container用于封装和管理Servlet,以及处理具体的Request请求。那么上文提到的Engine>Host>Context>Wrapper容器又是怎么回事呢? 我们来看下图:
在这里插入图片描述

服务配置容器

前面 uml 图中提到了一个关键类:NamedContextFactory,从命名可以看出,这是一个工厂类:抽象容器工厂。同 hystrix 线程隔离原理一样,该工厂根据不同的服务名称,创建不同的容器。该容器有2个实现类,FeignContext 和 SpringClientFactory,分别用来加载对应的配置。
在这里插入图片描述
查看其核心代码:

public abstract class NamedContextFactory<C extends NamedContextFactory.Specification>
		implements DisposableBean, ApplicationContextAware {

	//Feign 和Ribbon 配置抽象接口
	public interface Specification {
		String getName();
		Class<?>[] getConfiguration();
	}

	//Application集合
	private Map<String, AnnotationConfigApplicationContext> contexts = new ConcurrentHashMap<>();


	protected AnnotationConfigApplicationContext getContext(String name) {
			//根据服务名称获取对应配置工厂,如果没有,则创建
			if (!this.contexts.containsKey(name)) {
				synchronized (this.contexts) {
					if (!this.contexts.containsKey(name)) {
						//创建并进行缓存
						this.contexts.put(name, createContext(name));
					}
				}
			}
			return this.contexts.get(name);
		}

	protected AnnotationConfigApplicationContext createContext(String name) {
		//创建一个 Spring 容器
		AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
		if (this.configurations.containsKey(name)) {
			for (Class<?> configuration : this.configurations.get(name)
					.getConfiguration()) {
				//注入配置类
				context.register(configuration);
			}
		}
		//注入默认的Feign或Ribbon配置类
		for (Map.Entry<String, C> entry : this.configurations.entrySet()) {
			if (entry.getKey().startsWith("default.")) {
				for (Class<?> configuration : entry.getValue().getConfiguration()) {
					context.register(configuration);
				}
			}
		}
		context.register(PropertyPlaceholderAutoConfiguration.class,
				this.defaultConfigType);
		context.getEnvironment().getPropertySources().addFirst(new MapPropertySource(
				this.propertySourceName,
				Collections.<String, Object> singletonMap(this.propertyName, name)));
		if (this.parent != null) {
			// Uses Environment from parent as well as beans
			//设置父类为 SpringBoot 创建的Spring 容器
			context.setParent(this.parent);
		}
		//启动容器
		context.refresh();
		return context;
	}
}

所以,具体Feign 和 Ribbon配置类会创建多少实例,和项目本身依赖发服务有关。如果依赖10个服务,那就是20个微服务配置容器+SpringBoot容器+BootStrap容器
注意:由于Ribbon 默认会采用懒加载,也就是只有第一次请求的时候才会加载。所以idea这里不会显示 Ribbon 相关配置类容器,只显示项目启动流程中创建完成的 Spring 容器。
这也是微服务经常第一次超时的根本原理,创建并启动一个Spring容器需要一定的时间。

Logo

北京人形旗下天工造物具身智能开源社区,聚焦具身天工与慧思开物两大平台

更多推荐