目錄
- Autoconfiguration詳解——自動注入配置引數
- 一、 理解自動裝配bean
- 1. 常用注解
- 2. 定位自動裝配的候選類
- 3. 條件注解
- 3.1 有關類的判斷
- 3.2 有關bean的判斷
- 3.3 配置條件
- 3.4 源檔案條件
- 3.5 web 應用條件
- 3.6 Spel表單式條件
- 二、自動注入配置基礎
- 三、注釋切面 @Metrics
- 1. 注解@Metrics
- 2. 切面MetricsAspect
- 3. 自動注入AutoConfiguration
- 4. 組態檔MetricsProperties
- 5. 其它配置
- 四、自定義spring的profile限定注解
- 1. 注解@RunOnProfiles
- 2. 切面RunOnProfilesAspect
- 3. 自動注入AutoConfiguration
- 4. 其它配置
- 參考
- 一、 理解自動裝配bean
Autoconfiguration詳解——自動注入配置引數
一、 理解自動裝配bean
1. 常用注解
@AutoConfiguration(每個配置類都要加上)Class<?>[] after() default {};Class<?>[] before() default {};- 以上兩個配置可以控制加載順序;
- 不需要再增加
@Configuration注解;
@AutoConfigureBeforeand@AutoConfigureAfter@Configuration@Conditional(后面會詳細講到)@ConditianalOnClass@ConditionalOnMissingClass@ConditionalOnWebApplication:只在web應用中加載;
@EnableConfigurationProperties:組態檔引數內容,參照類RedisProperties;@ConfigurationProperties(prefix = "spring.redis"),該注解展示了組態檔前綴;
@DependsOn:列舉一些前置的注入bean,以備用,用在類上需要有@Component自動掃描的時候才能生效;- 實際上控制了bean加載的順序,優先加載指定的bean,然后加載當前bean;
- 銷毀的時候,注解的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的引數都會被匹配到,你也可以使用havingValue和matchIfMissing屬性闖將更多的校驗,
例子:@ConditionalOnProperty(name = "spring.redis.client-type", havingValue = "https://www.cnblogs.com/bloodcolding/p/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 應用特點:
- 使用 Spring
WebApplicationContext; - 定義了一個
session作用域的bean; - 有一個
WebApplicationContext;
reactive web 應用特點:
-
使用了
ReactiveWebApplicationContext; -
有一個
ConfigurableReactiveWebEnvironment;
ConditionalOnWarDeployment,僅限于使用war進行部署的場景,在嵌入式tomcat的場景里不會啟用;
3.6 Spel表單式條件
ConditionalOnWarDeployment ,使用Spel運算式回傳結果進行判斷,
注意:在運算式中參考一個bean會導致這個bean非常早的被加載,此時還沒有進行預加載(例如配置項的系結),可能會導致不完成的加載,
二、自動注入配置基礎
@EnableConfigurationProperties(CommonRedisProperties.class)注解configuration類;@ConfigurationProperties(prefix = "myserver")注解組態檔類,prefix標明組態檔的前綴;public RedisTemplate<String, Object> getRedisTemplate(CommonRedisProperties properties, RedisConnectionFactory redisConnectionFactory),加到需要使用的引數中即可;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 = https://www.cnblogs.com/bloodcolding/p/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 = https://www.cnblogs.com/bloodcolding/p/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 = "https://www.cnblogs.com/bloodcolding/p/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
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/548997.html
標籤:Java
