SpringBoot初始化器源码解析

146 阅读4分钟

在上一篇文章中用三种方式实现了向Spring容器中注入自定义的属性

第一种实现方式源码:

 在SpringBoot的启动类的SpringApplication构造方法中
 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();
 }

跟进getSpringFactoriesInstance(ApplicationContextInitializer.class)

 一直到达这个方法
 private <T> Collection<T> getSpringFactoriesInstances(Class<T> type,
            Class<?>[] parameterTypes, Object... args) {
        ClassLoader classLoader = getClassLoader();
        //加载我们配置文件(resource/META-INF/spring.facotries)中的自定义初始化器
        Set<String> names = new LinkedHashSet<>(
                SpringFactoriesLoader.loadFactoryNames(type, classLoader));
        //实例化我们的初始化器
        List<T> instances = createSpringFactoriesInstances(type, parameterTypes,
                classLoader, args, names);
        //根据初始化器上的@Order等注解进行排序
        AnnotationAwareOrderComparator.sort(instances);
        return instances;
    }
  

先来看下第一个方法 Set names = new LinkedHashSet<>( SpringFactoriesLoader.loadFactoryNames(type, classLoader));

这个方法在SpringFactoriesLoader类中
private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
        //查看缓存中是否已经有了类加载器,因为是第一次,所以不会有
        MultiValueMap<String, String> result = cache.get(classLoader);
        if (result != null) {
            return result;
        }

        try {
            //这里就是去加载配置文件了
            //public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
            //可以看到属性文件位置,这也是为什么在第一种实现方式中我们要在resource目录下新建文件夹META-INF
            //然后再建立配置文件spring.factories
            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 factoryClassName = ((String) entry.getKey()).trim();
                    for (String factoryName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
                        result.add(factoryClassName, factoryName.trim());
                    }
                }
            }
            cache.put(classLoader, result);
            return result;
        }
        catch (IOException ex) {
            throw new IllegalArgumentException("Unable to load factories from location [" +
                    FACTORIES_RESOURCE_LOCATION + "]", ex);
        }
    }
   
   
   
   

再来看第二个方法:List instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);

在第一个方法已经获取到了所有初始化器的类全路径名,所以就可以实例化了
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;
    }
    

第三个方法 AnnotationAwareOrderComparator.sort(instances); 就是根据@Order注解来进行排序了

最后会将这些实例全部放到SpringApplication类的变量 private List<ApplicationContextInitializer<?>> initializers;中去,所以在第二种实现方式中使用springApplication.addInitializers(new SecondInitializer()),其实本质也就是将这个实例添加到这个变量集合当中去

现在看下自定义初始化器如何执行的

这个是run()方法中
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;
    }

我们进入到prepareContext(context, environment, listeners, applicationArguments,printedBanner);

private void prepareContext(ConfigurableApplicationContext context,
            ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
            ApplicationArguments applicationArguments, Banner printedBanner) {
        context.setEnvironment(environment);
        postProcessApplicationContext(context);
        //初始化器调用的核心方法
        applyInitializers(context);
        listeners.contextPrepared(context);
        if (this.logStartupInfo) {
            logStartupInfo(context.getParent() == null);
            logStartupProfileInfo(context);
        }
        // Add boot specific singleton beans
        ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
        beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
        if (printedBanner != null) {
            beanFactory.registerSingleton("springBootBanner", printedBanner);
        }
        if (beanFactory instanceof DefaultListableBeanFactory) {
            ((DefaultListableBeanFactory) beanFactory)
                    .setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
        }
        // Load the sources
        Set<Object> sources = getAllSources();
        Assert.notEmpty(sources, "Sources must not be empty");
        load(context, sources.toArray(new Object[0]));
        listeners.contextLoaded(context);
    }

进入到applyInitializers(context);方法中

protected void applyInitializers(ConfigurableApplicationContext context) {
        for (ApplicationContextInitializer initializer : getInitializers()) {
            Class<?> requiredType = GenericTypeResolver.resolveTypeArgument(
                    initializer.getClass(), ApplicationContextInitializer.class);
            Assert.isInstanceOf(requiredType, context, "Unable to call initializer.");
            //在这里调用了我们自定义的方法将我们自定义的属性设置到Spring容器中
            initializer.initialize(context);
        }
    }
    
 在这里for循环中有一个getInitializers()方法,它的作用就是返回之前已经实例化好了我们自定义系统初始化器的集合
 public Set<ApplicationContextInitializer<?>> getInitializers() {
	return asUnmodifiableOrderedSet(this.initializers);
}

所以会依次调用 initializer.initialize(context); 这个方法,也就是我们实现了ApplicationContextInitializer这个接口需要实现的initialize()方法

第一,第二种方法我们已经知道Spring是如何将我们自定义属性添加到Spring容器中的,现在看下第三种方法

还记得第一种方式是如何找到我们自定义的初始化器类的嘛??不清楚可以看下文章开头,有一步是说实例化系统初始化器,我们看下返回的实例中有一个

没错就是DelegatingApplicationContextInitializer这个实例,在第三种方式中我们是在application.properties配置文件中配置了

context.initializer.classes=com.example.souce.initializer.ThirdInitializer

我们看下这个类的源码:我只截取了前面一部分

public class DelegatingApplicationContextInitializer implements
        ApplicationContextInitializer<ConfigurableApplicationContext>, Ordered {

    //这个配置是不是很熟悉
    private static final String PROPERTY_NAME = "context.initializer.classes";
    //这个order就是用来排序的
    private int order = 0;

    @Override
    public void initialize(ConfigurableApplicationContext context) {
        ConfigurableEnvironment environment = context.getEnvironment();
        //这里获取所有初始化器实例
        List<Class<?>> initializerClasses = getInitializerClasses(environment);
        //然后就可以注入我们自己自定义的属性了
        if (!initializerClasses.isEmpty()) {
            applyInitializerClasses(context, initializerClasses);
        }
    }

    //这个方法就是获取context.initializer.classes=com.example.souce.initializer.ThirdInitializer中的值,
    //如果有多个就用逗号隔开,然后获取所有类的全路径名,然后进行实例化
    //实例化后返回
    private List<Class<?>> getInitializerClasses(ConfigurableEnvironment env) {
        String classNames = env.getProperty(PROPERTY_NAME);
        List<Class<?>> classes = new ArrayList<>();
        if (StringUtils.hasLength(classNames)) {
            for (String className : StringUtils.tokenizeToStringArray(classNames, ",")) {
                classes.add(getInitializerClass(className));
            }
        }
        return classes;
    }

系统初始化器就介绍完了,那看下相关的面试题

1:介绍下SpringFactoriesLoader:

  它是Spring的一个通用的工厂加载类,SpringBoot用来它从jar包中读取指定文件来实现扩展类的载入
  

2:SpringFactoriesLoader是如何加载工厂类的

  首先会根据指定路径找到文件,然后将文件转换成properties文件,然后依次遍历文件内容,并封装成类名以及实现类,并通过@Order注解进行排序

3:系统初始化的作用:

  它其实是SpringBoot容器的一个回调接口,我们可以通过它向Spring容器中定义我们自己的属性

4:系统初始化调用的时机:

  在run方法中的prepareContext()方法中的applyInitializers(context)调用
  

5:如何自定义实现系统初始化器:

  在上一篇文章中已经实现了三种了

6:自定义初始化器需要注意的问题:

  order大小排序的问题,还有一个就是泛型的问题