动态代理作为代理模式的一种扩展形式,广泛应用于框架(尤其是基于AOP的框架)的设计与开发,本文将通过实例来讲解Java动态代理的实现过程。
友情提示:本文略有难度,读者需具备代理模式相关基础知识,微笑。
通常情况下,代理模式中的每一个代理类在编译之后都会生成一个class文件,代理类所实现的接口和所代理的方法都被固定,这种代理被称之为静态代理(Static Proxy)。那么有没有一种机制能够让系统在运行时动态创建代理类?答案就是本文将要介绍的动态代理(Dynamic Proxy)。动态代理是一种较为高级的代理模式,它在事务管理、AOP(Aspect-OrientedProgramming,面向方面编程)等领域都发挥了重要的作用。
在传统的代理模式中,客户端通过Proxy类调用RealSubject类的request()方法,同时还可以在代理类中封装其他方法(如preRequest()和postRequest()等)。如果按照这种方法使用代理模式,那么代理类和真实主题类都应该是事先已经存在的,代理类的接口和所代理方法都已明确指定,如果需要为不同的真实主题类提供代理类或者代理一个真实主题类中的不同方法,都需要增加新的代理类,这将导致系统中的类个数急剧增加,因此需要想办法减少系统中类的个数。动态代理可以让系统能够根据实际需要来动态创建代理类,让同一个代理类能够代理多个不同的真实主题类而且可以代理不同的方法。
从JDK 1.3开始,Java语言提供了对动态代理的支持,Java语言实现动态代理时需要用到位于java.lang.reflect包中的一些类,现简要说明如下:
(1) Proxy类
Proxy类提供了用于创建动态代理类和实例对象的方法,它是所创建的动态代理类的父类,它最常用的方法如下:
•public static Class> getProxyClass(ClassLoader loader,Class>... interfaces):该方法用于返回一个Class类型的代理类,在参数中需要提供类加载器并需要指定代理的接口数组(与真实主题类的接口列表一致)。
•public static Object newProxyInstance(ClassLoader loader, Class>[]interfaces, InvocationHandler h):该方法用于返回一个动态创建的代理类的实例,方法中第一个参数loader表示代理类的类加载器,第二个参数interfaces表示代理类所实现的接口列表(与真实主题类的接口列表一致),第三个参数h表示所指派的调用处理程序类。
(2) InvocationHandler接口
InvocationHandler接口是代理处理程序类的实现接口,该接口作为代理实例的调用处理者的公共父类,每一个代理类的实例都可以提供一个相关的具体调用处理者(InvocationHandler接口的子类)。在该接口中声明了如下方法:
•public Object invoke(Objectproxy, Method method, Object[] args):该方法用于处理对代理类实例的方法调用并返回相应的结果,当一个代理实例中的业务方法被调用时将自动调用该方法。invoke()方法包含三个参数,其中第一个参数proxy表示代理类的实例,第二个参数method表示需要代理的方法,第三个参数args表示代理方法的参数数组。
动态代理类需要在运行时指定所代理真实主题类的接口,客户端在调用动态代理对象的方法时,调用请求会将请求自动转发给InvocationHandler对象的invoke()方法,由invoke()方法来实现对请求的统一处理。
下面通过一个简单实例来学习如何使用动态代理模式:
Sunny软件公司欲为公司OA系统数据访问层DAO增加方法调用日志,记录每一个方法被调用的时间和调用结果,现使用动态代理进行设计和实现。
本实例完整代码如下所示:
[java] view plain copy
01.import java.lang.reflect.Proxy;
02.import java.lang.reflect.InvocationHandler;
03.import java.lang.reflect.InvocationTargetException;
04.import java.lang.reflect.Method;
05.import java.util.Calendar;
06.import java.util.GregorianCalendar;
07.
08.//抽象UserDAO:抽象主题角色
09.interface AbstractUserDAO {
10. public Boolean findUserById(String userId);
11.}
12.
13.//抽象DocumentDAO:抽象主题角色
14.interface AbstractDocumentDAO {
15. public Boolean deleteDocumentById(String documentId);
16.}
17.
18.//具体UserDAO类:真实主题角色
19.class UserDAO implements AbstractUserDAO {
20. public Boolean findUserById(String userId) {
21. if (userId.equalsIgnoreCase("张无忌")) {
22. System.out.println("查询ID为" + userId + "的用户信息成功!");
23. return true;
24. }
25. else {
26. System.out.println("查询ID为" + userId + "的用户信息失败!");
27. return false;
28. }
29. }
30.}
31.
32.//具体DocumentDAO类:真实主题角色
33.class DocumentDAO implements AbstractDocumentDAO {
34. public Boolean deleteDocumentById(String documentId) {
35. if (documentId.equalsIgnoreCase("D001")) {
36. System.out.println("删除ID为" + documentId + "的文档信息成功!");
37. return true;
38. }
39. else {
40. System.out.println("删除ID为" + documentId + "的文档信息失败!");
41. return false;
42. }
43. }
44.}
45.
46.//自定义请求处理程序类
47.class DAOLogHandler implements InvocationHandler {
48. private Calendar calendar;
49. private Object object;
50.
51. public DAOLogHandler() {
52. }
53.
54. //自定义有参构造函数,用于注入一个需要提供代理的真实主题对象
55. public DAOLogHandler(Object object) {
56. this.object = object;
57. }
58.
59. //实现invoke()方法,调用在真实主题类中定义的方法
60. public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
61. beforeInvoke();
62. Object result = method.invoke(object, args); //转发调用
63. afterInvoke();
64. return null;
65. }
66.
67. //记录方法调用时间
68. public void beforeInvoke(){
69. calendar = new GregorianCalendar();
70. int hour = calendar.get(Calendar.HOUR_OF_DAY);
71. int minute = calendar.get(Calendar.MINUTE);
72. int second = calendar.get(Calendar.SECOND);
73. String time = hour + ":" + minute + ":" + second;
74. System.out.println("调用时间:" + time);
75. }
76.
77. public void afterInvoke(){
78. System.out.println("方法调用结束!" );
79. }
80.}
编写如下客户端测试代码:
[java] view plain copy
01.class Client {
02. public static void main(String args[]) {
03. InvocationHandler handler = null;
04.
05. AbstractUserDAO userDAO = new UserDAO();
06. handler = new DAOLogHandler(userDAO);
07. AbstractUserDAO proxy = null;
08. //动态创建代理对象,用于代理一个AbstractUserDAO类型的真实主题对象
09. proxy = (AbstractUserDAO)Proxy.newProxyInstance(AbstractUserDAO. class.getClassLoader(), new Class[]{AbstractUserDAO.class}, handler);
10. proxy.findUserById("张无忌"); //调用代理对象的业务方法
11.
12. System.out.println("------------------------------");
13.
14. AbstractDocumentDAO docDAO = new DocumentDAO();
15. handler = new DAOLogHandler(docDAO);
16. AbstractDocumentDAO proxy_new = null;
17.//动态创建代理对象,用于代理一个AbstractDocumentDAO类型的真实主题对象
18. proxy_new = (AbstractDocumentDAO)Proxy.newProxyInstance(Abstract DocumentDAO.class.getClassLoader(), new Class[]{AbstractDocumentDAO.class}, handler);
19. proxy_new.deleteDocumentById("D002"); //调用代理对象的业务方法
20. }
21.}
编译并运行程序,输出结果如下:
调用时间:13:47:14
查询ID为张无忌的用户信息成功!
方法调用结束!
------------------------------
调用时间:13:47:14
删除ID为D002的文档信息失败!
方法调用结束!
通过使用动态代理,我们可以实现对多个真实主题类的统一代理和集中控制。
注:JDK中提供的动态代理只能代理一个或多个接口,如果需要动态代理具体类或抽象类,可以使用CGLib(Code Generation Library)等工具,CGLib是一个功能较为强大、性能和质量也较好的代码生成包,在许多AOP框架中都得以广泛应用,大家可以自行查阅相关资料来学习CGLib
Aop是什么?
AOP(Aspect Oriented Programming) 面向切面编程,是目前软件开发中的一个热点,是spring框架内容,利用AOP可以对业务逻辑的各个部分隔离,从而使的业务逻辑各部分的耦合性降低,提高程序的可重用性,踢开开发效率,主要功能:日志记录,性能统计,安全控制,事务处理,异常处理等。
AOP实现原理是Java动态代理,但是jdk的动态代理必须实现接口,所以spring的aop是用cglib这个库实现的,cglis使用里asm这个直接操纵字节码的框架,所以可以做到不使用接口的情况下实现动态代理。
AOP与OOP的却别:
OOP面向对象编程,针对业务处理过程的实体及其属性和行为进行抽象封装,以获得更加清晰高效的逻辑单元划分。而AOP则是针对业务处理过程中的切面进行提取,它所面对的是处理过程的某个步骤或阶段,以获得逻辑过程的中各部分之间低耦合的隔离效果。这两种设计思想在目标上有着本质的差异。
举例:
对于“雇员”这样一个业务实体进行封装,自然是OOP的任务,我们可以建立一个“Employee”类,并将“雇员”相关的属性和行为封装其中。而用AOP 设计思想对“雇员”进行封装则无从谈起。
同样,对于“权限检查”这一动作片段进行划分,则是AOP的目标领域。
OOP面向名次领域,AOP面向动词领域。
动态代理
动态代理作为代理模式的一种扩展形式,广泛应用于框架(尤其是基于AOP的框架)的设计与开发,本文将通过实例来讲解Java动态代理的实现过程。
通常情况下,代理模式中的每一个代理类在编译之后都会生成一个class文件,代理类所实现的接口和所代理的方法都被固定,这种代理被称之为静态代理(Static Proxy)。那么有没有一种机制能够让系统在运行时动态创建代理类?答案就是本文将要介绍的动态代理(Dynamic Proxy)。动态代理是一种较为高级的代理模式,它在事务管理、AOP(Aspect-OrientedProgramming,面向方面编程)等领域都发挥了重要的作用。
在传统的代理模式中,客户端通过Proxy类调用RealSubject类的request()方法,同时还可以在代理类中封装其他方法(如preRequest()和postRequest()等)。如果按照这种方法使用代理模式,那么代理类和真实主题类都应该是事先已经存在的,代理类的接口和所代理方法都已明确指定,如果需要为不同的真实主题类提供代理类或者代理一个真实主题类中的不同方法,都需要增加新的代理类,这将导致系统中的类个数急剧增加,因此需要想办法减少系统中类的个数。动态代理可以让系统能够根据实际需要来动态创建代理类,让同一个代理类能够代理多个不同的真实主题类而且可以代理不同的方法。
从JDK 1.3开始,Java语言提供了对动态代理的支持,Java语言实现动态代理时需要用到位于java.lang.reflect包中的一些类,现简要说明如下:
(1) Proxy类
Proxy类提供了用于创建动态代理类和实例对象的方法,它是所创建的动态代理类的父类,它最常用的方法如下:
(2) InvocationHandler接口
InvocationHandler接口是代理处理程序类的实现接口,该接口作为代理实例的调用处理者的公共父类,每一个代理类的实例都可以提供一个相关的具体调用处理者(InvocationHandler接口的子类)。在该接口中声明了如下方法:
动态代理类需要在运行时指定所代理真实主题类的接口,客户端在调用动态代理对象的方法时,调用请求会将请求自动转发给InvocationHandler对象的invoke()方法,由invoke()方法来实现对请求的统一处理。
下面通过一个简单实例来学习如何使用动态代理模式:
Sunny软件公司欲为公司OA系统数据访问层DAO增加方法调用日志,记录每一个方法被调用的时间和调用结果,现使用动态代理进行设计和实现。 |
本实例完整代码如下所示:
import java.lang.reflect.Proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Calendar;
import java.util.GregorianCalendar;
//抽象UserDAO:抽象主题角色
interface AbstractUserDAO {
public Boolean findUserById(String userId);
}
//抽象DocumentDAO:抽象主题角色
interface AbstractDocumentDAO {
public Boolean deleteDocumentById(String documentId);
}
//具体UserDAO类:真实主题角色
class UserDAO implements AbstractUserDAO {
public Boolean findUserById(String userId) {
if (userId.equalsIgnoreCase("张无忌")) {
System.out.println("查询ID为" + userId + "的用户信息成功!");
return true;
}
else {
System.out.println("查询ID为" + userId + "的用户信息失败!");
return false;
}
}
}
//具体DocumentDAO类:真实主题角色
class DocumentDAO implements AbstractDocumentDAO {
public Boolean deleteDocumentById(String documentId) {
if (documentId.equalsIgnoreCase("D001")) {
System.out.println("删除ID为" + documentId + "的文档信息成功!");
return true;
}
else {
System.out.println("删除ID为" + documentId + "的文档信息失败!");
return false;
}
}
}
//自定义请求处理程序类
class DAOLogHandler implements InvocationHandler {
private Calendar calendar;
private Object object;
public DAOLogHandler() {
}
//自定义有参构造函数,用于注入一个需要提供代理的真实主题对象
public DAOLogHandler(Object object) {
this.object = object;
}
//实现invoke()方法,调用在真实主题类中定义的方法
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
beforeInvoke();
Object result = method.invoke(object, args); //转发调用
afterInvoke();
return null;
}
//记录方法调用时间
public void beforeInvoke(){
calendar = new GregorianCalendar();
int hour = calendar.get(Calendar.HOUR_OF_DAY);
int minute = calendar.get(Calendar.MINUTE);
int second = calendar.get(Calendar.SECOND);
String time = hour + ":" + minute + ":" + second;
System.out.println("调用时间:" + time);
}
public void afterInvoke(){
System.out.println("方法调用结束!" );
}
}
编写如下客户端测试代码:
class Client {
public static void main(String args[]) {
InvocationHandler handler = null;
AbstractUserDAO userDAO = new UserDAO();
handler = new DAOLogHandler(userDAO);
AbstractUserDAO proxy = null;
//动态创建代理对象,用于代理一个AbstractUserDAO类型的真实主题对象
proxy = (AbstractUserDAO)Proxy.newProxyInstance(AbstractUserDAO. class.getClassLoader(), new Class[]{AbstractUserDAO.class}, handler);
proxy.findUserById("张无忌"); //调用代理对象的业务方法
System.out.println("------------------------------");
AbstractDocumentDAO docDAO = new DocumentDAO();
handler = new DAOLogHandler(docDAO);
AbstractDocumentDAO proxy_new = null;
//动态创建代理对象,用于代理一个AbstractDocumentDAO类型的真实主题对象
proxy_new = (AbstractDocumentDAO)Proxy.newProxyInstance(Abstract DocumentDAO.class.getClassLoader(), new Class[]{AbstractDocumentDAO.class}, handler);
proxy_new.deleteDocumentById("D002"); //调用代理对象的业务方法
}
}
编译并运行程序,输出结果如下:
调用时间:13:47:14 查询ID为张无忌的用户信息成功! 方法调用结束! ------------------------------ 调用时间:13:47:14 删除ID为D002的文档信息失败! 方法调用结束! |
通过使用动态代理,我们可以实现对多个真实主题类的统一代理和集中控制。
注:JDK中提供的动态代理只能代理一个或多个接口,如果需要动态代理具体类或抽象类,可以使用CGLib(Code Generation Library)等工具,CGLib是一个功能较为强大、性能和质量也较好的代码生成包,在许多AOP框架中都得以广泛应用,大家可以自行查阅相关资料来学习CGLib