【SpringBoot】【四】 启动前环境准备

1  前言

这节我们来看看 Spingboot 启动前的一些环境准备工作:

// 封装请求参数
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
// 准备环境变量,包含系统属性和用户配置的属性
ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
// 配置 beaninfo
configureIgnoreBeanInfo(environment);
// 打印 Banner
Banner printedBanner = printBanner(environment);

2  源码通读

2.1  封装参数

这一步比较简单,就是将run方式时传入的参数封装为ApplicationArguments对象

ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);

ApplicationArguments翻译过来是应用程序参数的意思,它的主要作用是提供对用于运行 SpringApplication 的参数的访问,也就是封装了我们在使用run方式时传入的参数,以便其他阶段访问使用。

该接口的内容有:

public interface ApplicationArguments {
    // 返回传递给应用程序的原始未处理参数
    String[] getSourceArgs();
    // 返回所有选项参数的名称
    Set<String> getOptionNames();
    // 返回从参数解析的选项参数集是否包含具有给定名称的选项
    boolean containsOption(String name); 
    // 返回与具有给定名称的参数选项关联的值的集合
    List<String> getOptionValues(String name);
    // 返回已解析的非选项参数的集合
    List<String> getNonOptionArgs();
}

它只有一个默认的实现类就是DefaultApplicationArguments

2.2  创建环境

prepareEnvironment准备环境方法中,会完成环境的创建、配置:

private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
        ApplicationArguments applicationArguments) {
    // Create and configure the environment
    // 1. 创建环境
    ConfigurableEnvironment environment = getOrCreateEnvironment();
    // 2. 配置环境
    configureEnvironment(environment, applicationArguments.getSourceArgs());
    // 3. 连接 --没太看懂这个
    ConfigurationPropertySources.attach(environment);
    // 4. 执行监听器
    listeners.environmentPrepared(environment);
    // 5. 将spring.main开头的配置信息绑定到SpringApplication类属性中
    bindToSpringApplication(environment);
    // 6. 没有设置 isCustomEnvironment 属性的情况下
    if (!this.isCustomEnvironment) {
        // 环境转换器
        environment = new EnvironmentConverter(getClassLoader()).convertEnvironmentIfNecessary(environment,
                deduceEnvironmentClass());
    }
    // 7. 再次连接并返回
    ConfigurationPropertySources.attach(environment);
    return environment;
}

ConfigurableEnvironment是Spring 提供的接口,并继承了多个接口:

  • PropertyResolver:属性解析器,用于针对任何底层源(比如文件)解析属性的接口,定义了一系列读取,解析,判断是否包含指定属性的方法,比如可以从YML、系统参数等加载属性。
  • ConfigurablePropertyResolver:可配置的属性解析器,定义用于访问和自定义将属性值从一种类型转换为另一种类型时功能,也就是比PropertyResolver多了一个类型转换。
  • Environment:表示当前应用程序运行环境的接口。对应用程序环境的两个关键方面进行建模:properties配置文件(应用程序配置文件)和profiles(激活环境命名空间)。

所以ConfigurableEnvironment 具备了属性解析器和当前应用环境的能力,而且在这个基础上扩展了很多自己的方法:

public interface ConfigurableEnvironment extends Environment, ConfigurablePropertyResolver {
    // 设置当前激活的 profile
    void setActiveProfiles(String... profiles);
    // 添加 profile
    void addActiveProfile(String profile);
    // 设置默认的 profile
    void setDefaultProfiles(String... profiles);
    // 获取PropertySource键值组合的集合
    MutablePropertySources getPropertySources();
    // 获取系统属性
    Map<String, Object> getSystemProperties();
    //    获取系统环境变量
    Map<String, Object> getSystemEnvironment();
    // 合并其他 ConfigurableEnvironment 
    void merge(ConfigurableEnvironment parent);
}

那我们看一下该方法都做了些什么,准备环境阶段,第一步就是根据Web 应用的类型创建环境对象:

