<返回更多

SpringBoot自定义自动配置这些知识点你需要了解

2023-03-02    Springboot实战案例锦集
加入收藏
如果使用@ConditionalOnClass或@ConditionalOnMissingClass作为元注释的一部分来组合自己的组合注释,则必须使用name,因为在这种情况下引用类不会被处理。

理解自动配置bean

在底层,自动配置是用标准的@Configuration类实现的。附加的@Conditional注释用于约束何时应用自动配置。通常,自动配置类使用@ConditionalOnClass和@ConditionalOnMissingBean注释。这确保了自动配置仅在找到相关类且尚未声明自己的@configuration时适用。

你可以浏览spring-boot-autoconfigure的源代码,以查看Spring提供的@Configuration类(参见META-INF/spring.factories 文件)。

定位候选自动配置

Spring Boot检查是否存在META-INF/spring.factories文件在你发布的jar中。该文件应该在EnableAutoConfiguration为key下列出你的配置类,如下例所示:

org.springframework.boot.autoconfigure.EnableAutoConfiguration=
com.pack.bus.autoconfigure.BusAutoConfiguration,
com.pack.bus.autoconfigure.BusWebAutoConfiguration

自动配置只能以这种方式加载。确保它们是在特定的包空间中定义的,并且它们永远不是组件扫描的目标。此外,自动配置类不应该允许组件扫描来查找其他组件。应该使用特定的@Imports。

如果你的配置需要按特定顺序应用,你可以使用@AutoConfigureAfter或@AutoConfigureBefore注释。例如,如果你提供了特定于web的配置,你的类可能需要应用在WebMvcAutoConfiguration之后。

可以使用@AutoConfigureOrder。该注释具有与常规@Order注释相同的语义,但为自动配置类提供了专用的顺序。

与标准的@Configuration类一样,自动配置类的应用顺序只影响其bean定义的顺序。随后创建这些bean的顺序不受影响,由每个bean的依赖关系和@DependsOn关系决定。

条件注释

你几乎总是希望在自动配置类中包含一个或多个@Conditional注解。@ConditionalOnMissingBean注解是一个常见的例子,它允许开发人员在对默认值不满足时覆盖自动配置。

Spring Boot包含很多@Conditional注解,你可以在自己的代码中重用这些注解,方法是注解@Configuration类或单独的@Bean方法。这些注释包括:

@ConditionalOnClass和@ConditionalOnMissingClass注解让@Configuration类根据特定类的存在与否被包含。由于注释元数据是通过ASM解析的,因此你可以使用value属性来引用真正的类,即使这个类可能实际上没有出现在正在运行的应用程序类路径中。如果想用字符串指定类名,也可以使用name属性。

这种机制不适用于@Bean方法,因为@Bean方法的返回类型通常是条件的目标:在方法的条件应用之前,JVM将加载类并可能处理方法引用,如果类不存在,则这些引用将失败。

为了处理这种情况,可以使用一个单独的@Configuration类来隔离这种情况,如下面的例子所示:

@Configuration(proxyBeanMethods = false)
// Some conditions ...
public class MyAutoConfiguration {
  // Auto-configured beans ...
  @Configuration(proxyBeanMethods = false)
  @ConditionalOnClass(SomeService.class)
  public static class SomeServiceConfiguration {
    @Bean
    @ConditionalOnMissingBean
    public SomeService someService() {
      return new SomeService();
    }


  }
}

如果使用@ConditionalOnClass或@ConditionalOnMissingClass作为元注释的一部分来组合自己的组合注释,则必须使用name,因为在这种情况下引用类不会被处理。

@ConditionalOnBean和@ConditionalOnMissingBean注解让一个bean根据特定bean的存在与否被包含进来。可以使用value属性按类型指定bean,也可以使用name指定bean。search属性允许您限制在搜索bean时应该考虑的ApplicationContext层次结构。

当放在@Bean方法上时,目标类型默认为方法的返回类型,如下面的例子所示:

@Configuration(proxyBeanMethods = false)
public class MyAutoConfiguration {


  @Bean
  @ConditionalOnMissingBean
  public SomeService someService() {
    return new SomeService();
  }
}

@ConditionalOnProperty注解让配置基于Spring环境属性包含。使用prefix和name属性指定要检查的属性。默认情况下,匹配任何存在且不等于false的属性。你还可以使用havingValue和matchIfMissing属性来创建更高级的检查。

@ConditionalOnResource注解让配置只在特定资源存在时才包含。可以使用常用的Spring约定来指定资源,如下面的例子所示。

