本文为大家讲解代理模式,包括静态代理的作用和代码实现、动态代理的作用、使用反射实现动态代理的过程,从而理解 AOP 的原理。
代理模式分为:静态代理和动态代理。代理模式实现的功能和我们生活中的代理一样,类似于中介公司。也就是代理对象帮助被代理对象完成功能,被代理对象可以在代理对象已有的功能基础上,扩展代理对象的功能。
比如在已存在的多个具有相同接口的目标类的各个方法上增加一些系统功能,经常会使用到代理模式,例如:方法执行前后的日志打印,计算方法的运行时间、异常处理、事务等等。
1
静态代理
1、静态代理的介绍与代码实现
下面我们通过一个例子来认识静态代理的作用,假设我们有一个操作数据库的 Dao 服务接口 DaoService ,里面包括查询方法 query() 和更新方法 update(),程序中需要操作数据库的有订单服务 OrderService 类,它实现了 DaoService 接口。
我们思考一个问题,如果需要在查询方法 query() 和更新方法 update() 执行的前后需要加入日志打印功能,也就是说在方法执行前打印一行日志,表示方法开始执行,在方法执行后打印一行日志,表示方法执行完毕。
大家可以思考,以上的问题我们可以怎么解决?
任何人都能想到的最直接的办法就是,在每个实现了 DaoService 接口的类的 query() 方法和 update() 方法的前后加上两行日志就可以了。这种方法其实产生了很多的重复代码,不是一个好的解决办法。如果实现了 DaoService 接口的类有很多,那么我们就会在这些类的 query() 方法和 update() 方法加很多日志代码。
另外一个解决办法就是用静态代理来实现,用一个代理类 DaoServiceProxy 实现 DaoService 接口,让它来对实现了 DaoService 接口的类进行代理,在代理类的query() 方法和 update() 方法的前后加上两行日志就可以了。这样做的好处是不管有多少 DaoService 的实现类,只需要加一个代理类,在代理类的具体方法上加两行日志就可以了,重复代码得到了极大的减少。
示例代码如下:
// DaoService接口
interface DaoService {
void query();
void update();
}
// 被代理类OrderService
class OrderService implements DaoService {
@Override
public void query() {
System.out.println("OrderService.query()");
}
@Override
public void update() {
System.out.println("OrderService.update()");
}
}
// 代理类
class DaoServiceProxy implements DaoService {
DaoService dao;
// 创建代理类对象的时候,实际上传入一个被代理类的对象
public DaoServiceProxy(DaoService dao) {
this.dao = dao;
}
@Override
public void query() {
System.out.println("query()开始执行!");
dao.query();
System.out.println("query()执行完毕!");
}
@Override
public void update() {
System.out.println("update()开始执行!");
dao.update();
System.out.println("update()执行完毕!");
}
}
// 测试
public class TestStaticProxy1 {
public static void main(String[] args) {
// 创建被代理类的对象
OrderService orderService = new OrderService();
// 创建代理类的对象
DaoServiceProxy orderServiceProxy = new DaoServiceProxy(orderService);
orderServiceProxy.query();
System.out.println("==================");
orderServiceProxy.update();
}
}
运行结果:
query()开始执行!
OrderService.query()
query()执行完毕!
==================
update()开始执行!
OrderService.update()
update()执行完毕!
假如我们现在新建了一个用户表,需要开发用户的查询和更新方法的代码,同时要求在方法执行前后输出日志,那么我们就可以非常方便的完成。
只需要写一个用户服务类 UserService 实现 DaoService 接口,在调用 UserService 方法的时候把 UserService 类的对象传递给代理类 DaoServiceProxy 类,然后调用代理类的 query() 方法和 update() 方法,就可以完美的打印出方法调用前后的日志。
// 用户服务类UserService
class UserService implements DaoService {
@Override
public void query() {
System.out.println("UserService.query()");
}
@Override
public void update() {
System.out.println("UserService.update()");
}
}
public class TestStaticProxy2 {
public static void main(String[] args) {
// 创建被代理类的对象 userService
UserService userService = new UserService();
// 创建代理类的对象
DaoServiceProxy orderServiceProxy = new DaoServiceProxy(userService);
orderServiceProxy.query();
System.out.println("==================");
orderServiceProxy.update();
}
}
运行结果:
query()开始执行!
UserService.query()
query()执行完毕!
==================
update()开始执行!
UserService.update()
update()执行完毕!
从以上的代码我们可以看出,使用了代理设计模式会使我们实际开发过程中的代码量大幅减少,这就是使用设计模式的威力!
2
动态代理
2、动态代理介绍与代码实现
静态代理的特点是一个代理类只能为一个接口服务,这样的话程序开发中必然会产生过多的代理类。
比如程序中除了有数据库操作 DaoService 接口,还有支付操作 PayService 接口,PayService 接口里定义了支付方法 pay(),要求在 pay() 方法的执行前后也要加两行日志的输出。
这样的话,不得不再建立一个实现了 PayService 接口的代理类,用来代理实现了 PayService 接口的被代理类的操作。如果还有其他的接口,则需要继续创建代理类,因此最好的解决办法是通过一个动态代理类完成所有接口的方法执行前后的日志输出功能。
在 Java 中实现动态代理机制,需要使用 java.lang.reflect.InvocationHandler 接口以及 java.lang.reflect.Proxy 类。
InvocationHandler 接口的定义如下:
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable;
在 InvocationHandler 接口中只定义了一个 invoke() 方法,此方法中有 3 个参数:
1、Object proxy:被代理的对象
2、Method method:要调用的方法
3、Object[] args:方法调用时需要传入的参数
Proxy 类是用来创建代理类的,它通过 newProxyInstance() 方法为一个或多个接口动态的生成实现类。
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException
1、ClassLoader loader:类加载器
2、Class<?>[] interfaces:获取被代理类的全部接口
3、InvocationHandler h:InvocationHandler 接口的子类的实例
下面我们用动态代理来实现刚才提到的需求场景的日志打印
//PayService接口
interface PayService {
void pay();
}
//被代理类WeChatPayService
class WeChatPayService implements PayService {
@Override
public void pay() {
System.out.println("WeChatPayService.pay()");
}
}
// 动态代理类DynamicProxy
class DynamicProxy implements InvocationHandler {
Object obj; // 实现了接口的被代理类的对象的声明
// 1、给被代理类的对象实例化,并且返回一个代理类对象,体会一下反射是动态语言的关键
public Object getInstance(Object obj) {
this.obj = obj;
Class<?> clazz = obj.getClass();
return Proxy.newProxyInstance(clazz.getClassLoader(), clazz.getInterfaces(), this);
}
// 当通过代理类的对象发起对被重写的方法的调用时,都会转换为对如下的invoke方法的调用
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println(method.getName() + "开始执行!");
// returnVal就是调用被重写的方法的返回值
Object returnVal = method.invoke(obj, args);
System.out.println(method.getName() + "执行完毕!");
return returnVal;
}
}
// 测试
public class TestDynamicProxy {
public static void main(String[] args) {
System.out.println("1、动态代理orderService");
// 被代理类的对象orderService
OrderService orderService = new OrderService();
// 创建一个实现了InvocationHandler接口的类的对象
DynamicProxy proxy = new DynamicProxy();
// 调用getInstance()方法,动态的返回一个同样实现了被代理类OrderService类实现的接口DaoService的代理类的对象
// dao就是代理类对象
DaoService order = (DaoService) proxy.getInstance(orderService);
order.query(); // 调用代理类对象的方法就会转换为调用handler类里invoke方法的调用
System.out.println("==================");
order.update();
System.out.println("2、动态代理UserService");
UserService userService = new UserService();
DaoService user = (DaoService) proxy.getInstance(userService);
user.query();
System.out.println("==================");
user.update();
System.out.println("3、动态代理WeChatPayService");
WeChatPayService weChatPayService = new WeChatPayService();
PayService weChat = (PayService) proxy.getInstance(weChatPayService);
weChat.pay();
}
}
运行结果:
1、动态代理orderService
query开始执行!
OrderService.query()
query执行完毕!
==================
update开始执行!
OrderService.update()
update执行完毕!
2、动态代理UserService
query开始执行!
UserService.query()
query执行完毕!
==================
update开始执行!
UserService.update()
update执行完毕!
3、动态代理WeChatPayService
pay开始执行!
WeChatPayService.pay()
pay执行完毕!
从以上的代码以及运行结果可以看出,动态代理可以对任意的接口实现类进行代理,避免了静态代理需要针对不同的接口开发对应的代理类。
动态代理可以对任何接口的所有方法实现前后增加相同功能的目的,Spring 框架中的 AOP 就是采用了动态代理的设计模式。AOP 切面编程对系统的设计与编码具有非常重要的作用,对于日志、事务、权限校验等可以在系统设计的阶段不予考虑,在设计后通过 AOP 的方式实现。
3
动态代理的实现模式
动态代理的实现模式有两种:
1、用 JDK 实现:代理对象必须实现一个接口,否则无法使用 JDK 自带的动态代理。上面的例子就是用 JDK 实现的动态代理。
2、用 CGLIB 实现:代理对象可以不实现接口,但是代理方法不能用 final 修饰。
1、用 CGLIB 实现动态代理
JDK 动态代理中提供一个 Proxy 类来创建代理类,而在 CGLib 动态代理中也提供了一个类 Enhancer 来创建代理类。使用 CGLib 创建动态代理类需要在项目中引入 CGLib 的 jar 包。
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.2.5</version>
</dependency>
使用CGLib 实现动态代理,首先需要动态代理类实现 MethodInterceptor 方法拦截器接口,然后通过构造函数传递被代理对象,然后利用 Enhancer 来实例化被代理对象,通过 Enhancer 设置被代理对象的字节码文件、设置回调函数、创建被代理对象,最后覆写 intercept() 方法。
使用CGLib 实现动态代理的示例代码如下:
// CGLIB动态代理类
class CglibProxy implements MethodInterceptor {
private Object obj; // 被代理对象
public CglibProxy(Object obj) {
this.obj = obj;
}
// 给目标对象创建一个被代理对象的示例
public Object getInstance() {
// 创建Enhancer对象,类似于JDK动态代理的Proxy类
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(obj.getClass()); // 设置被代理对象的字节码文件
enhancer.setCallback(this);// 设置回调函数
return enhancer.create();// 创建被代理对象(子类)
}
@Override
public Object intercept(Object obj, Method method, Object[] objects, MethodProxy proyx) throws Throwable {
System.out.println("cglib动态代理开始");
Object object = proyx.invokeSuper(obj, objects); // 关键代码
System.out.println("cglib动态代理结束");
return object;
}
}
// 测试
public class TestCglib {
public static void main(String[] args) {
System.out.println("1、动态代理orderService");
DaoService orderService = new OrderService();
CglibProxy orderProxy = new CglibProxy(orderService);
OrderService order = (OrderService) orderProxy.getInstance();
order.query();
System.out.println("==================");
order.update();
System.out.println("2、动态代理UserService");
DaoService userService = new UserService();
CglibProxy userProxy = new CglibProxy(userService);
UserService user = (UserService) userProxy.getInstance();
user.query();
System.out.println("==================");
user.update();
System.out.println("3、动态代理WeChatPayService");
PayService payService = new WeChatPayService();
CglibProxy payProxy = new CglibProxy(payService);
WeChatPayService weChat = (WeChatPayService) payProxy.getInstance();
weChat.pay();
}
}
本文详细介绍了静态代理和动态代理的作用和实现方式,并介绍了动态代理实现的两种方式,在一般的开发中很少会使用到动态代理,但是在编写一些底层代码或者框架代码的时候动态代理模式就比较常用了,掌握动态代理的实现模式是一个程序员走向高级的必备技能。
下一篇文章将会为大家介绍如何使用动态代理模拟 Spring 框架的 AOP 切面编程,实现统计方法执行前后共花费的时间功能,敬请关注.....
追梦Java
知识指导行动,行动决定命运。