Spring Boot 20天入門(day7)

一羣熱愛技術並且嚮往優秀的程序猿同學,不喜歡水文,不喜歡販賣焦慮,只喜歡談技術,分享的都是技術乾貨。Talk is cheap. Show me the code
在這裏插入圖片描述

Springboot啓動配置原理

啓動類註解@SpringbootApplication

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
		@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {

前面4個都是java的元註解,沒啥好看的,重要的是後面幾個:

1)、@SpringBootConfiguration

2)、@EnableAutoConfiguration

3)、@ComponentScan

@SpringBootConfiguration
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
public @interface SpringBootConfiguration {}

這個註解其實沒啥特別的,個人認爲就是用來做語義化的註解,裏面還是用的@Configuration,標註這是一個配置類

@EnableAutoConfiguration

開啓Springboot的自動配置,這個在以前的文章講過:

https://blog.csdn.net/WXZCYQ/article/details/106167302

@ComponentScan
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Repeatable(ComponentScans.class)
public @interface ComponentScan {

容器組件掃描的註解。用於掃描所有的容器組件,是最核心的註解之一。

SpringbootApplication.run()

我們都知道,Springboot的啓動類(標註了@SpringBootApplication註解和擁有main()),會執行一個叫

SpringApplication.run()的方法,來啓動Springboot應用。

讓我們來debug,跟一下Springboot到底是如何啓動應用的。

run()的截圖:

該方法會傳遞一個啓動類的class對象,以及啓動類參數,然後執行另外一個run()

在這裏插入圖片描述

點擊step into,將會看到新建一個SpringApplication, 並將配置類傳遞進去:

public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
		return new SpringApplication(primarySources).run(args);
}

繼續step into,查看SpringbootApplication的構造方法

