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注解。
在所有自动装配类出现的地方,我们都因该时加上@Conditional注解,允许使用的开发人员覆盖自动装配的bean,当然他们也可以选择什么也不做,使用默认的配置。
Spring Boot 提供了一些条件注解,可以注解在@Configuration类或者@Bean方法上。
对于@Configuration类来说,@ConditionalOnClass 和@ConditionalOnMissingClass代表了在指定类存在或者不存在的时候进行加载。因为实际上注解的元数据使用ASM技术进行解析,所以可以使用**value参数来指定特定的类的class对象(可以是多个),或者使用name参数**来指定特定的类名(可以是多个),两种方式所指向的类即使不存在也不影响正常执行。
当@Bean方法返回值是条件注解的的目标之时,可能会因为JVM加载顺序的问题导致加载失败,上文提到的两个注解可以用在@Bean方法上。
@ConditionalOnBean和@ConditionalOnMissingBean,代表在指定bean存在或者不存在时加载。value参数可以指定bean的class(多个),name可以指定bean的名称(多个)。search参数允许你限制ApplicationContext即应用上下文的搜索范围,可选当前上下文,继承上层,或者是全部(默认)。
在@Bean方法上使用时,默认参数为当前方法返回类型。
在使用@Bean注解时,建议使用具体类型而不是父类型进行指代。
@ConditionalOnProperty,指定配置项文件(例如dev,pro),prefix属性规定了配置前缀,name属性指定了应该被检查的参数。默认,所有存在且不等于false的参数都会被匹配到,你也可以使用havingValue和matchIfMissing属性闯将更多的校验。
例子:@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 |
@ConditionalOnResource,指定源文件存在时引入。
例如:@ConditionalOnResource(resources = {"classpath:test.log"});
@ConditionalOnWebApplication 和 @ConditionalOnNotWebApplication ,web应用或者不是web应用时启用,以下部分只要满足一个条件即为web 应用。
servlet-based web 应用特点:
reactive web 应用特点:
使用了ReactiveWebApplicationContext;
有一个ConfigurableReactiveWebEnvironment;
ConditionalOnWarDeployment,仅限于使用war进行部署的场景,在嵌入式tomcat的场景里不会启用;
ConditionalOnWarDeployment ,使用Spel表达式返回结果进行判断。
注意:在表达式中引用一个bean会导致这个bean非常早的被加载,此时还没有进行预加载(例如配置项的绑定),可能会导致不完成的加载。
{ "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" } ] }
@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;
@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; }
@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(); } }
@ConfigurationProperties(prefix = "common.metrics") public class MetricsProperties { public Boolean getKeepAlive() { return keepAlive; } public void setKeepAlive(Boolean keepAlive) { this.keepAlive = keepAlive; } private Boolean keepAlive = true; }
配置自动注入
配置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" } ] }
@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; }
@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; } }
@AutoConfiguration @Slf4j public class RunsOnProfilesAutoConfiguration { public RunsOnProfilesAutoConfiguration() { log.info("RunsOnProfilesAutoConfiguration initialize."); } @Bean public RunOnProfilesAspect runsOnProfilesAspect() { return new RunOnProfilesAspect(); } }
配置自动注入
配置resource.META-INF.spring.org.springframework.boot.autoconfigure.AutoConfiguration.imports文件,增加RunsOnProfilesAutoConfiguration类路径。
[1] springboot doc configuration metadata
上一篇:gyp ERR!报错解决