spring cloud,sping boot,微服务,tomcat容器关系及创建执行顺序详解
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容器需要一定的时间。
更多推荐
所有评论(0)