文档章节

Spring Boot启动过程(一)

drv
 drv
发布于 2017/01/17 13:15
字数 2234
阅读 36
收藏 1

  之前在排查一个线上问题时,不得不仔细跑了很多遍Spring Boot的代码,于是整理一下,我用的是1.4.3.RELEASE。

  首先,普通的入口,这没什么好说的,我就随便贴贴代码了:

SpringApplication.run(Application.class, args);
-->
  public static ConfigurableApplicationContext run(Object source, String... args) {
    return run(new Object[] { source }, args);
  }

  public static ConfigurableApplicationContext run(Object[] sources, String[] args) {
    return new SpringApplication(sources).run(args);
  }

   也就是一个静态方法,调用了构造函数创建实例,构造的参数是Object数组,这里new这个数组的时候传入了一个元素就是启动类的类对象实例(一般就是“new Object[] { Application.class” }),构造函数里调用了一个initialize方法。

  SpringApplication的initialize方法,首先在Object数组有值的情况下将数组放入一个final的类实例私有Object的Set集合中;然后用deduceWebEnvironment方法判断当前应用环境是否是web环境,判断逻辑是看Classpath是否同时存在javax.servlet.Servlet和org.springframework.web.context.ConfigurableWebApplicationContext,缺一就认为不是。然后,调用setInitializers方法,设置类实例的私有List<ApplicationContextInitializer<?>>类型变量initializers:

public void setInitializers( Collection<? extends ApplicationContextInitializer<?>> initializers) { this.initializers = new ArrayList<ApplicationContextInitializer<?>>(); this.initializers.addAll(initializers); }

  设置的时候回先new,也就是说这方法每次都是整体更换,不会追加。这个方法的参数都是各个模块中配置在META-INF/spring.factories中的key为org.springframework.context.ApplicationContextInitializer的值,这些类都是接口ApplicationContextInitializer<C extends ConfigurableApplicationContext>的泛型实现。

private <T> Collection<? extends T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) { ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); // Use names and ensure unique to protect against duplicates
        Set<String> names = new LinkedHashSet<String>( SpringFactoriesLoader.loadFactoryNames(type, classLoader)); List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names); AnnotationAwareOrderComparator.sort(instances); return instances; }

  使用SpringFactoriesLoader.loadFactoryNames方法去除上面说的被配置的ApplicationContextInitializer的名字放进Set<String>中,并用反射创建这些名字的实例。

  

  setInitializers方法之后是setInitializers,参数同上都是getSpringFactoriesInstances方法获取,只不过这次参数Class<T> type泛型类型是org.springframework.context.ApplicationListener。

  

   initialize方法的最后一个步是设置实例的Class<?>类型私有属性mainApplicationClass,获取设置值的方法deduceMainApplicationClass:

private Class<?> deduceMainApplicationClass() { try { StackTraceElement[] stackTrace = new RuntimeException().getStackTrace(); for (StackTraceElement stackTraceElement : stackTrace) { if ("main".equals(stackTraceElement.getMethodName())) { return Class.forName(stackTraceElement.getClassName()); } } } catch (ClassNotFoundException ex) { // Swallow and continue
 } return null; }

  实例化SpringApplication后调用了它的run实例方法(注意不是上面的静态方法)。一进run方法首先启动了StopWatch,这个StopWatch的功能在类的注释写可,大概意思是这是个简单的秒表,用于在开发过程中方便程序员调试性能等,非线程安全,不建议用于生产。configureHeadlessProperty设置使用Headless,对于只有远程登录使用的服务器来说这样性能要好一些。接着是加载用于这个run方法启动过程的监听器,依然是getSpringFactoriesInstances方法,这次的类型是org.springframework.boot.SpringApplicationRunListener:

# Run Listeners org.springframework.boot.SpringApplicationRunListener=\ org.springframework.boot.context.event.EventPublishingRunListener

  

 SpringApplicationRunListeners(Log log, Collection<? extends SpringApplicationRunListener> listeners) { this.log = log; this.listeners = new ArrayList<SpringApplicationRunListener>(listeners); }

  先是加载所有可用监听,然后初始化SpringApplicationRunListeners对象,最后循环启动所有SpringApplicationRunListener监听。启动监听的方法:

 @Override public void started() { this.initialMulticaster .multicastEvent(new ApplicationStartedEvent(this.application, this.args)); }

  ApplicationStartedEvent实例化传了两个参数,先看第一个参数this.application是怎么来的,实例的SpringApplication的run方法中,用于获取SpringApplicationRunListener,也就是前面说的getSpringFactoriesInstances被调用时:

private SpringApplicationRunListeners getRunListeners(String[] args) { Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class }; return new SpringApplicationRunListeners(logger, getSpringFactoriesInstances( SpringApplicationRunListener.class, types, this, args)); }

  getSpringFactoriesInstances方法的参数包含SpringApplication.class和this,这两个参数被传入createSpringFactoriesInstances方法:

  

  可以看到,是通过反射创建实例的时候,将SpringApplication中的this传进来EventPublishingRunListener构造的,然后EventPublishingRunListener构造:

public EventPublishingRunListener(SpringApplication application, String[] args) { this.application = application; this.args = args; this.initialMulticaster = new SimpleApplicationEventMulticaster(); for (ApplicationListener<?> listener : application.getListeners()) { this.initialMulticaster.addApplicationListener(listener); } }

  最后在构造ApplicationStartedEvent时传给它的基类EventObject的protected不可序列化属性source。实例化ApplicationStartedEvent后instance.getClass()并包装为ResolvableType类型以保存类型信息,并将它和event作为参数传入SimpleApplicationEventMulticaster的multicastEvent方法。multicastEvent首先获取ApplicationListener,使用getApplicationListeners方法,这个方法中抛开对listener做了一些缓存类工作外,主要就是将事件和对应的监听器做了下是否支持的验证,返回通过了retrieveApplicationListeners中通过了supportsEvent验证的监听器集合,这里就体现出了ResolvableType的作用,它保存了类型的信息同时对泛型类型也支持。

   得到了这些匹配的监听器后,判断当前Executor是否被设置过,如果为null则同步循环执行所有:invokeListener(listener, event);如果不为null则:

executor.execute(new Runnable() { @Override public void run() { invokeListener(listener, event); } });

  监听器执行的时候也会先判断是否是该由自己处理的事件,例如:

 @Override public void onApplicationEvent(ApplicationEvent event) { if (event instanceof ApplicationEnvironmentPreparedEvent) { onApplicationEnvironmentPreparedEvent( (ApplicationEnvironmentPreparedEvent) event); } if (event instanceof ApplicationPreparedEvent) { onApplicationPreparedEvent(event); } }

  监听启动后,只准备一些启动参数,和环境变量prepareEnvironment方法先是读取了应用的启动参数和profile配置,然后用listeners.environmentPrepared(environment)传给监听器:

public void environmentPrepared(ConfigurableEnvironment environment) { this.initialMulticaster.multicastEvent(new ApplicationEnvironmentPreparedEvent( this.application, this.args, environment)); }

   接着判断如果environment是org.springframework.web.context.ConfigurableWebEnvironment的实例,但webEnvironment不是true,也就是说存在org.springframework.web.context.ConfigurableWebEnvironmen但不存在javax.servlet.Servlet的情况,会多执行一步environment = convertToStandardEnvironment(environment)转换。

  之后的printBanner就不细说了,如果你在resource下自定义了一个banner.txt文件,启动时会输出内容,否则输出:

 . ____ _ __ _ _ /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| | ) ) ) ) '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/ :: Spring Boot :: (v1.4.3.RELEASE)

   接着创建ConfigurableApplicationContext实例,方法也很简单,如果是web环境就BeanUtils.instantiate一个org.springframework.boot.context.embedded. AnnotationConfigEmbeddedWebApplicationContext的实例并强转为ConfigurableApplicationContext,否则用org.springframework.context.annotation. AnnotationConfigApplicationContext的实例强转。

  创建FailureAnalyzers实例,记录了ConfigurableApplicationContext实例中需要关注的部分,如果启动出错了可以据此分析,可以配置,具体的逻辑依然是老方法spring.factories:

  不同的Analyzer关注不同的部分,自己可以扩展配置,最后prepareFailureAnalyzers方法给所有Analyzer实例setBeanFactory(context.getBeanFactory()),一旦启动过程进入catch,被注册的Analyzer实例的analyze方法就会被触发执行,分析结果会被loggedExceptions.add(exception)加入到抛出的异常中:

private FailureAnalysis analyze(Throwable failure, List<FailureAnalyzer> analyzers) { for (FailureAnalyzer analyzer : analyzers) { FailureAnalysis analysis = analyzer.analyze(failure); if (analysis != null) { return analysis; } } return null; } 例如:NoSuchBeanDefinitionFailureAnalyzer @Override protected FailureAnalysis analyze(Throwable rootFailure, NoSuchBeanDefinitionException cause, String description) { if (cause.getNumberOfBeansFound() != 0) { return null; } List<AutoConfigurationResult> autoConfigurationResults = getAutoConfigurationResults( cause); StringBuilder message = new StringBuilder(); message.append(String.format("%s required %s that could not be found.%n", description == null ? "A component" : description, getBeanDescription(cause))); if (!autoConfigurationResults.isEmpty()) { for (AutoConfigurationResult provider : autoConfigurationResults) { message.append(String.format("\t- %s%n", provider)); } } String action = String.format("Consider %s %s in your configuration.", (!autoConfigurationResults.isEmpty() ? "revisiting the conditions above or defining" : "defining"), getBeanDescription(cause)); return new FailureAnalysis(message.toString(), action, cause); }

   prepareContext方法中postProcessApplicationContext会在this.beanNameGenerator存在的情况下加载自定义命名策略,然后在this.resourceLoader存在的情况下为context设置resourceLoader和classLoader。applyInitializers方法调用之前加载的Initializer的实例并执行其initialize方法,例如加载环境变量信息、注册EmbeddedServletContainerInitializedEvent的监听、注册CachingMetadataReaderFactoryPostProcessor等。listeners.contextPrepared(context)由于EventPublishingRunListener的contextPrepared是空的,先不说了。logStartupInfo部分初始化了logger,然后根据配置情况打印了启动或运行以及profile是否配置的日志:

protected void logStartupInfo(boolean isRoot) { if (isRoot) { new StartupInfoLogger(this.mainApplicationClass) .logStarting(getApplicationLog()); } } protected Log getApplicationLog() { if (this.mainApplicationClass == null) { return logger; } return LogFactory.getLog(this.mainApplicationClass); } public void logStarting(Log log) { Assert.notNull(log, "Log must not be null"); if (log.isInfoEnabled()) { log.info(getStartupMessage()); } if (log.isDebugEnabled()) { log.debug(getRunningMessage()); } } protected void logStartupProfileInfo(ConfigurableApplicationContext context) { Log log = getApplicationLog(); if (log.isInfoEnabled()) { String[] activeProfiles = context.getEnvironment().getActiveProfiles(); if (ObjectUtils.isEmpty(activeProfiles)) { String[] defaultProfiles = context.getEnvironment().getDefaultProfiles(); log.info("No active profile set, falling back to default profiles: "
                        + StringUtils.arrayToCommaDelimitedString(defaultProfiles)); } else { log.info("The following profiles are active: "
                        + StringUtils.arrayToCommaDelimitedString(activeProfiles)); } } }

   接着prepareContext中注册启动参数(applicationArguments)到bean工厂,包括logger、commandLineArgs等。然后加载bean定义的来源并根据其中配置加载bean,这里的sources就是初始化启动类时传进来的那个sources:

 BeanDefinitionLoader(BeanDefinitionRegistry registry, Object... sources) { Assert.notNull(registry, "Registry must not be null"); Assert.notEmpty(sources, "Sources must not be empty"); this.sources = sources; this.annotatedReader = new AnnotatedBeanDefinitionReader(registry); this.xmlReader = new XmlBeanDefinitionReader(registry); if (isGroovyPresent()) { this.groovyReader = new GroovyBeanDefinitionReader(registry); } this.scanner = new ClassPathBeanDefinitionScanner(registry); this.scanner.addExcludeFilter(new ClassExcludeFilter(sources)); } 

  注意下面的sources是待加载的,和上面这段不是同一个:

public int load() { int count = 0; for (Object source : this.sources) { count += load(source); } return count; } private int load(Object source) { Assert.notNull(source, "Source must not be null"); if (source instanceof Class<?>) { return load((Class<?>) source); } if (source instanceof Resource) { return load((Resource) source); } if (source instanceof Package) { return load((Package) source); } if (source instanceof CharSequence) { return load((CharSequence) source); } throw new IllegalArgumentException("Invalid source type " + source.getClass()); }

  类型不同加载过程不同,其中Class<?>加载过程大概是通过BeanDefinitionLoader调用AnnotatedBeanDefinitionReader的registerBean方法:

public void registerBean(Class<?> annotatedClass, String name, Class<? extends Annotation>... qualifiers) { AnnotatedGenericBeanDefinition abd = new AnnotatedGenericBeanDefinition(annotatedClass); if (this.conditionEvaluator.shouldSkip(abd.getMetadata())) { return; } ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(abd); abd.setScope(scopeMetadata.getScopeName()); String beanName = (name != null ? name : this.beanNameGenerator.generateBeanName(abd, this.registry)); AnnotationConfigUtils.processCommonDefinitionAnnotations(abd); if (qualifiers != null) { for (Class<? extends Annotation> qualifier : qualifiers) { if (Primary.class == qualifier) { abd.setPrimary(true); } else if (Lazy.class == qualifier) { abd.setLazyInit(true); } else { abd.addQualifier(new AutowireCandidateQualifier(qualifier)); } } } BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(abd, beanName); definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry); BeanDefinitionReaderUtils.registerBeanDefinition(definitionHolder, this.registry); }

  可以看到有生成方法名,设置默认注入的实例、延迟以及过滤等等,注入的过程包括初始化一些信息,如构造、内部类、注解等:

protected AbstractBeanDefinition(ConstructorArgumentValues cargs, MutablePropertyValues pvs) { setConstructorArgumentValues(cargs); setPropertyValues(pvs); } public StandardAnnotationMetadata(Class<?> introspectedClass, boolean nestedAnnotationsAsMap) { super(introspectedClass); this.annotations = introspectedClass.getAnnotations(); this.nestedAnnotationsAsMap = nestedAnnotationsAsMap; }

   其他三种比如有的有输入流什么的就不细总结了,这部分介绍Spring IOC的相关文章应该不少。

   prepareContext方法最后listeners.contextLoaded(context),加载监听器到context并广播ApplicationPreparedEvent事件。

==========================================================

咱最近用的github:https://github.com/saaavsaaa

微信公众号:

                      

© 著作权归作者所有

drv

drv

粉丝 2
博文 57
码字总数 75382
作品 0
东城
架构师
私信 提问
Hello , Spring Boot

Spring Boot Spring Boot主要目的是简化了Spring的引用开发,遵循约定大于配置的思想,不需要我们再去配置就能运行Spring应用,Spring Boot管理Spring容器,第三方插件,并且提供了很多默认系...

胖先森
2018/09/04
0
0
如何自制一个Spring Boot Starter并推送到远端公服

概 述 传统的 Maven项目一般将需要被复用的组件做成 Module来进行管理,以便二次调用;而在 Spring Boot项目中我们则可以使用更加优雅的 Spring Boot Starter来完成这一切。基于Spring Boot...

CodeSheep
01/24
59
0
SpringBoot 自定义starter

1 先创建一个空项目(自己起名字) 2 在1步骤中创建的空项目中创建两个模块,创建方式如下图所示 1)创建启动器模块用maven项目我的启动器名称为 mao-spring-boot-starter pom.xml如下所示 ...

南桥北木
07/11
53
0
springboot之关于springboot与传统spring项目简单区别

前言 旧时我们开发一个普通spring的项目时,会存在很难麻烦的xml配置过程 准备jar包,包括spring、springmvc、redis、mybaits、log4j、mysql-connector-java 等等相关jar ... 配置web.xml文件,...

尾生
04/04
54
0
史上最简单的 SpringCloud 教程 | 第一篇: 服务的注册与发现(Eureka)

一、spring cloud简介spring cloud 为开发人员提供了快速构建分布式系统的一些工具,包括配置管理、服务发现、断路器、路由、微代理、事件总线、全局锁、决策竞选、分布式会话等等。它运行环...

方宏春
2018/04/14
319
1

没有更多内容

加载失败,请刷新页面

加载更多

哈希

第一个只出现一次的字符的位置

Garphy
29分钟前
21
0
Centos7.7之离线安装kubectl

Centos7.7,kubernates-1.13.5. 我的Centos7.7上已经安装了kubernates 1.13.5,但是没有kubectl命令,手动安装 浏览器中访问https://storage.googleapis.com/kubernetes-release/release/sta......

克虏伯
31分钟前
23
0
redis原理及应用

一、redis来源 二、数据类型 三、主流的应用场景 四、特性 五、补充 一、 redis来源 redis作者:Salvatore Sanfilippo (antirez),男,意大利人. 需求:一个访客信息追踪网站,网站可以通过...

天子剑毅
39分钟前
35
0
12_多线程

12_多线程 wait():一旦执行此方法,当前线程就进入阻塞状态,并释放同步监视器(释放锁)。 notify():一旦执行此方法,就会唤醒被wait的一个线程。如果有多个线程被wait,就唤醒优先级高的那个...

行者终成事
44分钟前
36
0
图片的切换功能

<!DOCTYPE html><html><head> <meta charset="UTF-8"> <title></title> <style type="text/css"> * { margin: 0; padding: 0; ......

zhengzhixiang
今天
25
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部