一、前言

    上一篇介绍了注解,也是为这一篇做铺垫,传统的都是通过配置文件来启动spring,那spring boot到底是做了什么能让我们快速开发昵?

二、启动原理

    看下程序启动的入口,主要两处地方一是SpringBootApplication注解,另外就是run方法,首先我们看注解部分,上一篇我们也说过注解应该不难看懂,我们看下这个注解里面有什么神奇的东西;

  1. @SpringBootApplication
  2. public class DemoApplication {
  3. public static void main(String[] args) {
  4. SpringApplication.run(DemoApplication.class, args);
  5. }
  6. }
  7. @Target(ElementType.TYPE)
  8. @Retention(RetentionPolicy.RUNTIME)
  9. @Documented
  10. @Inherited
  11. @SpringBootConfiguration
  12. @EnableAutoConfiguration
  13. @ComponentScan(excludeFilters = {
  14. @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
  15. @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
  16. public @interface SpringBootApplication {
  17. /**
  18. * Exclude specific auto-configuration classes such that they will never be applied.
  19. * @return the classes to exclude
  20. */
  21. @AliasFor(annotation = EnableAutoConfiguration.class)
  22. Class<?>[] exclude() default {};
  23. /**
  24. * Exclude specific auto-configuration class names such that they will never be
  25. * applied.
  26. * @return the class names to exclude
  27. * @since 1.3.0
  28. */
  29. @AliasFor(annotation = EnableAutoConfiguration.class)
  30. String[] excludeName() default {};
  31. /**
  32. * Base packages to scan for annotated components. Use {@link #scanBasePackageClasses}
  33. * for a type-safe alternative to String-based package names.
  34. * @return base packages to scan
  35. * @since 1.3.0
  36. */
  37. @AliasFor(annotation = ComponentScan.class, attribute = "basePackages")
  38. String[] scanBasePackages() default {};
  39. /**
  40. * Type-safe alternative to {@link #scanBasePackages} for specifying the packages to
  41. * scan for annotated components. The package of each class specified will be scanned.
  42. * <p>
  43. * Consider creating a special no-op marker class or interface in each package that
  44. * serves no purpose other than being referenced by this attribute.
  45. * @return base packages to scan
  46. * @since 1.3.0
  47. */
  48. @AliasFor(annotation = ComponentScan.class, attribute = "basePackageClasses")
  49. Class<?>[] scanBasePackageClasses() default {};
  50. }

View Code

   看上面代码,除去元注解,主要有3个注解,

   @ComponentScan

   这个不需要我们多说太多,这个主要有2个作用,组件扫描和自动装配;

   @SpringBootConfiguration

  这个我们也不需要说太多,这个注解主要是继承@Configuration注解,这个我们就是为了加载配置文件用的;

  @EnableAutoConfiguration

  这个是我们的重点:

   看图我们来走一下代码,这里有一个重点就是@Import注解,这个里面引入了AutoConfigurationImportSelector.class这个文件,所以我们就需要看下这里面有那些玩意,值得我们注意的,这个类里面代码有点多我将重点放到下一个代码片段中,让大家结构清晰一些;

  1. @Target(ElementType.TYPE)
  2. @Retention(RetentionPolicy.RUNTIME)
  3. @Documented
  4. @Inherited
  5. @AutoConfigurationPackage
  6. @Import(AutoConfigurationImportSelector.class)
  7. public @interface EnableAutoConfiguration {
  8. String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
  9. /**
  10. * Exclude specific auto-configuration classes such that they will never be applied.
  11. * @return the classes to exclude
  12. */
  13. Class<?>[] exclude() default {};
  14. /**
  15. * Exclude specific auto-configuration class names such that they will never be
  16. * applied.
  17. * @return the class names to exclude
  18. * @since 1.3.0
  19. */
  20. String[] excludeName() default {};
  21. }

View Code

  这是中间比较关键的代码,我们主要看下loadFactories方法,这个里面有个常量的配置,位置如下图所示,整段代码实现了把配置文件中的信息通过反射实例化成为@Configuration的配置文件,然后通过@Configuration最后汇总到容器当中;

  1. protected List<AutoConfigurationImportFilter> getAutoConfigurationImportFilters() {
  2. return SpringFactoriesLoader.loadFactories(AutoConfigurationImportFilter.class,
  3. this.beanClassLoader);
  4. }
  5. public abstract class SpringFactoriesLoader {
  6. /**
  7. * The location to look for factories.
  8. * <p>Can be present in multiple JAR files.
  9. */
  10. public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
  11. private static final Log logger = LogFactory.getLog(SpringFactoriesLoader.class);
  12. private static final Map<ClassLoader, MultiValueMap<String, String>> cache = new ConcurrentReferenceHashMap<>();
  13. /**
  14. * Load and instantiate the factory implementations of the given type from
  15. * {@value #FACTORIES_RESOURCE_LOCATION}, using the given class loader.
  16. * <p>The returned factories are sorted through {@link AnnotationAwareOrderComparator}.
  17. * <p>If a custom instantiation strategy is required, use {@link #loadFactoryNames}
  18. * to obtain all registered factory names.
  19. * @param factoryClass the interface or abstract class representing the factory
  20. * @param classLoader the ClassLoader to use for loading (can be {@code null} to use the default)
  21. * @see #loadFactoryNames
  22. * @throws IllegalArgumentException if any factory implementation class cannot
  23. * be loaded or if an error occurs while instantiating any factory
  24. */
  25. public static <T> List<T> loadFactories(Class<T> factoryClass, @Nullable ClassLoader classLoader) {
  26. Assert.notNull(factoryClass, "'factoryClass' must not be null");
  27. ClassLoader classLoaderToUse = classLoader;
  28. if (classLoaderToUse == null) {
  29. classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
  30. }
  31. List<String> factoryNames = loadFactoryNames(factoryClass, classLoaderToUse);
  32. if (logger.isTraceEnabled()) {
  33. logger.trace("Loaded [" + factoryClass.getName() + "] names: " + factoryNames);
  34. }
  35. List<T> result = new ArrayList<>(factoryNames.size());
  36. for (String factoryName : factoryNames) {
  37. result.add(instantiateFactory(factoryName, factoryClass, classLoaderToUse));
  38. }
  39. AnnotationAwareOrderComparator.sort(result);
  40. return result;
  41. }
  42. /**
  43. * Load the fully qualified class names of factory implementations of the
  44. * given type from {@value #FACTORIES_RESOURCE_LOCATION}, using the given
  45. * class loader.
  46. * @param factoryClass the interface or abstract class representing the factory
  47. * @param classLoader the ClassLoader to use for loading resources; can be
  48. * {@code null} to use the default
  49. * @see #loadFactories
  50. * @throws IllegalArgumentException if an error occurs while loading factory names
  51. */
  52. public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {
  53. String factoryClassName = factoryClass.getName();
  54. return loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());
  55. }
  56. private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
  57. MultiValueMap<String, String> result = cache.get(classLoader);
  58. if (result != null) {
  59. return result;
  60. }
  61. try {
  62. Enumeration<URL> urls = (classLoader != null ?
  63. classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
  64. ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
  65. result = new LinkedMultiValueMap<>();
  66. while (urls.hasMoreElements()) {
  67. URL url = urls.nextElement();
  68. UrlResource resource = new UrlResource(url);
  69. Properties properties = PropertiesLoaderUtils.loadProperties(resource);
  70. for (Map.Entry<?, ?> entry : properties.entrySet()) {
  71. List<String> factoryClassNames = Arrays.asList(
  72. StringUtils.commaDelimitedListToStringArray((String) entry.getValue()));
  73. result.addAll((String) entry.getKey(), factoryClassNames);
  74. }
  75. }
  76. cache.put(classLoader, result);
  77. return result;
  78. }
  79. catch (IOException ex) {
  80. throw new IllegalArgumentException("Unable to load factories from location [" +
  81. FACTORIES_RESOURCE_LOCATION + "]", ex);
  82. }
  83. }
  84. @SuppressWarnings("unchecked")
  85. private static <T> T instantiateFactory(String instanceClassName, Class<T> factoryClass, ClassLoader classLoader) {
  86. try {
  87. Class<?> instanceClass = ClassUtils.forName(instanceClassName, classLoader);
  88. if (!factoryClass.isAssignableFrom(instanceClass)) {
  89. throw new IllegalArgumentException(
  90. "Class [" + instanceClassName + "] is not assignable to [" + factoryClass.getName() + "]");
  91. }
  92. return (T) ReflectionUtils.accessibleConstructor(instanceClass).newInstance();
  93. }
  94. catch (Throwable ex) {
  95. throw new IllegalArgumentException("Unable to instantiate factory class: " + factoryClass.getName(), ex);
  96. }
  97. }
  98. }

View Code

   

  基本上注解这块就是说完了,但是中间少说了几个比较重要的东西,这里要说下需要注意的2个问题,

 1.exclude和excludeName这个两个主要时排除你不想加载的配置,用法很简答,不需要说他太多;

 2.scanBasePackages和scanBasePackageClasses这个是为了指定运行目录,好多小伙伴做了项目分离以后,会读取不到Mappr等,可以考虑下是不是这个错误;

 重点来了,上面说了加载什么东西,那这些东西啥时候被调用被触发,那我们看下我们重点run方法:

 1.调用run方法之前,首先初始化SpringApplication对象实例,这个对象初始化的过程中也做了不少事情让我们来慢慢看起来,接上上面思路,继续完成我们的取经;

  1. //初始化SpringApplication对象
  2. public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
  3. //加载classpatch文件下面的配置文件
  4. this.resourceLoader = resourceLoader;
  5. Assert.notNull(primarySources, "PrimarySources must not be null");
  6. this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
  7. //判断是否是web运行环境
  8. this.webApplicationType = deduceWebApplicationType();
  9. //使用SpringFactoriesLoader在应用的classpath中查找并加载所有可用的ApplicationContextInitializer。
  10. setInitializers((Collection) getSpringFactoriesInstances(
  11. ApplicationContextInitializer.class));
  12. //使用SpringFactoriesLoader在应用的classpath中查找并加载所有可用的ApplicationListener。
  13. setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
  14. //获得当前执行main方法的类对象
  15. this.mainApplicationClass = deduceMainApplicationClass();
  16. }