private ConfigurableEnvironment getOrCreateEnvironment() {
    // 1. 环境已初始化,直接返回
    if (this.environment != null) {
        return this.environment;
    }
    // 2. 判断环境的类型
    switch (this.webApplicationType) {
    // 3. SERVLET类型=>StandardServletEnvironment
    case SERVLET:
        return new StandardServletEnvironment();
    // REACTIVE类型=>StandardReactiveWebEnvironment
    case REACTIVE:
        return new StandardReactiveWebEnvironment();
    // 默认=>StandardEnvironment
    default:
        return new StandardEnvironment();
    }
}

new StandardServletEnvironment();调用父类的无参构造函数:

public AbstractEnvironment() {
    customizePropertySources(this.propertySources);
}
@Override
protected void customizePropertySources(MutablePropertySources propertySources) {
    // Servlet 上下文初始化参数属性源名称
    propertySources.addLast(new StubPropertySource(SERVLET_CONFIG_PROPERTY_SOURCE_NAME));
    // Servlet 配置初始化参数属性源名称
    propertySources.addLast(new StubPropertySource(SERVLET_CONTEXT_PROPERTY_SOURCE_NAME));
    // JNDI 属性源名称
    if (jndiPresent && JndiLocatorDelegate.isDefaultJndiEnvironmentAvailable()) {
        propertySources.addLast(new JndiPropertySource(JNDI_PROPERTY_SOURCE_NAME));
    }
    // 调用父类的
    super.customizePropertySources(propertySources);
}

在customizePropertySources可以看到创建了三个属性源,名称分别为:

  • servletConfigInitParams:Servlet 上下文初始化参数属性源名称:“servletContextInitParams”。
  • servletContextInitParams:Servlet 配置初始化参数属性源名称:“servletConfigInitParams”。
  • jndiProperties:JNDI 属性源名称:“jndiProperties”。

紧接着进入到customizePropertySources(propertySources)方法,也创建了systemPropertiessystemEnvironment属性源。

@Override
protected void customizePropertySources(MutablePropertySources propertySources) {
    // 添加System 系统属性
    propertySources.addLast(new PropertiesPropertySource(SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME, getSystemProperties()));
    // 添加系统环境属性
    propertySources.addLast(new SystemEnvironmentPropertySource(SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, getSystemEnvironment()));
}

最终这一步创建的ConfigurableEnvironment对象中,一共有4个PropertySource,其中前两个是没有放任何东西的:

 接着进入到配置环境方法中,会对属性、Profile 进行配置。

protected void configureEnvironment(ConfigurableEnvironment environment, String[] args) {
    // 添加默认的转换器服务
    if (this.addConversionService) {
        ConversionService conversionService = ApplicationConversionService.getSharedInstance();
        environment.setConversionService((ConfigurableConversionService) conversionService);
    }
    // 配置属性
    configurePropertySources(environment, args);
    // 配置 Profile
    configureProfiles(environment, args);
}

configurePropertySources方法主要是将启动命令中的参数和run 方法中的参数封装为PropertySource

protected void configurePropertySources(ConfigurableEnvironment environment, String[] args) {
    // 获取所有的属性源
    MutablePropertySources sources = environment.getPropertySources();
    if (this.defaultProperties != null && !this.defaultProperties.isEmpty()) {
        sources.addLast(new MapPropertySource("defaultProperties", this.defaultProperties));
    }
    // 是否添加启动命令参数,默认True
    if (this.addCommandLineProperties && args.length > 0) {
        String name = CommandLinePropertySource.COMMAND_LINE_PROPERTY_SOURCE_NAME;
        if (sources.contains(name)) {
            PropertySource<?> source = sources.get(name);
            CompositePropertySource composite = new CompositePropertySource(name);
            composite.addPropertySource(
                    new SimpleCommandLinePropertySource("springApplicationCommandLineArgs", args));
            composite.addPropertySource(source);
            sources.replace(name, composite);
        }
        else {
            sources.addFirst(new SimpleCommandLinePropertySource(args));
        }
    }
}

