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

相关推荐
_江南一点雨1 小时前
SpringBoot 3.3.5 试用CRaC,启动速度提升3到10倍
java·spring boot·后端
深情废杨杨1 小时前
后端-实现excel的导出功能(超详细讲解)
java·spring boot·excel
代码小鑫2 小时前
A034-基于Spring Boot的供应商管理系统的设计与实现
java·开发语言·spring boot·后端·spring·毕业设计
paopaokaka_luck2 小时前
基于Spring Boot+Vue的多媒体素材管理系统的设计与实现
java·数据库·vue.js·spring boot·后端·算法
程序猿麦小七2 小时前
基于springboot的景区网页设计与实现
java·spring boot·后端·旅游·景区
蓝田~2 小时前
SpringBoot-自定义注解,拦截器
java·spring boot·后端
theLuckyLong2 小时前
SpringBoot后端解决跨域问题
spring boot·后端·python
A陈雷2 小时前
springboot整合elasticsearch,并使用docker desktop运行elasticsearch镜像容器遇到的问题。
spring boot·elasticsearch·docker
.生产的驴2 小时前
SpringCloud Gateway网关路由配置 接口统一 登录验证 权限校验 路由属性
java·spring boot·后端·spring·spring cloud·gateway·rabbitmq
小扳2 小时前
Docker 篇-Docker 详细安装、了解和使用 Docker 核心功能(数据卷、自定义镜像 Dockerfile、网络)
运维·spring boot·后端·mysql·spring cloud·docker·容器