1. 反射机制概述

Java 的反射机制允许程序在运行时获取类的信息,并对其进行操作,而无需在编译时知道具体的类结构。简单来说,反射使得程序可以动态加载类、检查类的成员(字段、方法、构造函数等)、调用方法和创建对象。正因为这种动态性,反射在很多框架(如 Spring、Hibernate)、依赖注入、动态代理等领域中被广泛使用。


2. 基本使用

2.1 获取 Class 对象

反射的入口是 java.lang.Class 对象,获取方式主要有三种:

  1. 使用 .class 语法:

    java

    复制编辑

    Class<String> clazz1 = String.class;

  2. 通过对象的 getClass() 方法:

    java

    复制编辑

    String str = "Hello"; Class<?> clazz2 = str.getClass();

  3. 使用 Class.forName() 方法:
    适合动态加载类,传入类的全限定名。

    java

    复制编辑

    Class<?> clazz3 = Class.forName("java.lang.String");

2.2 访问字段(Field)

可以通过 getField()(仅限 public 字段)或 getDeclaredField()(包括 private 字段)来获取字段对象,并通过 Field.set()Field.get() 进行读写操作。

  • 示例:访问 public 字段

    java

    复制编辑

    class Person { public String name; } Person p = new Person(); Field nameField = p.getClass().getField("name"); nameField.set(p, "Alice"); System.out.println(p.name); // 输出:Alice

  • 示例:访问 private 字段

    java

    复制编辑

    class Person { private int age; } Person p = new Person(); Field ageField = p.getClass().getDeclaredField("age"); ageField.setAccessible(true); // 关闭访问控制检查 ageField.set(p, 30); System.out.println(ageField.get(p)); // 输出:30

2.3 调用方法(Method)

同样地,利用 getMethod()getDeclaredMethod() 分别获取 public 和所有方法(包括 private),通过 Method.invoke() 来调用方法。

  • 示例:调用 public 方法

    java

    复制编辑

    class Dog { public void bark() { System.out.println("Woof!"); } } Dog dog = new Dog(); Method barkMethod = dog.getClass().getMethod("bark"); barkMethod.invoke(dog); // 输出:Woof!

  • 示例:调用 private 方法

    java

    复制编辑

    class Dog { private void secret() { System.out.println("Secret method invoked!"); } } Dog dog = new Dog(); Method secretMethod = dog.getClass().getDeclaredMethod("secret"); secretMethod.setAccessible(true); secretMethod.invoke(dog); // 输出:Secret method invoked!

2.4 构造函数和对象创建(Constructor)

通过反射也可以调用构造函数来创建对象,即使构造函数是 private 的

  • isVarArgs : 是否泛型
  • getParamaterTypes: 以Class数组形式按照声明顺序获得构造方法的各个参数类型
  • getExceptionTypes: 以Class数组形式获得该构造方法可能抛出的异常
  • newInstance() : 通过对应参数的构造方法调用 public 或者 protected 构造函数
  • setAccessible() : 设置权限(true, false)
  • getModifiers() : 获得可以解析出该构造方法所修饰的整数

Modifiers类:

  • isPublic
  • isProtected
  • isPrivate
  • isStatic
  • isFinal
  • toString
  • 示例:通过构造函数创建对象
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
import java.io.*;  
import java.lang.reflect.Constructor;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.*;

public class ex1 {
String s;
int i, i2, i3;
private ex1() {

}
protected ex1(String s, int i) {
this.s = s;
this.i = i;
}
public ex1(String... strings) throws NumberFormatException {
if (strings.length > 0) {
i = Integer.valueOf(strings[0]);
} if (strings.length > 1) {
i = Integer.valueOf(strings[1]);
} if (strings.length > 2) {
i = Integer.valueOf(strings[2]);
} }
public void print() {
System.out.println("s = " + s);
System.out.println("i = " + i);
System.out.println("i2 = " + i2);
System.out.println("i3 = " + i3);
}
}

public class Main {
public static void main(String[] args) {
ex1 e = new ex1("10", "20", "30");
Class<? extends ex1> exc = e.getClass();
Constructor[] declareConstructors = e.getClass().getDeclaredConstructors();

for (int i = 0; i < declareConstructors.length; i++) {
Constructor<?> constructor = declareConstructors[i];
System.out.println("查看是否可变化参数变量:" + constructor.isVarArgs());

System.out.println("构造方法的参数依次是:");
Class[] parameterTypes = constructor.getParameterTypes();
for (var x : parameterTypes) {
System.out.println(" " + x);
}
System.out.println("可能抛出的异常");
Class[] exceptionTypes = constructor.getExceptionTypes();
for (var x : exceptionTypes) {
System.out.println(" " + x);
}
ex1 ex2 = null;
while (ex2 == null) {
try {
if (i == 2) {
ex2 = (ex1)constructor.newInstance();
} else if (i == 1) {
ex2 = (ex1)constructor.newInstance("7", 5);
} else {
Object[] tp = new Object[]{new String[]{ "100", "200", "300" }};
ex2 = (ex1)constructor.newInstance(tp);
}
} catch(Exception err) {
System.out.println("访问量私有构造方法,下面执行setAccessible方法");
constructor.setAccessible(true);
}
}
if (ex2 != null) {
ex2.print();
System.out.println();
}
}
}

}

3. 实现原理

