当前位置: 首页 > news >正文

一文讲解反射、注解

在 Java 的世界里,反射注解 是两大经常被提起却容易被初学者忽视的知识点。

  • 反射让我们能够在 运行时 动态获取类的信息,并对类的属性、方法、构造器等进行操作。它就像给 Java 提供了一把“万能钥匙”,能打开封装背后的世界。

  • 注解则是 Java5 引入的一种 元数据机制,它让我们能够在代码中添加描述信息,并配合反射或框架解析,完成自动化逻辑处理。在 Spring、MyBatis、Junit 等框架中,注解几乎无处不在。

如果说反射是“动态操作”的基石,那么注解就是“声明式编程”的灵魂。两者结合,才能构建出我们今天常用的简洁、高效、可扩展的开发模式。本篇文章将带你从零开始,依次理解反射和注解以及真实使用场景。

一、反射机制

在 Java 的世界里,反射(Reflection) 是一个非常重要的机制。它赋予程序在 运行时 检查和操作类、接口、方法、属性的能力。下面,我们将分几个方面来详细理解反射机制。


1.1 认识反射

所谓反射,是指在程序运行过程中,能够动态地获取类的信息,并能对类的成员(构造器、方法、变量)进行访问和操作。

在 Java 中,反射的核心类是 java.lang.Class,通过它我们可以拿到任何类的字节码对象,从而做进一步的操作。

获取 Class 对象的方式常见有三种:

// 方式 1:类名.class
Class<?> clazz1 = User.class;// 方式 2:对象.getClass()
User user = new User();
Class<?> clazz2 = user.getClass();// 方式 3:Class.forName("包名.类名")
Class<?> clazz3 = Class.forName("com.example.User");

1.2 反射:获取类的构造器

在运行时,我们可以通过反射获取类的构造器。相关 API 在 java.lang.reflect.Constructor 类中。

常用方法:

// 获取所有构造器(只能获取public修饰的)
Constructor<?>[] constructors = clazz.getConstructors();// 获取所有构造器(包括 public、protected、default、private)
Constructor<?>[] constructors = clazz.getDeclaredConstructors();// 获取单个构造器(只能获取public修饰的)
Constructor<?> constructor = clazz.getConstructor(Class<?>... paramterTypes);// 获取单个构造器(所有权限修饰符)
Constructor<?> declaredConstructor = clazz.getDeclaredConstructor(Class<?>... paramterTypes);

1.3 反射:获取类的构造器的作用

  1. 创建对象:即使构造器是 private,通过反射也能强行访问。

  2. 突破封装:可以实例化一些不对外暴露构造方法的类(如单例)。

  3. 灵活性:框架中常用来根据配置文件动态创建对象,而不用写死在代码里。

示例代码:

Class<?> clazz = Class.forName("com.example.User");
// 获取无参构造器
Constructor<?> constructor = clazz.getDeclaredConstructor();
// 如果是 private,需要设置可访问
constructor.setAccessible(true);
// 创建对象
Object obj = constructor.newInstance();

1.4 反射:获取类的成员变量

成员变量由 java.lang.reflect.Field 表示。

常用方法:

// 获取所有 public 字段
Field[] fields = clazz.getFields();// 获取所有字段(包括 private)
Field[] declaredFields = clazz.getDeclaredFields();// 获取某个字段
Field field = clazz.getDeclaredField("name");// 操作字段
field.setAccessible(true); // 取消权限检查
field.set(obj, "张三");     // 给对象属性赋值
Object value = field.get(obj); // 获取属性值

1.5 反射:获取类的成员方法

成员方法由 java.lang.reflect.Method 表示。

常用方法:

// 获取所有 public 方法(包括继承来的)
Method[] methods = clazz.getMethods();// 获取所有方法(包括 private)
Method[] declaredMethods = clazz.getDeclaredMethods();// 获取指定方法
Method method = clazz.getDeclaredMethod("setName", String.class);// 调用方法
method.setAccessible(true);
method.invoke(obj, "李四");

