日本综合一区二区|亚洲中文天堂综合|日韩欧美自拍一区|男女精品天堂一区|欧美自拍第6页亚洲成人精品一区|亚洲黄色天堂一区二区成人|超碰91偷拍第一页|日韩av夜夜嗨中文字幕|久久蜜综合视频官网|精美人妻一区二区三区

RELATEED CONSULTING
相關(guān)咨詢
選擇下列產(chǎn)品馬上在線溝通
服務(wù)時(shí)間:8:30-17:00
你可能遇到了下面的問題
關(guān)閉右側(cè)工具欄

新聞中心

這里有您想知道的互聯(lián)網(wǎng)營銷解決方案
玩轉(zhuǎn)SpringBoot—啟動(dòng)源碼及外部化配置

學(xué)習(xí)目標(biāo)

  • 理解springboot的總體啟動(dòng)流程,并能口述大概
  • 理清配置文件的加載流程

第1章 main入口

public static void main(String[] args) {
    //代碼很簡(jiǎn)單SpringApplication.run();
	SpringApplication.run(ConsumerApp.class, args);
}
public static ConfigurableApplicationContext run(Class primarySource,
                                                 String... args) {
    //這個(gè)里面調(diào)用了run() 方法,我們轉(zhuǎn)到定義
    return run(new Class[] { primarySource }, args);
}
//這個(gè)run方法代碼也很簡(jiǎn)單,就做了兩件事情
//1、new了一個(gè)SpringApplication() 這么一個(gè)對(duì)象
//2、執(zhí)行new出來的SpringApplication()對(duì)象的run()方法
public static ConfigurableApplicationContext run(Class[] primarySources,
                                                 String[] args) {
    return new SpringApplication(primarySources).run(args);
}

上面代碼主要做了兩件事情。

我們提供的服務(wù)有:成都網(wǎng)站建設(shè)、成都網(wǎng)站制作、微信公眾號(hào)開發(fā)、網(wǎng)站優(yōu)化、網(wǎng)站認(rèn)證、中寧ssl等。為千余家企事業(yè)單位解決了網(wǎng)站和推廣的問題。提供周到的售前咨詢和貼心的售后服務(wù),是有科學(xué)管理、有技術(shù)的中寧網(wǎng)站制作公司

  • 第一步new了一個(gè)SpringApplication對(duì)象
  • 第二步調(diào)用了run()方法。

一、SpringApplication

public SpringApplication(ResourceLoader resourceLoader, Class... primarySources) {
		this.resourceLoader = resourceLoader;
		Assert.notNull(primarySources, "PrimarySources must not be null");
		//1、先把主類保存起來
		this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
		//2、判斷運(yùn)行項(xiàng)目的類型
		this.webApplicationType = WebApplicationType.deduceFromClasspath();
		//3、掃描當(dāng)前路徑下META-INF/spring.factories文件的,加載ApplicationContextInitializer接口實(shí)例
		setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
		//4、掃描當(dāng)前路徑下META-INF/spring.factories文件的,加載ApplicationListener接口實(shí)例
		setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
		this.mainApplicationClass = deduceMainApplicationClass();
}

1、判斷運(yùn)行環(huán)境

構(gòu)造方法內(nèi)會(huì)調(diào)用枚舉WebApplicationType的deduceFromClasspath方法獲得應(yīng)用類型并設(shè)置當(dāng)前應(yīng)用是普通web應(yīng)用、響應(yīng)式web應(yīng)用還是非web應(yīng)用。

this.webApplicationType = WebApplicationType.deduceFromClasspath();

deduceFromClasspath方法由枚舉WebApplicationType提供,具體實(shí)現(xiàn)如下:

