Autoconfiguration詳解——自動註入配置參數

来源:https://www.cnblogs.com/bloodcolding/archive/2023/04/03/17284491.html
-Advertisement-
Play Games

Autoconfiguration詳解——自動註入配置參數 一、 理解自動裝配bean 1. 常用註解 @AutoConfiguration(每個配置類都要加上) Class<?>[] after() default {}; Class<?>[] before() default {}; 以上兩個配 ...


目錄

Autoconfiguration詳解——自動註入配置參數

一、 理解自動裝配bean

1. 常用註解

  1. @AutoConfiguration(每個配置類都要加上)
    1. Class<?>[] after() default {};
    2. Class<?>[] before() default {};
    3. 以上兩個配置可以控制載入順序;
    4. 不需要再增加@Configuration註解;
  2. @AutoConfigureBefore and @AutoConfigureAfter
  3. @Configuration
  4. @Conditional(後面會詳細講到)
    1. @ConditianalOnClass
    2. @ConditionalOnMissingClass
    3. @ConditionalOnWebApplication:只在web應用中載入;
  5. @EnableConfigurationProperties:配置文件參數內容,參照類RedisProperties;
    1. @ConfigurationProperties(prefix = "spring.redis"),該註解展示了配置文件首碼;
  6. @DependsOn:列舉一些前置的註入bean,以備用,用在類上需要有 @Component自動掃描的時候才能生效;
    1. 實際上控制了bean載入的順序,優先載入指定的bean,然後載入當前bean;
    2. 銷毀的時候,註解的bean優先與於依賴的bean銷毀;

2. 定位自動裝配的候選類

springboot 框架會自動掃描 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports 進行引入,所以需要自動註入的Configuration文件都寫在這個文件中。每個class一行。

這裡本質上是一個自動版的@Import。

示例:

# comments
org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration
org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration
org.springframework.boot.autoconfigure.web.servlet.HttpEncodingAutoConfiguration

如果需要引入特定的Component,使用@Import註解。

3. 條件註解

在所有自動裝配類出現的地方,我們都因該時加上@Conditional註解,允許使用的開發人員覆蓋自動裝配的bean,當然他們也可以選擇什麼也不做,使用預設的配置。

Spring Boot 提供了一些條件註解,可以註解在@Configuration類或者@Bean方法上。

3.1 有關類的判斷

對於@Configuration類來說,@ConditionalOnClass@ConditionalOnMissingClass代表了在指定類存在或者不存在的時候進行載入。因為實際上註解的元數據使用ASM技術進行解析,所以可以使用value參數來指定特定的類的class對象(可以是多個),或者使用name參數來指定特定的類名(可以是多個),兩種方式所指向的類即使不存在也不影響正常執行。

@Bean方法返回值是條件註解的的目標之時,可能會因為JVM載入順序的問題導致載入失敗,上文提到的兩個註解可以用在@Bean方法上。

3.2 有關bean的判斷

@ConditionalOnBean@ConditionalOnMissingBean,代表在指定bean存在或者不存在時載入。value參數可以指定bean的class(多個),name可以指定bean的名稱(多個)。search參數允許你限制ApplicationContext即應用上下文的搜索範圍,可選當前上下文,繼承上層,或者是全部(預設)。

@Bean方法上使用時,預設參數為當前方法返回類型。

在使用@Bean註解時,建議使用具體類型而不是父類型進行指代。

3.3 配置條件

@ConditionalOnProperty,指定配置項文件(例如dev,pro),prefix屬性規定了配置首碼,name屬性指定了應該被檢查的參數。預設,所有存在且不等於false的參數都會被匹配到,你也可以使用havingValuematchIfMissing屬性闖將更多的校驗。

例子:@ConditionalOnProperty(name = "spring.redis.client-type", havingValue = "lettuce", matchIfMissing = true);

屬性名 類型 解析
name String[] name() default {}; 配置項全稱,如果有prefix,可以省略prefix中的首碼部分
prefix String prefix() default ""; 統一的配置項首碼
havingValue String havingValue() default ""; 配置項需要匹配的內容,如果沒有指定,那麼配置的值等於false時結果為false,否則結果都為true
matchIfMissing boolean matchIfMissing() default false; 配置項不存在時的配置,預設為false

3.4 源文件條件

@ConditionalOnResource,指定源文件存在時引入。

例如:@ConditionalOnResource(resources = {"classpath:test.log"});

3.5 web 應用條件

