扒一扒SpringBoot启动类的run方法

SpringBoot程序的启动类

每个SpringBoot都有一个主程序启动类,那么这个启动类到底完成了什么工作呢,这次直接从github上下载一下Spring的源码,跟着启动类的run方法,看看到底做了哪些事情。SpringBoot源码地址,https://github.com/spring-projects/spring-boot/tags 文章中使用的源码版本是Spring boot 2.7.4。不同的版本源码肯定有区别,但是大流程应该是基本一致的。除此之外如果想了解SpringBoot启动注解的作用,可以参考这个 从注解入手扒一扒SpringBoot的自动配置原理


SpringBoot程序的启动过程分析


一、程序启动类main中调用 SpringApplication.run

@SpringBootApplication
public class SmsWeatherReminderApplication {
    public static void main(String[] args) {
        SpringApplication.run(SmsWeatherReminderApplication.class, args);
    }

}

1.1 官方对SpringApplication这个类的说明

  1. 该类可用于从Java主方法引导和启动Spring应用程序。默认情况下,class将执行以下步骤来引导您的应用程序:
    1. 根据传入的启动类路径,创建适当的ApplicationContext实例
    2. 注册一个CommandLinePropertySource,将命令行参数作为Spring属性公开
    3. 初始化应用程序上下文容器,加载所有单例bean
    4. 触发所有的CommandLineRunner bean (可在程序启动后进行自定义操作)
  2. 在大多数情况下,从main方法直接运行静态的SpringApplication.run(Class, String[])方法就可以引导你的应用程序启动。
  3. 如果需要进一步配置,也可以先构建出 SpringApplication对象,进行自定义配置后,再运行其run方法来启动应用程序。
  4. SpringApplications可以从不同的源来读取bean,一般推荐使用一个@Configuration注解类来引导你的应用程序,但是也可以从下面这些源来读取
    1. AnnotatedBeanDefinitionReader 去加载的权限定类名
    2. XmlBeanDefinitionReader 去加载xml资源的路径
    3. GroovyBeanDefinitionReader 去加载groovy脚本
    4. ClassPathBeanDefinitionScanner 去扫描包地址
  5. 配置文件的属性也会被_SpringApplication读取,所以可以动态的设置SpringApplicatio的一些配置。例如:_
    1. "spring.main.sources" - a CSV list 增加其他source
    2. spring.main.web-application-type=none 标识web程序类型
    3. spring.main.banner-mode=off 关闭启动时的banner



二、SpringApplication的静态run方法

public class SpringApplication {

    //1、启动类调用该静态run方法,实际上调用了其重载的方法
    public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
        return run(new Class<?>[] { primarySource }, args);
    }
    public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
        return new SpringApplication(primarySources).run(args);
    }
}
  1. 从main方法中传入应用启动类静态调用SpringApplication.run方法后其调用了重载方法,重载方法中调用了SpringApplication类的构造方法。根据传入的程序启动类构造了SpringApplication对象。
  2. 构造出SpringApplication对象后,调用了SpringApplication类的成员run方法


三、构造SpringApplication对象

//创建一个新的SpringApplication实例。
//应用程序上下文将从指定的启动类加载bean
//实例可以在调用run(String…)之前自定义
public SpringApplication(Class<?>... primarySources) {
    this(null, primarySources);
}

public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
    this.resourceLoader = resourceLoader;
    Assert.notNull(primarySources, "PrimarySources must not be null");
    //对项目的启动类作为class,进行了保存
    this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
    //第一步:
    //判断项目是一个传统的Web应用(Spring5之前的MVC),还是REACTIVE应用(Spring5 开始出现的WebFlux交互式应用)
    this.webApplicationType = WebApplicationType.deduceFromClasspath();
	
    //第二步:
    //使用SpringFactoriesLoader.loadFactoryNames 从jar的META-INF/spring.factories 中配置的注册器的实现
    this.bootstrapRegistryInitializers = new ArrayList<>(
            getSpringFactoriesInstances(BootstrapRegistryInitializer.class));
    //第三步:jar的META-INF/spring.factories中查找初始化器并设置
    setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
    //第四步 jar的META-INF/spring.factories中查找应用监听器并设置
    setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
    //第五步 设置查找main方法所在的类
    this.mainApplicationClass = deduceMainApplicationClass();
}




