动态代理就像是一位神奇的魔术师,它能悄无声息地在你的代码运行时,为你的对象添加各种功能,而这一切,你不需要修改任何已有的代码!
一、什么是代理?
想象一下,你想要出国旅游,但你不会外语,也不熟悉当地的风俗习惯。这时候,你就可以找一家旅行社,让他们帮你安排行程、预订酒店、办理签证等等。对你来说,旅行社就相当于你的代理,你只需要告诉他们你的需求,他们就会帮你完成所有的事情。
在软件开发中,代理也是类似的概念。代理对象就像中间人一样,它会拦截对目标对象的访问,并在访问前后执行一些额外的操作,比如记录日志、权限校验、缓存等等。
二、动态代理的魔力:运行时创建
代理模式有很多种实现方式,而动态代理则是其中最灵活的一种。它最大的特点就是在程序运行时,动态地创建代理对象和代理类,而不需要我们手动编写代理类的代码。
那么,Java是如何实现动态代理的呢?答案就是——反射!(点击查看什么是反射)
三、Java动态代理三剑客
Java提供了以下三种方式来实现动态代理:
-
JDK动态代理: 基于接口的动态代理,使用 java.lang.reflect.Proxy 类和 java.lang.reflect.InvocationHandler 接口。
-
CGLIB动态代理: 基于继承的动态代理,使用第三方库CGLIB。
-
ASM动态代理: 基于字节码操作的动态代理,使用第三方库ASM。
本文将重点介绍使用最广泛的JDK动态代理,其他两种方式留待后续文章中探讨。
四、JDK动态代理实战:打造万能日志记录器
假设我们有一个 UserService 接口和一个 UserServiceImpl 实现类,现在我们想在调用 UserServiceImpl 的每个方法前后都打印一条日志,记录方法的执行时间。
1. 定义接口和实现类:
// UserService 接口
public interface UserService {void createUser(String username, String password);void deleteUser(String username);
}// UserServiceImpl 实现类
public class UserServiceImpl implements UserService {@Overridepublic void createUser(String username, String password) {System.out.println("创建用户:" + username);// 模拟业务逻辑执行try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}}@Overridepublic void deleteUser(String username) {System.out.println("删除用户:" + username);// 模拟业务逻辑执行try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}}
}
2. 创建 InvocationHandler 实现类:
// InvocationHandler 实现类,用于处理代理逻辑
public class LogInvocationHandler implements InvocationHandler {// 目标对象private Object target;public LogInvocationHandler(Object target) {this.target = target;}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {// 方法执行前的逻辑long startTime = System.currentTimeMillis();System.out.println("开始执行方法:" + method.getName());// 调用目标对象的方法Object result = method.invoke(target, args);// 方法执行后的逻辑long endTime = System.currentTimeMillis();System.out.println("方法执行结束:" + method.getName() + ",耗时:" + (endTime - startTime) + "ms");return result;}
}
3. 创建代理对象:
public class Main {public static void main(String[] args) {// 创建目标对象UserService userService = new UserServiceImpl();// 创建 InvocationHandler 对象LogInvocationHandler handler = new LogInvocationHandler(userService);// 创建代理对象UserService proxy = (UserService) Proxy.newProxyInstance(userService.getClass().getClassLoader(), // 类加载器userService.getClass().getInterfaces(), // 接口数组handler); // InvocationHandler 对象// 调用代理对象的方法proxy.createUser("zhangsan", "123456");proxy.deleteUser("lisi");}
}
代码解释:
-
LogInvocationHandler 实现了 InvocationHandler 接口,并在 invoke() 方法中定义了代理逻辑,即在目标方法执行前后打印日志。
-
Proxy.newProxyInstance() 方法用于创建代理对象。它需要三个参数:
-
类加载器: 用于加载代理类。
-
接口数组: 目标对象实现的接口数组,代理类会实现这些接口。
-
InvocationHandler 对象: 用于处理代理逻辑。
-
-
调用代理对象的方法时,实际上会调用 LogInvocationHandler 的 invoke() 方法,从而实现记录日志的功能。
运行结果:
开始执行方法:createUser
创建用户:zhangsan
方法执行结束:createUser,耗时:104ms
开始执行方法:deleteUser
删除用户:lisi
方法执行结束:deleteUser,耗时:103ms
4.invoke() 方法
在Java中,invoke
方法主要用于反射机制,它属于java.lang.reflect.Method
类。通过这个方法,可以动态地调用某个类的实例方法或静态方法。
invoke
方法的基本用法
public Object invoke(Object obj, Object... args) throws IllegalAccessException, InvocationTargetException;
- 参数:
obj
:调用方法的对象实例。如果是静态方法,可以传null
。args
:调用方法时需要的参数,可以是可变参数数组。
使用示例
下面是一个简单的例子,演示如何使用invoke
方法:
import java.lang.reflect.Method;class Example {public void sayHello(String name) {System.out.println("Hello, " + name + "!");}
}public class Main {public static void main(String[] args) {try {// 创建 Example 类的实例Example example = new Example();// 获取 sayHello 方法Method method = Example.class.getMethod("sayHello", String.class);// 调用 sayHello 方法method.invoke(example, "World");} catch (Exception e) {e.printStackTrace();}}
}
注意事项
- 权限问题:如果方法是私有的,使用反射调用时需要设置为可访问。
- 异常处理:
invoke
方法可能会抛出多个异常,如IllegalAccessException
和InvocationTargetException
,需要适当处理。 - 性能开销:反射会比直接调用方法慢,因此在性能敏感的场合需谨慎使用。
五、总结
动态代理是Java中一个非常强大的机制,它可以帮助我们实现很多灵活的功能,比如:
-
AOP(面向切面编程)
-
RPC(远程过程调用)
-
事务管理
-
缓存等等
希望通过本文的介绍,你已经对Java动态代理有了初步的了解。在实际开发中,我们可以根据具体的需求,灵活运用动态代理,让我们的代码更加优雅高效!感谢各位看官的观看,下期见,谢谢~