首页 > 解决方案 > 在编译时使用注释处理器扩展类功能

问题描述

我有以下 Spring Boot 类,使用自定义注释进行注释Counted

@RestController
@RequestMapping("/identity")
public class IdentityController {

    @Autowired
    private IdentityService identityService;

    @PostMapping
    @Counted(value = "post_requests_identity")
    public Integer createIdentity() {
        return identityService.createIdentity();
    }
}

Counted注释定义如下:

@Target({ElementType.METHOD})
@Retention(RetentionPolicy.SOURCE)
public @interface Counted {
    String value();
}

我想要的是编写一个注释处理器,有效地使我的控制器表现得像下面的代码。

@RestController
@RequestMapping("/identity")
public class IdentityController {

    @Autowired
    private IdentityService identityService;

    @Autowired
    private PrometheusMeterRegistry registry;

    @PostConstruct
    public void init() {
        registry.counter("post_requests_identity");
    }

    @PostMapping
    public Integer createIdentity() {
        registry.counter("post_requests_identity").increment();
        return identityService.createIdentity();
    }
}

我已经能够在运行时通过反射来做到这一点,但这大大延长了启动时间。有没有办法只用注释和自定义注释处理器来完成上述工作?换句话说,我想创建一个注解,将一个带注解的方法添加到一个类中,并将一个任意方法调用添加到一个已经存在的方法中。

我知道注释处理并不真正支持修改源。我有兴趣知道任何其他方法来执行上述操作,而无需将注册表及其相关代码直接放在我的源代码中。

标签: javaspringannotationsannotation-processing

解决方案


您绝对可以制作自己的拦截器或创建自己的PostProcessor. 然而,Spring 有一个很好的内置特性(它实际上在整个框架中都使用过),称为Application Events。这是一个很好的小东西,它通过一个很好的小抽象来利用 DI 和 Spring 作为总线,完全可以满足您的需求。(另请参阅此博客文章了解更多信息)。

ApplicationEvent接受方面来说,您可以进行简单的设置:

// Create an ApplicationEvent type for your "count" event like so...
public class CountEvent extends ApplicationEvent {

    private String counterName;

    ...

}

// Create a class that accepts the count events and meters them...
@Component
public class MeterEventService {

    @Autowired
    private PrometheusMeterRegistry registry;

    @EventListener
    public void createIdentity(CountEvent countEvent) {
        String countedMethod = countEvent.getCounterName();
        registry.counter(countedMethod).increment();
    }
}

从发送方,您可以使用自定义注释轻松地从您在问题中中断的地方继续:

@Component
@Aspect
public class Mail {

    // Autowire in ApplicationContext
    private ApplicationContext applicationContext;

    @After("execution (@<package to annotation>.Counted * *(..))")
    public void countMetric(JoinPoint jp){
        MethodSignature signature = (MethodSignature) jp.getSignature();
        String method = signature.getMethod().getName();
        applicationContext.publishEvent(new CountEvent(method));
    }
}

恕我直言,对于拥有这样一个功能的极大便利来说,代码并不多。

此外,如果您更愿意使用valuefrom@Counted而不是方法名称,则可以类似地执行 this来取消注释。


推荐阅读