View Code

  ApplicationContextInitializer 接口是在spring容器刷新之前执行的一个回调函数,主要有2点作用:1.在上下文(ConfigurableApplicationContext)刷新(refresh)之前调用,2.通常被用作web应用,在一些程序设计在spring容器初始化使用。比如说注册一些配置或者激活一些配置文件针对(ConfigurableApplicationContext的getEnvironment()方法)。另外这个函数支持支持Order注解。并且代表着执行顺序。我在下面也写了一个简单的例子,同时这个也是支持在配置文件中配置的context.initializer.classes=后面加上回调函数的全限定名称;另外假设我们在当前项目中要引入别的jar,这个jar要在加载前做一些配置,这个时候我们项目下的resources下新建META-INF文件夹,文件夹下新建spring.factories文件,然后写上org.springframework.context.ApplicationContextInitializer=后面加上需要回调函数的全限定名称,这个是在主项目启动的时候就会优先加载了;

  ApplicationListener接口是spring boot的监听器,有7种类型,我准备好了demo大家执行一下,我相信对下面run方法的运行就不是很迷惑了;

  1. @Order(3)
  2. public class TestApplicationContextInitializer implements ApplicationContextInitializer {
  3. @Override
  4. public void initialize(ConfigurableApplicationContext applicationContext) {
  5. System.out.println(applicationContext.getBeanDefinitionCount()+applicationContext.getBeanDefinitionNames().toString());
  6. }
  7. }
  8. @Order(1)
  9. public class TestApplicationContextInitializer2 implements ApplicationContextInitializer {
  10. @Override
  11. public void initialize(ConfigurableApplicationContext applicationContext) {
  12. System.out.println(applicationContext.getDisplayName());
  13. }
  14. }
  15. @SpringBootApplication
  16. public class DemoApplication {
  17. public static void main(String[] args) {
  18. // SpringApplication.run(DemoApplication.class, args);
  19. SpringApplication springApplication=new SpringApplication(DemoApplication.class);
  20. springApplication.addListeners((ApplicationListener<ApplicationStartingEvent>) event->{
  21. System.out.println("Starting");
  22. });
  23. springApplication.addListeners((ApplicationListener<ApplicationStartedEvent>) event->{
  24. System.out.println("Started");
  25. });
  26. springApplication.addListeners((ApplicationListener<ApplicationFailedEvent>) event->{
  27. System.out.println("Failed");
  28. });
  29. springApplication.addListeners((ApplicationListener<ApplicationPreparedEvent>) event->{
  30. System.out.println("Prepared");
  31. });
  32. springApplication.addListeners((ApplicationListener<SpringApplicationEvent>) event->{
  33. System.out.println("SpringApplication");
  34. });
  35. springApplication.addListeners((ApplicationListener<ApplicationEnvironmentPreparedEvent>) event->{
  36. System.out.println("EnvironmentPrepare");
  37. });
  38. springApplication.addListeners((ApplicationListener<ApplicationReadyEvent>) event->{
  39. System.out.println("Ready");
  40. });
  41. springApplication.addInitializers(new TestApplicationContextInitializer());
  42. springApplication.addInitializers(new TestApplicationContextInitializer2());
  43. springApplication.run(args);
  44. }
  45. }