static WebApplicationType deduceFromClasspath() {
    //當(dāng)classpath下只存在org.springframework.web.reactive.DispatcherHandler,
    //且不存在org.springframework.web.servlet.DispatcherServlet,也不存在
    //org.glassfish.jersey.servlet.ServletContainer則運(yùn)行環(huán)境為reactive,該模式是非阻塞模式
    if (ClassUtils.isPresent(WEBFLUX_INDICATOR_CLASS, null) && !ClassUtils.isPresent(WEBMVC_INDICATOR_CLASS, null)
        && !ClassUtils.isPresent(JERSEY_INDICATOR_CLASS, null)) {
        return WebApplicationType.REACTIVE;
    }
    for (String className : SERVLET_INDICATOR_CLASSES) {
        if (!ClassUtils.isPresent(className, null)) {
            return WebApplicationType.NONE;
        }
    }
    return WebApplicationType.SERVLET;
}

推斷的過程中重點(diǎn)調(diào)用了ClassUtils.isPresent()方法,用來判斷指定類名的類是否存在,是否可以進(jìn)行加載。ClassUtils.isPresent()方法源代碼如下:

public static boolean isPresent(String className, @Nullable ClassLoader classLoader) {
    try {
        forName(className, classLoader);
        return true;
    }
    catch (IllegalAccessError err) {
        throw new IllegalStateException("Readability mismatch in inheritance hierarchy of class [" +
                                        className + "]: " + err.getMessage(), err);
    }
    catch (Throwable ex) {
        // Typically ClassNotFoundException or NoClassDefFoundError...
        return false;
    }
}

isPresent()方法調(diào)用了forName()方法,如果在調(diào)用forName()方法的過程中出現(xiàn)異常則返回false,也就是目標(biāo)類不存在。否則,返回true。

看一下forName()方法的部分代碼:

public static Class forName(String name, @Nullable ClassLoader classLoader)
			throws ClassNotFoundException, LinkageError {

	// 此處省略一些非空和基礎(chǔ)類型的判斷邏輯代碼

	ClassLoader clToUse = classLoader;
	if (clToUse == null) {
	    //如果為空則獲取默認(rèn)classLoader
		clToUse = getDefaultClassLoader();
	}
	try {
	    // 返回加載戶的Class。
		return Class.forName(name, false, clToUse);
	} catch (ClassNotFoundException ex) {
	    // 如果直接加載類出現(xiàn)異常,則嘗試加載內(nèi)部類。
		int lastDotIndex = name.lastIndexOf(PACKAGE_SEPARATOR);
		if (lastDotIndex != -1) {
		    // 拼接內(nèi)部類
			String innerClassName =
					name.substring(0, lastDotIndex) + INNER_CLASS_SEPARATOR + name.substring(lastDotIndex + 1);
			try {
				return Class.forName(innerClassName, false, clToUse);
			}
			catch (ClassNotFoundException ex2) {
				// Swallow - let original exception get through
			}
		}
		throw ex;
	}
}

通過以上核心代碼,可得知forName()方法主要做的事情就是獲得類加載器,嘗試直接加載類,如果失敗則嘗試加載該類的內(nèi)部類,如果依舊失敗,則拋出異常。

因此,整個(gè)應(yīng)用類型的推斷分以下步驟:

  • SpringBoot調(diào)用SpringApplication構(gòu)造方法;
  • SpringApplication構(gòu)造方法調(diào)用枚舉類的類型推斷方法deduceFromClasspath()。
  • deduceFromClasspath()方法通過ClassUtils.isPresent()返回結(jié)果為true或false來確定是否加載成功指定的類。
  • ClassUtils.isPresent()方法通過調(diào)用forName()方法并捕獲異常來確定是否能夠成功加載該類。
  • forName()方法通過嘗試加載指定類和指定類的內(nèi)部類來確定該類是否存在,存在則返回該類,不存在則拋異常。

在類型推斷的過程中枚舉類WebApplicationType定義了具體去加載哪些類:

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";
  • 如果應(yīng)用程序存在DispatcherHandler并且不存在DispatcherServlet和ServletContainer則為響應(yīng)式web應(yīng)用,需加載并啟動(dòng)內(nèi)嵌的響應(yīng)式web服務(wù)。
  • 如果應(yīng)用程序不包含Servlet和ConfigurableWebApplicationContext則為普通應(yīng)用程序。
  • 其他情況則為基于servlet的web應(yīng)用,需加載并啟動(dòng)內(nèi)嵌的web服務(wù)。

