Springboot自定义@EnableXX注解

前言

在SpringBoot中,经常可以看到许多以 @Enable 开头的注解,例如:@EnableAutoConfiguration,@EnableAsync……,那么我们是否可以自己定义一个注解呢?

其实自定义注解最终都是利用到了 ImportBeanDefinitionRegistrar 这个类,通过手动的方式,将一个类注册成为 Bean,然后在进行一系列的操作。相比SPI机制, 该实现相当于手动装配

实现

假设现在有一个需求是要写一个切面,这个切面负责打印controller 的log。但是可能某些系统有自己的日志格式,不太需要这个切面AOP,所以希望可以增加一个开关,然后是按需引用。
下面就来开始写一个这样的demo,项目结构如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
├── pom.xml
└── src
├── main
│   ├── java
│   │   └── com
│   │   ├── anno
│   │   │   ├── EnableLog.java
│   │   │   ├── LogAop.java
│   │   │   └── LogRegister.java
│   │   └── example
│   │   ├── App.java
│   │   ├── controller
│   │   │   └── AnnoController.java

在这里为什么会初始化两个文件夹,是因为 @SpringBootApplication 这个注解会默认将当前目录以及它的下级目录下的 Bean 注入到容器中,所以新建一个同级目录anno 就是为了不让 @SpringBootApplication 加载切面。

Controller

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
* @author cuishiying
* @date 2021-01-22
*/
@RestController
@RequestMapping("/anno")
public class AnnoController {

// http://localhost:8080/anno/hello
@GetMapping("/hello")
public Object hello() {
return "hello world";
}
}

然后请求 http://localhost:8080/anno/hello, 可以看到在控制台没有任何输出。那就说明自定义的切面还没有生效。此时在 Application 上添加我们的自定义注解,添加后代码如下:

1
2
3
4
5
6
7
@EnableLog
@SpringBootApplication
public class App {
public static void main(String[] args) {
SpringApplication.run(App.class, args);
}
}

此时在此启动项目,你会发现已经已经在控制台有我们的日志log了。

自定义注解实现

首先我们需要注册到spring容器的是aop, 这里先实现AOP切面。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/**
* @author cuishiying
* @date 2021-01-22
*/
@Slf4j
@Aspect
public class LogAop {

@Pointcut("execution(public * com.example.controller.*.*(..))")
public void req() {}


@Around("req()")
public Object around(ProceedingJoinPoint point) throws Throwable {
log.info("start:{}", System.currentTimeMillis());
Object proceed = point.proceed();
log.info("end:{}", System.currentTimeMillis());
return proceed;
}

}

然后实现自动注入配置(需要@Import的类)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
* @author cuishiying
* @date 2021-01-22
*/
public class LogRegister implements ImportBeanDefinitionRegistrar {

/**
* BeanDefinitionRegistry 将我们的 LogAop 注册成一个Bean,只有当注册成一个 Bean 以后,该切面才会生效
* @param importingClassMetadata
* @param registry
*/
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
BeanDefinition beanDefinition = BeanDefinitionBuilder.genericBeanDefinition(LogAop.class).getBeanDefinition();
registry.registerBeanDefinition("logAspect", beanDefinition);
}
}

最后定义注入开关注解

1
2
3
4
5
6
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Documented
@Import({LogRegister.class})
public @interface EnableLog {
}

再次访问 http://localhost:8080/anno/hello 可以看到aop已经生效了。

1
2
2021-02-28 16:24:31.567  INFO 58297 --- [nio-8080-exec-2] com.anno.LogAop                          : start:1614500671567
2021-02-28 16:24:31.576 INFO 58297 --- [nio-8080-exec-2] com.anno.LogAop : end:1614500671576

最后

本文到此结束,感谢阅读。如果您觉得不错,请关注公众号【当我遇上你】,您的支持是我写作的最大动力。

参考