日韩成人免费在线_国产成人一二_精品国产免费人成电影在线观..._日本一区二区三区久久久久久久久不

當前位置:首頁 > 科技  > 軟件

玩轉SpringBoot—啟動源碼及外部化配置

來源: 責編: 時間:2023-10-08 07:05:34 278觀看
導讀學習目標理解springboot的總體啟動流程,并能口述大概理清配置文件的加載流程第1章 main入口public static void main(String[] args) { //代碼很簡單SpringApplication.run(); SpringApplication.run(ConsumerApp.c

lZL28資訊網——每日最新資訊28at.com

學習目標

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

第1章 main入口

lZL28資訊網——每日最新資訊28at.com

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

上面代碼主要做了兩件事情。lZL28資訊網——每日最新資訊28at.com

  • 第一步new了一個SpringApplication對象
  • 第二步調用了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、判斷運行項目的類型		this.webApplicationType = WebApplicationType.deduceFromClasspath();		//3、掃描當前路徑下META-INF/spring.factories文件的,加載ApplicationContextInitializer接口實例		setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));		//4、掃描當前路徑下META-INF/spring.factories文件的,加載ApplicationListener接口實例		setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));		this.mainApplicationClass = deduceMainApplicationClass();}

1、判斷運行環境

構造方法內會調用枚舉WebApplicationType的deduceFromClasspath方法獲得應用類型并設置當前應用是普通web應用、響應式web應用還是非web應用。lZL28資訊網——每日最新資訊28at.com

this.webApplicationType = WebApplicationType.deduceFromClasspath();

deduceFromClasspath方法由枚舉WebApplicationType提供,具體實現如下:lZL28資訊網——每日最新資訊28at.com

static WebApplicationType deduceFromClasspath() {    //當classpath下只存在org.springframework.web.reactive.DispatcherHandler,    //且不存在org.springframework.web.servlet.DispatcherServlet,也不存在    //org.glassfish.jersey.servlet.ServletContainer則運行環境為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;}

推斷的過程中重點調用了ClassUtils.isPresent()方法,用來判斷指定類名的類是否存在,是否可以進行加載。ClassUtils.isPresent()方法源代碼如下:lZL28資訊網——每日最新資訊28at.com

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()方法調用了forName()方法,如果在調用forName()方法的過程中出現異常則返回false,也就是目標類不存在。否則,返回true。lZL28資訊網——每日最新資訊28at.com

看一下forName()方法的部分代碼:lZL28資訊網——每日最新資訊28at.com

public static Class<?> forName(String name, @Nullable ClassLoader classLoader)			throws ClassNotFoundException, LinkageError {	// 此處省略一些非空和基礎類型的判斷邏輯代碼	ClassLoader clToUse = classLoader;	if (clToUse == null) {	    //如果為空則獲取默認classLoader		clToUse = getDefaultClassLoader();	}	try {	    // 返回加載戶的Class。		return Class.forName(name, false, clToUse);	} catch (ClassNotFoundException ex) {	    // 如果直接加載類出現異常,則嘗試加載內部類。		int lastDotIndex = name.lastIndexOf(PACKAGE_SEPARATOR);		if (lastDotIndex != -1) {		    // 拼接內部類			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()方法主要做的事情就是獲得類加載器,嘗試直接加載類,如果失敗則嘗試加載該類的內部類,如果依舊失敗,則拋出異常。lZL28資訊網——每日最新資訊28at.com

因此,整個應用類型的推斷分以下步驟:lZL28資訊網——每日最新資訊28at.com

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

在類型推斷的過程中枚舉類WebApplicationType定義了具體去加載哪些類:lZL28資訊網——每日最新資訊28at.com

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

2、初始化器和監聽器

利用SPI機制掃描 META-INF/spring.factories 這個文件,并且加載ApplicationContextInitializer、ApplicationListener 接口實例。lZL28資訊網——每日最新資訊28at.com

  • ApplicationContextInitializer 這個類當springboot上下文Context初始化完成后會調用。
  • ApplicationListener 當springboot啟動時事件change后都會觸發。

總結:上面就是SpringApplication初始化的代碼,new SpringApplication()沒做啥事情 ,利用SPI機制主要加載了META-INF/spring.factories 下面定義的事件監聽器接口實現類。lZL28資訊網——每日最新資訊28at.com

二、執行run方法

public ConfigurableApplicationContext run(String... args) {    <!--1、這個是一個計時器,沒什么好說的-->    StopWatch stopWatch = new StopWatch();    stopWatch.start();    DefaultBootstrapContext bootstrapContext = createBootstrapContext();    ConfigurableApplicationContext context = null;        <!--2、這個也不是重點,就是設置了一些環境變量-->    configureHeadlessProperty();     <!--3、獲取事件監聽器SpringApplicationRunListener類型,并且執行starting()方法-->    SpringApplicationRunListeners listeners = getRunListeners(args);    listeners.starting(bootstrapContext, this.mainApplicationClass);    try {        <!--4、把參數args封裝成DefaultApplicationArguments,這個了解一下就知道-->        ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);        <!--5、這個很重要準備環境了,并且把環境跟spring上下文綁定好,并且執行environmentPrepared()方法-->            //準備容器環境、這里會加載配置文件。在這個方法里面會調用所有監聽器Listener的onApplicationEvent(event);            // 此時有一個與配置文件相關的監聽器就會被加載`ConfigFileApplicationListener`        ConfigurableEnvironment environment = prepareEnvironment(listeners,applicationArguments);        <!--6、判斷一些環境的值,并設置一些環境的值-->        configureIgnoreBeanInfo(environment);        <!--7、打印banner-->        Banner printedBanner = printBanner(environment);        <!--8、創建上下文,根據項目類型創建上下文-->        context = createApplicationContext();		context.setApplicationStartup(this.applicationStartup);                <!--9、準備上下文,執行完成后調用contextPrepared()方法,contextLoaded()方法-->        prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);        <!--10、這個是spring啟動的代碼了,這里就回去里面就回去掃描并且初始化單實列bean了-->        //這個refreshContext()加載了bean,還啟動了內置web容器,需要細細的去看看        refreshContext(context);        <!--11、啥事情都沒有做-->        afterRefresh(context, applicationArguments);        stopWatch.stop();        if (this.logStartupInfo) {            new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);        }        <!--12、執行ApplicationRunListeners中的started()方法-->        listeners.started(context);        <!--執行Runner(ApplicationRunner和CommandLineRunner)-->        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章 環境變量及配置

一、prepareEnvironment

private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,                  DefaultBootstrapContext bootstrapContext, ApplicationArguments applicationArguments) {    // 創建和配置環境變量    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

/***  該方法根據webApplicationType判斷當前項目是什么類型項目*/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()進行賦值

枚舉WebApplicationType中定義了三個應用類型:lZL28資訊網——每日最新資訊28at.com

  • NONE:應用程序不作為web應用啟動,不啟動內嵌的服務。
  • SERVLET:應用程序以基于servlet的web應用啟動,需啟動內嵌servlet web服務。
  • REACTIVE:應用程序以響應式web應用啟動,需啟動內嵌的響應式web服務。

這里調用newStandardServletEnvironment()方法。lZL28資訊網——每日最新資訊28at.com

StandardServletEnvironment繼承了StandardEnvironment方法,StandardEnvironment又繼承了AbstractEnvironment方法;在AbstractEnvironment方法中調用了customizePropertySources方法。lZL28資訊網——每日最新資訊28at.com

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

customizePropertySources方法會回調StandardServletEnvironment方法中的customizePropertySources方法。lZL28資訊網——每日最新資訊28at.com

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));    }    //在這里又調用了父類StandardEnvironment的方法    super.customizePropertySources(propertySources);}

到這里為止propertySources里面就加載了servletConfigInitParams、servletContextInitParams、systemProperties、systemEnvironment。lZL28資訊網——每日最新資訊28at.com

然后回到prepareEnvironment方法中,在listeners.environmentPrepared(bootstrapContext, environment);方法中去進行監聽。lZL28資訊網——每日最新資訊28at.com

三、environmentPrepared

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

繼續進入environmentPrepared方法,會進入到SpringApplicationRunListener接口,這個接口在run方法中的getRunListeners里面獲取,最終是在sprin.factories里面進行加載實現類EventPublishingRunListener,執行的是EventPublishingRunListener類中的environmentPrepared方法。lZL28資訊網——每日最新資訊28at.com

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;        }    }}

