元注解
元注解的作用就是负责注解其他注解,Java 5.0定义了 4个meta-annotation 类型,用来提供对爱他的 annotation 类型做说明。
java.lang.annotation
@Target
@Retention
@Document
@Inhrited
@Target
修饰的对象范围:packages、types(类、接口、枚举、Annotation类型)、类型成员(方法、构造方法、成员变量、枚举值)、方法参数和本地变量(如循环变量、catch参数)。
作用:用于描述注解的使用范围。
ElementType 取值:
1. CONSTRUCTOR:用于描述构造器
2. FIELD:用于描述域
3. LOCAL_VARIABLE:用于描述局部变量
4. METHOD:用于描述方法
5. PACKAGE:用于描述包
6. PARAMETER:用于描述参数
7. TYPE:用于描述类、接口(包括注解类型) 或enum声明
例如,Name 可以注解类的成员变量:
@Target(ElementType.FIELD)@Documented public @interface Name { String value() default ""; }Person 可以注解类、接口(包括注解类型)、或者enum声明:
@Target(ElementType.TYPE) public @interface Person { String value() default ""; }@Retention
定义了该 Annotation 被保留的时间长短:某些 Annotation 仅出现在源代码中,而被编译器丢弃;而另一些却被编译在class文件中;编译在class文件中的Annotation可能会被虚拟机忽略,而另一些在 class 被装载时将被读取(请注意并不影响 class 的执行,因为 Annotation 与 class 在使用上是被分离的)。使用这个 meta-Annotation 可以对 Annotation 的“生命周期”限制。
作用:表示需要在什么级别保存该注释信息,用于描述注解的生命周期(即:被描述的注解在什么范围内有效)
RetentionPoicy取值:
SOURCE:在源文件中有效(即源文件保留)
CLASS:在class文件中有效(即class保留)
RUNTIME:在运行时有效(即运行时保留)
例如:Name 注解的 RetentionPolicy 的值为 RUNTIME,这样注解处理器可以通过反射,获取到该注解的属性,从而做一些运行时的逻辑处理。
@Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) public @interface Name { String value() default ""; }@Document
用于描述其它类型的 annotation 应该被作为被标注的程序成员的公共API,因此可以被例如 javadoc 此类的工具文档化。Documented 是一个标记注解,没有成员。
作用:表示需要在什么级别保存该注释信息,用于描述注解的生命周期(即:被描述的注解在什么范围内有效)。
@Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface Name { String value() default ""; }@Inhrited
是一个标记注解,@Inherited 阐述了某个被标注的类型是被继承的。如果一个使用了@Inherited 修饰的 annotation类型 被用于一个 class,则这个 annotation 将被用于该class的子类。
@Inherited annotation类型 是被标注过的class的子类所继承。类并不从它所实现的接口继承annotation,方法并不从它所重载的方法继承annotation
当 @Inherited annotation类型 标注的 annotation 的 Retention 是 RetentionPolicy.RUNTIME,则反射API增强了这种继承性。如果我们使用java.lang.reflect 去查询一个 @Inherited annotation类型 的 annotation 时,反射代码检查将展开工作:检查class和其父类,直到发现指定的 annotation 类型被发现,或者到达类继承结构的顶层。
自定义注解
使用 @interface 自定义注解时,自动继承了 java.lang.annotation.Annotation 接口,由编译程序自动完成其他细节。在定义注解时,不能继承其他的注解或接口。@interface 用来声明一个注解,其中的每一个方法实际上是声明了一个配置参数。方法的名称就是参数的名称,返回值类型就是参数的类型(返回值类型只能是 基本类型、Class、String、enum)。可以通过 default 来声明参数的默认值。
自定义注解格式:
public @interface 注解名{注解体}
所有基本数据类型(int,float,boolean,byte,double,char,long,short)
String类型
Class类型
enum类型
Annotation类型
以上所有类型的数组
Annotation类型里面的参数该怎么设定:
第一,只能用 public 或 默认(default) 这两个访问权修饰.例如,String value();这里把方法设为 defaul 默认类型;
第二,参数成员只能用基本类型 byte,short,char,int,long,float,double,boolean 八种基本数据类型 和 String,Enum,Class,annotations 等数据类型,以及这一些类型的数组.例如,String value();这里的参数成员就为String;
第三,如果只有一个参数成员,最好把参数名称设为"value",后加小括号.
1.创建一个自定义注解
2.定义切面类
3.接上添加注解
打印日志
切点访问顺序
异常情况 (最终通知报错之后依然可以执行)
4.代码粘贴如下
自定义注解
package com.jlink.common.annotation;
import java.lang.annotation.*;
/**
* @author jyg
* @create 2024-07-16-11:25
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyAnnotation {
String text() default "";
}
定义切面类
package com.jlink.framework.aspectj;
import com.jlink.common.annotation.MyAnnotation;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
/**
* @author jyg
* @create 2024-07-16-13:19
*/
@Aspect
@Component
public class MyAnnotationAspect {
private static final Logger log = LoggerFactory.getLogger(RateLimiterAspect.class);
// 定义通用的表达式切点
@Pointcut("@annotation(com.jlink.common.annotation.MyAnnotation)")
public void pointcut() {
// 方法只是一个标记,方法名随意,方法体也不需要写任何代码
}
/**
* 前置通知
*
* @param point
*/
@Before("pointcut()") // 切面表达式
public void doBefore(JoinPoint point) {
// 这里可以获取到连接点的信息,如方法名、参数等
MethodSignature signature = (MethodSignature) point.getSignature();
// 但是这种方法只能通过反射机制获取注解
MyAnnotation myAnnotation = signature.getMethod().getAnnotation(MyAnnotation.class);
System.out.println("接口访问前,入参的值:" + myAnnotation.text());
}
/**
* 后置通知
*
* @param joinPoint 切点
*/
// pointcut 属性定义了切点表达式,用于指定哪些方法应该被拦截。
// returning 属性(可选)用于指定一个参数名,该参数将绑定到目标方法的返回值上。
@AfterReturning(pointcut = "@annotation(myAnnotation)", returning = "result")
public void doAfterReturning(JoinPoint joinPoint, MyAnnotation myAnnotation, Object result) {
// 注解的值
String text = myAnnotation.text();
System.out.println("接口访问完了" + text);
// joinPoint 参数包含了被拦截方法的详细信息
// result 参数包含了被拦截方法的返回值
System.out.println(
"Method "
+ joinPoint.getSignature().getName()
+ " executed successfully. Returned: "
+ result);
}
/**
* 环绕通知
*
* @param proceedingJoinPoint proceedingJoinPoint
* @param myAnnotation myAnnotation
* @throws Throwable
*/
@Around("@annotation(myAnnotation)")
public void doAround(ProceedingJoinPoint proceedingJoinPoint, MyAnnotation myAnnotation)
throws Throwable {
// 方法需要接受一个ProceedingJoinPoint类型的参数
String before = proceedingJoinPoint.getSignature().getName();
System.out.println("环绕通知前" + before);
// 调用目标方法
Object proceed = proceedingJoinPoint.proceed();
// 在这里可以添加后置逻辑,比如打印方法调用后的信息或处理返回值
String after = proceedingJoinPoint.getSignature().getName();
System.out.println("环绕通知后" + after);
}
/**
* 异常通知
*
* @param joinPoint 连接点
* @param ex 异常
* @param myAnnotation 注解
*/
@AfterThrowing(pointcut = "@annotation(myAnnotation)", throwing = "ex")
public void logAfterThrowing(JoinPoint joinPoint, Exception ex, MyAnnotation myAnnotation) {
// 在这里记录日志、发送警报或其他处理异常的代码
System.out.println(
"异常就走这里------Exception in method " + joinPoint.getSignature().getName() + ": " + ex.getMessage());
}
/**
* 最终通知
*
* @param myAnnotation
*/
@After("@annotation(myAnnotation)")
public void afterAdvice(MyAnnotation myAnnotation) {
// 这里的代码会在 MyAnnotation 方法执行后执行
// 无论是正常执行完成还是抛出异常
System.out.println("最终通知------Method executed.");
}
}