反射 + AOP + 注解
侧边栏壁纸
  • 累计撰写 36 篇文章
  • 累计收到 1 条评论

反射 + AOP + 注解

ASN__
2026-06-03 / 0 评论 / 6 阅读 / 正在检测是否收录...

反射 + AOP + 注解 讲解


目录

  1. 注解(Annotation)

    • 什么是注解
    • 元注解与自定义注解
    • 如何在反射中读取注解
  2. 反射(Reflection)

    • 反射的核心类:ClassMethodField
    • 动态创建对象、调用方法、访问字段
    • 反射与注解的结合
  3. AOP(面向切面编程)

    • AOP 的核心概念
    • Spring AOP 的实现原理(代理)
    • 通知类型与切入点表达式
    • 如何在切面中使用反射和注解
  4. 三剑合璧实战案例

    • 案例1:自定义 @Log 日志记录
    • 案例2:自定义 @RequirePermission 权限控制
  5. 总结与关系图谱

一、注解(Annotation)

1.1 什么是注解?

注解是 Java 5 引入的一种元数据,它本身不影响代码逻辑,但可以被编译、运行时工具或框架读取,从而赋予代码额外的语义或行为。

常见注解如 @Override@Deprecated@Autowired 等。

1.2 元注解

定义注解时需要用到的注解,共有四个:

