依靠SpringBoot这一优秀的框架,我们构建Web服务的工作变得十分便捷。这在简化了企业级开发过程的同时也使服务的运行对于开发者来说不透明了。在这种背景下,深入理解框架的运行原理就显得十分有必要了。本文将从源码角度来分析SpringBoot框架在启动后做了哪些工作。

从SpringApplication.run()开始

我们首先来看一下一个典型的SpringBoot启动类代码:

@SpringBootApplication
public class RestfulWebServiceApplication {

    public static void main(String[] args) {
        SpringApplication.run(RestfulWebServiceApplication.class, args);
    }

}

注解在后面讲解。我们这重点关注一下SpringApplication.run()。

由源码不难看出,SpringApplication.run()作为一个静态方法,其作用是以传入的class作为参数构造SpringApplication对象,并调用run方法。

    /**
     * Static helper that can be used to run a {@link SpringApplication} from the
     * specified source using default settings.
     * @param primarySource the primary source to load
     * @param args the application arguments (usually passed from a Java main method)
     * @return the running {@link ApplicationContext}
     */
    public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
        return run(new Class<?>[] { primarySource }, args);
    }

    /**
     * Static helper that can be used to run a {@link SpringApplication} from the
     * specified sources using default settings and user supplied arguments.
     * @param primarySources the primary sources to load
     * @param args the application arguments (usually passed from a Java main method)
     * @return the running {@link ApplicationContext}
     */
    public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
        return new SpringApplication(primarySources).run(args);
    }

接着我们就顺着思路来看一下SpringApplication构造器的源码。

SpringApplication构造器

    /**
     * Create a new {@link SpringApplication} instance. The application context will load
     * beans from the specified primary sources (see {@link SpringApplication class-level}
     * documentation for details. The instance can be customized before calling
     * {@link #run(String...)}.
     * @param primarySources the primary bean sources
     * @see #run(Class, String[])
     * @see #SpringApplication(ResourceLoader, Class...)
     * @see #setSources(Set)
     */
    public SpringApplication(Class<?>... primarySources) {
        this(null, primarySources);
    }

    /**
     * Create a new {@link SpringApplication} instance. The application context will load
     * beans from the specified primary sources (see {@link SpringApplication class-level}
     * documentation for details. The instance can be customized before calling
     * {@link #run(String...)}.
     * @param resourceLoader the resource loader to use
     * @param primarySources the primary bean sources
     * @see #run(Class, String[])
     * @see #setSources(Set)
     */
    @SuppressWarnings({ "unchecked", "rawtypes" })
    public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
        this.resourceLoader = resourceLoader;
        Assert.notNull(primarySources, "PrimarySources must not be null");
        this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
        this.webApplicationType = WebApplicationType.deduceFromClasspath();
        setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
        setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
        this.mainApplicationClass = deduceMainApplicationClass();
    }

SpringApplication.run()方法调用的构造函数按照如下逻辑构造SpringApplication:

  1. 设置resourceLoader(资源加载器),这里调用的run方法resourceLoader直接设为null(资源加载器相关知识帖:Spring Resource接口进行资源访问Spring使用ResourceLoader接口获取资源
  2. 判断传入的class是否为null(为null抛异常)。
  3. 根据传入的参数设置primarySources(启动类)。
  4. 用deduceFromClasspath方法定义webApplicationType(Web应用类型),有NONE、SERVLET、REACTIVE三种类型。

    • NONE:应用程序不作为web应用启动,不启动内嵌的服务。
    • SERVLET:应用程序以基于servlet的web应用启动,需启动内嵌servlet web服务。
    • REACTIVE:应用程序以响应式web应用启动,需启动内嵌的响应式web服务。
// 这里主要用到了反射
static WebApplicationType deduceFromClasspath() {
        if (ClassUtils.isPresent(WEBFLUX_INDICATOR_CLASS, null) && !ClassUtils.isPresent(WEBMVC_INDICATOR_CLASS, null)
                && !ClassUtils.isPresent(JERSEY_INDICATOR_CLASS, null)) {
            return WebApplicationType.REACTIVE;
        }
        for (String className : SERVLET_INDICATOR_CLASSES) {
            if (!ClassUtils.isPresent(className, null)) {
                return WebApplicationType.NONE;
            }
        }
        return WebApplicationType.SERVLET;
    }
  1. 接下来是设置初始化器,利用了getSpringFactoriesInstances()方法,传递的参数为ApplicationContextInitializer.class。
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type) {
  return getSpringFactoriesInstances(type, new Class<?>[] {});
}

private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
  ClassLoader classLoader = getClassLoader();
  // Use names and ensure unique to protect against duplicates
  Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
  List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
  AnnotationAwareOrderComparator.sort(instances);
  return instances;
}
  • 在 getSpringFactoriesInstances 方法中会通过 SpringFactoriesLoader.loadFactoryNames() 获取指定类型的名称的集合,它会先根据类加载器从 cache 中找是否加载过,否则会通过类加载器去加载 META-INF/spring.factories 文件中的内容,以Map返回。
public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";