3.1 第一步 判断web应用的应用类型

this.webApplicationType = WebApplicationType.deduceFromClasspath();

public enum WebApplicationType {
	//应用不是Web应用程序
	NONE,

    //应用是基于servlet的Web应用程序,启动内嵌的servlet应用服务
	SERVLET,

    //应用是一个基于REACTIVE的web应用,启动内嵌reactive应用服务
	REACTIVE;
	
	private static final String[] SERVLET_INDICATOR_CLASSES = { "javax.servlet.Servlet",
			"org.springframework.web.context.ConfigurableWebApplicationContext" };

	private static final String WEBMVC_INDICATOR_CLASS = "org.springframework.web.servlet.DispatcherServlet";

	private static final String WEBFLUX_INDICATOR_CLASS = "org.springframework.web.reactive.DispatcherHandler";

	private static final String JERSEY_INDICATOR_CLASS = "org.glassfish.jersey.servlet.ServletContainer";

	static WebApplicationType deduceFromClasspath() {
		//通过类加载器,判断是否存在REACTIVE相关的class
		if (ClassUtils.isPresent(WEBFLUX_INDICATOR_CLASS, null) && !ClassUtils.isPresent(WEBMVC_INDICATOR_CLASS, null)
				&& !ClassUtils.isPresent(JERSEY_INDICATOR_CLASS, null)) {
			return WebApplicationType.REACTIVE;
		}
		//如果还不存在的Servlet类 那么就不是web应用程序了
		for (String className : SERVLET_INDICATOR_CLASSES) {
			if (!ClassUtils.isPresent(className, null)) {
				return WebApplicationType.NONE;
			}
		}
		return WebApplicationType.SERVLET;
	}

}

这句代码,对SpringApplication的webApplicationType进行了配置,直接通过判断Classpath类路径是否存在对应服务的特征类,来确定是servlet应用,还是reactive(spring5出现的webFlux交互式应用)应用。


3.2 第二步 注册 BootstrapRegistryInitializer

new ArrayList<>(getSpringFactoriesInstances(BootstrapRegistryInitializer.class));

private <T> Collection<T> getSpringFactoriesInstances(Class<T> type) {
    return getSpringFactoriesInstances(type, new Class<?>[] {});
}

private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
    ClassLoader classLoader = getClassLoader();
    // Use names and ensure unique to protect against duplicates
    //使用指定的类加载器从“META-INF/spring.factories”中加载给定类型的工厂实现的完全限定类名
    Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
    //利用反射创建了对应对象
    List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
    AnnotationAwareOrderComparator.sort(instances);
    return instances;
}
  1. 第一步:这里使用了指定的加载器,从META-INF/spring.factories中加载BootstrapRegistryInitializer.class配置的对应的全限定类名。这个方法很熟悉,因为上次探究注解的自动配置时,就看到了该方法从META-INF/spring.factories中加载自动配置类。
  2. 从spring.factories中获得了对应的全限定类名后,createSpringFactoriesInstances方法中,又使用了BeanUtils依据全限定类名,进行了初始化。

BootstrapRegistryInitializer是什么?

@FunctionalInterface
public interface BootstrapRegistryInitializer {
	/**
	 * 用任何必需的注册初始化给定的BootstrapRegistry
	 * @param registry the registry to initialize
	 */
	void initialize(BootstrapRegistry registry);
}