進入ConfigFileApplicationListener實現類中的onApplicationEvent方法。lZL28資訊網——每日最新資訊28at.com

onApplicationEvent

public void onApplicationEvent(ApplicationEvent event) {    if (event instanceof ApplicationEnvironmentPreparedEvent) {        //在這個方法里面讀取配置文件        onApplicationEnvironmentPreparedEvent((ApplicationEnvironmentPreparedEvent) event);    }    if (event instanceof ApplicationPreparedEvent) {        onApplicationPreparedEvent(event);    }}
private void onApplicationEnvironmentPreparedEvent(ApplicationEnvironmentPreparedEvent event) {    List<EnvironmentPostProcessor> postProcessors = loadPostProcessors();    postProcessors.add(this);    AnnotationAwareOrderComparator.sort(postProcessors);    for (EnvironmentPostProcessor postProcessor : postProcessors) {        //進入        postProcessor.postProcessEnvironment(event.getEnvironment(), event.getSpringApplication());    }}
public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {    //進入    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(); 這段代碼,這段代碼主要是根據項目類型創建上下文,并且會注入幾個核心組件類。lZL28資訊網——每日最新資訊28at.com

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:會去注入一些spring核心組件	   this.reader = new AnnotatedBeanDefinitionReader(this);	   this.scanner = new ClassPathBeanDefinitionScanner(this);}

Web類型項目創建上下文對象AnnotationConfigServletWebServerApplicationContext 。這里會把 ConfigurationClassPostProcessor 、AutowiredAnnotationBeanPostProcessor 等一些核心組件加入到Spring容器。lZL28資訊網——每日最新資訊28at.com

五、refreshContext

下面一起來看下refreshContext(context) 這個方法,這個方法啟動spring的代碼加載了bean,還啟動了內置web容器。lZL28資訊網——每日最新資訊28at.com

private void refreshContext(ConfigurableApplicationContext context) {        // 轉到定義看看		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();}

轉到AbstractApplicationContext - >refresh()方法里面發現這是spring容器啟動代碼。lZL28資訊網——每日最新資訊28at.com

/*** 加載或刷新一個持久化的配置,可能是XML文件、屬性文件或關系數據庫模式。* 由于這是一種啟動方法,如果失敗,應該銷毀已經創建的單例,以避免懸空資源。* 換句話說,在調用該方法之后,要么全部實例化,要么完全不實例化。* @throws 如果bean工廠無法初始化,則拋出 BeansException 異常* @throws 如果已經初始化且不支持多次刷新,則會拋出 IllegalStateException 異常*/@Overridepublic void refresh() throws BeansException, IllegalStateException {    //加載或刷新配置前的同步處理    synchronized (this.startupShutdownMonitor) {        // 為刷新而準備此上下文        prepareRefresh();        // 告訴子類去刷新內部bean工廠。        ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();        // 準備好bean工廠,以便在此上下文中使用。        prepareBeanFactory(beanFactory);        try {            // 允許在上下文子類中對bean工廠進行后置處理。            postProcessBeanFactory(beanFactory);            // 調用在上下文中注冊為bean的工廠處理器。            invokeBeanFactoryPostProcessors(beanFactory);            // 注冊攔截bean創建的bean處理器。            registerBeanPostProcessors(beanFactory);            // 初始化此上下文的 message resource 消息資源。            initMessageSource();            // 為這個上下文初始化事件多路廣播器。            initApplicationEventMulticaster();            // 初始化特定上下文子類中的其他特殊bean。            onRefresh();            // 注冊監聽器(檢查監聽器的bean并注冊它們)。            registerListeners();            // 實例化所有剩余的(非 lazy-init 懶初始化的)單例。            finishBeanFactoryInitialization(beanFactory);            // 最后一步: 發布相應的事件。            finishRefresh();        }        catch (BeansException ex) {            if (logger.isWarnEnabled()) {                logger.warn("Exception encountered during context initialization - " +                            "cancelling refresh attempt: " + ex);            }            // 銷毀已經創建的單例,以避免懸空資源。            destroyBeans();            // 重置 'active' 表示.            cancelRefresh(ex);            // 將異常傳播給調用者。            throw ex;        }        finally {            // 重置Spring內核中的共用的緩存,因為我們可能再也不需要單例bean的元數據了……            resetCommonCaches();        }    }}

本文鏈接:http://www.www897cc.com/showinfo-26-12316-0.html玩轉SpringBoot—啟動源碼及外部化配置

聲明:本網頁內容旨在傳播知識,若有侵權等問題請及時與本網聯系,我們將在第一時間刪除處理。郵件:2376512515@qq.com

上一篇: 實用!Python數據可視化與圖表繪制:讓數據一目了然

下一篇: Golang 中的 bufio 包詳解之Bufio.Writer

標簽:
  • 熱門焦點
Top 主站蜘蛛池模板: 宜城市| 盐城市| 侯马市| 淄博市| 石阡县| 临沧市| 大安市| 海林市| 辽宁省| 庆阳市| 五指山市| 辰溪县| 荆州市| 永安市| 城步| 琼海市| 慈溪市| 漳平市| 鄯善县| 临朐县| 原平市| 舞阳县| 扶风县| 新巴尔虎右旗| 朝阳市| 瓦房店市| 长丰县| 贵阳市| 贡嘎县| 孟村| 逊克县| 珲春市| 龙胜| 原阳县| 黑龙江省| 澄江县| 遵义市| 东丽区| 神池县| 汝南县| 滦南县|