/**
     * Load the fully qualified class names of factory implementations of the
     * given type from {@value #FACTORIES_RESOURCE_LOCATION}, using the given
     * class loader.
     * @param factoryType the interface or abstract class representing the factory
     * @param classLoader the ClassLoader to use for loading resources; can be
     * {@code null} to use the default
     * @throws IllegalArgumentException if an error occurs while loading factory names
     * @see #loadFactories
     */
public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
  String factoryTypeName = factoryType.getName();
  return loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());
}

private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
  // 先查cache
  MultiValueMap<String, String> result = cache.get(classLoader);
  if (result != null) {
    return result;
  }

  // cache没有则加载META-INF/spring.factories中的ApplicationContextInitializer
  try {
    Enumeration<URL> urls = (classLoader != null ?
                             classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
                             ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
    result = new LinkedMultiValueMap<>();
    while (urls.hasMoreElements()) {
      URL url = urls.nextElement();
      UrlResource resource = new UrlResource(url);
      Properties properties = PropertiesLoaderUtils.loadProperties(resource);
      for (Map.Entry<?, ?> entry : properties.entrySet()) {
        String factoryTypeName = ((String) entry.getKey()).trim();
        for (String factoryImplementationName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
          result.add(factoryTypeName, factoryImplementationName.trim());
        }
      }
    }
    cache.put(classLoader, result);
    return result;
  }
  catch (IOException ex) {
    throw new IllegalArgumentException("Unable to load factories from location [" +
                                       FACTORIES_RESOURCE_LOCATION + "]", ex);
  }
}
  • META-INF/spring.factories的相关内容:
# Application Context Initializers
org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.context.ConfigurationWarningsApplicationContextInitializer,\
org.springframework.boot.context.ContextIdApplicationContextInitializer,\
org.springframework.boot.context.config.DelegatingApplicationContextInitializer,\
org.springframework.boot.rsocket.context.RSocketPortInfoApplicationContextInitializer,\
org.springframework.boot.web.context.ServerPortInfoApplicationContextInitializer
  • 然后就很好理解了,利用获取到的类型名称集合,getSpringFactoriesInstances()方法会利用反射创建对应的实例
    private <T> List<T> createSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes,
            ClassLoader classLoader, Object[] args, Set<String> names) {
        List<T> instances = new ArrayList<>(names.size());
        for (String name : names) {
            try {
                Class<?> instanceClass = ClassUtils.forName(name, classLoader);
                Assert.isAssignable(type, instanceClass);
                Constructor<?> constructor = instanceClass.getDeclaredConstructor(parameterTypes);
                T instance = (T) BeanUtils.instantiateClass(constructor, args);
                instances.add(instance);
            }
            catch (Throwable ex) {
                throw new IllegalArgumentException("Cannot instantiate " + type + " : " + name, ex);
            }
        }
        return instances;
    }
  • 这里是阅读源码第一个比较难理解的地方,做个归纳:设置初始化器() -> 获取ApplicationContextInitializer类型的实例 -> 利用SpringFactoriesLoader.loadFactoryNames()获取cache或META-INF/spring.factories下的ApplicationContextInitializer的实现类型的名称 -> 利用反射获取得到的名称对应的类型的实例。综合语意设置ApplicationContextInitializer的实现类为初始化器:
  1. 用和加载ApplicationContextInitializer一样的方法加载所有的可用的 ApplicationListener实例。
  2. 设置mainApplicationClass,即找到带有 main 方法的主类。

run方法

/**
     * Run the Spring application, creating and refreshing a new
     * {@link ApplicationContext}.
     * @param args the application arguments (usually passed from a Java main method)
     * @return a running {@link ApplicationContext}
     */
    public ConfigurableApplicationContext run(String... args) {
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        ConfigurableApplicationContext context = null;
        Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
        configureHeadlessProperty();
        SpringApplicationRunListeners listeners = getRunListeners(args);
        listeners.starting();
        try {
            ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
            ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
            configureIgnoreBeanInfo(environment);
            Banner printedBanner = printBanner(environment);
            context = createApplicationContext();
            exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
                    new Class[] { ConfigurableApplicationContext.class }, context);
            prepareContext(context, environment, listeners, applicationArguments, printedBanner);
            refreshContext(context);
            afterRefresh(context, applicationArguments);
            stopWatch.stop();
            if (this.logStartupInfo) {
                new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
            }
            listeners.started(context);
            callRunners(context, applicationArguments);
        }
        catch (Throwable ex) {
            handleRunFailure(context, ex, exceptionReporters, listeners);
            throw new IllegalStateException(ex);
        }

        try {
            listeners.running(context);
        }
        catch (Throwable ex) {
            handleRunFailure(context, ex, exceptionReporters, null);
            throw new IllegalStateException(ex);
        }
        return context;
    }