/**
*一个简单的对象注册表,在启动和环境后处理期间可用,直到ApplicationContext准备好为止。 
可以用来注册创建的代价可能很高的实例,或者在ApplicationContext可用之前需要共享的实例。 
注册中心使用Class作为键,这意味着只能存储给定类型的单个实例。 
可以使用addCloseListener(ApplicationListener)方法添加一个侦听器,
该侦听器可以在BootstrapContext关闭且ApplicationContext完全准备好时执行操作。
例如,一个实例可以选择将自己注册为一个常规Spring bean,以便应用程序可以使用它
*/
public interface BootstrapRegistry {
	....
	//添加一个ApplicationListener,当BootstrapContext被关闭并且ApplicationContext已经准备好时,
    //它将被BootstrapContextClosedEvent调用
    void addCloseListener(ApplicationListener<BootstrapContextClosedEvent> listener);
}

  1. BootstrapRegistryInitializer接口只有一个方法,就是初始化给定的BootstrapRegistry引导注册器。
  2. 目前在SpringBoot 2.7.4 的源码中对应的spring.factories中未包含BootstrapRegistryInitializer的配置,后续可能会有对应的拓展。
  3. BootstrapRegistry是一个启动过程中的的对象注册器,可以在从启动开始,到后处理流程之前都可用。
  4. 可以用来注册消耗很大的对象,或者注册在ApplicationContext初始化完成之前就需要共享的Bean。
  5. BootstrapRegistry注册的类,使用class名称作为已注册的class的key,所以名称相同的只存在一个实例。这一点从他的默认实现类,DefaultBootstrapContext中也可以看到,它使用了一个Class<?> 为key的map来对创建好的实例进行了保存。
  6. BootstrapRegistry可以以注册一个监听器,监听DefaultBootstrapContextClosedEvent也就是说在启动完成,ApplicationContext就绪BootstrapContext关闭时,会调用该方法。

3.3 第三步 设置应用初始化器

setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));用于SpringApplication应用的初始化器设置。在初始化器设置过程中,会使用Spring类加载器
SpringFactoriesLoader从META-INF/spring.factories类路径下的META-INF下的spring.factores文件中
获取所有可用的应用初始化器类ApplicationContextInitializer 。过程和3.2的注册过程基本一致

3.4 第四步 设置所有的应用监听器

setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
用于SpringApplication应用的监听器设置。监听器设置的过程与上一步初始化器设置的过程基本一样,
也是使用SpringFactoriesLoader从META-INF/spring.factories类路径下的META-INF下的
spring.factores文件中获取所有可用的监听器类ApplicationListener。

3.5 第五步 推断main方法所在的class

this.mainApplicationClass = deduceMainApplicationClass();

private Class<?> deduceMainApplicationClass() {
    try {
        StackTraceElement[] stackTrace = new RuntimeException().getStackTrace();
        for (StackTraceElement stackTraceElement : stackTrace) {
            if ("main".equals(stackTraceElement.getMethodName())) {
                return Class.forName(stackTraceElement.getClassName());
            }
        }
    }
    catch (ClassNotFoundException ex) {
        // Swallow and continue
    }
    return null;
}

可以看到,SpringBoot推断main方法所在的类的方式,是通过构建一个RuntimeException然后从堆栈信息中获取,很巧妙。



四、由SpringApplication初始化和启动 Spring

