SpringBoot 源码解析(一)

系统初始化器

简介:ApplicationContextInitializer是Spring框架提供的一个接口,用于在ApplicationContext(应用上下文)被创建之前对其进行初始化或配置,实现了该接口的类可以在Spring应用程序启动时被调用 ,在Spring Boot中,ApplicationContextInitializer常用于执行一些与应用程序配置和环境相关的初始化操作

三种使用方式

首先,需要实现ApplicationContextInitializer这个类

java 复制代码
@Order(0)
public class MyInitializer implements ApplicationContextInitializer {
    @Override
    public void initialize(ConfigurableApplicationContext applicationContext) {
        ConfigurableEnvironment environment = applicationContext.getEnvironment();
        HashMap<String, Object> hashMap = new HashMap<>();
        hashMap.put("key1", "value1");
        MapPropertySource mapPropertySource = new MapPropertySource("name", hashMap);
        environment.getPropertySources().addLast(mapPropertySource);
​
        System.out.println("Initializer init");
    }
}   

实现完成后可以通过三种不同的方式加载

  1. META-INF/spring.factories 文件配置

src/main/resources 目录下创建 META-INF/spring.factories 文件,并在该文件中指定 ApplicationContextInitializer 的实现类。Spring Boot 在启动时会自动加载该文件,并执行相应的初始化操作。

ini 复制代码
org.springframework.context.ApplicationContextInitializer=org.springframework.context.ApplicationContextInitializer=com.huakai.initializer.MyInitializer
  1. SpringApplication.run() 方法中指定 ApplicationContextInitializer 实现类
arduino 复制代码
public static void main(String[] args) {
    SpringApplication application = new SpringApplication(MyApplication.class);
    application.addInitializers(new MyInitializer());
    application.run(args);
}
  1. application.properties配置

在配置文件中,通过配置 context.initializer.classes 属性来指定要执行的 ApplicationContextInitializer 实现类

ini 复制代码
context.initializer.classes=com.huakai.initializer.MyInitializer

实现接口时我们可以通过@Order(0)来指定容器的加载顺序,但是通过配置文件(application.properties)来指定实现类的这种方式,Spring 会按照配置的顺序(逗号分割)来加载这些初始化器,它的优先级是高于其他方式注册的系统自定义实现的

环境变量获取

通过初始化器配置的环境变量可以通过ApplicationContext类获取

kotlin 复制代码
@Controller
@RequestMapping("/api/v1/init")
public class InitTestController {
​
    private final ApplicationContext application;
​
    public InitTestController(ApplicationContext application) {
        this.application = application;
    }
​
    @ResponseBody
    @GetMapping("/first")
    public String getFirstInitializer() {
        return application.getEnvironment().getProperty("key1");
    }
​
}

访问本地项目接口地址即可获取到对应的值

调用流程

run() -> prepareContext() 准备上下文 -> applyInitializers() 调用系统初始化器-> 遍历调用初始化器

SpringFactoriesLoader

SpringFactoriesLoaderSpring 框架提供的一个工具类,用于加载并解析 META-INF/spring.factories 文件中的配置信息。我们可以将自己的实现注册到 spring.factories 文件中,Spring 在启动时会使用 SpringFactoriesLoader 加载这些配置信息,从而实现自动化的配置和装配。

首先来看下SpringApplication的构造方法

kotlin 复制代码
/**
 * 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();
}

注意注释里的这句话The instance can be customized before calling {@link #run(String...)} 调用run方法之前可以自定义实例

typescript 复制代码
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type,
       Class<?>[] parameterTypes, Object... args) {
    ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
    // Use names and ensure unique to protect against duplicates
    // 在META-INF/spring.factories 文件中查找对应类型的工厂类的配置
    Set<String> names = new LinkedHashSet<>(
          SpringFactoriesLoader.loadFactoryNames(type, classLoader));
    // 根据names(加载工厂名字)创建工厂实例并返回
    List<T> instances = createSpringFactoriesInstances(type, parameterTypes,
          classLoader, args, names);
    AnnotationAwareOrderComparator.sort(instances);
    return instances;
}

getSpringFactoriesInstances这个方法用于加载 Spring 配置中定义的各种工厂类的实例,其中就包括了加载自动配置类,如何加载呢?SpringFactoriesLoader终于来了

typescript 复制代码
/**
 * Load the fully qualified class names of factory implementations of the
 * given type from {@value #FACTORIES_RESOURCE_LOCATION}, using the given
 * class loader.
 * @param factoryClass 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
 * @see #loadFactories
 * @throws IllegalArgumentException if an error occurs while loading factory names
 */