元注解作用
@Target指定注解能用在哪里(类、方法、字段等)
@Retention指定注解的存活周期(源码、字节码、运行时)
@Documented是否包含在 JavaDoc 中
@Inherited是否允许子类继承父类的注解
  • @Retention 的取值:

    • RetentionPolicy.SOURCE:只在源码中存在,编译时丢弃(如 @Override
    • RetentionPolicy.CLASS:在字节码中存在,但运行时不可见(默认)
    • RetentionPolicy.RUNTIME:运行时可通过反射读取(自定义注解必须选这个

1.3 自定义一个注解

import java.lang.annotation.*;

@Target(ElementType.METHOD)            // 只能用在方法上
@Retention(RetentionPolicy.RUNTIME)    // 运行时保留
public @interface Loggable {
    String value() default "";         // 注解属性
}

使用:

@Loggable("删除用户")
public void deleteUser(Long id) { ... }

1.4 在反射中读取注解

无论是类、方法还是字段,反射 API 都提供了读取注解的方法:

  • getAnnotation(Class<T> annotationClass) —— 获取指定类型的注解
  • getAnnotations() —— 获取所有注解(含继承)
  • getDeclaredAnnotations() —— 获取直接声明的注解(不含继承)
  • isAnnotationPresent(Class<?> annotationClass) —— 判断是否存在某注解

示例:读取方法上的 @Loggable

Method method = ...; // 通过反射拿到方法对象
Loggable log = method.getAnnotation(Loggable.class);
if (log != null) {
    System.out.println("描述:" + log.value());
}

二、反射(Reflection)

2.1 反射的核心类

作用获取方式
Class类的元信息对象.getClass()类名.classClass.forName("全类名")
Method方法信息Class 对象的 getMethod(...)getDeclaredMethods()
Field字段信息Class 对象的 getField(...)getDeclaredFields()
Constructor构造器信息Class 对象的 getConstructor(...)getDeclaredConstructors()

2.2 获取 Class 对象的三种方式

// 1. 通过类名.class
Class<String> clz1 = String.class;

// 2. 通过实例的 getClass()
String s = "hello";
Class<?> clz2 = s.getClass();

// 3. 通过完全限定名字符串
Class<?> clz3 = Class.forName("java.lang.String");

2.3 获取 Method 对象

方法说明
getMethod(name, paramTypes...)获取指定名称和参数的 public 方法(含继承)
getMethods()获取所有 public 方法(含继承)
getDeclaredMethod(name, paramTypes...)获取本类中声明的方法(不含继承,可访问私有)
getDeclaredMethods()获取本类中声明的所有方法(不含继承)

示例:获取 deleteUser(Long) 方法

Method method = UserService.class.getMethod("deleteUser", Long.class);

2.4 动态调用方法

UserService target = new UserService();
Method method = UserService.class.getMethod("deleteUser", Long.class);
Object result = method.invoke(target, 123L);  // 相当于 target.deleteUser(123L)

如果方法是私有的,需要先 setAccessible(true)

2.5 反射与注解的结合

反射的 MethodFieldClass 等都实现了 AnnotatedElement 接口,因此可以直接读取注解。

Method method = ...;
if (method.isAnnotationPresent(Loggable.class)) {
    Loggable log = method.getAnnotation(Loggable.class);
    System.out.println("需要记录日志:" + log.value());
}

应用场景

  • 框架(Spring、Hibernate)通过反射扫描注解,完成依赖注入、ORM 映射等。
  • 自定义工具类,根据注解动态生成 SQL、校验参数等。

三、AOP(面向切面编程)

3.1 AOP 核心概念

  • 切面(Aspect):封装横切逻辑的模块(比如日志、权限)。
  • 连接点(JoinPoint):程序执行过程中的一个点(方法调用、字段访问等),Spring AOP 只支持方法连接点。
  • 切入点(Pointcut):匹配连接点的表达式,决定哪些方法会被增强。
  • 通知(Advice):在切入点处执行的动作(具体逻辑)。
  • 目标对象(Target):被代理的原始对象。
  • 代理(Proxy):包裹目标对象,加入切面逻辑后生成的对象。

3.2 Spring AOP 的实现

Spring AOP 基于动态代理

  • 如果目标类实现了接口,默认使用 JDK 动态代理java.lang.reflect.Proxy)。
  • 如果没有接口,则使用 CGLIB 生成子类作为代理。
  • 从 Spring Boot 2.0 开始,默认使用 CGLIB 代理(即使有接口也会用 CGLIB)。

3.3 通知类型与切入点表达式

通知类型

注解说明
@Before目标方法执行前
@AfterReturning目标方法正常返回后
@AfterThrowing目标方法抛出异常后
@After目标方法结束后(类似 finally)
@Around包围目标方法,可控制是否执行

常用切入点表达式

表达式含义
execution(* com.example.service.*.*(..))匹配 service 包下的所有方法
@annotation(com.example.Loggable)匹配带有 @Loggable 注解的方法
execution(public * *(..))匹配所有 public 方法
within(com.example.service.*)匹配 service 包下的所有类的所有方法

3.4 切面中如何使用反射和注解

@Around 通知中,通过 ProceedingJoinPoint 可以获取方法签名(MethodSignature),然后通过反射拿到 Method 对象,进而读取注解。

@Around("@annotation(loggable)")
public Object log(ProceedingJoinPoint pjp, Loggable loggable) throws Throwable {
    // 获得方法签名
    MethodSignature signature = (MethodSignature) pjp.getSignature();
    Method method = signature.getMethod();  // 反射 Method 对象

    // 可以读取更多注解
    AnotherAnnotation anno = method.getAnnotation(AnotherAnnotation.class);

    // 执行原方法
    return pjp.proceed();
}

为什么要 MethodSignature
因为 pjp.getSignature() 返回的是通用的 Signature 接口,只有强转成 MethodSignature 才能获取反射 Method 以及参数名、返回类型等详细信息。

3.5 JoinPoint 信息树(速查)

JoinPoint (ProceedingJoinPoint 继承它)
├─ getTarget()           → 目标对象(被代理的原始 Bean)
├─ getThis()             → 当前代理对象
├─ getArgs()             → 方法参数值数组
├─ getSignature()        → Signature (实际是 MethodSignature)
│   ├─ getName()         → 方法名
│   ├─ getDeclaringTypeName() → 声明该方法的类全名
│   └─ (MethodSignature 特有)
│       ├─ getMethod()           → 反射 Method ★
│       │   └─ getAnnotation(..)  → 获取注解
│       ├─ getParameterNames()   → 参数名数组(需编译配置)
│       ├─ getReturnType()       → 返回类型
│       └─ getParameterTypes()   → 参数类型数组
└─ getStaticPart()       → 同 getSignature()

四、三剑合璧实战案例

4.1 案例 1:@Log 注解实现方法日志记录

需求:在方法执行时自动打印入参、出参和耗时。

步骤

  1. 自定义 @Log 注解
  2. 编写切面 LogAspect,使用 @Around + @annotation
  3. 在切面中通过 MethodSignature 获取方法名,通过 getArgs() 获取参数值
// 注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Log 

// 切面
@Aspect
@Component
public class LogAspect {
    @Around("@annotation(com.example.Log)")
    public Object around(ProceedingJoinPoint pjp) throws Throwable {
        MethodSignature sig = (MethodSignature) pjp.getSignature();
        String method = sig.getDeclaringTypeName() + "." + sig.getName();
        Object[] args = pjp.getArgs();
        System.out.println(">>> 调用 " + method + ",参数:" + Arrays.toString(args));

        long start = System.currentTimeMillis();
        Object result = pjp.proceed();
        long time = System.currentTimeMillis() - start;

        System.out.println("<<< 返回:" + result + " 耗时:" + time + "ms");
        return result;
    }
}

// 业务使用
@Service
public class UserService {
    @Log
    public String getUser(Long id) { return "User#" + id; }
}

4.2 案例 2:@RequirePermission 权限控制

需求:标记方法需要某权限,没有则拒绝访问。

实现思路

  1. 定义注解 @RequirePermission("user:delete")
  2. 在拦截器中把用户权限存入 ThreadLocal
  3. 切面中读取注解所需的权限值,与用户权限对比,不满足抛异常
// 注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RequirePermission {
    String value();
}