public ConfigurableApplicationContext run(String... args) {
    //统计启动耗时用的
    long startTime = System.nanoTime();

    //创建了一个启动时的上下文容器实现了BootstrapRegistry,对构建SpringApplication时保存的需要启动时创建的类进行保存注册
    DefaultBootstrapContext bootstrapContext = createBootstrapContext();
    ConfigurableApplicationContext context = null;
    configureHeadlessProperty();

    /*
    第一步
    1.从spring.factories中获取SpringApplicationRunListener配置的运行监听器,并创建监听器集合对象。
    2.监听器启动,向所有监听器发布开始启动的事件,并传入启动时上下文。listeners相当于是一个事件源对象。
    */
    SpringApplicationRunListeners listeners = getRunListeners(args);
    listeners.starting(bootstrapContext, this.mainApplicationClass);
    try {

        //args是启动Spring应用的命令行参数(例如--server.port=8080),参数可以在Spring中被访问。
        ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);

        /**
         * 第二步:
         * 1、传入监听器和启动上下文以及应用参数,来构建项目启动环境
         * 2、listeners 广播通知所有监听器,environmentPrepared
         * 3、保存命令行参数、servletConfigInitParams、servletContextinitParams、
         * systemProperties、systemEnvironment、random、application.yml等到环境配置中
         */
        ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);
        configureIgnoreBeanInfo(environment);
        //准备打印banner
        Banner printedBanner = printBanner(environment);

        /**
         * 第三步 创建Spring容器
         * 1、利用构建SpringApplication时设置的 webApplicationType传入ApplicationContextFactory对象中来构建
         * 2、SpringApplication会根据webApplicationType创建默认的ApplicationContextFactory
         *
         */
        context = createApplicationContext();
        //收集启动时间
        context.setApplicationStartup(this.applicationStartup);

        /**
         * 第四步:容器前置处理
         * 1、设置容器环境、变量等。并且将启动类注入容器类
         */
        prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);

        /**
         * 第五步:刷新容器
         * 1、开启刷新Spring容器,通过refresh方法对整个Ioc容器的初始化,包括Bean的定位、解析、注册等等
         * 2、向JVM注册一个关机回调,JVM关机时会关闭这个容器上下文。
         */
        refreshContext(context);

        /**
         * 第六步:容器后处理* 
         */
        afterRefresh(context, applicationArguments);
        //统计耗时
        Duration timeTakenToStartup = Duration.ofNanos(System.nanoTime() - startTime);
        if (this.logStartupInfo) {
            new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), timeTakenToStartup);
        }
        /**
         * 第七步:发布启动完成通知
         */
        listeners.started(context, timeTakenToStartup);

        /**
         * 调用自定义的xxxRunner类,使得项目启动后执行一些特定程序。
         */
        callRunners(context, applicationArguments);
    } catch (Throwable ex) {
        handleRunFailure(context, ex, listeners);
        throw new IllegalStateException(ex);
    }
    try {
        Duration timeTakenToReady = Duration.ofNanos(System.nanoTime() - startTime);
        listeners.ready(context, timeTakenToReady);
    } catch (Throwable ex) {
        handleRunFailure(context, ex, null);
        throw new IllegalStateException(ex);
    }
    return context;
}

按照启动的过程,将启动流程大致分为以下部分,嫌烦的话呢直接看上方的源码也是一样的,已经注释好了。

4.1 第一步 创建了启动时上下文容器

DefaultBootstrapContext bootstrapContext = createBootstrapContext();

  1. 这个容器的作用,在3.2有提到,是一个启动过程中的的对象注册器,可以在从启动开始,到后处理流程之前都可用。可以用来注册消耗很大的对象,或者注册在ApplicationContext初始化完成之前就需要共享的Bean。
  2. 注册的过程就是使用3.2中收集到的注册器初始化器,调用其初始化方法注入到容器中。

4.2 第二步 创建监听器的事件源对象 ApplicationArguments

SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting(bootstrapContext, this.mainApplicationClass);

private SpringApplicationRunListeners getRunListeners(String[] args) {
    Class<?>[] types = new Class<?>[]{SpringApplication.class, String[].class};
    return new SpringApplicationRunListeners(logger,
            getSpringFactoriesInstances(
                SpringApplicationRunListener.class, types, this, args),this.applicationStartup);
}
  1. 可以看到,这一步调用了getSpringFactoriesInstances()方法,从spring.factories文件中取出了SpringApplicationRunListener.class的配置对象,并且创建了ApplicationArguments 对象。
  2. 这里的ApplicationArguments 应该可以理解为事件源,保存了监听器的集合,并且事件通知由其向其保存的监听器进行发布。

