如果使用@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约定来指定资源,如下面的例子所示。
- Web Application Conditions
@ConditionalOnWebApplication和@ConditionalOnNotWebApplication注解让应用程序根据是否是“web应用程序”来包含配置。基于servlet的web应用程序是任何使用Spring WebApplicationContext、定义会话范围或具有ConfigurableWebEnvironment的应用程序。任何使用ReactiveWebApplicationContext或者ConfigurableReactiveWebEnvironment的应用都可以被称为响应式web应用。
@ConditionalOnWarDeployment注解根据应用程序是否是部署到容器中的传统WAR应用程序来包含配置。此条件不适用于与嵌入式服务器一起运行的应用程序。
- SpEL Expression Conditions
@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-INF\spring.factories
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.pack.config.LogsAutoConfiguration
以上就实现自定义starter的流程。