@ConditionalOnWebApplication@ConditionalOnNotWebApplication ,web應用或者不是web應用時啟用,以下部分只要滿足一個條件即為web 應用。

servlet-based web 應用特點:

  1. 使用 Spring WebApplicationContext;
  2. 定義了一個session作用域的bean;
  3. 有一個WebApplicationContext;

reactive web 應用特點:

  1. 使用了ReactiveWebApplicationContext;

  2. 有一個ConfigurableReactiveWebEnvironment;

ConditionalOnWarDeployment,僅限於使用war進行部署的場景,在嵌入式tomcat的場景里不會啟用;

3.6 Spel表單式條件

ConditionalOnWarDeployment ,使用Spel表達式返回結果進行判斷。

註意:在表達式中引用一個bean會導致這個bean非常早的被載入,此時還沒有進行預載入(例如配置項的綁定),可能會導致不完成的載入。

二、自動註入配置基礎

  1. @EnableConfigurationProperties(CommonRedisProperties.class) 註解configuration類;
  2. @ConfigurationProperties(prefix = "myserver")註解配置文件類,prefix標明配置文件的首碼;
  3. public RedisTemplate<String, Object> getRedisTemplate(CommonRedisProperties properties, RedisConnectionFactory redisConnectionFactory) ,加到需要使用的參數中即可;
  4. META-INF目錄下添加additional-spring-configuration-metadata.json文件,格式如下
{
  "groups": [
    {
      "name": "server",
      "type": "com.huawei.workbenchcommon.redis.CommonRedisProperties",
      "sourceType": "com.huawei.workbenchcommon.redis.CommonRedisProperties"
    }
  ],
  "properties": [
    {
      "name": "myserver.database",
      "type": "java.lang.String",
      "sourceType": "org.springframework.boot.autoconfigure.web.ServerProperties"
    }
  ]
}

三、註釋切麵 @Metrics

1. 註解@Metrics

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.TYPE})
public @interface Metrics {
    /**
     * 在方法成功執行後打點,記錄方法的執行時間發送到指標系統,預設開啟
     */
    boolean recordSuccessMetrics() default true;

    /**
     * 在方法成功失敗後打點,記錄方法的執行時間發送到指標系統,預設開啟
     */
    boolean recordFailMetrics() default true;

    /**
     * 通過日誌記錄請求參數,預設開啟
     */
    boolean logParameters() default true;

    /**
     * 通過日誌記錄方法返回值,預設開啟
     */
    boolean logReturn() default true;

    /**
     * 出現異常後通過日誌記錄異常信息,預設開啟
     */
    boolean logException() default true;

    /**
     * 出現異常後忽略異常返回預設值,預設關閉
     */
    boolean ignoreException() default false;

2. 切麵MetricsAspect

@Aspect
@Slf4j
@Order(Ordered.HIGHEST_PRECEDENCE)
public class MetricsAspect {
    /**
     * 讓Spring幫我們註入ObjectMapper,以方便通過JSON序列化來記錄方法入參和出參
     */
    @Resource
    private ObjectMapper objectMapper;

    /**
     * 實現一個返回Java基本類型預設值的工具。其實,你也可以逐一寫很多if-else判斷類型,然後手動設置其預設值。
     * 這裡為了減少代碼量用了一個小技巧,即通過初始化一個具有1個元素的數組,然後通過獲取這個數組的值來獲取基本類型預設值
     */
    private static final Map<Class<?>, Object> DEFAULT_VALUES = Stream
            .of(boolean.class, byte.class, char.class, double.class, float.class, int.class, long.class, short.class)
            .collect(toMap(clazz -> clazz, clazz -> Array.get(Array.newInstance(clazz, 1), 0)));

    public static <T> T getDefaultValue(Class<T> clazz) {
        //noinspection unchecked
        return (T) DEFAULT_VALUES.get(clazz);
    }

    /**
     * 標記了Metrics註解的方法進行匹配
     */
    @Pointcut("@annotation(com.common.config.metrics.annotation.Metrics)")
    public void withMetricsAnnotationMethod() {
    }

    /**
     * within指示器實現了匹配那些類型上標記了@RestController註解的方法
     * 註意這裡使用了@,標識了對註解標註的目標進行切入
     */
    @Pointcut("within(@org.springframework.web.bind.annotation.RestController *)")
    public void controllerBean() {
    }

    @Pointcut("@within(com.common.config.metrics.annotation.Metrics)")
    public void withMetricsAnnotationClass() {
    }