参数封装完,就是执行环境准备的监听器的执行了,这个我们之前看过了哈,就不说了哈,接下来就是绑定,调用bindToSpringApplication,其作用是将spring.main开头的配置信息绑定到SpringApplication类对应的属性中。

protected void bindToSpringApplication(ConfigurableEnvironment environment) {
    try {
        Binder.get(environment).bind("spring.main", Bindable.ofInstance(this));
    }
    catch (Exception ex) {
        throw new IllegalStateException("Cannot bind to SpringApplication", ex);
    }
}

2.3  配置beaninfo

环境创建成功后,进入到configureIgnoreBeanInfo方法,就是配置了一个spring.beaninfo.ignore属性,默认为TRUE,跳过对 BeanInfo 的搜索,这个BeanInfo是JDK 声明的一个接口,可以使用Introspector.getBeanInfo获取一个对象的方法、属性、事件和其他功能的显式信息。

设置spring.beaninfo.ignore,将使用低级反射和应用标准设计模式自动分析某个类的信息,可以提升一些性能。

private void configureIgnoreBeanInfo(ConfigurableEnvironment environment) {
    if (System.getProperty(CachedIntrospectionResults.IGNORE_BEANINFO_PROPERTY_NAME) == null) {
        Boolean ignore = environment.getProperty("spring.beaninfo.ignore", Boolean.class, Boolean.TRUE);
        System.setProperty(CachedIntrospectionResults.IGNORE_BEANINFO_PROPERTY_NAME, ignore.toString());
    }

2.4  打印Banner

环境准备完成后,开始打印Banner :

private Banner printBanner(ConfigurableEnvironment environment) {
    // 关闭的话就不打印了
    if (this.bannerMode == Banner.Mode.OFF) {
        return null;
    }
    // 创建资源加载器
    ResourceLoader resourceLoader = (this.resourceLoader != null) ? this.resourceLoader
            : new DefaultResourceLoader(getClassLoader());
    // 创建打印对象
    SpringApplicationBannerPrinter bannerPrinter = new SpringApplicationBannerPrinter(resourceLoader, this.banner);
    // 根据配置模式,选择打印到日志还是控制台
    if (this.bannerMode == Mode.LOG) {
        return bannerPrinter.print(environment, this.mainApplicationClass, logger);
    }
    return bannerPrinter.print(environment, this.mainApplicationClass, System.out);
}
public Banner print(Environment environment, Class<?> sourceClass, Pri
    // 从环境中获取Banner 对象并打印
    Banner banner = getBanner(environment);
    // 调用获取到的Banner并打印
    banner.printBanner(environment, sourceClass, out);
    return new PrintedBanner(banner, sourceClass);
}
private Banner getBanner(Environment environment) {
    Banners banners = new Banners();
    // 从spring.banner.image.location 配置中,查询图片
    // 没有配置,则查询banner.gif banner.jpg  banner.png图片
    // 都没有返回NULL
    banners.addIfNotNull(getImageBanner(environment));
    // 获取spring.banner.location 配置,默认是banner.txt
    // 查询banner.txt 文件
    banners.addIfNotNull(getTextBanner(environment));
    if (banners.hasAtLeastOneBanner()) {
        return banners;
    }
    if (this.fallbackBanner != null) {
        return this.fallbackBanner;
    }
    return DEFAULT_BANNER;
}

给大家看一下默认的:

3  小结

好了,环境准备的我们就看到这里哈,主要是创建环境变量对象读取系统配置,初始化件、读取应用配置文件等加载各种属性源,有理解不对的地方欢迎指正哈。

posted @ 2023-05-06 13:51  酷酷-  阅读(165)  评论(0编辑  收藏  举报