Java 反射机制依赖于 JVM 在加载类时将类的元数据信息(如类名、字段、方法、构造函数、注解等)存储在内存中的 Class 对象中。反射 API 就是通过读取这些元数据实现动态操作的:

  • 类元数据:
    每个被加载的类都有一个对应的 Class 对象,它包含了所有类的结构信息。这个信息主要来源于编译后的 .class 文件。

  • 安全检查:
    通过反射操作时(例如访问 private 字段),JVM 会进行安全检查。如果存在安全管理器(SecurityManager),在调用 setAccessible(true) 时会验证权限,防止随意突破封装。

  • 动态调用:
    当你调用 Method.invoke() 时,JVM 内部会动态查找对应的方法并执行。由于这种动态查找机制,相比于直接调用方法,反射通常要慢一些。

  • 优化手段:
    为了减少性能损失,常见的做法是缓存反射得到的 MethodFieldConstructor 对象。此外,Java 7 引入了 MethodHandle,通过这种方式可以获得比传统反射更高效的调用。

Java 注解(Annotation)详解

1. 什么是 Annotation(注解)?

注解(Annotation) 是 Java 语言提供的一种元数据(Metadata)机制,可以用来标注代码,为编译器、运行时环境或其他工具提供额外的信息。

注解本身不会影响代码的逻辑运行,它们通常用于:

  • 编译时检查(如 @Override
  • 代码生成(如 Lombok 的 @Getter@Setter
  • 运行时处理(如 Spring 框架的 @Autowired

2. 注解的常见作用

  1. 提供信息给编译器
    • 帮助编译器检查错误,例如 @Override
  2. 在运行时进行反射操作
    • 通过反射获取注解信息,实现配置驱动开发,如 Spring 的 @Component
  3. 生成代码
    • 例如 Lombok 自动生成 getter/setter 方法
  4. 替代 XML 配置
    • 例如 Spring Boot 用 @Configuration 代替 XML 进行 Bean 配置

3. 常见的 Java 内置注解(标准注解)

(1)编译时注解

注解名 作用
@Override 标记一个方法重写了父类方法,避免拼写错误
@Deprecated 标记方法已过时,调用时会有警告
@SuppressWarnings 关闭编译器警告,如 @SuppressWarnings("unchecked")
@SafeVarargs 让编译器忽略泛型参数的类型安全警告

(2)元注解(用于定义自定义注解)

注解名 作用
@Retention 指定注解的生命周期(SOURCE / CLASS / RUNTIME)
@Target 指定注解可用于哪些元素(类、方法、字段等)
@Documented 使注解出现在 JavaDoc 文档中
@Inherited 让注解可被子类继承

4. 自定义注解

(1)定义一个简单的注解

java

复制编辑

import java.lang.annotation.*; @Retention(RetentionPolicy.RUNTIME) // 运行时可用 @Target(ElementType.METHOD) // 只能用于方法 public @interface MyAnnotation { String value() default "默认值"; // 注解的参数 }

(2)在方法上使用该注解

java

复制编辑

class Test { @MyAnnotation(value = "Hello") public void sayHello() { System.out.println("Hello, Annotation!"); } }

(3)通过反射读取注解

java

复制编辑

import java.lang.reflect.Method; public class Main { public static void main(String[] args) throws Exception { Method method = Test.class.getMethod("sayHello"); if (method.isAnnotationPresent(MyAnnotation.class)) { MyAnnotation annotation = method.getAnnotation(MyAnnotation.class); System.out.println("注解值:" + annotation.value()); } } }

输出:

复制编辑

注解值:Hello


5. 注解的生命周期(RetentionPolicy)

注解的生命周期由 @Retention 指定,有三种:

作用
SOURCE 仅存在于源码中,编译后会被丢弃(如 @Override
CLASS 存在于class 文件中,但不会进入 JVM 运行时
RUNTIME 存在于运行时,可以通过反射获取(如 Spring 的 @Autowired

6. @Target 注解的作用

@Target 指定注解可以作用的代码元素:

作用
ElementType.TYPE 只能作用于
ElementType.FIELD 只能作用于字段
ElementType.METHOD 只能作用于方法
ElementType.PARAMETER 只能作用于参数
ElementType.CONSTRUCTOR 只能作用于构造方法
ElementType.LOCAL_VARIABLE 只能作用于局部变量
ElementType.ANNOTATION_TYPE 只能作用于注解
ElementType.PACKAGE 只能作用于

7. Java 框架中常见的注解

(1)Spring 相关

注解 作用
@Component 标记为 Spring 组件(Bean)
@Service 标记为业务层组件
@Repository 标记为数据访问层组件
@Autowired 自动注入依赖
@Transactional 事务管理
@RestController 组合了 @Controller + @ResponseBody

(2)Junit 相关

注解 作用
@Test 标记测试方法
@BeforeEach 在每个测试方法前执行
@AfterEach 在每个测试方法后执行

(3)Lombok

注解 作用
@Getter 自动生成 getter 方法
@Setter 自动生成 setter 方法
@Data 组合 @Getter@Setter@ToString
@Builder 生成建造者模式代码

8. 什么时候使用注解?

  • 简化代码:例如 Lombok 自动生成 getter/setter
  • 配置驱动:Spring 依赖注入(@Autowired
  • 运行时处理:Spring AOP(@Transactional
  • 编译时检查@Override 避免拼写错误
  • 替代 XML:Spring @Configuration 代替 XML 配置

9. 总结

  • 注解是 Java 的元数据机制,可以标记类、方法、字段等
  • 注解不会影响程序逻辑,但可以提供额外信息给编译器、工具或框架
  • 常见内置注解@Override@Deprecated@SuppressWarnings
  • 注解有生命周期(SOURCE、CLASS、RUNTIME),影响其能否被反射获取
  • 可以自定义注解,并通过反射获取注解信息
  • 注解在 Java 框架(如 Spring、Junit、Lombok)中广泛使用