    @Around("controllerBean() || withMetricsAnnotationMethod() || withMetricsAnnotationClass()")
    public Object metrics(ProceedingJoinPoint pjp) throws Throwable {
        // 通過連接點獲取方法簽名和方法上Metrics註解,並根據方法簽名生成日誌中要輸出的方法定義描述
        MethodSignature signature = (MethodSignature) pjp.getSignature();
        Metrics metrics = signature.getMethod().getAnnotation(Metrics.class);

        String name = String.format("【%s】【%s】", signature.getDeclaringType().toString(), signature.toLongString());

        if (metrics == null) {
            @Metrics
            final class InnerClass {
            }
            metrics = InnerClass.class.getAnnotation(Metrics.class);
        }
        // 嘗試從請求上下文獲得請求URL
        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
        if (requestAttributes != null) {
            HttpServletRequest request = ((ServletRequestAttributes) requestAttributes).getRequest();
            name += String.format("【%s】", request.getRequestURL().toString());
        }
        // 入參的日誌輸出
        if (metrics.logParameters()) {
            log.info(String.format("【入參日誌】調用 %s 的參數是:【%s】", name, objectMapper.writeValueAsString(pjp.getArgs())));
        }
        // 連接點方法的執行,以及成功失敗的打點,出現異常的時候記錄日誌
        Object returnValue;
        Instant start = Instant.now();
        try {
            returnValue = pjp.proceed();
            if (metrics.recordSuccessMetrics()) {
                // 在生產級代碼中,應考慮使用類似Micrometer的指標框架,把打點信息記錄到時間序列資料庫中,實現通過圖表來查看方法的調用次數和執行時間,
                log.info(String.format("【成功打點】調用 %s 成功,耗時:%d ms", name, Duration.between(start, Instant.now()).toMillis()));
            }
        } catch (Exception ex) {
            if (metrics.recordFailMetrics()) {
                log.info(String.format("【失敗打點】調用 %s 失敗,耗時:%d ms", name, Duration.between(start, Instant.now()).toMillis()));
            }
            if (metrics.logException()) {
                log.error(String.format("【異常日誌】調用 %s 出現異常!", name), ex);
            }
            if (metrics.ignoreException()) {
                returnValue = getDefaultValue(signature.getReturnType());
            } else {
                throw ex;
            }
        }
        // 返回值輸出
        if (metrics.logReturn()) {
            log.info(String.format("【出參日誌】調用 %s 的返回是:【%s】", name, returnValue));
        }
        return returnValue;
    }

3. 自動註入AutoConfiguration

@AutoConfiguration
@Slf4j
@EnableConfigurationProperties(MetricsProperties.class)
@ConditionalOnProperty(prefix = "common.metrics", name = {"keep-alive"}, havingValue = "true", matchIfMissing = true)
public class AspectAutoConfiguration {

    public AspectAutoConfiguration() {
        log.info("AspectAutoConfiguration initialize.");
    }

    @Bean
    public MetricsAspect metricsAspect() {
        return new MetricsAspect();
    }
}

4. 配置文件MetricsProperties

@ConfigurationProperties(prefix = "common.metrics")
public class MetricsProperties {
    public Boolean getKeepAlive() {
        return keepAlive;
    }

    public void setKeepAlive(Boolean keepAlive) {
        this.keepAlive = keepAlive;
    }