在这里插入图片描述

  1. SpringApplicationRunListener 对比ApplicationListener
    1. SpringApplicationRunListener 只监听run方法运行时的事件,每次都在run方法运行时重新注册。
    2. ApplicationListener 由应用程序事件监听器实现的接口。 基于Observer设计模式的标准,在构建SpringApplication时被注册成为SpringApplication的列表成员中。
  2. 疑问1: 这里的SpringApplicationRunListener监听器,与3.4中注册的监听器有所不同。并且spring为何要将两种监听器分开注册呢? 我的理解:SpringApplicationRunListener只在run方法运行时有作用,所以没有必要像3.4 中的ApplicationListener,长期维护在SpringApplication对象中。
  3. 疑问2: 这里的只注册了SpringApplicationRunListener,那3.4里提前保存好的ApplicationListener就监听不到run方法的事件了吗?**结果:**spring.factories文件里,目前配置了一个SpringApplicationRunListener就是EventPublishingRunListener,这个对象在构建时,就会取出3.4保存的监听器加到自己的成员变量,每次收到事件时在向他们也发布一遍。这样这些ApplicationListener就也能监控到run方法的运行通知了。

4.3 根据SpringApplicationRunListeners以及参数来准备环境

ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);

  1. 传入监听器和启动上下文以及应用参数,来构建项目启动环境
  2. listeners 广播通知所有监听器,发布事件,environmentPrepared
  3. 保存命令行参数、servletConfigInitParams、servletContextinitParams、systemProperties、systemEnvironment、random、application.yml等到环境配置中
  4. configureIgnoreBeanInfo(environment);方法排除一些不需要的运行环境

4.4 第四步 依据webApplicationType创建Spring容器

context = createApplicationContext();

ConfigurableApplicationContext context = null;


//1、可以发现,容器由 applicationContextFactory 负责创建
protected ConfigurableApplicationContext createApplicationContext() {
		return this.applicationContextFactory.create(this.webApplicationType);
}

//2、SpringApplication中的 ApplicationContextFactory 是一个默认值
private ApplicationContextFactory applicationContextFactory = ApplicationContextFactory.DEFAULT;


/**
 * 3、默认ApplicationContextFactory 用了一段lambda表达式创建了一个对象
 * 一个默认的ApplicationContextFactory实现,它将为WebApplicationType创建一个适当的上下文。
 */
ApplicationContextFactory DEFAULT = (webApplicationType) -> {
    try {
        for (ApplicationContextFactory candidate : SpringFactoriesLoader
                .loadFactories(ApplicationContextFactory.class, ApplicationContextFactory.class.getClassLoader())) {
            ConfigurableApplicationContext context = candidate.create(webApplicationType);
            if (context != null) {
                return context;
            }
        }
        return new AnnotationConfigApplicationContext();
    }
    catch (Exception ex) {
        throw new IllegalStateException("Unable create a default ApplicationContext instance, "
                + "you may need a custom ApplicationContextFactory", ex);
    }
};

# Application Context Factories
org.springframework.boot.ApplicationContextFactory=\
org.springframework.boot.web.reactive.context.AnnotationConfigReactiveWebServerApplicationContext.Factory,\
org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext.Factory
static class Factory implements ApplicationContextFactory {

    //如果web应用环境,不是这个工厂需要的环境,那么工厂create方法,就返回空。
    @Override
    public ConfigurableApplicationContext create(WebApplicationType webApplicationType) {
        return (webApplicationType != WebApplicationType.REACTIVE) ? null
                : new AnnotationConfigReactiveWebServerApplicationContext();
    }

}
  1. 可以发现,Spring对context的创建改变挺大的,之前直接在createApplicationContext方法里,就根据webApplicationType判断了需要使用哪个context。但是现在一眼无法发现到底使用的哪个context
  2. 默认的ApplicationContextFactory的方法中,先从spring.factories加载配置的ApplicationContextFactory
  3. 配置文件中,分别保存了 AnnotationConfigReactiveWebServerApplicationContext 和AnnotationConfigServletWebServerApplicationContext的工厂类
  4. 这两个文件的工厂类,都是context的内部类。根据webApplicationType判断是否初始化对应的context。
  5. 那么Spring容器的创建结果也是一目了然了
    1. 如果 webApplicationType=WebApplicationType._REACTIVE _那么context的实现了就是AnnotationConfigReactiveWebServerApplicationContext
    2. 如果 webApplicationType= _SERVLET _那么context的实现就是AnnotationConfigServletWebServerApplicationContext
    3. 如果webApplicationType= _NONE 那么context的实现就是 _AnnotationConfigApplicationContext

