jjzjj

Autoconfiguration详解——自动注入配置参数

bloodcolding 2023-04-08 原文

目录

Autoconfiguration详解——自动注入配置参数

一、自动注入配置基础

  1. @EnableConfigurationProperties(CommonRedisProperties.class) 注解configuration类;
  2. @ConfigurationProperties(prefix = "myserver")注解配置文件类,prefix标明配置文件的前缀;
  3. public RedisTemplate<String, Object> 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<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 = 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

有关Autoconfiguration详解——自动注入配置参数的更多相关文章

  1. ruby-on-rails - 使用 Ruby on Rails 进行自动化测试 - 最佳实践 - 2

    很好奇,就使用ruby​​onrails自动化单元测试而言,你们正在做什么?您是否创建了一个脚本来在cron中运行rake作业并将结果邮寄给您?git中的预提交Hook?只是手动调用?我完全理解测试,但想知道在错误发生之前捕获错误的最佳实践是什么。让我们理所当然地认为测试本身是完美无缺的,并且可以正常工作。下一步是什么以确保他们在正确的时间将可能有害的结果传达给您? 最佳答案 不确定您到底想听什么,但是有几个级别的自动代码库控制:在处理某项功能时,您可以使用类似autotest的内容获得关于哪些有效,哪些无效的即时反馈。要确保您的提

  2. ruby-on-rails - 如何在 ruby​​ 中使用两个参数异步运行 exe? - 2

    exe应该在我打开页面时运行。异步进程需要运行。有什么方法可以在ruby​​中使用两个参数异步运行exe吗?我已经尝试过ruby​​命令-system()、exec()但它正在等待过程完成。我需要用参数启动exe,无需等待进程完成是否有任何ruby​​gems会支持我的问题? 最佳答案 您可以使用Process.spawn和Process.wait2:pid=Process.spawn'your.exe','--option'#Later...pid,status=Process.wait2pid您的程序将作为解释器的子进程执行。除

  3. ruby - RSpec - 使用测试替身作为 block 参数 - 2

    我有一些Ruby代码,如下所示:Something.createdo|x|x.foo=barend我想编写一个测试,它使用double代替block参数x,这样我就可以调用:x_double.should_receive(:foo).with("whatever").这可能吗? 最佳答案 specify'something'dox=doublex.should_receive(:foo=).with("whatever")Something.should_receive(:create).and_yield(x)#callthere

  4. ruby - 如何在 Ruby 中拆分参数字符串 Bash 样式? - 2

    我正在为一个项目制作一个简单的shell,我希望像在Bash中一样解析参数字符串。foobar"helloworld"fooz应该变成:["foo","bar","helloworld","fooz"]等等。到目前为止,我一直在使用CSV::parse_line,将列分隔符设置为""和.compact输出。问题是我现在必须选择是要支持单引号还是双引号。CSV不支持超过一个分隔符。Python有一个名为shlex的模块:>>>shlex.split("Test'helloworld'foo")['Test','helloworld','foo']>>>shlex.split('Test"

  5. ruby-on-rails - 独立 ruby​​ 脚本的配置文件 - 2

    我有一个在Linux服务器上运行的ruby​​脚本。它不使用rails或任何东西。它基本上是一个命令行ruby​​脚本,可以像这样传递参数:./ruby_script.rbarg1arg2如何将参数抽象到配置文件(例如yaml文件或其他文件)中?您能否举例说明如何做到这一点?提前谢谢你。 最佳答案 首先,您可以运行一个写入YAML配置文件的独立脚本:require"yaml"File.write("path_to_yaml_file",[arg1,arg2].to_yaml)然后,在您的应用中阅读它:require"yaml"arg

  6. ruby - 检查方法参数的类型 - 2

    我不确定传递给方法的对象的类型是否正确。我可能会将一个字符串传递给一个只能处理整数的函数。某种运行时保证怎么样?我看不到比以下更好的选择:defsomeFixNumMangler(input)raise"wrongtype:integerrequired"unlessinput.class==FixNumother_stuffend有更好的选择吗? 最佳答案 使用Kernel#Integer在使用之前转换输入的方法。当无法以任何合理的方式将输入转换为整数时,它将引发ArgumentError。defmy_method(number)

  7. ruby-on-rails - 在默认方法参数中使用 .reverse_merge 或 .merge - 2

    两者都可以defsetup(options={})options.reverse_merge:size=>25,:velocity=>10end和defsetup(options={}){:size=>25,:velocity=>10}.merge(options)end在方法的参数中分配默认值。问题是:哪个更好?您更愿意使用哪一个?在性能、代码可读性或其他方面有什么不同吗?编辑:我无意中添加了bang(!)...并不是要询问nobang方法与bang方法之间的区别 最佳答案 我倾向于使用reverse_merge方法:option

  8. ruby - RuntimeError(自动加载常量 Apps 多线程时检测到循环依赖 - 2

    我收到这个错误:RuntimeError(自动加载常量Apps时检测到循环依赖当我使用多线程时。下面是我的代码。为什么会这样?我尝试多线程的原因是因为我正在编写一个HTML抓取应用程序。对Nokogiri::HTML(open())的调用是一个同步阻塞调用,需要1秒才能返回,我有100,000多个页面要访问,所以我试图运行多个线程来解决这个问题。有更好的方法吗?classToolsController0)app.website=array.join(',')putsapp.websiteelseapp.website="NONE"endapp.saveapps=Apps.order("

  9. Ruby Sinatra 配置用于生产和开发 - 2

    我已经在Sinatra上创建了应用程序,它代表了一个简单的API。我想在生产和开发上进行部署。我想在部署时选择,是开发还是生产,一些方法的逻辑应该改变,这取决于部署类型。是否有任何想法,如何完成以及解决此问题的一些示例。例子:我有代码get'/api/test'doreturn"Itisdev"end但是在部署到生产环境之后我想在运行/api/test之后看到ItisPROD如何实现? 最佳答案 根据SinatraDocumentation:EnvironmentscanbesetthroughtheRACK_ENVenvironm

  10. ruby - 定义方法参数的条件 - 2

    我有一个只接受一个参数的方法:defmy_method(number)end如果使用number调用方法,我该如何引发错误??通常,我如何定义方法参数的条件?比如我想在调用的时候报错:my_method(1) 最佳答案 您可以添加guard在函数的开头,如果参数无效则引发异常。例如:defmy_method(number)failArgumentError,"Inputshouldbegreaterthanorequalto2"ifnumbereputse.messageend#=>Inputshouldbegreaterthano

随机推荐