SpringApplication.run()方法的注释阐述此方法的作用为启动 Spring 应用程序,创建和更新一个 ApplicationContext,我们讲一下重要部分。

  1. getRunListeners()用和上面一致的方法加载SpringApplicationRunListeners,该listener只负责run方法的更新。
  2. prepareEnvironment()根据 listeners 和 applicationArguments 配置 SpringBoot 应用的环境。
  3. configureIgnoreBeanInfo()根据环境信息配置要忽略的 bean 信息。
  4. printBanner() 打印 spring boot 的标志,可以自定义。
  5. createApplicationContext() 根据应用类型来确定该 Spring Boot 项目应该创建什么类型的 ApplicationContext ,默认情况下,如果没有明确设置的应用程序上下文或应用程序上下文类,该方法会在返回合适的默认值。
  6. get SpringBootExceptionReporter:也是通过 getSpringFactoriesInstances 方法获取到 classpath 下的 SpringBootExceptionReporter。
  7. prepareContext()完成整个容器的创建与启动以及 bean 的注入功能。
  8. refreshContext()更新应用上下文。
  9. afterRefresh(): 在上下文刷新后调用该方法,其内部没有做任何操作。
  10. listeners.started(context): 应用上下文更新了,应用也开启了,但是 _CommandLineRunner _和 _ApplicationRunner _还没有调用。
  11. callRunners:ApplicationRunner 和 CommandLineRunner 进行回调。
  12. listeners.running(context):接着就是改变状态为 running 了。

@SpringBootApplication

先看一下哪些注解注解了@SpringBootApplication:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
        @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication 

这里的元注解就不作相关的解释了,关注一下比较重要的注解。

  1. @ComponentScan:作用是指定扫描路径,默认是扫描当前类下的package。例如:类top.zzqsbk.Demo类上标注了@ComponentScan 注解,则top.zzqsbk.controllercn.shiyujun.service等等包下的类都可以被扫描到。

这个注解一共包含以下几个属性:

    • basePackages:指定多个包名进行扫描
    • basePackageClasses:对指定的类和接口所属的包进行扫
    • excludeFilters:指定不扫描的过滤器
    • includeFilters:指定扫描的过滤器
    • lazyInit:是否对注册扫描的bean设置为懒加载
    • nameGenerator:为扫描到的bean自动命名
    • resourcePattern:控制可用于扫描的类文件
    • scopedProxy:指定代理是否应该被扫描
    • scopeResolver:指定扫描bean的范围
    • useDefaultFilters:是否开启对@Component,@Repository,@Service,@Controller的类进行检测
    1. @SpringBootConfiguration:更简单,底层是Configuration注解,说白了就是支持JavaConfig的方式来进行配置(使用Configuration配置类等同于XML文件)。
    2. @EnableAutoConfiguration: 三个注解里最重点的一个,SpringBoot号称的约定大于配置,自动装配的重心就在这里了。简单来说,这个注解可以帮助我们自动载入应用程序所需要的所有默认配置

    @EnableAutoConfiguration

    @EnableAutoConfiguration利用@Import注解,将所有符合自动装配条件的bean注入到IOC容器中。

    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Inherited
    @AutoConfigurationPackage
    @Import(AutoConfigurationImportSelector.class)
    public @interface EnableAutoConfiguration {
    
        String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
    
        /**
         * Exclude specific auto-configuration classes such that they will never be applied.
         * @return the classes to exclude
         */
        Class<?>[] exclude() default {};
    
        /**
         * Exclude specific auto-configuration class names such that they will never be
         * applied.
         * @return the class names to exclude
         * @since 1.3.0
         */
        String[] excludeName() default {};
    
    }

    我们看一下AutoConfigurationImportSelector类的selectImports方法。

    @Override
        public String[] selectImports(AnnotationMetadata annotationMetadata) {
            if (!isEnabled(annotationMetadata)) {
                return NO_IMPORTS;
            }
            AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata);
            return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
        }

    方法的开头判断当前系统是不是禁用了自动装配的功能,如果当前系统禁用了自动装配的功能则会返回一个空的数组。

    如果启用,则会走下列逻辑:

    protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
            if (!isEnabled(annotationMetadata)) {
                return EMPTY_ENTRY;
            }
            AnnotationAttributes attributes = getAttributes(annotationMetadata);
            List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
            configurations = removeDuplicates(configurations);
            Set<String> exclusions = getExclusions(annotationMetadata, attributes);
            checkExcludedClasses(configurations, exclusions);
            configurations.removeAll(exclusions);
            configurations = getConfigurationClassFilter().filter(configurations);
            fireAutoConfigurationImportEvents(configurations, exclusions);
            return new AutoConfigurationEntry(configurations, exclusions);
        }

    逻辑大致如下:

    • 加载所有Spring预先定义的配置条件信息。
    • 删除重复的自动配置类。
    • 去除我们指定排除的配置类。
    • 发布自动装配完成事件,然后返回所有能够自动装配的类的全限定名。

    小结

    这篇博文是参考了网上很多其他博主的文章结合源码写的,很多地方写的不清晰,因为相关的源码确实还没有完全看懂。写这篇博文的目的是帮助自己对SpringBoot的底层机制有个初步立体的了解,作为一个从SpringBoot直接入门而没有纯Spring经验的人来说开头的过程还是有些艰难,后续对自动装配有更深理解了再来写新文。

    最后修改:2021 年 01 月 19 日 09 : 51 PM