public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {
    String factoryClassName = factoryClass.getName();
    return loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());
}
​
private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
    // 获取缓存
    MultiValueMap<String, String> result = cache.get(classLoader);
    // 存在则直接返回
    if (result != null) {
       return result;
    }
​
    try {
        // 从spring.factories中获取配置资源
       Enumeration<URL> urls = (classLoader != null ?
             classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
             ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
       result = new LinkedMultiValueMap<>();
        // 遍历资源存入Properties对象
       while (urls.hasMoreElements()) {
          URL url = urls.nextElement();
          UrlResource resource = new UrlResource(url);
          Properties properties = PropertiesLoaderUtils.loadProperties(resource);
           // 遍历Properties
          for (Map.Entry<?, ?> entry : properties.entrySet()) {
              // 将属性值解析为逗号分隔的字符串数组放入集合
             List<String> factoryClassNames = Arrays.asList(
                   StringUtils.commaDelimitedListToStringArray((String) entry.getValue()));
              // 存入result,key是属性的键
             result.addAll((String) entry.getKey(), factoryClassNames);
          }
       }
        // 存入缓存
       cache.put(classLoader, result);
       return result;
    }
    catch (IOException ex) {
       throw new IllegalArgumentException("Unable to load factories from location [" +
             FACTORIES_RESOURCE_LOCATION + "]", ex);
    }
}

这段代码的具体流程如下:

  1. 首先从缓存中获取已经加载的工厂类配置信息,如果已经存在,则直接返回

  2. 如果缓存中不存在,则尝试从指定的类加载器中获取配置文件 META-INF/spring.factories 的URL资源

  3. 遍历所有找到的URL资源,对每个资源执行以下操作:

    • 创建一个 UrlResource 对象,表示该资源的URL

    • 使用 PropertiesLoaderUtils 加载资源中的属性信息,将其存储在 Properties 对象中

    • 遍历属性集合,对每个属性进行处理:

      • 将属性值解析为逗号分隔的字符串数组,表示多个工厂类的类名
      • 将解析后的工厂类名列表添加到结果集中,键为属性的键
  4. 将加载到的工厂类配置信息存入缓存

  5. 如果在加载过程中发生I/O异常,则抛出 IllegalArgumentException 异常,提示无法从指定位置加载工厂类信息

相关推荐
A尘埃6 分钟前
SpringBoot的数据访问
java·spring boot·后端
yang-23077 分钟前
端口冲突的解决方案以及SpringBoot自动检测可用端口demo
java·spring boot·后端
代码之光_198019 分钟前
SpringBoot校园资料分享平台:设计与实现
java·spring boot·后端
苹果醋32 小时前
快速玩转 Mixtral 8x7B MOE大模型!阿里云机器学习 PAI 推出最佳实践
spring boot·nginx·毕业设计·layui·课程设计
程序员大金2 小时前
基于SpringBoot+Vue+MySQL的装修公司管理系统
vue.js·spring boot·mysql
【D'accumulation】3 小时前
令牌主动失效机制范例(利用redis)注释分析
java·spring boot·redis·后端
2401_854391083 小时前
高效开发:SpringBoot网上租赁系统实现细节
java·spring boot·后端
wxin_VXbishe3 小时前
springboot合肥师范学院实习实训管理系统-计算机毕业设计源码31290
java·spring boot·python·spring·servlet·django·php
OEC小胖胖4 小时前
Spring Boot + MyBatis 项目中常用注解详解(万字长篇解读)
java·spring boot·后端·spring·mybatis·web
2401_857617624 小时前
SpringBoot校园资料平台:开发与部署指南
java·spring boot·后端