JavaSE反射与动态代理详细代码示例
作者:无止境ww
一、引言
在Java生态中,反射与动态代理是支撑框架设计的基石,无论是Spring中的AOP,还是Mybatis的Mapper接口,都离不开这两项技术。在学习之前先了解反射和动态代理分别可以做什么,简单说反射赋予程序“看清类结构”的功能,通过反射可以知道类中构造方法,成员方法,成员变量等信息;动态代理则实现了“无侵入式增强”,二者相辅相成,让代码具备灵活性与扩展性。
二、Java反射:探索类的“隐藏世界”
2.1 反射的定义
在Java中,反射(Reflection)是一种强大的机制,允许程序在运行时检查或修改其自身的结构和行为。通过反射,你可以查询类的元数据(如类名、方法、字段、注解等),动态地进行创建对象,调用方法,访问字段等操作。
2.2反射的核心API
Java反射的核心操作集中在java.lang.reflect包中,其中关键类包括Class、Method、Field、Constructor,核心操作分为“获取Class对象”“操作类成员”两步
(1)如何获取类对象?
首先在这里定义一个User类
public class User {
private String name;
public Integer age;
public User(){}
private User(String name) {
this.name = name;
}
public String getName() {
return name;
}
private void setAge(Integer age) {
this.age = age;
}
}获取类对象有三种方式分别是:类名.class,对象.getClass(),Class.forName(“全类名”)
//1.类名.class(编译时确定,最高效)
Class<?> class1 = User.class;
System.out.println(class1); //class User
//2.对象.getClass() (运行时获取,需要实例化)
User user = new User();
Class<?> class2 = user.getClass();
System.out.println(class2); //class User
//3.Class.forName("全类名") (运行时加载,最灵活)
Class<?> class3 = Class.forName("User");
System.out.println(class3); //class User
//判断上面三种方法获取的是否是同一个对象
System.out.println(class1.equals(class2)); //true(2) 利用反射操作类
得到类成员后就可以利用反射操作类的构造方法,成员方法,成员变量等元数据,核心API也很易懂
示例1. 构造器
//1.获取类
Class<User> userClass = User.class;
//2.获取构造器
//2.1获取公共构造器
Constructor<?>[] publicConstructors = userClass.getConstructors();
System.out.println(Arrays.toString(publicConstructors)); //[public User()]
//2.2获取所有构造器
Constructor<?>[] declaredConstructors = userClass.getDeclaredConstructors();
System.out.println(Arrays.toString(declaredConstructors)); //[public User(), private User(java.lang.String)]
//2.3获取一个公共构造器 (可以传递参数,参数是构造器中的形参类)
Constructor<User> constructor = userClass.getConstructor();
System.out.println(constructor); //public User()
//2.4获取一个构造器 (可以传递参数,参数是构造器中的形参类)
Constructor<User> declaredConstructor = userClass.getDeclaredConstructor(String.class);
System.out.println(declaredConstructor); // private User(java.lang.String)然后可以对获得的构造器进行操作:
//2.4获取一个构造器
Constructor<User> declaredConstructor = userClass.getDeclaredConstructor(String.class);
System.out.println(declaredConstructor); // private User(java.lang.String)
//3.Constructor类常用方法
//3.1获取构造器的权限修饰符
int modifiers = declaredConstructor.getModifiers();
System.out.println(declaredConstructor + "的构造方法的修饰符为" + modifiers);//private User(java.lang.String)的构造方法的修饰符为2(对应private)
//3.2获取构造器的形参类型
Class<?>[] types = declaredConstructor.getParameterTypes();
System.out.println(declaredConstructor + "的形参类型有" + Arrays.toString(types)); //private User(java.lang.String)的形参类型有[class java.lang.String]
//3.3通过Constructor实例化对象
//因为获取的是私有方法,所以需要暴力反射
declaredConstructor.setAccessible(true);//将私有构造器设置为可访问状态
User user = declaredConstructor.newInstance( "无止境");
System.out.println(user.getName()); //无止境在 Java 中,java.lang.reflect.Constructor类的getModifiers()方法返回一个整数,该整数是构造器修饰符的位掩码(Bitmask)。每个修饰符对应一个二进制位,不同修饰符通过按位或(OR)组合成最终的整数值。
获取字段,方法等API与获取构造器类似,演示一下通过反射操作私有成员
//1.获取类
Class<User> userClass = User.class;
//2.实例私有构造器
Constructor<User> privateConstructor = userClass.getDeclaredConstructor(String.class);
privateConstructor.setAccessible(true);
User user = privateConstructor.newInstance("无止境");
//3.调用私有化方法setAge
Method setAge = userClass.getDeclaredMethod("setAge", Integer.class);
setAge.setAccessible(true);
setAge.invoke(user, 18);
System.out.println(user.getName() + "的年龄为" + user.getAge()); //无止境的年龄为18
//4.获取私有属性 name
Field nameField = userClass.getDeclaredField("name");
nameField.setAccessible(true);
String name = (String)nameField.get(user);
System.out.println("利用反射获取的私有属性" + name); //利用反射获取的私有属性无止境Method类中invoke()方法比较常用,传递的参数表示1:调用方法的对象, 2:调用方法传递的参数;返回值就是原方法返回值
三、动态代理,无侵入式增强的实现利器
3.1动态代理定义
动态代理是一种设计模式,允许程序在运行时动态生成代理类,代理类会包裹目标对象,在不修改目标代码的情况下,对目标方法进行增强。Java中动态代理主要分为两种JDK动态代理和CGLIB动态代理。
3.2JDK动态代理(接口增强)
(1)核心组件
- InvocationHandler:定义代理逻辑,所有代理方法的调用都会委托给该接口的invoke方法。
- Proxy:用于创建动态代理类和实例的工厂
(2)实现步骤
- 定义接口:目标类需实现接口
- 实现InvocationHandler:处理方法调用逻辑
- 生成代理对象:通过Proxy.newProxyInstance()创建代理
(3)示例代码
定义接口:目标类需实现接口
//目标接口
public interface UserService {
void addUser(String name);
void deleteUser(Integer id);
}
//目标实现类
public class UserServiceImpl implements UserService {
@Override
public void addUser(String name) {
System.out.println("执行添加姓名为 : " + name + " 用户的操作");
}
@Override
public void deleteUser(Integer id) {
System.out.println("执行删除id为 : " + id + " 用户的操作");
}
}实现InvocationHandler:处理方法调用逻辑
public class LogInvocationHandler implements InvocationHandler {
private Object target; //目标对象
public LogInvocationHandler(Object target) {
this.target = target;
}
/**
*
* @param proxy 代理对象
* @param method 目标方法
* @param args 方法参数
* @return 方法返回值
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//前置日志,记录日志
System.out.println("【日志】 开始调用方法 : " + method.getName() + ",方法参数为 : " + Arrays.toString(args));
//调用目标方法
Object result = method.invoke(target, args);
//后置日志,记录方法完成
System.out.println("【日志】方法调用结束 : " + method.getName());
return result;
}
}生成代理对象:通过Proxy.newProxyInstance()创建代理
public static void main(String[] args) {
//1.创建目标对象
UserServiceImpl target = new UserServiceImpl();
//2.创建InvocationHandler实例
LogInvocationHandler handler = new LogInvocationHandler(target);
//3.生成动态代理对象
UserService proxy = (UserService) Proxy.newProxyInstance(
target.getClass().getClassLoader(), //类加载器
target.getClass().getInterfaces(), //目标实现的接口
handler
);
//4.调用代理对象的方法
proxy.addUser("无止境");
proxy.deleteUser(1001);
}最后控制台输出结果
【日志】 开始调用方法 : addUser,方法参数为 : [无止境]
执行添加姓名为 : 无止境 用户的操作
【日志】方法调用结束 : addUser
【日志】 开始调用方法 : deleteUser,方法参数为 : [1001]
执行删除id为 : 1001 用户的操作
【日志】方法调用结束 : deleteUser
至此就完成了一个简单的日志增强效果
(4)核心API用法
- public Object invoke(Object proxy, Method method, Object[] args):三个参数分别是代理对象本身(通常不需要使用);被调用的目标方法;方法参数数组。
- public static Object newProxyInstance(
ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h
):里面要传递三个参数分别是类加载器(通常使用目标接口的类加载器);目标接口数组(至少一个接口);代理逻辑处理器。
3.3CFLIB实现动态代理(类增强)
(1)核心组件
- MethodInterceptor:拦截目标类的方法调用,定义增强逻辑
- Enhancer:生成代理类和实例的工具
(2)实现步骤
- 引入CGLIB依赖
- 定义无接口的目标类
- 实现MethodInterceptor
- 生成CGLIB代理对象:利用Enhancer.create()方法
(3)示例代码
引入CGLIB依赖(maven)
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>public class OrderService {
//模拟创建订单方法
public void createOrder(String orderNo) {
System.out.println("创建订单,订单号为 : " + orderNo);
}
}自定义类实现MethodInterceptor,处理方法调用逻辑
public class TransactionMethodInterceptor implements MethodInterceptor {
/**
*
* @param o 目标对象
* @param method 目标方法
* @param objects 方法参数
* @param methodProxy 方法代理对象
* @return 方法返回值
* @throws Throwable
*/
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
//前置增强:开启事务
System.out.println("【事务】开启数据事务");
//调用目标方法(通过methodProxy提高性能)
Object result = methodProxy.invokeSuper(o, objects);
//后置增强:提交事务
System.out.println("【事务】提交数据事务");
return result;
}
}通过Enhancer.create()生成代理对象
public class CglibProxyDemo {
public static void main(String[] args) {
//1.创建Enhancer实例(CGLIB核心)
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(OrderService.class); //设置目标类
enhancer.setCallback(new TransactionMethodInterceptor()); //设置增强器
//2.生成动态代理对象
OrderService proxy = (OrderService) enhancer.create();
//3.调用代理对象方法
proxy.createOrder("order_112233");
}
}最终输出结果:
【事务】开启数据事务
创建订单,订单号为 : order_112233
【事务】提交数据事务
(4)核心API用法
- public void setSuperclass(Class<?> superclass); 指定目标类
- public void setCallback(Callback callback); 设置拦截器
- public Object create(); 生成代理实例
- public Object intercept(
Object obj,
Method method,
Object[] args,
MethodProxy proxy
):参数分别是obj:代理对象(通常不需要使用);method:被调用的目标方法;args:方法参数数组;proxy:MethodProxy 对象,比反射调用更高效
3.3JDK动态代理和CGLIB动态代理横向对比
| 组件 | JDK动态代理 | CGLIB动态代理 |
|---|---|---|
| 接口 | InvocationHandler | MethodInterceptor |
| 生成代理方式 | Proxy.newProxyInstance | Enhancer.create() |
| 目标类型 | 接口 | 类 |
| 调用目标方法 | method.invoke(target, args) | proxy.invokSuper(obj, args) |
四、反射与动态代理的实际应用
(1)反射的实际应用
- Spring 框架:Spring 使用反射来创建和管理 Bean。例如,通过配置文件(如 XML 或注解)指定要创建的类,Spring 容器利用反射机制根据类名动态加载类,并调用其构造函数创建对象实例。
- JUnit:JUnit 利用反射来发现和执行测试方法。它通过反射遍历测试类中的方法,根据特定的注解(如@Test)识别出测试方法,并动态调用这些方法来执行测试。例如,在一个测试类中定义了多个测试方法,JUnit 通过反射获取这些方法并依次执行,从而实现对代码的自动化测试。
(2) 动态代理的实际应用
- Spring AOP:Spring AOP 大量使用动态代理来实现切面功能。例如,在一个企业级应用中,可能需要对业务方法进行日志记录、事务管理等操作。通过动态代理,可以在不修改目标业务类代码的前提下,为目标方法添加这些额外的功能。Spring 会为目标对象创建动态代理对象,当调用代理对象的方法时,会先执行切面逻辑(如记录日志),然后再调用目标对象的实际方法,最后还可以在方法调用后执行一些清理或补充逻辑(如事务提交)。
- 在数据库事务管理中,动态代理可以用于管理事务的开启、提交和回滚。以一个银行转账操作的业务方法为例,动态代理可以在方法调用前开启事务,方法执行过程中如果没有异常则提交事务,若出现异常则回滚事务。这样可以将事务管理的逻辑从业务代码中分离出来,提高代码的可维护性和复用性。
通过上面的例子可以初步了解反射与动态代理,想要深度学习的话可以多看看框架中的源码,学习优秀的代码风格以及设计模式。
到此这篇关于JavaSE反射与动态代理的文章就介绍到这了,更多相关JavaSE反射与动态代理内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!