1.6 反射:应用场景

  • 框架设计

    • Spring IOC 容器:通过反射动态创建和注入 Bean。

    • MyBatis:通过反射给对象的字段赋值。

  • 动态代理

    • JDK 动态代理底层依赖反射来调用目标方法。

  • 工具类

    • JSON 解析库(如 Jackson、Fastjson)通过反射获取对象属性并赋值。

  • 灵活配置

    • 某些类名、方法名从配置文件里读取后,通过反射动态加载和执行。

  • 测试和调试

    • 反射可以访问和修改类的私有成员,在单元测试中很有用。

二、注解

在 Java 语言的发展历程中,注解(Annotation) 是一个非常重要的特性。它不仅能让代码更加简洁易读,还在框架、工具、编译器优化等方面起着至关重要的作用。

1.1 注解:认识注解

1.1.1 什么是注解

注解(Annotation)是一种 特殊的标记,它可以添加到 类、方法、属性、参数、局部变量 上,用于在 编译期运行时 被工具或框架解析,并执行相应的逻辑。

注解不会改变代码本身的逻辑,但可以携带信息,帮助开发者或程序做更多事。

👉 举个例子:

public class User {@Deprecated // 表示这个方法已过时public void oldMethod() {System.out.println("Old method...");}@Override // 表示这是一个重写方法public String toString() {return "User{}";}
}
1.1.2 注解的作用
  • 编译检查:如 @Override,帮助编译器检查方法是否正确重写。

  • 代码生成:如 Lombok 的 @Data,编译期自动生成 getter/setter。

  • 配置驱动:如 Spring 的 @Autowired@Service,通过注解完成依赖注入与配置。

  • 运行时逻辑增强:如 JUnit 的 @Test,运行时通过反射执行测试方法。


2.2 注解:元注解

元注解(Meta-Annotation) 是作用在 注解上的注解,用于描述注解的行为特征。Java 提供了 4 个核心元注解:

2.2.1 @Target
  • 作用:限定注解可以放在哪些位置。

  • 取值(ElementType 枚举):

    • TYPE:类、接口、枚举

    • FIELD:字段

    • METHOD:方法

    • PARAMETER:方法参数

    • CONSTRUCTOR:构造器

示例:

@Target(ElementType.METHOD) // 只能用在方法上
public @interface MyMethodAnno {}

2.2.2 @Retention
  • 作用:定义注解的生命周期。

  • 取值(RetentionPolicy 枚举):

    • SOURCE:只存在于源码中,编译后丢弃(如 @Override)。

    • CLASS:编译期保留到字节码,运行时丢弃(默认)。

    • RUNTIME:运行时也保留,可通过反射获取(如 Spring 注解)。

示例:

@Retention(RetentionPolicy.RUNTIME) // 运行时可获取 public @interface MyRuntimeAnno {}

2.2.3 @Documented
  • 作用:是否将注解信息写入 javadoc 文档。

@Documented

public @interface ApiDoc {}


2.2.4 @Inherited
  • 作用:注解是否能被子类继承。

  • 默认情况下,类上的注解不会自动继承。

@Inherited

@Retention(RetentionPolicy.RUNTIME)

@interface ParentAnno {}

@ParentAnno

class Parent {}

class Child extends Parent {} // Child 自动继承 ParentAnno


三、场景实例(苍穹外卖-公共字段自动填充)

在数据库表中,我们通常有一些 公共字段,比如:

  • createTime(创建时间)

  • createUser(创建人)

  • updateTime(修改时间)

  • updateUser(修改人)

这些字段在 INSERTUPDATE 时都需要自动填充,否则每次都手动写,很繁琐。
以下代码就是通过 AOP + 自定义注解 + 反射 来实现 公共字段自动填充

3.1 枚举类 OperationType

public enum OperationType {UPDATE,INSERT
}

作用:定义了两种数据库操作类型,方便注解传参。

  • INSERT → 插入操作

  • UPDATE → 更新操作

3.2 注解 @AutoFill

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface AutoFill {OperationType value();
}

