实现自定义注解
在 springBoot 中,注解是编码时必不可少的,它可以帮助我们更方便快捷的去开发。常见的注解如:@Autowired、@Slf4j、@Data 等等。
然而这些注解都是别人已经封装好给我们用的,如果我们想自定义一个拥有特别功能的注解,该怎么操作呢?
看完这篇文章,给你答案~
今天以日志功能为例,灵活的运用自定义注解方便快捷的记录每个接口的日志。
在项目中,有众多的接口,如果接口报错了,该怎么去快速定位代码呢?这个时候就要用到日志了。当接口接收到请求的时候,我希望可以记录下来这个接口的各种信息。比如请求时间、请求参数,结束时间等,还可以在接口开始和结束的时候加一个标记,方便出现报错或者 bug 的时候可以快速定位到是哪个接口出了问题。
接下来就用日志系统来介绍自定义注解~
不使用注解
我们可以在接口方法的开头和结尾加一行日志。
1 | |
执行结果:
方法的首尾两行都会有一个日志输出,把这个方法的所有运行包在了日志里面,如果个方法出现了问题,就很容易定位到这里了。
比如我故意写一个报错:int i = 2/0;
1 | |
那么输出结果如下:
可以看到报错的上一行日志定位了 get 方法。我们只需在 get 方法里面找问题就好了。
每个方法的首尾都要这样写一个日志记录,代码就会大量冗余。想获取入参的话,还得再写一段代码来实现,并且根据每个方法的入参数量、类型的不同,可能代码也要相应的变动。
既然这个是重复性的工作,而且逻辑上都是:在方法开始之前和方法结束之后做一个标记。那么我们能不能把这一部分抽取出来,只写一次代码,就能作用在每一个方法上面呢?
毫无疑问,答案是可以!
使用自定义注解
在一个事情的开始和结束插入另一个事情,很容易联想到 Spring 的一个重要特性 ——AOP。
Spring 的 AOP(Aspect-Oriented Programming,面向切面编程)是 Spring 框架中的一个重要特性,用于将横切关注点从应用程序的主业务逻辑中分离出来,使得关注点的代码可以被模块化、重用,并且与主业务逻辑解耦。
定义注解
使用 @interface 关键字定义一个注解
1 | |
在自定义注解中,根据需要标注元注解,如果没有特定需求的话也可以不标注。
一共有以下 5 个元注解:
@Retention
(保留策略):
RetentionPolicy.SOURCE:注解仅存在于源代码中,在编译时会被丢弃。这种类型的注解通常用于提供编译时的辅助信息,不会对运行时产生影响。RetentionPolicy.CLASS:注解存在于编译后的字节码文件中,但在运行时会被丢弃。这种类型的注解可以在编译时对代码进行一些处理,但不会影响程序运行时的行为。RetentionPolicy.RUNTIME:注解在运行时可以通过反射获取到。这种类型的注解可以在运行时对程序的行为进行动态调整,例如在 AOP(面向切面编程)中经常使用。
@Target
(目标类型):
ElementType.METHOD:指定注解可以应用于方法。ElementType.FIELD:指定注解可以应用于字段。ElementType.TYPE:指定注解可以应用于类、接口(包括注解类型)。ElementType.PARAMETER:指定注解可以应用于参数。ElementType.CONSTRUCTOR:指定注解可以应用于构造函数等。
@Documented
(文档化):
- 当一个注解被 @Documented 修饰时,这个注解将会包含在 Javadoc 生成的文档中,使得注解的信息可以被文档化展示。
@Inherited
(继承性):
- 如果一个注解被 @Inherited 修饰,那么子类会继承父类的该注解。这对于一些需要在继承关系中传递注解的情况非常有用。
@Repeatable
(可重复性):
- 允许一个注解在同一个目标上被多次应用,而不需要使用容器注解来包裹多个相同的注解实例。这样可以使代码更加简洁和易读。
引 AOP 依赖
要实现 AOP 自定义注解,第一步先引入 AOP 的依赖:
1 | |
编写 AOP 程序
新建一个 AOP 类,针对于特定方法根据业务需要进行编程 (加 @Aspect 注解声明为 AOP 类)
这个类中,我们要实现自定义注解的功能,比如在方法开始之前,做一个标记,记录该方法的入参,方法结束之后再做一个标记。
新建一个 AOP 类:
1 | |
@Aspect 注解:标记该类为切面类,Spring AOP 会自动识别带有
@Aspect注解的类,并将其视为切面,然后根据定义的通知和切点来实现横切逻辑。@Component:用来表示一个受 Spring 容器管理的组件的注解。可以让 Spring 自动扫描并识别被注解的类,然后将其实例化并加入到 Spring 容器中管理。
写一个在接口执行之前要执行的逻辑方法:
用 @Before 注解标注,里面的 @annotation 是用于定义切点表达式的一种特殊用法,
下列代码中 @Before("@annotation(LogInfo)") 表示在执行被自定义注解标记的方法前执行 logBefore方法
1 | |
之前有了,理应也要有一个之后的。写一个在接口执行之后要执行的逻辑方法:
用 @After 注解标注
1 | |
使用自定义注解
在接口处使用自定义注解标记:
1 | |
执行结果如下:
即使我们没有在接口方法中写任何的日志逻辑,只要标记了注解,就会自动调用注解方法!
整合成 @Around 注解
有了之前,有了之后,还会有一个包围的注解!
上面的 @Before 和 @After 可以合并为一个注解:@Around
一般开发中都是使用 @Around 注解比较多,因为这样只用写一个注解方法就可以了。
使用方法也很简单,就是用 Object result = point.proceed(); 来隔开之前和之后执行的两部分。
Object result = point.proceed(); 语句就是执行接口方法的意思,执行完这条语句,接口方法就执行完了。
特别注意:用 @Around 注解标注的方法入参必须是:**ProceedingJoinPoint 类型**的,因为 proceed() 方法是在 ProceedingJoinPoint 接口中定义的,JoinPoint 接口中没有定义。
把之前的 logBefore方法和 logAfter方法都注释掉,然后写一个新的 logAround方法:
1 | |
然后再来请求一下接口,看看控制台输出:
可以看到效果是跟之前的。