Java 高级特性:反射机制与动态代理
在 Java 开发中,反射机制与动态代理属于进阶技能。掌握它们,你才能真正理解 Spring、Hibernate 等框架的底层原理,也能写出更灵活、更具扩展性的代码。这篇文章将用最直白的方式,带你彻底搞懂这两个核心概念。
一、反射机制:程序自我的"X光透视"
1.1 什么是反射?
反射是指在程序运行期间,能够动态获取类的信息、调用对象方法的能力。普通代码是"编译时确定"的——你写的代码在写死的那一刻就知道要操作哪个类、哪个方法。而反射让你在程序运行时"现场发现"类的结构,就像给程序装了一双透视眼。
举个例子:传统开发中,你写 userService.save(),编译器早就知道 userService 是哪个类、是否有 save 方法。但如果你想让程序自己决定"今天要调用哪个类的哪个方法",传统写法就办不到了——这正是反射的用武之地。
1.2 反射的核心入口:Class 对象
所有反射操作的起点都是 Class 对象。这个对象代表了一个类的完整信息:它的构造方法有哪些、有什么成员变量、实现了哪些接口、继承了哪个父类。获取 Class 对象有三种常用方式:
方式一:通过对象的 getClass() 方法
String str = "Hello";
Class<?> clazz = str.getClass();
System.out.println(clazz.getName()); // 输出:java.lang.String
方式二:通过类的 class 属性
Class<?> clazz = String.class;
System.out.println(clazz.getName()); // 输出:java.lang.String
方式三:通过 Class.forName() 动态加载
Class<?> clazz = Class.forName("java.lang.String");
System.out.println(clazz.getName()); // 输出:java.lang.String
第三种方式最灵活,因为类的名字可以放在配置文件里,实现真正的"运行时决定"。
1.3 反射操作三板斧
掌握反射,实际上就是学会三类操作:操作构造方法、操作成员变量、操作成员方法。
操作构造方法——创建对象
Class<?> personClass = Person.class;
// 获取无参构造方法
Constructor<?> noArgConstructor = personClass.getDeclaredConstructor();
Object person1 = noArgConstructor.newInstance();
// 获取有参构造方法
Constructor<?> paramConstructor = personClass.getDeclaredConstructor(String.class, int.class);
Object person2 = paramConstructor.newInstance("张三", 25);
如果你想调用私有构造方法,需要先设置可访问权限:
noArgConstructor.setAccessible(true); // 强制访问私有构造方法
操作成员变量——读取和修改值
Field nameField = personClass.getDeclaredField("name");
nameField.setAccessible(true); // 访问私有字段
Object person = ...; // 已有的Person对象
String name = (String) nameField.get(person); // 读取字段值
nameField.set(person, "李四"); // 修改字段值
操作成员方法——动态调用
Method sayHelloMethod = personClass.getMethod("sayHello", String.class);
Object result = sayHelloMethod.invoke(person, "World"); // 调用方法
System.out.println(result);
invoke 方法的第一个参数是方法所属的对象,如果是静态方法则传 null。
1.4 反射的实际应用场景
反射不是花架子,它在真实项目中用途广泛。
场景一:通用序列化框架
像 Jackson、Gson 这样的 JSON 序列化框架,不可能为每个类写解析逻辑。它们通过反射遍历类的所有字段,自动把对象转换成 JSON。这正是反射最典型的应用。
场景二:依赖注入容器
Spring 的 IoC 容器启动时,扫描所有被 @Component、@Service 注解标记的类,通过反射创建对象实例,并自动注入依赖。这就是 Spring"控制反转"的底层实现。
场景三:绕过编译期检查调用私有方法
在单元测试中,有时候需要测试一个类的私有方法是否正确。直接调用私有方法会编译报错,但用反射可以绕过这个限制。
二、动态代理:给对象"贴牌"的能力
2.1 为什么需要代理?
理解代理最好的方式是先看它的对立面:直接调用。
UserService userService = new UserServiceImpl();
userService.addUser(new User()); // 直接调用
这段代码没有任何问题。但如果现在有个新需求:在每次调用 addUser 前后记录日志。直接修改 UserServiceImpl 的代码不是好主意——代码会变脏,而且如果 UserServiceImpl 是第三方提供的,你就无权修改。
代理模式的核心思想是:创建一个"代理对象",代为处理请求。代理对象持有真实对象的引用,在调用真实方法前后可以插入额外逻辑。
2.2 静态代理:自己写代理类
先来看静态代理的实现方式。假设有个接口和实现类:
interface UserService {
void addUser(User user);
void deleteUser(Long id);
}
class UserServiceImpl implements UserService {
@Override
public void addUser(User user) {
System.out.println("添加用户成功");
}
@Override
public void deleteUser(Long id) {
System.out.println("删除用户成功");
}
}
现在写一个代理类,在调用真实方法前后打印日志:
class UserServiceProxy implements UserService {
private UserService target; // 持有真实对象的引用
public UserServiceProxy(UserService target) {
this.target = target;
}
@Override
public void addUser(User user) {
System.out.println("[日志] 开始添加用户: " + user.getName());
target.addUser(user); // 调用真实方法
System.out.println("[日志] 添加用户完成");
}
@Override
public void deleteUser(Long id) {
System.out.println("[日志] 开始删除用户: " + id);
target.deleteUser(id);
System.out.println("[日志] 删除用户完成");
}
}
使用时,替换成代理对象:
UserService userService = new UserServiceProxy(new UserServiceImpl());
userService.addUser(new User("张三"));
运行这段代码,你会看到日志被打印出来了。静态代理确实解决了问题,但它的缺点也很明显:每个接口都需要写一个代理类。如果接口有 20 个方法,代理类就要写 20 个方法调用——这太繁琐了。
2.3 JDK 动态代理:自动生成代理类
动态代理的核心是:代理类不是手写的,而是在运行时由 JVM 自动生成的。JDK 提供了 java.lang.reflect.Proxy 类来干这件事。
使用 JDK 动态代理,只需实现 InvocationHandler 接口:
class LogInvocationHandler implements InvocationHandler {
private 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() + " 开始");
Object result = method.invoke(target, args); // 调用真实方法
System.out.println("[日志] 调用方法 " + method.getName() + " 结束");
return result;
}
}
然后,用 Proxy.newProxyInstance() 创建代理对象:
UserService userServiceImpl = new UserServiceImpl();
// 创建代理对象
UserService userService = (UserService) Proxy.newProxyInstance(
userServiceImpl.getClass().getClassLoader(), // 类加载器
userServiceImpl.getClass().getInterfaces(), // 被代理类实现的接口
new LogInvocationHandler(userServiceImpl) // 调用处理器
);
userService.addUser(new User("张三"));
这就是动态代理的精妙之处:一个 InvocationHandler 可以代理所有接口方法,你不用为每个方法单独写代理逻辑。运行时,JVM 会根据接口自动生成代理类。
2.4 动态代理的原理
动态代理生成的代理类结构大致如下:
public class $Proxy0 implements UserService {
private InvocationHandler h;
public $Proxy0(InvocationHandler h) {
this.h = h;
}
@Override
public void addUser(User user) {
// 实际上是这样的调用
h.invoke(this, addUser方法对象, new Object[]{user});
}
}
所有的方法调用都会转发到 InvocationHandler 的 invoke 方法。这就是为什么你只需要在一个地方写增强逻辑,所有方法都能享受到增强效果。
2.5 CGLIB 代理:没有接口也能代理
JDK 动态代理有个硬性要求:被代理的类必须实现接口。但如果一个类没有实现任何接口,JDK 代理就无能为力了。这时候要用 CGLIB(Code Generation Library)。
CGLIB 的原理不同于 JDK 代理:它是通过继承被代理类来创建代理类的。代理类是目标类的子类,重写父类的方法来实现增强。
使用 CGLIB 需要引入额外依赖:
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>
实现 MethodInterceptor 接口:
class CglibInterceptor implements MethodInterceptor {
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("[CGLIB日志] 调用方法 " + method.getName() + " 开始");
Object result = proxy.invokeSuper(obj, args); // 调用父类方法
System.out.println("[CGLIB日志] 调用方法 " + method.getName() + " 结束");
return result;
}
}
创建代理对象:
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(UserServiceImpl.class); // 设置父类
enhancer.setCallback(new CglibInterceptor()); // 设置回调
UserServiceImpl proxy = (UserServiceImpl) enhancer.create();
proxy.addUser(new User("李四"));
2.6 代理方式对比与选择
| 特性 | JDK 动态代理 | CGLIB 代理 |
|---|---|---|
| 实现原理 | 基于接口的动态代理类 | 基于继承的子类 |
| 是否需要接口 | 必须实现接口 | 不需要接口 |
| 执行效率 | 较高 | 略低于 JDK 代理 |
| 能否代理 final 类 | 能 | 不能(无法继承) |
| 能否代理 final 方法 | 能 | 不能(无法重写) |
实际开发中的选择建议很简单:如果目标对象实现了接口,优先用 JDK 动态代理;如果没有接口,用 CGLIB。Spring 框架就是按照这个策略自动选择的。
三、实战案例:实现一个简易数据库访问框架
理论讲完了,现在用一个完整案例把反射和动态代理结合起来。目标是实现一个类似 MyBatis 的简化版:把对象自动保存到数据库,开发者只需定义接口和 POJO 类。
3.1 定义注解
首先定义两个注解:一个标记实体类,一个标记主键字段。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface Entity {
String tableName();
}
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@interface Id {}
3.2 定义实体类和 DAO 接口
@Entity(tableName = "users")
class User {
@Id
private Long id;
private String name;
private Integer age;
// getter 和 setter 省略
}
interface UserDao {
void insert(User user);
User findById(Long id);
void update(User user);
void delete(Long id);
}
3.3 实现动态 DAO 框架
核心实现是一个 DaoInvocationHandler,它利用反射把方法调用转换成 SQL 执行。
class DaoInvocationHandler implements InvocationHandler {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String methodName = method.getName();
Class<?> entityClass = method.getDeclaringClass().getInterfaces()[0].getGenericInterfaces()[0];
if (methodName.startsWith("insert")) {
return insert(args[0], entityClass);
} else if (methodName.startsWith("findById")) {
return findById(args[0], entityClass);
} else if (methodName.startsWith("update")) {
return update(args[0], entityClass);
} else if (methodName.startsWith("delete")) {
return delete(args[0], entityClass);
}
return null;
}
private Object insert(Object entity, Class<?> entityClass) {
Entity entityAnnotation = entityClass.getAnnotation(Entity.class);
String tableName = entityAnnotation.tableName();
StringBuilder sql = new StringBuilder();
sql.append("INSERT INTO ").append(tableName).append(" (");
Field[] fields = entityClass.getDeclaredFields();
List<Object> values = new ArrayList<>();
for (Field field : fields) {
if (field.isAnnotationPresent(Id.class)) continue; // 假设主键自增
field.setAccessible(true);
sql.append(field.getName()).append(",");
values.add(field.get(entity));
}
sql.deleteCharAt(sql.length() - 1);
sql.append(") VALUES (");
for (int i = 0; i < values.size(); i++) {
sql.append("?,");
}
sql.deleteCharAt(sql.length() - 1);
sql.append(")");
System.out.println("执行SQL: " + sql);
System.out.println("参数: " + values);
return null;
}
private Object findById(Object id, Class<?> entityClass) {
Entity entityAnnotation = entityClass.getAnnotation(Entity.class);
String tableName = entityAnnotation.tableName();
String sql = "SELECT * FROM " + tableName + " WHERE id = ?";
System.out.println("执行SQL: " + sql);
System.out.println("参数: " + id);
// 简化处理,返回一个空对象
Object entity = entityClass.getDeclaredConstructor().newInstance();
Field idField = null;
for (Field field : entityClass.getDeclaredFields()) {
if (field.isAnnotationPresent(Id.class)) {
idField = field;
break;
}
}
if (idField != null) {
idField.setAccessible(true);
idField.set(entity, id);
}
return entity;
}
private Object update(Object entity, Class<?> entityClass) {
Entity entityAnnotation = entityClass.getAnnotation(Entity.class);
String tableName = entityAnnotation.tableName();
StringBuilder sql = new StringBuilder();
sql.append("UPDATE ").append(tableName).append(" SET ");
Field[] fields = entityClass.getDeclaredFields();
Object idValue = null;
Field idField = null;
for (Field field : fields) {
field.setAccessible(true);
if (field.isAnnotationPresent(Id.class)) {
idField = field;
idValue = field.get(entity);
continue;
}
sql.append(field.getName()).append(" = ?,");
}
sql.deleteCharAt(sql.length() - 1);
sql.append(" WHERE id = ?");
System.out.println("执行SQL: " + sql);
return null;
}
private Object delete(Object id, Class<?> entityClass) {
Entity entityAnnotation = entityClass.getAnnotation(Entity.class);
String tableName = entityAnnotation.tableName();
String sql = "DELETE FROM " + tableName + " WHERE id = ?";
System.out.println("执行SQL: " + sql);
System.out.println("参数: " + id);
return null;
}
}
3.4 创建 DAO 工厂
最后写一个工厂方法,动态创建 DAO 实现:
class DaoFactory {
@SuppressWarnings("unchecked")
public static <T> T createDao(Class<T> daoInterface) {
Class<?> entityClass = getEntityClass(daoInterface);
return (T) Proxy.newProxyInstance(
daoInterface.getClassLoader(),
new Class<?>[] { daoInterface },
new DaoInvocationHandler()
);
}
private static Class<?> getEntityClass(Class<?> daoInterface) {
// 简化处理:假设 DAO 接口有一个泛型参数
Type[] types = daoInterface.getGenericInterfaces();
for (Type type : types) {
if (type instanceof ParameterizedType) {
return (Class<?>) ((ParameterizedType) type).getActualTypeArguments()[0];
}
}
return null;
}
}
3.5 使用框架
现在,开发者只需要写接口,不用写实现类:
public class FrameworkDemo {
public static void main(String[] args) {
UserDao userDao = DaoFactory.createDao(UserDao.class);
User user = new User();
user.setName("王五");
user.setAge(30);
userDao.insert(user);
User foundUser = userDao.findById(1L);
userDao.update(user);
userDao.delete(1L);
}
}
运行这段代码,你会看到框架自动生成了 SQL 语句打印出来。这个简易框架展示了反射和动态代理如何配合工作:反射负责读取类和注解信息,把 POJO 转换成 SQL;动态代理负责拦截 DAO 方法调用,把参数传递给框架处理。
四、总结关键点
这篇文章介绍了 Java 高级特性中的反射机制与动态代理。反射的核心是 Class 对象,通过它可以动态获取类的构造方法、成员变量和成员方法信息,实现"运行时决定调用哪个类"的能力。动态代理则解决了一类通用问题:通过代理对象在方法调用前后插入额外逻辑,JDK 动态代理基于接口实现,而 CGLIB 基于继承实现。在实际项目中,这两个特性经常配合使用,掌握它们对于理解框架底层和设计灵活架构都至关重要。

暂无评论,快来抢沙发吧!