// 权限上下文
public class UserContext {
    private static final ThreadLocal<List<String>> PERMISSIONS = new ThreadLocal<>();
    public static void set(List<String> p) { PERMISSIONS.set(p); }
    public static List<String> get() {
        return PERMISSIONS.get() != null ? PERMISSIONS.get() : Collections.emptyList();
    }
    public static void clear() { PERMISSIONS.remove(); }
}

// 切面
@Aspect
@Component
public class PermissionAspect {
    @Around("@annotation(RequirePermission)")
    public Object check(ProceedingJoinPoint pjp) throws Throwable {
        MethodSignature sig = (MethodSignature) pjp.getSignature();
        Method method = sig.getMethod();
        RequirePermission permission = method.getAnnotation(RequirePermission.class);
        String required = permission.value();

        if (!UserContext.get().contains(required)) {
            throw new RuntimeException("无权限:" + required);
        }
        return pjp.proceed();
    }
}

// 拦截器(简化)
public class AuthInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, ...) {
        String user = request.getHeader("X-User");
        List<String> permissions = loadPermissions(user); // 从数据库查
        UserContext.set(permissions);
        return true;
    }
    @Override
    public void afterCompletion(...) { UserContext.clear(); }
}

五、总结与关系图谱

三者关系

  • 注解 是标记,贴在代码上描述元数据。
  • 反射 是读取这些标记和操作代码结构(类、方法)的能力。
  • AOP 是利用反射机制,在运行时动态地将横切逻辑织入到带有特定注解(或符合表达式)的方法周围。

常见协作模式

  1. 自定义一个注解(如 @Log
  2. 在 AOP 切面中,通过切入点表达式 @annotation(Log) 拦截所有标注了该注解的方法
  3. 在切面通知方法里,利用 JoinPoint/ProceedingJoinPoint 获取方法签名(MethodSignature
  4. 通过 MethodSignature.getMethod() 拿到反射 Method 对象,再用 getAnnotation() 读取注解的具体值,实现差异化逻辑

学习路径建议

  1. 先熟练 反射ClassMethod、注解读取)
  2. 理解 代理模式(JDK 动态代理、CGLIB)
  3. 掌握 Spring AOP 的基本用法(切入点表达式、通知类型)
  4. 最后将三者结合,实现自定义注解驱动切面

当你遇到如下代码时,大脑能快速解析:

MethodSignature sig = (MethodSignature) joinPoint.getSignature();
Method method = sig.getMethod();
MyAnnotation ann = method.getAnnotation(MyAnnotation.class);

它正在用 AOP 的连接点 获取 反射的 Method,然后 读取自定义注解,从而执行相应逻辑——这。

0

评论

博主关闭了所有页面的评论