2、初始化器和監(jiān)聽器

利用SPI機(jī)制掃描 META-INF/spring.factories 這個(gè)文件,并且加載ApplicationContextInitializer、ApplicationListener 接口實(shí)例。

  • ApplicationContextInitializer 這個(gè)類當(dāng)springboot上下文Context初始化完成后會(huì)調(diào)用。
  • ApplicationListener 當(dāng)springboot啟動(dòng)時(shí)事件change后都會(huì)觸發(fā)。

總結(jié):上面就是SpringApplication初始化的代碼,new SpringApplication()沒做啥事情 ,利用SPI機(jī)制主要加載了META-INF/spring.factories 下面定義的事件監(jiān)聽器接口實(shí)現(xiàn)類。

二、執(zhí)行run方法

public ConfigurableApplicationContext run(String... args) {

    
    StopWatch stopWatch = new StopWatch();
    stopWatch.start();
    DefaultBootstrapContext bootstrapContext = createBootstrapContext();
    ConfigurableApplicationContext context = null;
    
    
    configureHeadlessProperty();
 
    
    SpringApplicationRunListeners listeners = getRunListeners(args);
    listeners.starting(bootstrapContext, this.mainApplicationClass);

    try {
        
        ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);

        
            //準(zhǔn)備容器環(huán)境、這里會(huì)加載配置文件。在這個(gè)方法里面會(huì)調(diào)用所有監(jiān)聽器Listener的onApplicationEvent(event);
            // 此時(shí)有一個(gè)與配置文件相關(guān)的監(jiān)聽器就會(huì)被加載`ConfigFileApplicationListener`
        ConfigurableEnvironment environment = prepareEnvironment(listeners,applicationArguments);

        
        configureIgnoreBeanInfo(environment);

        
        Banner printedBanner = printBanner(environment);


        
        context = createApplicationContext();
		context.setApplicationStartup(this.applicationStartup);
        
        
        prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);

        
        //這個(gè)refreshContext()加載了bean,還啟動(dòng)了內(nèi)置web容器,需要細(xì)細(xì)的去看看
        refreshContext(context);

        
        afterRefresh(context, applicationArguments);
        stopWatch.stop();
        if (this.logStartupInfo) {
            new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
        }

        
        listeners.started(context);

        
        callRunners(context, applicationArguments);
    }
    catch (Throwable ex) {
        handleRunFailure(context, ex, listeners);
        throw new IllegalStateException(ex);
    }
    try {
        listeners.running(context);
    }
    catch (Throwable ex) {
        handleRunFailure(context, ex, null);
        throw new IllegalStateException(ex);
    }
    return context;
}

第2章 環(huán)境變量及配置

一、prepareEnvironment

private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
                  DefaultBootstrapContext bootstrapContext, ApplicationArguments applicationArguments) {
    // 創(chuàng)建和配置環(huán)境變量
    ConfigurableEnvironment environment = getOrCreateEnvironment();
    configureEnvironment(environment, applicationArguments.getSourceArgs());
    ConfigurationPropertySources.attach(environment);
    listeners.environmentPrepared(bootstrapContext, environment);
    DefaultPropertiesPropertySource.moveToEnd(environment);
    Assert.state(!environment.containsProperty("spring.main.environment-prefix"),
                 "Environment prefix cannot be set via properties.");
    bindToSpringApplication(environment);
    if (!this.isCustomEnvironment) {
        environment = new EnvironmentConverter(getClassLoader()).convertEnvironmentIfNecessary(environment,
                                                                                               deduceEnvironmentClass());
    }
    ConfigurationPropertySources.attach(environment);
    return environment;
}

二、getOrCreateEnvironment

/**
*  該方法根據(jù)webApplicationType判斷當(dāng)前項(xiàng)目是什么類型項(xiàng)目
*/
private ConfigurableEnvironment getOrCreateEnvironment() {
    if (this.environment != null) {
        return this.environment;
    }
    switch (this.webApplicationType) {
        case SERVLET:
            return new StandardServletEnvironment();
        case REACTIVE:
            return new StandardReactiveWebEnvironment();
        default:
            return new StandardEnvironment();
    }
}