public SpringApplication(Class<?>... primarySources) {
		this(null, primarySources);
	}

	/**
	 * 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));
        // 獲得當前應用的類型 ,WebApplicationType 是個枚舉對象,NONE,SERVLET(servlet類型),REACTIVE(響應式)
		this.webApplicationType = WebApplicationType.deduceFromClasspath();
        // 從類路徑下的/META-INF/spring.factories中獲取所有的ApplicationContextInitializer並保存起來
		setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
         // 從類路徑下的/META-INF/spring.factories中獲取所有的ApplicationListener並保存起來
		setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
         // 尋找擁有main方法的啓動類
		this.mainApplicationClass = deduceMainApplicationClass();
	}

如何判斷是啓動類的呢?deduceMainApplicationClass()是這樣判斷的:

// 獲取所有的方法名,匹配是不是main方法
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;
	}

創建完SpringApplication後,來到我們的重頭戲,SpringApplication.run():

public ConfigurableApplicationContext run(String... args) {
    // 用於監控啓動時間的計時器
		StopWatch stopWatch = new StopWatch();
		stopWatch.start();
		ConfigurableApplicationContext context = null;
    // 支持報告關於啓動的錯誤對象的集合
		Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
    // java.awt.headless是J2SE的一種模式用於在缺少顯示屏、鍵盤或者鼠標時的系統配置,很多監控工具如jconsole 需要將該值設置爲true,系統變量默認爲true
		configureHeadlessProperty();
    //獲取並啓動監聽器
		SpringApplicationRunListeners listeners = getRunListeners(args);
		listeners.starting();
		try {
            // 獲取命令行參數
			ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
            // 配置容器環境(spring.profile.active)
			ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
            // 配置不需要初始化的bean對象,可以在配置文件中設置,前綴是spring.beaninfo.ignore
			configureIgnoreBeanInfo(environment);
            // 打印Banner,也就是我們看到的那個大大的Spring
			Banner printedBanner = printBanner(environment);
            // 創建容器的上下文對象(ApplicationContext)
			context = createApplicationContext();
            // 註冊支持報告關於啓動錯誤的對象
			exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
					new Class[] { ConfigurableApplicationContext.class }, context);
            // 將上文代碼中註冊的配置注入進上下文對象中
			prepareContext(context, environment, listeners, applicationArguments, printedBanner);
            // 刷新容器,清除之前的緩存,從配置文件中導入環境,組件,如果是Web應用,還會啓動web容器(Tomcat)
			refreshContext(context);
            // 這是個空方法,個人認爲應該是留着擴展,因爲傳遞的是命令行參數
			afterRefresh(context, applicationArguments);
            // 容器啓動完畢,關閉計時器,並在控制檯打印 : Root WebApplicationContext: initialization completed in 27695 ms
			stopWatch.stop();
			if (this.logStartupInfo) {
				new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
			}
            //回調所有 SpringApplicationRunListener實現類 的starting方法
			listeners.started(context);
            // 從容器中獲取所有ApplicationRunner,CommandLineRunner,並調用他們的run方法
			callRunners(context, applicationArguments);
		}
		catch (Throwable ex) {
			handleRunFailure(context, ex, exceptionReporters, listeners);
			throw new IllegalStateException(ex);
		}

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

prepareContext

private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment,
			SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) {
    	// 給容器設置環境(spring.profile.active)
		context.setEnvironment(environment);
		postProcessApplicationContext(context);
		applyInitializers(context);
		listeners.contextPrepared(context);
		if (this.logStartupInfo) {
			logStartupInfo(context.getParent() == null);
			logStartupProfileInfo(context);
		}
		// Add boot specific singleton beans
		ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
    	// 給容器註冊一些組件
		beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
		if (printedBanner != null) {
			beanFactory.registerSingleton("springBootBanner", printedBanner);
		}
		if (beanFactory instanceof DefaultListableBeanFactory) {
			((DefaultListableBeanFactory) beanFactory)
					.setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
		}
		if (this.lazyInitialization) {
			context.addBeanFactoryPostProcessor(new LazyInitializationBeanFactoryPostProcessor());
		}
		// Load the sources
		Set<Object> sources = getAllSources();
		Assert.notEmpty(sources, "Sources must not be empty");
		load(context, sources.toArray(new Object[0]));
		listeners.contextLoaded(context);
	}

refreshContext

調用另一個refresh方法,將上下文對象傳遞進去

private void refreshContext(ConfigurableApplicationContext context) {
		refresh((ApplicationContext) context);
		if (this.registerShutdownHook) {
			try {
				context.registerShutdownHook();
			}
			catch (AccessControlException ex) {
				// Not allowed in some environments.
			}
		}
	}

調用容器的refresh方法

// 該方法是一個接口方法,最終會調用一個叫AbstractApplicationContext的refresh方法
protected void refresh(ConfigurableApplicationContext applicationContext) {
		applicationContext.refresh();
	}

AbstractApplicationContextrefresh方法:

@Override
	public void refresh() throws BeansException, IllegalStateException {
		synchronized (this.startupShutdownMonitor) {
			// Prepare this context for refreshing.
            // 從容器中獲取當前應用環境,初始化Web容器環境
			prepareRefresh();

			// Tell the subclass to refresh the internal bean factory.
			ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

			// Prepare the bean factory for use in this context.
            // 爲容器註冊一些依賴
			prepareBeanFactory(beanFactory);

			try {
				// Allows post-processing of the bean factory in context subclasses.
                // 爲servletContext上下文對象註冊web應用的域對象和環境
				postProcessBeanFactory(beanFactory);

				// Invoke factory processors registered as beans in the context.
				invokeBeanFactoryPostProcessors(beanFactory);

				// Register bean processors that intercept bean creation.
				registerBeanPostProcessors(beanFactory);

				// Initialize message source for this context.
				initMessageSource();

				// Initialize event multicaster for this context.
				initApplicationEventMulticaster();

				// Initialize other special beans in specific context subclasses.
                // 最終會調用AbstractRefreshableWebApplicationContext的onRefresh方法
				onRefresh();

				// Check for listener beans and register them.
				registerListeners();

				// Instantiate all remaining (non-lazy-init) singletons.
				finishBeanFactoryInitialization(beanFactory);

				// Last step: publish corresponding event.
				finishRefresh();
			}

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

				// Destroy already created singletons to avoid dangling resources.
				destroyBeans();

				// Reset 'active' flag.
				cancelRefresh(ex);

				// Propagate exception to caller.
				throw ex;
			}

			finally {
				// Reset common introspection caches in Spring's core, since we
				// might not ever need metadata for singleton beans anymore...
				resetCommonCaches();
			}
		}
	}

自定義starter

starter:

​ 1、場景需要使用的依賴是什麼?

​ 2、如何編寫自動配置

@Configuration  // 指定是一個配置類
@ConditionOnxxx  // 載指定的條件成立的情況下配置生效
@AutoConfigureAfter // 指定自動配置類的順序
@Bean  //給容器中添加組件
@ConfigurationProperties //結合線管的xxxproperties類來綁定 
@EnableConfigurationProperties // 讓xxxProperties生效加入到容器中

自動配置類要能加載
將需要啓動就加載的自動配置類,配置在META-INF/spring.factories
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\

3、模式:

啓動器只用來做依賴導入:

例如spring-boot-starter-web:

在這裏插入圖片描述

啓動器依賴自動配置,打包成xxx-starter,使用時引入就行

編寫自定義starter

創建兩個模塊,一個只引入必要的自定義依賴,一個用來編寫自定義的核心代碼

在這裏插入圖片描述

weleness-spring-boot-starter-autoconfigurer的pom文件:

因爲這個模塊是寫核心代碼的,所以需要引入springboot的自動配置包

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.3.0.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.github.anhTom2000</groupId>
    <artifactId>weleness-spring-boot-starter-autoconfigurer</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>weleness-spring-boot-starter-autoconfigurer</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <java.version>11</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>

    </dependencies>



</project>

weleness-starter的pom文件:

這個模塊是被別人當成自定義starter引入的,不需要寫任何代碼,只需要將編寫核心功能代碼的依賴引入進來

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.githun.starter</groupId>
    <artifactId>weleness</artifactId>
    <version>1.0-SNAPSHOT</version>
<!-- 引入自動配置模塊-->
    <dependencies>
        <dependency>
            <groupId>com.github.anhTom2000</groupId>
            <artifactId>weleness-spring-boot-starter-autoconfigurer</artifactId>
            <version>0.0.1-SNAPSHOT</version>
        </dependency>
    </dependencies>

</project>

編寫配置文件類

編寫一個測試用的配置文件類,在配置文件中以weleness.hello進行配置

import org.springframework.boot.context.properties.ConfigurationProperties;

@ConfigurationProperties(prefix = "weleness.hello")
public class HelloProperties {

    private String prefix;

    private String sufffix;

    public String getPrefix() {
        return prefix;
    }

    public void setPrefix(String prefix) {
        this.prefix = prefix;
    }

    public String getSufffix() {
        return sufffix;
    }

    public void setSufffix(String sufffix) {
        this.sufffix = sufffix;
    }
}

編寫測試用服務

public class HelloService {
    HelloProperties helloProperties;

    public HelloProperties getHelloProperties() {
        return helloProperties;
    }

    public void setHelloProperties(HelloProperties helloProperties) {
        this.helloProperties = helloProperties;
    }

    public String sayHello(String name) {
        return helloProperties.getPrefix() + name + helloProperties.getSufffix();
    }
}

編寫自動配置類

瞭解springboot自動配置原理的同學就不必我多說了,懂的人自然懂。

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * @Description : TODO
 * @Author : Weleness
 * @Date : 2020/05/22
 */
@Configuration
@ConditionalOnWebApplication// web應用才生效
@EnableConfigurationProperties(HelloProperties.class)
public class HelloServiceAutoConfiguration {

    @Autowired
    HelloProperties helloProperties;

    @Bean
    public HelloService helloService(){
        HelloService helloService = new HelloService();
        helloService.setHelloProperties(helloProperties);
        return helloService;
    }
}

在類路徑下編寫Springboot的自動配置文件

在這裏插入圖片描述
這樣我們自定義的starter就創建好了,現在將他們install到maven倉庫中:

在這裏插入圖片描述

測試自定義starter

新建一個工程,引入剛剛install完畢的依賴

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.3.0.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.github</groupId>
    <artifactId>weleness-test-starter</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>weleness-test-starter</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!--引入自定義starter-->
        <dependency>
            <groupId>com.githun.starter</groupId>
            <artifactId>weleness</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

編寫controller測試

@RestController
public class HelloController {
    @Autowired
    HelloService helloService;

    @RequestMapping("/hello")
    public String hello(){
        return helloService.sayHello("weleness");
    }
}

編寫配置文件

server.port=8889
weleness.hello.prefix=nihao
weleness.hello.sufffix=woshi

以上…

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章