作用:

  • 可以标注在 方法 上(比如 Mapper 接口的 insertupdate 方法)。

  • value() 参数用来指定当前方法的数据库操作类型(INSERTUPDATE)。

👉 这样 Mapper 层方法上就可以写:

@AutoFill(OperationType.INSERT)
void insert(Employee employee);@AutoFill(OperationType.UPDATE)
void update(Employee employee);

3.3 切面类 AutoFillAspect

这是核心逻辑,使用 AOP 实现公共字段填充。

3.3.1 切点定义
@Pointcut("execution(* com.sky.mapper.*.*(..)) && @annotation(com.sky.annotation.AutoFill)")
public void autoFillPointCut(){}

含义:

  • 拦截 com.sky.mapper 包下所有方法;

  • 且这些方法必须带有 @AutoFill 注解。


3.3.2 前置通知
@Before("autoFillPointCut()")
public void autoFill(JoinPoint joinPoint)
  • 在目标方法执行前,进行公共字段填充。

  • 获取方法上的注解 → 确认是 INSERT 还是 UPDATE

  • 获取方法的第一个参数(通常是实体类,比如 Employee)。

3.3.3 反射赋值

情况 1:INSERT

if(operationType == OperationType.INSERT){Method setCreateTime = entity.getClass().getDeclaredMethod("setCreateTime", LocalDateTime.class);Method setCreateUser = entity.getClass().getDeclaredMethod("setCreateUser", Long.class);Method setUpdateTime = entity.getClass().getDeclaredMethod("setUpdateTime", LocalDateTime.class);Method setUpdateUser = entity.getClass().getDeclaredMethod("setUpdateUser", Long.class);setCreateTime.invoke(entity, now);setCreateUser.invoke(entity, currentId);setUpdateTime.invoke(entity, now);setUpdateUser.invoke(entity, currentId);
}

插入时,需要填充 4 个字段:创建时间、创建人、更新时间、更新人


情况 2:UPDATE

else if(operationType == OperationType.UPDATE){Method setUpdateTime = entity.getClass().getDeclaredMethod("setUpdateTime", LocalDateTime.class);Method setUpdateUser = entity.getClass().getDeclaredMethod("setUpdateUser", Long.class);setUpdateTime.invoke(entity, now);setUpdateUser.invoke(entity, currentId);
}

更新时,只需要填充:更新时间、更新人

执行过程是:

  1. AOP 切面拦截到 insert 方法。

  2. 读取方法上的 @AutoFill(OperationType.INSERT)

  3. 发现是 INSERT → 通过反射自动给 employee 设置:createTime = 当前时间;createUser = 当前登录用户ID;updateTime = 当前时间;updateUser = 当前登录用户ID

  4. 最后再去执行真正的 SQL 插入。

👉 开发者在写 Mapper 时,就不需要每次都写这些公共字段的赋值逻辑。

AOP切面类的具体代码:

/*** 自定义切面类 , 实现公共字段自动填充处理逻辑*/
@Aspect
@Component
@Slf4j
public class AutoFillAspect {
​/*** 切入点*/@Pointcut("execution(* com.sky.mapper.*.*(..)) && @annotation(com.sky.annotation.AutoFill)") // 切点表达式 : 对那些方法进行拦截public void autoFillPointCut(){
​}
​/*** 用前置通知 : 在通知中进行公共字段赋值*/@Before("autoFillPointCut()")public void autoFill(JoinPoint joinPoint){ // 参数 : 连接点log.info("开始进行公共字段的自动填充");
​// 获取到当前被拦截的方法上的数据库操作类型MethodSignature signature = (MethodSignature) joinPoint.getSignature(); // 方法签名对象AutoFill autoFill = signature.getMethod().getAnnotation(AutoFill.class); // 获得方法上的注释对象OperationType operationType = autoFill.value(); // 获取数据库操作类型
​// 获取当前被拦截的方法的参数 -- 实体对象(比如employee)Object[] args = joinPoint.getArgs(); // 获取所有参数if(args == null || args.length == 0){return ;}
​Object entity = args[0];// 获取第一个实体
​// 准备赋值数据LocalDateTime now = LocalDateTime.now() ;Long currentId = BaseContext.getCurrentId();
​
​// 根据当前不同的操作类型,为对应的属性通过反射来进行赋值if(operationType == OperationType.INSERT){// 为4个公共字段赋值 -- 通过反射来进行赋值// 获取Set方法try {
//                Method setCreateTime = entity.getClass().getDeclaredMethod("setCreateTime" , LocalDateTime.class);
//                Method setCreateUser = entity.getClass().getDeclaredMethod("setCreateUser" , Long.class);
//                Method setUpdateTime = entity.getClass().getDeclaredMethod("setUpdateTime", LocalDateTime.class);
//                Method setUpdateUser = entity.getClass().getDeclaredMethod("setUpdateUser", Long.class);
​// 用定义好的常量实现Method setCreateTime = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_CREATE_TIME, LocalDateTime.class);Method setCreateUser = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_CREATE_USER, Long.class);Method setUpdateTime = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_TIME, LocalDateTime.class);Method setUpdateUser = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_USER, Long.class);
​//通过反射为对象属性赋值setCreateTime.invoke(entity,now);setCreateUser.invoke(entity,currentId);setUpdateTime.invoke(entity,now);setUpdateUser.invoke(entity,currentId);
​} catch (Exception e) {e.printStackTrace();}}else if(operationType == OperationType.UPDATE){// 为2个公共字段赋值try {Method setUpdateTime = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_TIME, LocalDateTime.class);Method setUpdateUser = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_USER, Long.class);
​//通过反射为对象属性赋值setUpdateTime.invoke(entity,now);setUpdateUser.invoke(entity,currentId);} catch (Exception e) {e.printStackTrace();}}
​
​}
}

http://www.dtcms.com/a/427032.html

相关文章:

  • 学习日报 20250930|优惠券事务处理模块
  • 【Nest.js】模块之间依赖关系,以及导出导入链的完整性
  • MyBatis —— 多表操作和注解开发
  • 自动化脚本的自动化执行实践
  • 有颜二维码 1.0.5| 告别单调,一键生成有颜色的二维码
  • 信创浪潮下的国产组态软件——紫金桥RealSCADA
  • 做网站新闻移动动态网络规划设计师资料及视频教程
  • 机器学习之三大学习范式:监督学习、无监督学习、强化学习
  • 18002.机器人电机姿态控制
  • mysql语句基本操作之select查询
  • 做mp3链接的网站宁波专业seo外包
  • Spring Boot 集成 EHCache 缓存解决方案
  • Spring Boot 缓存与验证码生成
  • 进攻------绕后------互换野区
  • Unity 3D笔记(脚本部分)——《B站阿发你好》
  • C++之类的组合
  • 服装购物网站策划书wordpress菜单栏移动下移
  • 【第五章:计算机视觉-项目实战之生成对抗网络实战】1.对抗生成网络原理-(1)对抗生成网络算法基础知识:基本思想、GAN的基本架构、应用场景、标注格式
  • win10软实时设置
  • leetcode 812. 最大三角形面积 简单
  • 机器学习+数字孪生:从诊断到自主决策的跨越
  • 若依前后端分离版学习笔记(十八)——页面权限,页签缓存以及图标,字典,参数的使用
  • 莱芜网站建设哪家好在线logo制作生成免费
  • 哈尔滨网站建设价格网站设计怎么学
  • 再发《管理世界》!智能制造试点DID(2000-2023)
  • SpringCloudGateway:像快递分拣中心一样的API网关
  • 真家宽IP vs 数据中心IP:Cliproxy为何成为跨境电商首选?
  • 声光可调滤光器(AOTF):光谱相机的“电子调谐旋钮”
  • skynet-socket.lua源码分析
  • 悠然无界大模型BLM-1.0:跨空间、跨任务与跨本体泛化的里程碑