//webApplicationType是在new SpringApplication方法中通過WebApplicationType.deduceFromClasspath()進(jìn)行賦值

枚舉WebApplicationType中定義了三個(gè)應(yīng)用類型:

  • NONE:應(yīng)用程序不作為web應(yīng)用啟動(dòng),不啟動(dòng)內(nèi)嵌的服務(wù)。
  • SERVLET:應(yīng)用程序以基于servlet的web應(yīng)用啟動(dòng),需啟動(dòng)內(nèi)嵌servlet web服務(wù)。
  • REACTIVE:應(yīng)用程序以響應(yīng)式web應(yīng)用啟動(dòng),需啟動(dòng)內(nèi)嵌的響應(yīng)式web服務(wù)。

這里調(diào)用newStandardServletEnvironment()方法。

StandardServletEnvironment繼承了StandardEnvironment方法,StandardEnvironment又繼承了AbstractEnvironment方法;在AbstractEnvironment方法中調(diào)用了customizePropertySources方法。

public AbstractEnvironment() {
    customizePropertySources(this.propertySources);
}

customizePropertySources方法會(huì)回調(diào)StandardServletEnvironment方法中的customizePropertySources方法。

protected void customizePropertySources(MutablePropertySources propertySources) {
    propertySources.addLast(new StubPropertySource(SERVLET_CONFIG_PROPERTY_SOURCE_NAME));
    propertySources.addLast(new StubPropertySource(SERVLET_CONTEXT_PROPERTY_SOURCE_NAME));
    if (JndiLocatorDelegate.isDefaultJndiEnvironmentAvailable()) {
        propertySources.addLast(new JndiPropertySource(JNDI_PROPERTY_SOURCE_NAME));
    }
    //在這里又調(diào)用了父類StandardEnvironment的方法
    super.customizePropertySources(propertySources);
}

到這里為止propertySources里面就加載了servletConfigInitParams、servletContextInitParams、systemProperties、systemEnvironment。

然后回到prepareEnvironment方法中,在listeners.environmentPrepared(bootstrapContext, environment);方法中去進(jìn)行監(jiān)聽。

三、environmentPrepared

void environmentPrepared(ConfigurableEnvironment environment) {
    for (SpringApplicationRunListener listener : this.listeners) {
        listener.environmentPrepared(environment);
    }
}

繼續(xù)進(jìn)入environmentPrepared方法,會(huì)進(jìn)入到SpringApplicationRunListener接口,這個(gè)接口在run方法中的getRunListeners里面獲取,最終是在sprin.factories里面進(jìn)行加載實(shí)現(xiàn)類EventPublishingRunListener,執(zhí)行的是EventPublishingRunListener類中的environmentPrepared方法。

public void environmentPrepared(ConfigurableEnvironment environment) {
    this.initialMulticaster
        .multicastEvent(new ApplicationEnvironmentPreparedEvent(this.application, this.args, environment));
}

multicastEvent

public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {
    ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
    Executor executor = getTaskExecutor();
    for (ApplicationListener listener : getApplicationListeners(event, type)) {
        if (executor != null) {
            executor.execute(() -> invokeListener(listener, event));
        }
        else {
            invokeListener(listener, event);
        }
    }
}

invokeListener

protected void invokeListener(ApplicationListener listener, ApplicationEvent event) {
    ErrorHandler errorHandler = getErrorHandler();
    if (errorHandler != null) {
        try {
            doInvokeListener(listener, event);
        }
        catch (Throwable err) {
            errorHandler.handleError(err);
        }
    }
    else {
        doInvokeListener(listener, event);
    }
}

doInvokeListener

private void doInvokeListener(ApplicationListener listener, ApplicationEvent event) {
    try {
        listener.onApplicationEvent(event);
    }
    catch (ClassCastException ex) {
        String msg = ex.getMessage();
        if (msg == null || matchesClassCastMessage(msg, event.getClass())) {
            // Possibly a lambda-defined listener which we could not resolve the generic event type for
            // -> let's suppress the exception and just log a debug message.
            Log logger = LogFactory.getLog(getClass());
            if (logger.isTraceEnabled()) {
                logger.trace("Non-matching event type for listener: " + listener, ex);
            }
        }
        else {
            throw ex;
        }
    }
}