    private Boolean keepAlive = true;

}

5. 其它配置

配置自動註入

配置resource.META-INF.spring.org.springframework.boot.autoconfigure.AutoConfiguration.imports文件,增加AspectAutoConfiguration類路徑。

配置文件提示

{
  "groups": [],
  "properties": [
    {
      "name": "common.metrics.keepAlive",
      "type": "java.lang.Boolean",
      "sourceType": "com.common.config.metrics.properties.MetricsProperties"
    }
  ]
}

四、自定義spring的profile限定註解

1. 註解@RunOnProfiles

@Target({ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Indexed
public @interface RunOnProfiles {
    /**
     * Profile name array,eg,dev,pro.
     */
    String[] value() default {};

    /**
     * Skip the code of  the method of the class or method itself.
     */
    boolean skip() default true;

}

2. 切麵RunOnProfilesAspect

@Aspect
@Slf4j
@Order(Ordered.HIGHEST_PRECEDENCE)
@Component
public class RunOnProfilesAspect {
    @Autowired
    private ApplicationContext applicationContext;

    @Pointcut("@annotation(com.common.config.profiles.annotation.RunOnProfiles)")
    public void withAnnotationMethod() {
    }

    @Pointcut("@within(com.common.config.profiles.annotation.RunOnProfiles)")
    public void withAnnotationClass() {
    }

    @Around("withAnnotationMethod() || withAnnotationClass()")
    public Object runsOnAspect(ProceedingJoinPoint pjp) throws Throwable {
        var activeArray = applicationContext.getEnvironment().getActiveProfiles();
        MethodSignature signature = (MethodSignature) pjp.getSignature();
        RunOnProfiles runOnProfiles = signature.getMethod().getAnnotation(RunOnProfiles.class);
        if (runOnProfiles == null) {
            return null;
        }
        var profilesArray = runOnProfiles.value();
        if (profilesArray == null || profilesArray.length == 0) {
            return pjp.proceed();
        }
        for (var profile : profilesArray) {
            for (var p : activeArray) {
                if (p.equals(profile)) {
                    return pjp.proceed();
                }
            }
        }
        return null;
    }
}

3. 自動註入AutoConfiguration

@AutoConfiguration
@Slf4j
public class RunsOnProfilesAutoConfiguration {

    public RunsOnProfilesAutoConfiguration() {
        log.info("RunsOnProfilesAutoConfiguration initialize.");
    }

    @Bean
    public RunOnProfilesAspect runsOnProfilesAspect() {
        return new RunOnProfilesAspect();
    }
}

4. 其它配置

配置自動註入

配置resource.META-INF.spring.org.springframework.boot.autoconfigure.AutoConfiguration.imports文件,增加RunsOnProfilesAutoConfiguration類路徑。

參考

[1] springboot doc configuration metadata


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 簡介 組合模式(Composite Pattern),又叫部分整體模式,是一種結構型設計模式。用於把一組類似的對象當作一個單一的對象來看。組合模式依據樹形結構來組合對象,用不同組件來構建某個部分或整體對象。 如果你需要實現樹狀對象結構,可以使用組合模式。如果你希望客戶端代碼以相同方式處理簡單和複雜元 ...
  • 經典設計模式源碼詳解,用不同語言來實現,包括Java/JS/Python/TypeScript/Go等。結合實際場景,充分註釋說明,每一行代碼都經過檢驗,確保可靠。 設計模式是一個程式員進階高級的必然選擇,不懂設計模式,就像寫文章不懂得層次,蓋房子沒有結構。只有充分懂得設計之道,才能真正設計出良好的... ...
  • 如果你需要一個自動化的工具幫助你或者你的團隊發現代碼中的缺陷,在提升代碼質量同時減少人工Code Review的成本,那這篇文章非常的適合你。 ...
  • super關鍵字的一些註意事項 子類在執行構造方法時,如果顯式使用super()顯式調用父類構造方法,則該調用必須放代碼塊在第一行 super必須出現在子類的方法或者構造方法中 使用this()顯示調用構造方法,則該調用必須放在代碼塊第一行 由於第一條和第三條限制,super和this不能同時調用構 ...
  • P2 List介面和常用方法 一、List介面基本介紹 List介面是 Collection 介面的子介面 List集合類中元素有序(即添加順序和取出順序一致)、且可重覆 List集合類中的每一個元素都有其對應的順序索引,即支持索引。 List容器中都對應一個整數型的序號記載其在容器中的位置,可以根 ...
  • 給大家分享一個Github倉庫,上面有大彬整理的300多本經典的電腦書籍PDF,包括C語言、C++、Java、Python、前端、資料庫、操作系統、電腦網路、數據結構和演算法、機器學習、編程人生等,可以star一下,下次找書直接在上面搜索,倉庫持續更新中~ Github地址 C語言教程——翁凱老師 ...
  • java.util.regex 包主要包括以下三個類: Pattern 類: pattern 對象是一個正則表達式的編譯表示。Pattern 類沒有公共構造方法。要創建一個 Pattern 對象,你必須首先調用其公共靜態編譯方法,它返回一個 Pattern 對象。該方法接受一個正則表達式作為它的第一 ...
  • 搭建微服務基礎環境01 1.創建父工程,用於聚合其他微服務模塊 1.1創建父項目 說明:我們先創建一個父項目,該父項目會去管理多個微服務模塊(module),如下: (1)File-New-Project-Maven,選擇如下: (2)輸入項目名稱等信息,然後next (3)選擇Maven,然後Fi ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...