一、引言
在现代Java开发中,面向切面编程(Aspect-Oriented Programming,AOP)已经成为一种重要的编程范式。它允许我们将横切关注点(如日志记录、事务管理、权限校验等)从业务逻辑中分离出来,从而提高代码的可维护性和可扩展性。而自定义注解则是实现AOP功能的一种强大工具。通过自定义注解,我们可以清晰地标识出需要进行切面处理的方法或类,让AOP框架能够根据注解来动态地织入增强逻辑。本文将深入探讨Java自定义注解与AOP的结合,从自定义注解的创建到在AOP中的应用,帮助读者更好地理解和使用这一技术。
二、AOP
AOP概述
AOP:Aspect Oriented Programming(面向切面编程、面向方面编程),其实就是面向特定方法编程。
我们想象一个场景,我们写的系统中的某个部分功能运行较慢,需要定位执行耗时较长的业务方法,此时需要统计每一个业务方法的执行耗时。
如果我们对每一个业务方法手动的插入检查,这将会是巨大的工程量,且改动原始方法的过程中也会不小心疏忽。
所以,我们提出 AOP 概念,面对不同的方法,我们重复使用一个功能的时候,我们设计出一个模板套用即可。
AOP依赖
<!-- aop -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
AOP 核心概念
连接点 : ProceedingJoinPoint JoinPoint,可以被AOP控制的方法(暗含方法执行时的相关信息)
注解通知 : Advice,指哪些重复的逻辑,也就是共性功能(最终体现为一个方法)
注解切入点 : Pointcut,匹配连接点的条件,通知仅会在切入点方法执行时被应用
类注解切面:Aspect,描述这个类的通知与切入点的对应关系(通知+切入点)
(注解切入点的参数)目标对象:Target,通知所应用的对象
视图参考如下:
AOP进阶
通知类型
- @Around:环绕通知,此注解标注的通知方法在目标方法前、后都被执行
- @Before :前置通知,此注解标注的通知方法在目标方法前被执行
- @After :后置通知,此注解标注的通知方法在目标方法后被执行,无论是否有异常都会执行
- @AfterReturning :返回后通知,此注解标注的通知方法在目标方法后被执行,有异常不会执行
- @AfterThrowing:异常后通知,此注解标注的通知方法发生异常后执行
通知顺序
1、不同切面类中,默认按照切面类的类名字母排序
- 目标方法前的通知方法:字母排名靠前的先执行
- 目标方法后的通知方法:字母排名靠前的后执行
2、用 @Order(数字) 加在切面类上来控制顺序
- 目标方法前的通知方法:数字小的先执行
- 目标方法后的通知方法:数字小的后执行
常见的切入点表达式
1. execution(不太推荐)
execution
是最常用的切入点表达式,用于匹配方法的执行。
2. @annotation
注解
@annotation
用于匹配那些被指定注解修饰的方法。它关注的是方法上的注解,而不是类或参数上的注解。
3. @within
注解
@within
用于匹配那些定义在带有指定注解的类中的所有方法,而不仅仅是被注解修饰的方法。
三、Java自定义注解基础
(一)什么是注解
注解是Java语言中的一种元数据形式,它提供了关于程序代码的额外信息,但并不直接影响代码的运行。注解可以应用于类、方法、字段等程序元素上。Java内置了一些注解,如@Override
、@Deprecated
等,但Java也允许我们自定义注解。
(二)元注解
在Java中,注解本身也可以被注解修饰,这些用于修饰注解的注解被称为元注解(Meta-Annotation)。元注解主要用于定义注解的属性和行为,为注解提供更丰富的语义和功能。Java提供了几种标准的元注解,这些元注解在自定义注解时非常关键。
(1)@Retention
@Retention
元注解用于指定注解的保留策略,即注解在程序的生命周期中可以被保留到什么阶段。它接受一个RetentionPolicy
枚举值,定义了注解的保留范围:
-
RetentionPolicy.SOURCE
:注解仅在源代码阶段保留,编译时会被丢弃,不会进入字节码文件。这种保留策略通常用于编译时的检查或代码生成工具。 -
RetentionPolicy.CLASS
:注解在编译阶段保留,会被写入字节码文件,但在运行时无法通过反射访问。这种策略适用于一些需要在字节码层面进行处理的场景。 -
RetentionPolicy.RUNTIME
:注解在运行时保留,可以通过反射获取注解的信息。这是AOP和框架(如Spring)中常用的保留策略,因为它们需要在运行时动态地处理注解。
(2)@Target
@Target
元注解用于指定注解可以应用的目标类型。它接受一个ElementType
枚举值,定义了注解可以修饰的程序元素类型:
-
ElementType.TYPE
:注解可以应用于类、接口(包括注解类型)或枚举声明。 -
ElementType.FIELD
:注解可以应用于字段(包括枚举常量)。 -
ElementType.METHOD
:注解可以应用于方法。 -
ElementType.PARAMETER
:注解可以应用于方法或构造器的参数。 -
ElementType.CONSTRUCTOR
:注解可以应用于构造器。 -
ElementType.LOCAL_VARIABLE
:注解可以应用于局部变量。 -
ElementType.ANNOTATION_TYPE
:注解可以应用于注解类型。 -
ElementType.PACKAGE
:注解可以应用于包声明。
(3)@Documented
@Documented
元注解用于指定注解是否会被包含在JavaDoc文档中。当一个注解被@Documented
修饰时,它会在生成的JavaDoc文档中显示。这对于开发框架或库时非常有用,因为它可以帮助开发者更好地理解注解的用途。
(4)@Inherited
@Inherited
元注解用于指定注解是否可以被子类继承。当一个类被某个注解修饰,且该注解被@Inherited
修饰时,其子类也会继承该注解。需要注意的是,@Inherited
仅对类的继承有效,对方法或字段的继承无效。
(5)@Repeatable
@Repeatable
元注解是Java 8引入的,用于支持注解的重复使用。在默认情况下,一个注解在一个程序元素上只能使用一次。通过@Repeatable
,可以在同一个程序元素上多次使用同一个注解。
元注解在Java注解体系中起着关键作用,它们定义了注解的生命周期、适用范围、是否可继承、是否可重复等重要属性。通过合理使用元注解,可以为自定义注解赋予更丰富的语义和行为,使其能够更好地满足开发需求。
在实际开发中,元注解是自定义注解的基础,它们帮助开发者明确注解的使用方式和范围,从而提高代码的可读性和可维护性。同时,我们懂得如何使用常用的注解即可。
(三)自定义注解登录拦截
根据我上一篇文章 Java拦截器应用_java异常拦截器-CSDN博客 拦截器知识。
我们拦截器有时候调节拦截比较吃力,总是需要微调所拦截的控制类
这次我们学会自定义注解后,可以通过注解方式通知系统我们拦截哪个控制器类,不拦截哪个控制器类
定义登录校验拦截的自定义注解
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD,ElementType.TYPE})
public @interface LoginCheck {
String value() default "";
}
定义无需登录校验拦截的自定义注解
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD,ElementType.TYPE})
public @interface SkipLoginCheck {
String value() default "";
}
定义切面类
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
@Slf4j
@Aspect
@Component
public class LoginCheckAspect{
// 定义方法切点
@Pointcut("@annotation(com.angindem.aop.interfaces.LoginCheck)")
private void at() {
}
// 定义类方法切点
@Pointcut("@within(com.angindem.aop.interfaces.LoginCheck)")
private void wt() {
}
// 匹配标注有 @Skip 注解的类或方法
@Pointcut("@within(com.angindem.aop.interfaces.SkipLoginCheck) || @annotation(com.angindem.aop.interfaces.SkipLoginCheck)")
public void st() {
}
// 逻辑拦截,当遇到 Skip 注解忽略拦截
@Before("(at() || wt()) && !st()")
public void befor() {
// 登录拦截逻辑
}
@Around("(at() || wt()) && !st()")
public Object after(ProceedingJoinPoint joinPoint) throws Throwable {
// 调用原来方法执行
return joinPoint.proceed();
}
}
通过以上逻辑,可以直接使用 两个 自定义注解灵活拦截。