全網最全SpringBoot幹貨知識總結(超詳細,Java零基礎教學視頻

程序員Ifni 2021-09-20 02:37:37 阅读数:916

最全 springboot java 零基

2.4、@Conditional

@Conditional注解錶示在滿足某種條件後才初始化一個bean或者啟用某些配置。

它一般用在由@Component、@Service、@Configuration等注解標識的類上面,或者由@Bean標記的方法上。如果一個@Configuration類標記了@Conditional,則該類中所有標識了@Bean的方法和@Import注解導入的相關類將遵從這些條件。

在Spring裏可以很方便的編寫你自己的條件類,所要做的就是實現Condition接口,並覆蓋它的matches()方法。

舉個例子,下面的簡單條件類錶示只有在Classpath裏存在JdbcTemplate類時才生效:


public?class?JdbcTemplateCondition?implements?Condition?{
[email protected]
?????public?boolean?matches(ConditionContext
?conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata)?{
?????????try?{
?????????conditionContext.getClassLoader().loadClass("org.springframework.jdbc.core.JdbcTemplate");
?????????????return?true;
?????????}
?catch?(ClassNotFoundException e) {
?????????????e.printStackTrace();
?????????}
?????????return?false;
?????}
?}

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.

當你用Java來聲明bean的時候,可以使用這個自定義條件類:


@Conditional(JdbcTemplateCondition.class)[email protected]?public MyService service() { ...... }

  • 1.
  • 2.
  • 3.

這個例子中只有當JdbcTemplateCondition類的條件成立時才會創建MyService這個bean。

也就是說MyService這bean的創建條件是classpath裏面包含JdbcTemplate,否則這個bean的聲明就會被忽略掉。

Spring Boot定義了很多有趣的條件,並把他們運用到了配置類上,這些配置類構成了Spring Boot的自動配置的基礎。

Spring Boot運用條件化配置的方法是:定義多個特殊的條件化注解,並將它們用到配置類上。

下面列出了Spring Boot提供的部分條件化注解:

全網最全SpringBoot幹貨知識總結(超詳細,Java零基礎教學視頻_後端

2.5、@ConfigurationProperties與@EnableConfigurationProperties

當某些屬性的值需要配置的時候,我們一般會在application.properties文件中新建配置項,然後在bean中使用@Value注解來獲取配置的值,比如下面配置數據源的代碼。


// jdbc config
?jdbc.mysql.url=jdbc:mysql://localhost:3306/sampledb
?jdbc.mysql.username=root
?jdbc.mysql.password=123456
?......
??// 配置數據源
[email protected]
?public?class?HikariDataSourceConfiguration {
[email protected]("jdbc.mysql.url")
?????public?String?url;
[email protected]("jdbc.mysql.username")
?????public?String?user;
[email protected]("jdbc.mysql.password")
?????public?String?password;
[email protected]
?????public?HikariDataSource dataSource() {
?????????HikariConfig hikariConfig =?new?HikariConfig();
?????????hikariConfig.setJdbcUrl(url);
?????????hikariConfig.setUsername(user);
?????????hikariConfig.setPassword(password);
?????????// 省略部分代碼
?????????return?new?HikariDataSource(hikariConfig);
?????}
?}

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.
  • 54.
  • 55.

使用@Value注解注入的屬性通常都比較簡單,如果同一個配置在多個地方使用,也存在不方便維護的問題(考慮下,如果有幾十個地方在使用某個配置,而現在你想改下名字,你改怎麼做?)

對於更為複雜的配置,Spring Boot提供了更優雅的實現方式,那就是@ConfigurationProperties注解。

我們可以通過下面的方式來改寫上面的代碼:


@Component
?// 還可以通過@PropertySource("classpath:jdbc.properties")來指定配置文件
[email protected]("jdbc.mysql")
?// 前綴=jdbc.mysql,會在配置文件中尋找jdbc.mysql.*的配置項
?pulic?class?JdbcConfig {
?????public?String?url;
?????public?String?username;
?????public?String?password;
?}
[email protected]
?public?class?HikariDataSourceConfiguration {
[email protected]
?????public?JdbcConfig config;
[email protected]
?????public?HikariDataSource dataSource() {
?????????HikariConfig hikariConfig =?new?HikariConfig();
?????????hikariConfig.setJdbcUrl(config.url);
?????????hikariConfig.setUsername(config.username);
?????????hikariConfig.setPassword(config.password);
?????????// 省略部分代碼
?????????return?new?HikariDataSource(hikariConfig);
???}
?}

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.

@ConfigurationProperties對於更為複雜的配置,處理起來也是得心應手,比如有如下配置文件:


#App
?app.menus[0].title=Home
?app.menus[0].name=Home
?app.menus[0].path=/
?app.menus[1].title=Login
?app.menus[1].name=Login
?app.menus[1].path=/login
??app.compiler.timeout=5
?app.compiler.output-folder=/temp/
??app.error=/error/

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.

可以定義如下配置類來接收這些屬性:


@Component
[email protected]("app")
?public?class?AppProperties {
??????public?String?error;
?????public?List<Menu> menus =?new?ArrayList<>();
?????public?Compiler compiler =?new?Compiler();
??????public?static?class?Menu {
?????????public?String?name;
?????????public?String?path;
?????????public?String?title;
?????}
??????public?static?class?Compiler {
?????????public?String?timeout;
?????????public?String?outputFolder;
?????}
?}

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.

@EnableConfigurationProperties注解錶示對@ConfigurationProperties的內嵌支持默認會將對應Properties Class作為bean注入的IOC容器中,即在相應的Properties類上不用加@Component注解。

三、削鐵如泥:SpringFactoriesLoader詳解

JVM提供了3種類加載器:

BootstrapClassLoader、ExtClassLoader、AppClassLoader分別加載Java核心類庫、擴展類庫以及應用的類路徑(CLASSPATH)下的類庫。

JVM通過雙親委派模型進行類的加載,我們也可以通過繼承java.lang.classloader實現自己的類加載器。

何為雙親委派模型?當一個類加載器收到類加載任務時,會先交給自己的父加載器去完成,因此最終加載任務都會傳遞到最頂層的BootstrapClassLoader,只有當父加載器無法完成加載任務時,才會嘗試自己來加載。

采用雙親委派模型的一個好處是保證使用不同類加載器最終得到的都是同一個對象,這樣就可以保證Java 核心庫的類型安全,比如,加載比特於rt.jar包中的java.lang.Object類,不管是哪個加載器加載這個類,最終都是委托給頂層的BootstrapClassLoader來加載的,這樣就可以保證任何的類加載器最終得到的都是同樣一個Object對象。

查看ClassLoader的源碼,對雙親委派模型會有更直觀的認識:


protected?Class<?> loadClass(String?name,?boolean?resolve) { ?????synchronized (getClassLoadingLock(name)) { ?????// 首先,檢查該類是否已經被加載,如果從JVM緩存中找到該類,則直接返回 ?????Class<?> c = findLoadedClass(name);
?????if?(c ==?null) {
?????????try?{
?????????????// 遵循雙親委派的模型,首先會通過遞歸從父加載器開始找,
?????????????// 直到父類加載器是BootstrapClassLoader為止
?????????????if?(parent !=?null) {
?????????????????c = parent.loadClass(name,?false);
?????????????}?else?{
?????????????????c = findBootstrapClassOrNull(name);
?????????????}
????????}?catch?(ClassNotFoundException e) {}
?????????if?(c ==?null) {
?????????????// 如果還找不到,嘗試通過findClass方法去尋找
?????????????// findClass是留給開發者自己實現的,也就是說
?????????????// 自定義類加載器時,重寫此方法即可
????????????c = findClass(name);
?????????}
?????}
?????if?(resolve) {
????????resolveClass(c);
?????}
?????return?c;
?????}
?}

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.
  • 54.
  • 55.

但雙親委派模型並不能解决所有的類加載器問題,比如,Java 提供了很多服務提供者接口(Service Provider Interface,SPI),允許第三方為這些接口提供實現。

常見的 SPI 有 JDBC、JNDI、JAXP 等,這些SPI的接口由核心類庫提供,卻由第三方實現這樣就存在一個問題:SPI 的接口是 Java 核心庫的一部分,是由BootstrapClassLoader加載的;SPI實現的Java類一般是由AppClassLoader來加載的。BootstrapClassLoader是無法找到 SPI 的實現類的,因為它只加載Java的核心庫。它也不能代理給AppClassLoader,因為它是最頂層的類加載器。也就是說,雙親委派模型並不能解决這個問題。

線程上下文類加載器(ContextClassLoader)正好解决了這個問題。

從名稱上看,可能會誤解為它是一種新的類加載器,實際上,它僅僅是Thread類的一個變量而已,可以通過setContextClassLoader(ClassLoader cl)和getContextClassLoader()來設置和獲取該對象。

如果不做任何的設置,Java應用的線程的上下文類加載器默認就是AppClassLoader。

在核心類庫使用SPI接口時,傳遞的類加載器使用線程上下文類加載器,就可以成功的加載到SPI實現的類。

全網最全SpringBoot幹貨知識總結(超詳細,Java零基礎教學視頻_程序員_02

線程上下文類加載器在很多SPI的實現中都會用到。但在JDBC中,你可能會看到一種更直接的實現方式,比如,JDBC驅動管理java.sql.Driver中的loadInitialDrivers()方法中

你可以直接看到JDK是如何加載驅動的:


for?(String?aDriver : driversList) {
?????try?{
?????????// 直接使用AppClassLoader
?????????Class.forName(aDriver,?true, ClassLoader.getSystemClassLoader());
?????}?catch?(Exception ex) {
?????????println("DriverManager.Initialize: load failed: "?+ ex);
??????}
???}

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.

其實講解線程上下文類加載器,最主要是讓大家在看到:


Thread.currentThread().getClassLoader()Thread.currentThread().getContextClassLoader()

  • 1.
  • 2.
  • 3.

時不會一臉懵逼.

這兩者除了在許多底層框架中取得的ClassLoader可能會有所不同外,其他大多數業務場景下都是一樣的,大家只要知道它是為了解决什麼問題而存在的即可。

類加載器除了加載class外,還有一個非常重要功能,就是加載資源,它可以從jar包中讀取任何資源文件,比如,ClassLoader.getResources(String name)方法就是用於讀取jar包中的資源文件,其代碼如下:


public Enumeration<URL>?getResources(String name) throws IOException {
?????Enumeration<URL>[] tmp = (Enumeration<URL>[]) new Enumeration<?>[2];
?????if?(parent?!=?null) {
?????????tmp[0] =?parent.getResources(name);
?????}?else?{
?????????tmp[0] = getBootstrapResources(name);
?????}
?????tmp[1] = findResources(name);
?????return?new?CompoundEnumeration<>(tmp);
?}

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.

是不是覺得有點眼熟,不錯,它的邏輯其實跟類加載的邏輯是一樣的,首先判斷父類加載器是否為空,不為空則委托父類加載器執行資源查找任務, 直到BootstrapClassLoader,最後才輪到自己查找。

而不同的類加載器負責掃描不同路徑下的jar包,就如同加載class一樣,最後會掃描所有的jar包,找到符合條件的資源文件。

類加載器的findResources(name)方法會遍曆其負責加載的所有jar包,找到jar包中名稱為name的資源文件,這裏的資源可以是任何文件,甚至是.class文件,比如下面的示例,用於查找Array.class文件:


// 尋找Array.class文件
?public?static?void?main(String[] args) throws Exception{
?????// Array.class的完整路徑
?????String name =?"java/sql/Array.class";
?????Enumeration<URL> urls = Thread.currentThread().getContextClassLoader().getResources(name);
?????while?(urls.hasMoreElements()) {
?????????URL url = urls.nextElement();
?????????System.out.println(url.toString());
?????}
?}

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.

運行後可以得到如下結果:


$JAVA_HOME/jre/lib/rt.jar!/java/sql/Array.class

  • 1.
  • 2.
  • 3.

根據資源文件的URL,可以構造相應的文件來讀取資源內容。

看到這裏,你可能會感到挺奇怪的,你不是要詳解SpringFactoriesLoader嗎?

上來講了一ClassLoader是幾個意思?看下它的源碼你就知道了:


public?static?final?String?FACTORIES_RESOURCE_LOCATION =?"META-INF/spring.factories";
?// spring.factories文件的格式為:key=value1,value2,value3
?// 從所有的jar包中找到META-INF/spring.factories文件
?// 然後從文件中解析出key=factoryClass類名稱的所有value值
?public?static?List<String> loadFactoryNames(Class<?> factoryClass, ClassLoader classLoader) {
?????String?factoryClassName = factoryClass.getName();
?????// 取得資源文件的URL
?????Enumeration<URL> urls = (classLoader !=?null?? classLoader.getResources(FACTORIES_RESOURCE_LOCATION) : ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
?????List<String> result =?new?ArrayList<String>();
?????// 遍曆所有的URL
?????while?(urls.hasMoreElements()) {
?????????URL url = urls.nextElement();
?????????// 根據資源文件URL解析properties文件
?????????Properties properties = PropertiesLoaderUtils.loadProperties(new?UrlResource(url));
?????????String?factoryClassNames = properties.getProperty(factoryClassName);
?????????// 組裝數據,並返回
?????????result.addAll(Arrays.asList(StringUtils.commaDelimitedListToStringArray(factoryClassNames)));
?????}
?????return?result;
?}

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.

有了前面關於ClassLoader的知識,再來理解這段代碼,是不是感覺豁然開朗:

從CLASSPATH下的每個Jar包中搜尋所有META-INF/spring.factories配置文件,然後將解析properties文件,找到指定名稱的配置後返回。

需要注意的是,其實這裏不僅僅是會去ClassPath路徑下查找,會掃描所有路徑下的Jar包,只不過這個文件只會在Classpath下的jar包中。

來簡單看下spring.factories文件的內容吧:


// 來自 org.springframework.boot.autoconfigure下的META-INF/spring.factories
?// EnableAutoConfiguration後文會講到,它用於開啟Spring Boot自動配置功能
?org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
?org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
?org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
?org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration\

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.

執行loadFactoryNames(EnableAutoConfiguration.class, classLoader)後,得到對應的一組@Configuration類,我們就可以通過反射實例化這些類然後注入到IOC容器中,最後容器裏就有了一系列標注了@Configuration的JavaConfig形式的配置類。

這就是SpringFactoriesLoader,它本質上屬於Spring框架私有的一種擴展方案,類似於SPI,Spring Boot在Spring基礎上的很多核心功能都是基於此,希望大家可以理解。

四、另一件武器:Spring容器的事件監聽機制

過去,事件監聽機制多用於圖形界面編程,比如:點擊按鈕、在文本框輸入內容等操作被稱為事件,而當事件觸發時,應用程序作出一定的響應則錶示應用監聽了這個事件,而在服務器端,事件的監聽機制更多的用於异步通知以及監控和异常處理。

Java提供了實現事件監聽機制的兩個基礎類:自定義事件類型擴展自java.util.EventObject、事件的監聽器擴展自java.util.EventListener。

來看一個簡單的實例:簡單的監控一個方法的耗時。

首先定義事件類型,通常的做法是擴展EventObject,隨著事件的發生,相應的狀態通常都封裝在此類中:


public?class?MethodMonitorEvent?extends?EventObject?{
?????// 時間戳,用於記錄方法開始執行的時間
?????public?long?timestamp;
??????public?MethodMonitorEvent(Object source)?{
?????????super(source);
?????}
?}

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.

事件發布之後,相應的監聽器即可對該類型的事件進行處理,我們可以在方法開始執行之前發布一個begin事件.

在方法執行結束之後發布一個end事件,相應地,事件監聽器需要提供方法對這兩種情况下接收到的事件進行處理:


// 1、定義事件監聽接口
?public?interface?MethodMonitorEventListener?extends?EventListener?{
?????// 處理方法執行之前發布的事件
?????public?void?onMethodBegin(MethodMonitorEvent?event);
?????// 處理方法結束時發布的事件
?????public?void?onMethodEnd(MethodMonitorEvent?event);
?}
?// 2、事件監聽接口的實現:如何處理
?public?class?AbstractMethodMonitorEventListener?implements?MethodMonitorEventListener?{
[email protected]
?????public?void?onMethodBegin(MethodMonitorEvent?event)?{
?????????// 記錄方法開始執行時的時間
?????????event.timestamp = System.currentTimeMillis();
?????}
[email protected]
?????public?void?onMethodEnd(MethodMonitorEvent?event)?{
?????????// 計算方法耗時
?????????long?duration = System.currentTimeMillis() -?event.timestamp;
?????????System.out.println("耗時:"?+ duration);
?????}
?}

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.

事件監聽器接口針對不同的事件發布實際提供相應的處理方法定義,最重要的是,其方法只接收MethodMonitorEvent參數,說明這個監聽器類只負責監聽器對應的事件並進行處理。

有了事件和監聽器,剩下的就是發布事件,然後讓相應的監聽器監聽並處理。

通常情况,我們會有一個事件發布者,它本身作為事件源,在合適的時機,將相應的事件發布給對應的事件監聽器:


public?class?MethodMonitorEventPublisher?{
??????private?List<MethodMonitorEventListener> listeners =?new?ArrayList<MethodMonitorEventListener>();
??????public?void?methodMonitor()?{
?????????MethodMonitorEvent eventObject =?new?MethodMonitorEvent(this);
?????????publishEvent("begin",eventObject);
?????????// 模擬方法執行:休眠5秒鐘
?????????TimeUnit.SECONDS.sleep(5);
?????????publishEvent("end",eventObject);
??????}
??????private?void?publishEvent(String status,MethodMonitorEvent?event)?{
?????????// 避免在事件處理期間,監聽器被移除,這裏為了安全做一個複制操作
?????????List<MethodMonitorEventListener> copyListeners = ??new?ArrayList<MethodMonitorEventListener>(listeners);
?????????for?(MethodMonitorEventListener listener : copyListeners) {
?????????????if?("begin".equals(status)) {
?????????????????listener.onMethodBegin(event);
?????????????}?else?{
?????????????????listener.onMethodEnd(event);
?????????????}
?????????}
?????}
?????????public?static?void?main(String[] args)?{
?????????MethodMonitorEventPublisher publisher =?new?MethodMonitorEventPublisher();
?????????publisher.addEventListener(new?AbstractMethodMonitorEventListener());
?????????publisher.methodMonitor();
?????}
?????// 省略實現
?????public?void?addEventListener(MethodMonitorEventListener listener)?{}
?????public?void?removeEventListener(MethodMonitorEventListener listener)?{}
?????public?void?removeAllListeners()?{}

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.
  • 54.
  • 55.
  • 56.
  • 57.
  • 58.
  • 59.
  • 60.
  • 61.
  • 62.
  • 63.
  • 64.
  • 65.

對於事件發布者(事件源)通常需要關注兩點:

1. 在合適的時機發布事件。此例中的methodMonitor()方法是事件發布的源頭,其在方法執行之前和結束之後兩個時間點發布MethodMonitorEvent事件,每個時間點發布的事件都會傳給相應的監聽器進行處理。

在具體實現時需要注意的是,事件發布是順序執行,為了不影響處理性能,事件監聽器的處理邏輯應盡量簡單。

2. 事件監聽器的管理。publisher類中提供了事件監聽器的注册與移除方法,這樣客戶端可以根據實際情况决定是否需要注册新的監聽器或者移除某個監聽器。

如果這裏沒有提供remove方法,那麼注册的監聽器示例將一直MethodMonitorEventPublisher引用,即使已經廢弃不用了,也依然在發布者的監聽器列錶中,這會導致隱性的內存泄漏。

Spring容器內的事件監聽機制

Spring的ApplicationContext容器內部中的所有事件類型均繼承自org.springframework.context.AppliationEvent,容器中的所有監聽器都實現org.springframework.context.ApplicationListener接口,並且以bean的形式注册在容器中。

一旦在容器內發布ApplicationEvent及其子類型的事件,注册到容器的ApplicationListener就會對這些事件進行處理。

你應該已經猜到是怎麼回事了。

ApplicationEvent繼承自EventObject,Spring提供了一些默認的實現,比如:

ContextClosedEvent錶示容器在即將關閉時發布的事件類型,ContextRefreshedEvent

錶示容器在初始化或者刷新的時候發布的事件類型…容器內部使用ApplicationListener作為事件監聽器接口定義,它繼承自EventListener。

ApplicationContext容器在啟動時,會自動識別並加載EventListener類型的bean一旦容器內有事件發布,將通知這些注册到容器的EventListener。

ApplicationContext接口繼承了ApplicationEventPublisher接口,該接口提供了void publishEvent(ApplicationEvent event)方法定義,不難看出,ApplicationContext容器擔當的就是事件發布者的角色。

如果有興趣可以查看AbstractApplicationContext.publishEvent(ApplicationEvent event)方法的源碼:ApplicationContext將事件的發布以及監聽器的管理工作委托給 ApplicationEventMulticaster接口的實現類。

在容器啟動時,會檢查容器內是否存在名為applicationEventMulticaster的 ApplicationEventMulticaster對象實例。

如果有就使用其提供的實現,沒有就默認初始化一個SimpleApplicationEventMulticaster作為實現。

最後,如果我們業務需要在容器內部發布事件,只需要為其注入ApplicationEventPublisher 依賴即可:實現ApplicationEventPublisherAware接口或者ApplicationContextAware接口.

五、出神入化:揭秘自動配置原理

典型的Spring Boot應用的啟動類一般均比特於src/main/java根路徑下

比如MoonApplication類:


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

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.

其中@SpringBootApplication開啟組件掃描和自動配置,而SpringApplication.run則負責啟動引導應用程序。

@SpringBootApplication是一個複合Annotation,它將三個有用的注解組合在一起:


@Target(ElementType.TYPE)
[email protected](RetentionPolicy.RUNTIME)
@[email protected]
[email protected]
[email protected]
[email protected](excludeFilters = {
[email protected](type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
[email protected](type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
[email protected]?SpringBootApplication {
?????// ......
?}

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.

@SpringBootConfiguration就是@Configuration,它是Spring框架的注解,標明該類是一個JavaConfig配置類。

而@ComponentScan啟用組件掃描,前文已經詳細講解過,這裏著重關注@EnableAutoConfiguration。@EnableAutoConfiguration注解錶示開啟Spring Boot自動配置功能,Spring Boot會根據應用的依賴、自定義的bean、classpath下有沒有某個類 等等因素來猜測你需要的bean,

然後注册到IOC容器中。

那@EnableAutoConfiguration是如何推算出你的需求?

首先看下它的定義:


@Target(ElementType.TYPE)
[email protected](RetentionPolicy.RUNTIME)
[email protected]
[email protected]
[email protected]
[email protected](EnableAutoConfigurationImportSelector.class)
[email protected]?EnableAutoConfiguration {
?????// ......
?}

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.

你的關注點應該在@Import(EnableAutoConfigurationImportSelector.class)上了,前文說過,@Import注解用於導入類,並將這個類作為一個bean的定義注册到容器中,這裏將把EnableAutoConfigurationImportSelector作為bean注入到容器中,而這個類會將所有符合條件的@Configuration配置都加載到容器中,看看它的代碼:


public?String[] selectImports(AnnotationMetadata annotationMetadata) {
?????// 省略了大部分代碼,保留一句核心代碼
?????// 注意:SpringBoot最近版本中,這句代碼被封裝在一個單獨的方法中
?????// SpringFactoriesLoader相關知識請參考前文
?????List<String> factories =?new?ArrayList<String>(new?LinkedHashSet<String>(
???????????SpringFactoriesLoader.loadFactoryNames(EnableAutoConfiguration.class,?this.beanClassLoader)));
?}

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.

這個類會掃描所有的jar包,將所有符合條件的@Configuration配置類注入的容器中何為符合條件,看看META-INF/spring.factories的文件內容:


// 來自 org.springframework.boot.autoconfigure下的META-INF/spring.factories
?// 配置的key = EnableAutoConfiguration,與代碼中一致
?org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
?org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration,\
?org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
?org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration\
?.....

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.

以DataSourceAutoConfiguration為例,看看Spring Boot是如何自動配置的:


@Configuration
[email protected]({ DataSource.class, EmbeddedDatabaseType.class })
[email protected](DataSourceProperties.class)
[email protected]({ Registrar.class, DataSourcePoolMetadataProvidersConfiguration.class })
?public class DataSourceAutoConfiguration {
?}

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.

分別說一說:

@ConditionalOnClass({ DataSource.class, EmbeddedDatabaseType.class }):當Classpath中存在DataSource或者EmbeddedDatabaseType類時才啟用這個配置,否則這個配置將被忽略。

@EnableConfigurationProperties(DataSourceProperties.class):將DataSource的默認配置類注入到IOC容器中,DataSourceproperties定義為:


// 提供對datasource配置信息的支持,所有的配置前綴為:spring.datasource
[email protected](prefix =?"spring.datasource")
?public?class?DataSourceProperties {
?????private?ClassLoader classLoader;
?????private?Environment environment;
# 完結
Redis**基於內存**,常用作於**緩存**的一種技術,並且Redis存儲的方式是以key-value的形式。Redis是如今互聯網技術架構中,使用最廣泛的緩存,在工作中常常會使用到。Redis也是中高級後端工程師技術面試中,面試官最喜歡問的問題之一,因此作為Java開發者,Redis是我們必須要掌握的。
Redis 是 NoSQL 數據庫領域的佼佼者,如果你需要了解 Redis 是如何實現高並發、海量數據存儲的,那麼這份騰訊專家手敲《Redis源碼日志筆記》將會是你的最佳選擇。
![全網最全SpringBoot幹貨知識總結(超詳細,Java零基礎教學視頻_Java_03](https://s2.51cto.com/images/20210920/1632076030424986.jpg)
**[CodeChina開源項目:【一線大廠Java面試題解析+核心總結學習筆記+最新講解視頻】](https://ali1024.coding.net/public/P7/Java/git)**
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
版权声明:本文为[程序員Ifni]所创,转载请带上原文链接,感谢。 https://gsmany.com/2021/09/20210920023736911D.html