相关推荐recommended
Autoconfiguration详解
作者:mmseoamin日期:2024-01-19

文章目录

  • 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. 其它配置
            • 参考

              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的参数都会被匹配到,你也可以使用havingValue和matchIfMissing属性闯将更多的校验。

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

              属性名类型解析
              nameString[] name() default {};配置项全称,如果有prefix,可以省略prefix中的前缀部分
              prefixString prefix() default “”;统一的配置项前缀
              havingValueString havingValue() default “”;配置项需要匹配的内容,如果没有指定,那么配置的值等于false时结果为false,否则结果都为true
              matchIfMissingboolean 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 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, 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 getDefaultValue(Class 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