反射与注解
一、反射概述
反射(Reflection) 是 Java 提供的一种运行时机制,允许程序在运行状态下:
- 查看任意类的内部信息(类名、方法、字段、构造器等)
- 动态创建对象、调用方法、访问字段
- 绕过访问权限限制(如访问
private成员)
反射的核心类是 java.lang.reflect 包下的 Class、Constructor、Method、Field 等。
反射是一把双刃剑:它带来了极大的灵活性,但也会牺牲一定的性能,并破坏封装性。在实际开发中应谨慎使用。
1.1 一个简单的入门示例
// 假设有一个 User 类
public class User {
private String name;
private int age;
public User() {}
public User(String name, int age) {
this.name = name;
this.age = age;
}
public void sayHello() {
System.out.println("你好,我是 " + name);
}
private void secretMethod() {
System.out.println("这是一个私有方法");
}
}通过反射,我们可以在运行时获取 User 类的所有信息:
import java.lang.reflect.*;
public class ReflectionDemo {
public static void main(String[] args) throws Exception {
// 获取 Class 对象
Class<?> clazz = User.class;
System.out.println("类名: " + clazz.getName());
System.out.println("简单类名: " + clazz.getSimpleName());
System.out.println("\n--- 所有声明的方法 ---");
for (Method m : clazz.getDeclaredMethods()) {
System.out.println(m.getName() + " -> " + m.getReturnType().getSimpleName());
}
System.out.println("\n--- 所有声明的字段 ---");
for (Field f : clazz.getDeclaredFields()) {
System.out.println(f.getName() + " : " + f.getType().getSimpleName());
}
}
}输出:
类名: User
简单类名: User
--- 所有声明的方法 ---
sayHello -> void
secretMethod -> void
--- 所有声明的字段 ---
name : String
age : int二、获取 Class 对象的三种方式
操作反射的第一步是获取 Class<?> 对象。Java 提供了三种获取方式:
2.1 类名.class
最直接的方式,编译期就已确定。
// 基本类型和 void 也可以使用
Class<String> strClass = String.class;
Class<Integer> intClass = int.class;
Class<Void> voidClass = void.class;
System.out.println(strClass.getName()); // java.lang.String
System.out.println(intClass.getName()); // int
System.out.println(voidClass.getName()); // void2.2 对象.getClass()
通过对象的 getClass() 方法(继承自 Object)获取。
String text = "Hello";
Class<?> clazz = text.getClass();
System.out.println(clazz.getName()); // java.lang.String
User user = new User();
Class<?> userClass = user.getClass();
System.out.println(userClass.getName()); // User2.3 Class.forName("全限定名")
最灵活的方式,传入类的全限定名(包名 + 类名),适合在运行时动态加载。
// 动态加载类
Class<?> clazz = Class.forName("java.util.ArrayList");
System.out.println(clazz.getSimpleName()); // ArrayList
// 也可加载自定义类
Class<?> userClass = Class.forName("User");注意:
Class.forName()会触发类的静态初始化块(static 代码块)。如果不想执行静态代码块,可以使用ClassLoader.loadClass()。
2.4 三种方式对比
| 方式 | 编译期依赖 | 性能 | 典型场景 |
|---|---|---|---|
| 类名.class | 需要 | 最快 | 明确知道类名时 |
| 对象.getClass() | 需要 | 较快 | 已有对象实例时 |
| Class.forName() | 不需要 | 较慢(含类加载) | 动态加载未知类(SPI、JDBC 驱动等) |
三、反射操作构造器
通过反射可以获取类的构造器,并动态创建实例。
3.1 获取构造器
import java.lang.reflect.*;
public class ConstructorDemo {
public static void main(String[] args) throws Exception {
Class<?> clazz = Class.forName("User");
// 获取所有 public 构造器
Constructor<?>[] publicConstructors = clazz.getConstructors();
System.out.println("public 构造器数量: " + publicConstructors.length);
// 获取所有构造器(包括非 public)
Constructor<?>[] allConstructors = clazz.getDeclaredConstructors();
System.out.println("所有构造器数量: " + allConstructors.length);
for (Constructor<?> c : allConstructors) {
System.out.print(" 参数: ");
Class<?>[] paramTypes = c.getParameterTypes();
for (Class<?> p : paramTypes) {
System.out.print(p.getSimpleName() + " ");
}
System.out.println();
}
}
}3.2 通过构造器创建对象
import java.lang.reflect.*;
public class NewInstanceDemo {
public static void main(String[] args) throws Exception {
Class<?> clazz = Class.forName("User");
// 方式一:调用无参构造器(已过时,但仍常用)
User user1 = (User) clazz.newInstance(); // 要求必须有 public 无参构造器
System.out.println("无参构造创建: " + user1);
// 方式二:通过 Constructor 对象创建(推荐)
Constructor<?> constructor = clazz.getConstructor(String.class, int.class);
User user2 = (User) constructor.newInstance("张三", 25);
System.out.println("有参构造创建: " + user2);
// 方式三:调用 private 构造器
Constructor<?> privateConstructor = clazz.getDeclaredConstructor(String.class);
privateConstructor.setAccessible(true); // 暴力访问
User user3 = (User) privateConstructor.newInstance("李四");
System.out.println("私有构造创建: " + user3);
}
}| 方法 | 说明 |
|---|---|
getConstructors() | 返回所有 public 构造器 |
getDeclaredConstructors() | 返回所有构造器(包括 private、protected) |
getConstructor(Class...) | 获取指定参数类型的 public 构造器 |
getDeclaredConstructor(Class...) | 获取指定参数类型的构造器(任意访问权限) |
四、反射操作方法
4.1 获取方法信息
import java.lang.reflect.*;
public class MethodInfoDemo {
public static void main(String[] args) throws Exception {
Class<?> clazz = Class.forName("java.util.ArrayList");
// 获取所有 public 方法(包括继承的)
Method[] methods = clazz.getMethods();
System.out.println("ArrayList public 方法数量: " + methods.length);
// 获取本类声明的所有方法(不限访问权限,不包括继承的)
Method[] declaredMethods = clazz.getDeclaredMethods();
System.out.println("ArrayList 声明的方法数量: " + declaredMethods.length);
// 打印前 5 个方法
for (int i = 0; i < 5 && i < declaredMethods.length; i++) {
Method m = declaredMethods[i];
System.out.printf(" %s %s(%s)%n",
m.getReturnType().getSimpleName(),
m.getName(),
String.join(", ",
java.util.Arrays.stream(m.getParameterTypes())
.map(Class::getSimpleName)
.toArray(String[]::new)));
}
}
}4.2 调用方法
import java.lang.reflect.*;
public class InvokeMethodDemo {
public static void main(String[] args) throws Exception {
Class<?> clazz = Class.forName("User");
Object user = clazz.getConstructor(String.class, int.class)
.newInstance("王五", 30);
// 调用 public 方法
Method sayHello = clazz.getMethod("sayHello");
sayHello.invoke(user); // 输出:你好,我是 王五
// 调用带参数的方法
Method setName = clazz.getMethod("setName", String.class);
setName.invoke(user, "赵六");
sayHello.invoke(user); // 输出:你好,我是 赵六
// 调用 private 方法
Method secret = clazz.getDeclaredMethod("secretMethod");
secret.setAccessible(true); // 必须设置,否则 IllegalAccessException
secret.invoke(user); // 输出:这是一个私有方法
// 调用静态方法(invoke 第一个参数传 null)
Method staticMethod = clazz.getMethod("staticInfo");
staticMethod.invoke(null);
}
}| 方法 | 说明 |
|---|---|
getMethods() | 获取所有 public 方法(含继承的) |
getDeclaredMethods() | 获取本类所有方法(不含继承的,不限权限) |
getMethod(String, Class...) | 根据名称和参数类型获取 public 方法 |
getDeclaredMethod(String, Class...) | 根据名称和参数类型获取任意方法 |
method.invoke(对象, 参数...) | 调用方法。静态方法对象传 null |
关键点:调用
private方法前必须调用setAccessible(true)。
五、反射操作字段
5.1 获取字段信息
import java.lang.reflect.*;
public class FieldInfoDemo {
public static void main(String[] args) throws Exception {
Class<?> clazz = Class.forName("User");
// 获取所有 public 字段(包括继承的)
Field[] fields = clazz.getFields();
System.out.println("public 字段: " + fields.length);
// 获取所有字段(不限权限,不包括继承的)
Field[] declaredFields = clazz.getDeclaredFields();
System.out.println("所有字段: " + declaredFields.length);
for (Field f : declaredFields) {
System.out.printf(" %s %s%n",
f.getType().getSimpleName(), f.getName());
}
}
}5.2 读取和设置字段值
import java.lang.reflect.*;
public class FieldAccessDemo {
public static void main(String[] args) throws Exception {
Class<?> clazz = Class.forName("User");
Object user = clazz.getConstructor(String.class, int.class)
.newInstance("小明", 18);
// 读取 public 字段
Field publicField = clazz.getField("publicField"); // 假设有 public 字段
Object value = publicField.get(user);
System.out.println("publicField = " + value);
// 读取 private 字段
Field nameField = clazz.getDeclaredField("name");
nameField.setAccessible(true); // 暴力反射
String name = (String) nameField.get(user);
System.out.println("name = " + name); // 小明
// 修改 private 字段
nameField.set(user, "小红");
System.out.println("修改后 name = " + nameField.get(user)); // 小红
// 操作静态字段(对象传 null)
Field staticField = clazz.getDeclaredField("staticCount");
staticField.setAccessible(true);
int count = staticField.getInt(null);
System.out.println("staticCount = " + count);
}
}| 方法 | 说明 |
|---|---|
getFields() | 获取所有 public 字段(含继承的) |
getDeclaredFields() | 获取本类所有字段(不含继承的) |
field.get(对象) | 读取字段值,静态字段传 null |
field.set(对象, 值) | 设置字段值 |
field.setAccessible(true) | 访问私有字段前必须调用 |
六、动态代理
动态代理允许在运行时动态创建代理类,拦截对目标对象的方法调用。Java 原生动态代理基于接口实现,核心涉及两个类:
java.lang.reflect.Proxy— 创建代理对象java.lang.reflect.InvocationHandler— 处理方法调用
6.1 基本用法
import java.lang.reflect.*;
// 1. 定义接口
interface SayService {
void say(String message);
String getMessage();
}
// 2. 实现类
class SayServiceImpl implements SayService {
@Override
public void say(String message) {
System.out.println("说: " + message);
}
@Override
public String getMessage() {
return "Hello from impl";
}
}
// 3. 调用处理器
class LogInvocationHandler implements InvocationHandler {
private final Object target;
public LogInvocationHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("[前置日志] 调用方法: " + method.getName());
long start = System.nanoTime();
// 调用目标对象的方法
Object result = method.invoke(target, args);
long end = System.nanoTime();
System.out.println("[后置日志] " + method.getName() + " 耗时: " + (end - start) / 1_000_000 + "ms");
return result;
}
}
// 4. 使用
public class ProxyDemo {
public static void main(String[] args) {
SayService target = new SayServiceImpl();
SayService proxy = (SayService) Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
new LogInvocationHandler(target)
);
proxy.say("你好,动态代理!");
System.out.println("返回值: " + proxy.getMessage());
}
}输出:
[前置日志] 调用方法: say
说: 你好,动态代理!
[后置日志] say 耗时: 1ms
[前置日志] 调用方法: getMessage
[后置日志] getMessage 耗时: 0ms
返回值: Hello from impl6.2 Proxy.newProxyInstance 参数说明
public static Object newProxyInstance(
ClassLoader loader, // 类加载器,通常用目标类的类加载器
Class<?>[] interfaces, // 要实现的接口数组
InvocationHandler h // 方法调用处理器
)6.3 常用场景
- AOP(面向切面编程):如 Spring 的事务管理、日志、权限控制
- 远程调用:如 RPC 框架中屏蔽网络通信细节
- 数据绑定:如 MyBatis 的 Mapper 接口代理
- 延迟加载:如 Hibernate 的懒加载代理
局限:JDK 动态代理只能代理接口。要代理类需使用 CGLIB、ByteBuddy 等第三方库。
七、注解定义
注解(Annotation) 是一种元数据,为代码提供额外信息。从 JDK 5 开始引入。
7.1 内置注解
@Override // 标记方法重写
@Deprecated // 标记已废弃
@SuppressWarnings // 抑制编译器警告
@FunctionalInterface // 函数式接口(JDK 8)7.2 自定义注解
使用 @interface 关键字定义注解:
import java.lang.annotation.*;
/**
* 标记某个方法为"日志记录"方法
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Loggable {
// 带默认值的属性
String value() default "";
// 非基本类型属性
String[] tags() default {};
// 基本类型属性
int level() default 1;
}使用注解:
public class MyService {
@Loggable(value = "查询用户", tags = {"user", "query"}, level = 2)
public void findUser() {
System.out.println("查找用户...");
}
@Loggable("简单日志") // 只有 value 属性时可以省略 "value="
public void simpleMethod() {
System.out.println("简单方法...");
}
}7.3 注解属性规则
- 属性类型必须是:基本类型、
String、Class、enum、注解类型或以上类型的数组 - 可以用
default提供默认值 - 如果注解只有一个属性,建议命名为
value - 使用注解时,若只需设置
value属性,可省略value=语法糖
八、元注解
元注解是用于声明其他注解的注解。Java 提供了以下四个核心元注解:
8.1 @Target
指定注解可以应用的目标元素类型。
| ElementType 取值 | 目标元素 |
|---|---|
TYPE | 类、接口、枚举 |
METHOD | 方法 |
FIELD | 字段(含枚举常量) |
PARAMETER | 方法参数 |
CONSTRUCTOR | 构造器 |
LOCAL_VARIABLE | 局部变量 |
ANNOTATION_TYPE | 注解类型 |
PACKAGE | 包 |
TYPE_PARAMETER | 类型参数(JDK 8) |
TYPE_USE | 类型使用处(JDK 8) |
// 该注解只能用在方法和字段上
@Target({ElementType.METHOD, ElementType.FIELD})
public @interface CustomAnnotation {
}8.2 @Retention
指定注解保留到哪个阶段。
| RetentionPolicy | 说明 |
|---|---|
SOURCE | 仅在源码中保留,编译时丢弃(如 @SuppressWarnings) |
CLASS | 保留到 class 文件中,但运行时不可通过反射读取(默认值) |
RUNTIME | 保留到运行时,可通过反射读取 |
// 只有 RUNTIME 级别的注解才能在运行时通过反射读取
@Retention(RetentionPolicy.RUNTIME)
public @interface RuntimeAnnotation {
}8.3 @Inherited
让子类继承父类的注解。
@Inherited
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Inheritable {
String value();
}
@Inheritable("父类注解")
class Parent {}
class Child extends Parent {} // Child 也会拥有 @Inheritable 注解8.4 @Documented
表示注解应被包含在 Javadoc 中。
@Documented
@Retention(RetentionPolicy.RUNTIME)
public @interface DocumentedAnnotation {
}九、运行时读取注解
要读取注解,注解的 @Retention 必须为 RUNTIME,然后通过反射 API 读取。
9.1 读取类上的注解
import java.lang.annotation.*;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@interface Table {
String name();
}
@Table(name = "t_user")
class EntityUser {
// ...
}
public class ReadClassAnnotation {
public static void main(String[] args) {
Class<EntityUser> clazz = EntityUser.class;
// 方式一:直接获取
if (clazz.isAnnotationPresent(Table.class)) {
Table table = clazz.getAnnotation(Table.class);
System.out.println("表名: " + table.name()); // t_user
}
// 方式二:获取所有注解
for (Annotation ann : clazz.getAnnotations()) {
System.out.println("注解: " + ann.annotationType().getSimpleName());
}
}
}9.2 读取方法上的注解
import java.lang.annotation.*;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@interface RequestMapping {
String path();
String method() default "GET";
}
class MyController {
@RequestMapping(path = "/user/list", method = "GET")
public void listUsers() {}
@RequestMapping(path = "/user/create", method = "POST")
public void createUser() {}
}
public class ReadMethodAnnotation {
public static void main(String[] args) throws Exception {
Class<MyController> clazz = MyController.class;
for (Method method : clazz.getDeclaredMethods()) {
if (method.isAnnotationPresent(RequestMapping.class)) {
RequestMapping mapping = method.getAnnotation(RequestMapping.class);
System.out.printf("方法 %s -> %s %s%n",
method.getName(),
mapping.method(),
mapping.path());
}
}
}
}输出:
listUsers -> GET /user/list
createUser -> POST /user/create9.3 读取字段上的注解
import java.lang.annotation.*;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
@interface Column {
String name();
boolean primaryKey() default false;
}
class UserEntity {
@Column(name = "id", primaryKey = true)
private Long id;
@Column(name = "user_name")
private String userName;
@Column(name = "age")
private int age;
}
public class ReadFieldAnnotation {
public static void main(String[] args) throws Exception {
Class<UserEntity> clazz = UserEntity.class;
for (Field field : clazz.getDeclaredFields()) {
if (field.isAnnotationPresent(Column.class)) {
Column column = field.getAnnotation(Column.class);
System.out.printf("字段 %s -> 列名: %s, 主键: %b%n",
field.getName(),
column.name(),
column.primaryKey());
}
}
}
}输出:
字段 id -> 列名: id, 主键: true
字段 userName -> 列名: user_name, 主键: false
字段 age -> 列名: age, 主键: false9.4 读取注解属性
// 完整读取注解的所有属性
@Retention(RetentionPolicy.RUNTIME)
@interface ApiInfo {
String author();
String version() default "1.0";
String[] tags();
Class<?>[] dependsOn() default {};
}
@ApiInfo(author = "张三", tags = {"user", "api"}, dependsOn = {String.class})
class SomeApi {}
public class ReadAnnotationAttributes {
public static void main(String[] args) {
ApiInfo info = SomeApi.class.getAnnotation(ApiInfo.class);
System.out.println("author: " + info.author());
System.out.println("version: " + info.version());
System.out.println("tags: " + java.util.Arrays.toString(info.tags()));
System.out.println("dependsOn: " + java.util.Arrays.toString(info.dependsOn()));
}
}十、实战示例:@LogExecutionTime 统计方法耗时
结合反射 + 注解实现一个小框架:标注了 @LogExecutionTime 的方法在被调用时自动打印执行耗时。
10.1 定义注解
import java.lang.annotation.*;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface LogExecutionTime {
String unit() default "ms"; // 时间单位:ms / ns
}10.2 实现代理工厂
import java.lang.reflect.*;
public class LogExecutionProxy {
@SuppressWarnings("unchecked")
public static <T> T createProxy(T target) {
return (T) Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 检查目标方法是否有 @LogExecutionTime 注解
Method targetMethod = target.getClass()
.getMethod(method.getName(), method.getParameterTypes());
LogExecutionTime logAnn = targetMethod
.getAnnotation(LogExecutionTime.class);
// 如果没有注解,直接调用不记录
if (logAnn == null) {
return method.invoke(target, args);
}
// 记录耗时
long start = System.nanoTime();
Object result = method.invoke(target, args);
long elapsed = System.nanoTime() - start;
// 根据单位输出
String unit = logAnn.unit();
double displayValue;
if ("ns".equalsIgnoreCase(unit)) {
displayValue = elapsed;
} else {
displayValue = elapsed / 1_000_000.0; // 默认 ms
}
System.out.printf("[执行耗时] %s.%s = %.2f %s%n",
target.getClass().getSimpleName(),
method.getName(),
displayValue,
unit);
return result;
}
}
);
}
}10.3 使用示例
interface TaskService {
void quickTask();
void slowTask();
void noLogTask();
}
class TaskServiceImpl implements TaskService {
@LogExecutionTime
@Override
public void quickTask() {
System.out.println("执行快速任务...");
}
@LogExecutionTime(unit = "ns")
@Override
public void slowTask() {
System.out.println("执行慢速任务...");
// 模拟耗时
for (int i = 0; i < 1_000_000; i++) {
Math.sqrt(i);
}
}
@Override
public void noLogTask() {
System.out.println("没有注解的方法,不记录耗时");
}
}
public class LogExecutionDemo {
public static void main(String[] args) {
TaskService service = LogExecutionProxy.createProxy(new TaskServiceImpl());
service.quickTask();
service.slowTask();
service.noLogTask();
}
}输出示例:
执行快速任务...
[执行耗时] TaskServiceImpl.quickTask = 0.12 ms
执行慢速任务...
[执行耗时] TaskServiceImpl.slowTask = 3.45 ms
没有注解的方法,不记录耗时10.4 扩展思路
基于同样的模式,可以轻松实现更多功能:
- @Retry:方法调用失败时自动重试
- @Cache:缓存方法返回值
- @RateLimit:限制调用频率
- @AuditLog:记录操作日志
// 示例:实现 @Retry 的思路
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Retry {
int times() default 3;
long delayMs() default 1000;
}
// 在 InvocationHandler 中捕获异常并重试
// if (method.isAnnotationPresent(Retry.class)) {
// Retry retry = method.getAnnotation(Retry.class);
// for (int i = 0; i < retry.times(); i++) {
// try { return method.invoke(target, args); }
// catch (Exception e) { Thread.sleep(retry.delayMs()); }
// }
// }小结
| 知识点 | 核心要点 |
|---|---|
| 反射概述 | 运行时查看和操作类信息,核心在 java.lang.reflect 包 |
| 获取 Class 对象 | 三种方式:类名.class、对象.getClass()、Class.forName() |
| 操作构造器 | getConstructor / getDeclaredConstructor / newInstance / setAccessible |
| 操作方法 | getMethod / getDeclaredMethod / invoke |
| 操作字段 | getField / getDeclaredField / get / set |
| 动态代理 | Proxy.newProxyInstance(ClassLoader, interfaces, InvocationHandler) |
| 注解定义 | @interface + 元注解 @Target / @Retention / @Inherited / @Documented |
| 读取注解 | 反射 API:isAnnotationPresent / getAnnotation / getAnnotations |
| 实战框架 | 动态代理 + 运行时注解 = 轻量级 AOP 框架 |
反射和注解是 Java 生态中很多框架(Spring、MyBatis、JUnit、Lombok 等)的基石。理解它们不仅有助于日常开发,更能帮助你深入理解框架背后的设计思想。