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 异常,提示无法从指定位置加载工厂类信息

相关推荐
悟空码字19 小时前
Spring Boot 整合 MongoDB 最佳实践:CRUD、分页、事务、索引全覆盖
java·spring boot·后端
皮皮林5512 天前
拒绝写重复代码,试试这套开源的 SpringBoot 组件,效率翻倍~
java·spring boot
用户908324602735 天前
Spring AI 1.1.2 + Neo4j:用知识图谱增强 RAG 检索(上篇:图谱构建)
java·spring boot
用户8307196840826 天前
Spring Boot 集成 RabbitMQ :8 个最佳实践,杜绝消息丢失与队列阻塞
spring boot·后端·rabbitmq
Java水解6 天前
Spring Boot 视图层与模板引擎
spring boot·后端
Java水解6 天前
一文搞懂 Spring Boot 默认数据库连接池 HikariCP
spring boot·后端
洋洋技术笔记6 天前
Spring Boot Web MVC配置详解
spring boot·后端
初次攀爬者7 天前
Kafka 基础介绍
spring boot·kafka·消息队列
用户8307196840827 天前
spring ai alibaba + nacos +mcp 实现mcp服务负载均衡调用实战
spring boot·spring·mcp
Java水解7 天前
SpringBoot3全栈开发实战:从入门到精通的完整指南
spring boot·后端