一文讲解反射、注解
在 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 反射:获取类的构造器的作用
创建对象:即使构造器是
private
,通过反射也能强行访问。突破封装:可以实例化一些不对外暴露构造方法的类(如单例)。
灵活性:框架中常用来根据配置文件动态创建对象,而不用写死在代码里。
示例代码:
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
(修改人)
这些字段在 INSERT
和 UPDATE
时都需要自动填充,否则每次都手动写,很繁琐。
以下代码就是通过 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 接口的
insert
或update
方法)。value()
参数用来指定当前方法的数据库操作类型(INSERT
或UPDATE
)。
👉 这样 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);
}
更新时,只需要填充:更新时间、更新人
执行过程是:
AOP 切面拦截到
insert
方法。读取方法上的
@AutoFill(OperationType.INSERT)
。发现是
INSERT
→ 通过反射自动给employee
设置:createTime
= 当前时间;createUser
= 当前登录用户ID;updateTime
= 当前时间;updateUser
= 当前登录用户ID最后再去执行真正的 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();}}
}
}