4.5 容器前置处理

prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);

private void prepareContext(DefaultBootstrapContext bootstrapContext, ConfigurableApplicationContext context,
								ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
								ApplicationArguments applicationArguments, Banner printedBanner) {
    //设置容器环境
    context.setEnvironment(environment);
    //设置上下文的bean生器械 和ApplicationContext相关后处理
    postProcessApplicationContext(context);
    //执行容器中的应用初始化器(包括自定义和spring.factories)
    applyInitializers(context);
    //触发监听器事件
    listeners.contextPrepared(context);
    //引导启动容器到此被关闭
    bootstrapContext.close(context);
    //记录启动日志
    if (this.logStartupInfo) {
        logStartupInfo(context.getParent() == null);
        logStartupProfileInfo(context);
    }
    // Add boot specific singleton beans
    //注册启动时需要的参数Bean 封装金融其
    ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
    beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
    if (printedBanner != null) {
        beanFactory.registerSingleton("springBootBanner", printedBanner);
    }
    if (beanFactory instanceof AbstractAutowireCapableBeanFactory) {
        ((AbstractAutowireCapableBeanFactory) beanFactory).setAllowCircularReferences(this.allowCircularReferences);
        if (beanFactory instanceof DefaultListableBeanFactory) {
            ((DefaultListableBeanFactory) beanFactory)
                    .setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
        }
    }
    //懒加载和工厂bean的后处理器处理
    if (this.lazyInitialization) {
        context.addBeanFactoryPostProcessor(new LazyInitializationBeanFactoryPostProcessor());
    }
    context.addBeanFactoryPostProcessor(new PropertySourceOrderingBeanFactoryPostProcessor(context));
    // 加载所有资源
    Set<Object> sources = getAllSources();
    Assert.notEmpty(sources, "Sources must not be empty");
    //将启动类注入容器,为自动配置奠定基础
    load(context, sources.toArray(new Object[0]));
    listeners.contextLoaded(context);
}
  1. 该方法重要的地方在于,将启动类注入到了容器中,为自动化配置奠定了基础
  2. 这里可以看到,4.1创建的启动时的引导类注册器,在这里被关闭。所以3.2 注册的bean,在run方法启动过程中,生命周期就是从run开始,到refresh之前。

4.6 刷新容器 refreshContext(context);

这一步里,完成了对整个IOC容器的初始化,和单独使用Spring类似,同时向JVM注册了一个回调方法,JVM关机时将关闭容器。


4.7 容器后处理 afterRefresh(context, applicationArguments);

拓展接口,设计模式中的模板方法。默认为空实现,重写时可以自定义该处的逻辑。


4.8 发布通知,容器已启动

listeners.started(context, timeTakenToStartup);
这里的started传入的context不再是启动时候的context了,因为bootstrapContext 已经关闭了。


4.9 执行Runners

用于调用项目中自定义的执行器XxxRunner类,使得在项目启动完成后立即执行一些特定程序。其中,
Spring Boot提供的执行器接口有ApplicationRunner 和CommandLineRunner两种,在使用时只需要
自定义一个执行器类实现其中一个接口并重写对应的run()方法接口,然后Spring Boot项目启动后会立
即执行这些特定程序.

@Component
@Slf4j
public class StartCommandLineRunner implements CommandLineRunner {

    @Autowired
    DataIndiCacheUtil dataIndiCacheUtil;

    @Value("${spring.profiles.active}")
    String SERVER_ZONE;

    @Override
    public void run(String... args) throws Exception {
        log.info("程序启动时操作开始执行");
        dataIndiCacheUtil.initLocalMemoryCache();
        log.info("程序启动时操作执行完毕");
    }
}



五、总结

在这里插入图片描述

可以看到源码中出于各种原因的考虑,一个类或者方法都被拆的很细,并且这一段也有各种设计模式的应用,比如观察者模式、策略模式、工厂模式等。自己翻的源码,可能解释或理解不正确,如果有错希望可以帮忙指正。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值