進(jìn)入ConfigFileApplicationListener實(shí)現(xiàn)類中的onApplicationEvent方法。

onApplicationEvent

public void onApplicationEvent(ApplicationEvent event) {
    if (event instanceof ApplicationEnvironmentPreparedEvent) {
        //在這個(gè)方法里面讀取配置文件
        onApplicationEnvironmentPreparedEvent((ApplicationEnvironmentPreparedEvent) event);
    }
    if (event instanceof ApplicationPreparedEvent) {
        onApplicationPreparedEvent(event);
    }
}
private void onApplicationEnvironmentPreparedEvent(ApplicationEnvironmentPreparedEvent event) {
    List postProcessors = loadPostProcessors();
    postProcessors.add(this);
    AnnotationAwareOrderComparator.sort(postProcessors);
    for (EnvironmentPostProcessor postProcessor : postProcessors) {
        //進(jìn)入
        postProcessor.postProcessEnvironment(event.getEnvironment(), event.getSpringApplication());
    }
}
public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
    //進(jìn)入
    addPropertySources(environment, application.getResourceLoader());
}
protected void addPropertySources(ConfigurableEnvironment environment, ResourceLoader resourceLoader) {
    RandomValuePropertySource.addToEnvironment(environment);
    //load方法是讀取配置文件的核心方法
    new Loader(environment, resourceLoader).load();
}
void load() {
    FilteredPropertySource.apply(this.environment, DEFAULT_PROPERTIES, LOAD_FILTERED_PROPERTY,
                                 (defaultProperties) -> {
                                     this.profiles = new LinkedList<>();
                                     this.processedProfiles = new LinkedList<>();
                                     this.activatedProfiles = false;
                                     this.loaded = new LinkedHashMap<>();
                                     initializeProfiles();
                                     while (!this.profiles.isEmpty()) {
                                         Profile profile = this.profiles.poll();
                                         if (isDefaultProfile(profile)) {
                                             addProfileToEnvironment(profile.getName());
                                         }
                                         load(profile, this::getPositiveProfileFilter,
                                              addToLoaded(MutablePropertySources::addLast, false));
                                         this.processedProfiles.add(profile);
                                     }
                                     load(null, this::getNegativeProfileFilter, addToLoaded(MutablePropertySources::addFirst, true));
                                     addLoadedPropertySources();
                                     applyActiveProfiles(defaultProperties);
                                 });
}

createApplicationContext

一起來看下context = createApplicationContext(); 這段代碼,這段代碼主要是根據(jù)項(xiàng)目類型創(chuàng)建上下文,并且會(huì)注入幾個(gè)核心組件類。

protected ConfigurableApplicationContext createApplicationContext() {
	Class contextClass = this.applicationContextClass;
	if (contextClass == null) {
		try {
			switch (this.webApplicationType) {
			case SERVLET:
				contextClass = Class.forName(DEFAULT_SERVLET_WEB_CONTEXT_CLASS);
				break;
			case REACTIVE:
				contextClass = Class.forName(DEFAULT_REACTIVE_WEB_CONTEXT_CLASS);
				break;
			default:
				contextClass = Class.forName(DEFAULT_CONTEXT_CLASS);
			}
		}
		catch (ClassNotFoundException ex) {
			throw new IllegalStateException(
					"Unable create a default ApplicationContext, please specify an ApplicationContextClass", ex);
		}
	}
	return (ConfigurableApplicationContext) BeanUtils.instantiateClass(contextClass);
}
public AnnotationConfigServletWebServerApplicationContext(DefaultListableBeanFactory beanFactory) {
	   super(beanFactory);
       //1:會(huì)去注入一些spring核心組件
	   this.reader = new AnnotatedBeanDefinitionReader(this);
	   this.scanner = new ClassPathBeanDefinitionScanner(this);

}