@ConditionalOnWebApplication和@ConditionalOnNotWebApplication注解让应用程序根据是否是“web应用程序”来包含配置。基于servlet的web应用程序是任何使用Spring WebApplicationContext、定义会话范围或具有ConfigurableWebEnvironment的应用程序。任何使用ReactiveWebApplicationContext或者ConfigurableReactiveWebEnvironment的应用都可以被称为响应式web应用。

@ConditionalOnWarDeployment注解根据应用程序是否是部署到容器中的传统WAR应用程序来包含配置。此条件不适用于与嵌入式服务器一起运行的应用程序。

@ConditionalOnExpression注解让配置基于SpEL表达式的结果包含。

创建自己的Starter

你应该确保为你的starter程序提供适当的命名空间。即使你用了不同的Maven groupId,也不要用spring-boot来启动模块名。我们可能会在未来为你的自动配置提供官方支持。

根据经验,你应该在starter之后命名一个组合模块。例如,假设你正在为“acme”创建一个starter程序,并且你将自动配置模块命名为acme-spring-boot,而starter程序命名为acme-spring-boot-starter。如果只有一个模块组合了这两个模块,请将其命名为acme-spring-boot-starter。

如果starter提供了配置key,它们使用唯一的命名空间。不要把key放在Spring Boot使用的命名空间中(比如server、management、Spring等)。

为每个属性添加JAVAdoc,确保配置项有文档记录,如下面的例子所示。

@ConfigurationProperties("acme")
public class AcmeProperties {


  /**
   * Whether to check the location of acme resources.
   */
  private boolean checkLocation = true;
  /**
   * Timeout for establishing a connection to the acme server.
   */
  private Duration loginTimeout = Duration.ofSeconds(3);
}

本示例主要功能是实现日志记录功能

自动配置类

@Configuration
@EnableConfigurationProperties(LogsProperties.class)
@ConditionalOnProperty(prefix = "logs", name = "enabled", havingValue = "true")
@EnableAspectJAutoProxy
public class LogsAutoConfiguration {
  
  private static final Logger logger = LoggerFactory.getLogger(LogsAutoConfiguration.class) ;
  
  @Resource
  private LogsProperties logsProperties ;
  
  @Bean
  public AspectJExpressionPointcutAdvisor logAdvisor() {
    AspectJExpressionPointcutAdvisor advisor = new AspectJExpressionPointcutAdvisor() ;
    logger.info("执行表达式:{}", logsProperties.getPointcut()) ;
    advisor.setExpression(logsProperties.getPointcut()) ;
    advisor.setAdvice(new SystemAroundOperator()) ;
    return advisor ;
  }
}

自定义注解

@Documented
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface SystemLog {
  
  /**
   * <p>操作说明</p>
   * @return
  */
  String value() default "" ;
}

属性key配置

/**
 *   日志功能属性配置
 *   @author xg
 */
@ConfigurationProperties(prefix = "logs")
public class LogsProperties {
  /**
   *   切入点定义<br/>
   *    示例:execution(public * com.pack.controller.*.*(..))
   */
  private String pointcut ;
  /**
   *   是否开启日志功能
   */
  private boolean enabled = true ;
}

Advice定义

public class SystemAroundOperator implements MethodInterceptor {


  private static final Logger logger = LoggerFactory.getLogger(SystemAroundOperator.class);


  @Override
  public Object invoke(MethodInvocation invocation) throws Throwable {
    // 开始执行时间
    long start = System.currentTimeMillis();
    Method method = invocation.getMethod() ;
    SystemLog annoLog = null ;
    if (method.isAnnotationPresent(SystemLog.class)) {
      annoLog = method.getAnnotation(SystemLog.class) ;
      String value = annoLog.value() ;
      try {
        Object result = invocation.proceed() ;
        // 方法执行时间
        Long execTime = System.currentTimeMillis() - start ;
        logger.info("{}, 业务执行时间:{} ms", value, execTime) ;
        return result ;
      } catch (Throwable t) {
        Long execTime = System.currentTimeMillis() - start ;
        logger.info("{}, 业务执行时间:{} ms,发生异常信息:{}", value, execTime, t.getMessage()) ;
        throw t ;
      }
    }
    return invocation.proceed();
  }
}

配置META-INFspring.factories

org.springframework.boot.autoconfigure.EnableAutoConfiguration=
com.pack.config.LogsAutoConfiguration

以上就实现自定义starter的流程。

声明:本站部分内容来自互联网,如有版权侵犯或其他问题请与我们联系,我们将立即删除或处理。
▍相关推荐
更多资讯 >>>