View Code

2.实例化完成开始执行run方法,这个里面流程比较多,我们先来看一个继承关系,然后结合上面ApplicationListener的demo我相信大家已经对其广播实现已经有了一个了解,这里我还是提一下通过SpringApplicationRunListener在ApplicationContext初始化过程中各个时点发布各种广播事件,并由ApplicationListener负责接收广播事件。接下来我们看下启动流程:

   

  1. public ConfigurableApplicationContext run(String... args) {
  2. StopWatch stopWatch = new StopWatch();
  3. stopWatch.start();
  4. ConfigurableApplicationContext context = null;
  5. //收集异常
  6. Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
  7. //设置Headless模式为全局
  8. configureHeadlessProperty();
  9. //加载所有classpath下面的META-INF/spring.factories SpringApplicationRunListener(不同的时间点发送事件通知)
  10. SpringApplicationRunListeners listeners = getRunListeners(args);
  11. //spring boot启动初始化开始
  12. listeners.starting();
  13. try {
  14. //装配参数和环境
  15. ApplicationArguments applicationArguments = new DefaultApplicationArguments(
  16. args);
  17. ConfigurableEnvironment environment = prepareEnvironment(listeners,
  18. applicationArguments);
  19. configureIgnoreBeanInfo(environment);
  20. //打印Banner
  21. Banner printedBanner = printBanner(environment);
  22. //创建ApplicationContext()
  23. context = createApplicationContext();
  24. //返回异常
  25. exceptionReporters = getSpringFactoriesInstances(
  26. SpringBootExceptionReporter.class,
  27. new Class[] { ConfigurableApplicationContext.class }, context);
  28. //装配Context
  29. prepareContext(context, environment, listeners, applicationArguments,
  30. printedBanner);
  31. //执行context的refresh方法,并且调用context的registerShutdownHook方法(这一步执行完成之后,spring容器加载完成)
  32. refreshContext(context);
  33. //回调,获取容器中所有的ApplicationRunner、CommandLineRunner接口
  34. afterRefresh(context, applicationArguments);
  35. stopWatch.stop();
  36. if (this.logStartupInfo) {
  37. new StartupInfoLogger(this.mainApplicationClass)
  38. .logStarted(getApplicationLog(), stopWatch);
  39. }
  40. //容器初始化完成
  41. listeners.started(context);
  42. //遍历所有注册的ApplicationRunner和CommandLineRunner,并执行其run()方法。
  43. //该过程可以理解为是SpringBoot完成ApplicationContext初始化前的最后一步工作,
  44. callRunners(context, applicationArguments);
  45. }
  46. catch (Throwable ex) {
  47. handleRunFailure(context, ex, exceptionReporters, listeners);
  48. throw new IllegalStateException(ex);
  49. }
  50. try {
  51. //容器开始被调用
  52. listeners.running(context);
  53. }
  54. catch (Throwable ex) {
  55. handleRunFailure(context, ex, exceptionReporters, null);
  56. throw new IllegalStateException(ex);
  57. }
  58. return context;
  59. }

View Code

  写了这么多我忘记放入执行结果了这里补进去:

  

、总结

  要是想在spring boot初始化的时候搞点事情的化,那么有3种方法:

  1.创建ApplicationContextInitializer的实现类

  2.创建ApplicationListener的实现类

  3.创建ApplicationRunner和CommandLineRunner的实现类

  上面2种已经有了demo,我再来写一个第3种的demo;

  1. @Order(2)
  2. @Component
  3. public class CommandLineRunnerDemo implements CommandLineRunner {
  4. @Override
  5. public void run(String... args) throws Exception {
  6. System.out.println("CommandLineRunnerDemo");
  7. }
  8. }
  9. @Order(1)
  10. @Component
  11. public class ApplicationRunnerDemo implements ApplicationRunner {
  12. @Override
  13. public void run(ApplicationArguments args) throws Exception {
  14. System.out.println("ApplicationRunner");
  15. }
  16. }

View Code

  知道启动的流程又懂了扩展,我们接下来开始spring cloud吧。

  上面有什么的不懂的可以加群:438836709

  也可以关注我公众号

  

版权声明:本文为wtzbk原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://www.cnblogs.com/wtzbk/p/9193914.html