Web類型項(xiàng)目創(chuàng)建上下文對(duì)象AnnotationConfigServletWebServerApplicationContext 。這里會(huì)把 ConfigurationClassPostProcessor 、AutowiredAnnotationBeanPostProcessor 等一些核心組件加入到Spring容器。

五、refreshContext

下面一起來看下refreshContext(context) 這個(gè)方法,這個(gè)方法啟動(dòng)spring的代碼加載了bean,還啟動(dòng)了內(nèi)置web容器。

private void refreshContext(ConfigurableApplicationContext context) {
        // 轉(zhuǎn)到定義看看
		refresh(context);
		if (this.registerShutdownHook) {
			try {
				context.registerShutdownHook();
			}
			catch (AccessControlException ex) {
				// Not allowed in some environments.
			}
		}
}
protected void refresh(ApplicationContext applicationContext) {
		Assert.isInstanceOf(AbstractApplicationContext.class, applicationContext);
        //看看refresh()方法去
		((AbstractApplicationContext) applicationContext).refresh();

}

轉(zhuǎn)到AbstractApplicationContext - >refresh()方法里面發(fā)現(xiàn)這是spring容器啟動(dòng)代碼。

/**
* 加載或刷新一個(gè)持久化的配置,可能是XML文件、屬性文件或關(guān)系數(shù)據(jù)庫模式。
* 由于這是一種啟動(dòng)方法,如果失敗,應(yīng)該銷毀已經(jīng)創(chuàng)建的單例,以避免懸空資源。
* 換句話說,在調(diào)用該方法之后,要么全部實(shí)例化,要么完全不實(shí)例化。
* @throws 如果bean工廠無法初始化,則拋出 BeansException 異常
* @throws 如果已經(jīng)初始化且不支持多次刷新,則會(huì)拋出 IllegalStateException 異常
*/
@Override
public void refresh() throws BeansException, IllegalStateException {
    //加載或刷新配置前的同步處理
    synchronized (this.startupShutdownMonitor) {
        // 為刷新而準(zhǔn)備此上下文
        prepareRefresh();

        // 告訴子類去刷新內(nèi)部bean工廠。
        ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

        // 準(zhǔn)備好bean工廠,以便在此上下文中使用。
        prepareBeanFactory(beanFactory);

        try {
            // 允許在上下文子類中對(duì)bean工廠進(jìn)行后置處理。
            postProcessBeanFactory(beanFactory);

            // 調(diào)用在上下文中注冊(cè)為bean的工廠處理器。
            invokeBeanFactoryPostProcessors(beanFactory);

            // 注冊(cè)攔截bean創(chuàng)建的bean處理器。
            registerBeanPostProcessors(beanFactory);

            // 初始化此上下文的 message resource 消息資源。
            initMessageSource();

            // 為這個(gè)上下文初始化事件多路廣播器。
            initApplicationEventMulticaster();

            // 初始化特定上下文子類中的其他特殊bean。
            onRefresh();

            // 注冊(cè)監(jiān)聽器(檢查監(jiān)聽器的bean并注冊(cè)它們)。
            registerListeners();

            // 實(shí)例化所有剩余的(非 lazy-init 懶初始化的)單例。
            finishBeanFactoryInitialization(beanFactory);

            // 最后一步: 發(fā)布相應(yīng)的事件。
            finishRefresh();
        }

        catch (BeansException ex) {
            if (logger.isWarnEnabled()) {
                logger.warn("Exception encountered during context initialization - " +
                            "cancelling refresh attempt: " + ex);
            }

            // 銷毀已經(jīng)創(chuàng)建的單例,以避免懸空資源。
            destroyBeans();

            // 重置 'active' 表示.
            cancelRefresh(ex);

            // 將異常傳播給調(diào)用者。
            throw ex;
        }

        finally {
            // 重置Spring內(nèi)核中的共用的緩存,因?yàn)槲覀兛赡茉僖膊恍枰獑卫齜ean的元數(shù)據(jù)了……
            resetCommonCaches();
        }
    }
}

標(biāo)題名稱:玩轉(zhuǎn)SpringBoot—啟動(dòng)源碼及外部化配置
文章地址:http://www.dlmjj.cn/article/djdidpc.html