java动态代理的实现

动态代理作为代理模式的一种扩展形式,广泛应用于框架(尤其是基于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
  1. import java.lang.reflect.Proxy;  
  2. import java.lang.reflect.InvocationHandler;  
  3. import java.lang.reflect.InvocationTargetException;  
  4. import java.lang.reflect.Method;  
  5. import java.util.Calendar;  
  6. import java.util.GregorianCalendar;  
  7.   
  8. //抽象UserDAO:抽象主题角色  
  9. 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
  1. class Client {  
  2.     public static void main(String args[]) {  
  3.         InvocationHandler handler = null;  
  4.           
  5.         AbstractUserDAO userDAO = new UserDAO();  
  6.         handler = new DAOLogHandler(userDAO);  
  7.         AbstractUserDAO proxy = null;  
  8.         //动态创建代理对象,用于代理一个AbstractUserDAO类型的真实主题对象  
  9.         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。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/458987.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

C++基础之this指针的详解

*************************************************** 更多精彩&#xff0c;欢迎进入&#xff1a;http://shop115376623.taobao.com *************************************************** 关于C中的this指针&#xff0c;建议大家看看这篇文章&#xff0c;《C中的this指针》&a…

如何用参数化SQL语句污染你的计划缓存

你的SQL语句的参数化总是个好想法。使用参数化SQL语句你不会污染你的计划缓存——错&#xff01;&#xff01;&#xff01;在这篇文章里我想向你展示下用参数化SQL语句就可以污染你的计划缓存&#xff0c;这是非常简单的&#xff01; ADO.NET-AddWithValue ADO.NET是实现像SQL …

Ios: 如何保護iOS束文件屬性列表,圖像,SQLite,媒體文件

Ios: 如何保護iOS束文件屬性列表&#xff0c;圖像&#xff0c;SQLite&#xff0c;媒體文件我創建了Hello World示例項目&#xff0c;然後添加data.plist文件到資源文件夾。現在人們可以很容易得到束文件解壓縮。國際音標。有任何的方法來保護data.plist文件保存在iPhone應用程序…

w3wp oracle,w3wp.exe占用CPU超过50%的处理

w3wp.exe占用CPU超过50%的处理1.查看CPU占用高的进程&#xff1a;任务管理器C:\Documents andSettings\Administrator>iisappW3WP.exe PID: 18008 AppPoolId: STATW3WP.exe PID: 8328 AppPoolId: STATW3WP.exe PID: 17868 AppPoolId: JYCV16W3WP.exe PID: 16652 AppPoolId: …

论两种学习模式

引言 A&#xff1a;你是如何学习的&#xff0c;通过视频、书籍和实践结合&#xff1f;B&#xff1a;不是&#xff0c;一般情况是以一个问题为点去画线和面。 两种学习模式 按部就班方式获取知识(通过书、视频)缺点 信息接收者缺乏深度思考和探索信息发布者的知识体系不一定适合…

启动mq命令 linux,RocketMQ:Linux下启动server和broker的命令

目录QUESTION:RocketMQ&#xff1a;Linux下启动server和broker的命令?ANSWER:一、启动mqnamesrv1.1当前执行1.2后台运行二、启动mqbroker2.1当前执行2.2后台运行QUESTION:RocketMQ&#xff1a;Linux下启动server和broker的命令?ANSWER:一、启动mqnamesrv1.1当前执行进入rocke…

C++中int *p[4]和 int (*q)[4]的区别

*************************************************** 更多精彩&#xff0c;欢迎进入&#xff1a;http://shop115376623.taobao.com *************************************************** C中int *p[4]和 int (*q)[4]的区别 前者是指针数组&#xff0c;后者是指向数组的指针…

linux不用命令开启ssh,不用密码也能ssh登陆Linux?

Linux的一个后门引发对PAM的探究1.1 起因今天在搜索关于Linux下的后门姿势时&#xff0c;发现一条命令如下&#xff1a;ln -sf /usr/sbin/sshd /tmp/su; /tmp/su -oPort5555;经典后门。直接对sshd建立软连接&#xff0c;之后用任意密码登录即可。ssh rootx.x.x.x -p 5555这个是…

ScrollView常用(暂时用上了的)代理方法

2019独角兽企业重金招聘Python工程师标准>>> ScrollView常用代理方法: #pragma mark - 滚动结束调用 -(void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {// 计算 滑动到了第几页double page scrollView.contentOffset.x / scrollView.width;self.p…

Android ListView性能优化实例讲解

前言&#xff1a; 对于ListView&#xff0c;大家绝对都不会陌生&#xff0c;只要是做过Android开发的人&#xff0c;哪有不用ListView的呢&#xff1f; 只要是用过ListView的人&#xff0c;哪有不关心对它性能优化的呢&#xff1f; 关于如何对ListView进行性能优化&#xff0c;…

Bzoj 3289: Mato的文件管理 莫队,树状数组,逆序对,离散化,分块

3289: Mato的文件管理 Time Limit: 40 Sec Memory Limit: 128 MBSubmit: 1539 Solved: 665[Submit][Status][Discuss]Description Mato同学从各路神犇以各种方式&#xff08;你们懂的&#xff09;收集了许多资料&#xff0c;这些资料一共有n份&#xff0c;每份有一个大小和一…

vs2010创建和使用动态链接库(dll)

*************************************************** 更多精彩&#xff0c;欢迎进入&#xff1a;http://shop115376623.taobao.com *************************************************** 本文将创建一个简单的动态链接库&#xff0c;并编写一个应用台控制程序使用该动态链接…

magenta内核与linux,谷歌将推出新操作系统Fuchsia:Magenta语言为内核

谷歌现在研发出来并且推出使用的系统有Chrome OS、Android和Chromecasts&#xff0c;这三者在操作系统的市场中占得份额很高&#xff0c;但是好像谷歌对此并不满意&#xff0c;因为有相关消息显示&#xff0c;谷歌正在研发新的操作系统Fuchsia&#xff0c;该系统采用Magenta语言…

BZOJ 1968: [Ahoi2005]COMMON 约数研究 水题

1968: [Ahoi2005]COMMON 约数研究 Time Limit: 20 Sec Memory Limit: 256 MB 题目连接 http://www.lydsy.com/JudgeOnline/problem.php?id1968 Description Input 只有一行一个整数 N&#xff08;0 < N < 1000000&#xff09;。 Output 只有一行输出&#xff0c;为整数M…

VC内存对齐准则(Memory alignment)

*************************************************** 更多精彩&#xff0c;欢迎进入&#xff1a;http://shop115376623.taobao.com *************************************************** 本文所有内容在建立在一个前提下&#xff1a;使用VC编译器。着重点在于&#xff1a;VC…

mysql 主主互备实现

今天星期天&#xff0c;么事就写个MYSQL的主主架构的博客&#xff0c;原理如下图&#xff0c;不是我画的网上找的。主机作用操作系统mysql版本对应IPvip数据库mysqlA(主)centos6.4mysql 5.5.48192.168.48.129192.168.48.126mysqlB(备)centos6.4mysql 5.5.48192.168.48.132一&am…

动手实践 Linux VLAN - 每天5分钟玩转 OpenStack(13)

本节我们来看如何在实验环境中实施和配置如下 VLAN 网络 配置 VLAN 编辑 /etc/network/interfaces&#xff0c;配置 eth0.10、brvlan10、eth0.20 和 brvlan20。 下面用 vmdiff 展示了对 /etc/network/interfaces 的修改 重启宿主机&#xff0c;ifconfig 各个网络接口 用 brct…

Socket的3次握手链接与4次断开握手

*************************************************** 更多精彩&#xff0c;欢迎进入&#xff1a;http://shop115376623.taobao.com *************************************************** 连接握手&#xff1a; 1.客户端发送建立连接请求 &#xff08;发送请求&#xff09;2.…

Linux桌面需要强制访问控制,RHCSA 系列(十三): 在 RHEL 7 中使用 SELinux 进行强制访问控制...

RHCSA 认证&#xff1a;SELinux 精要和控制文件系统的访问尽管作为第一级别的权限和访问控制机制是必要的&#xff0c;但它们同样有一些局限&#xff0c;而这些局限则可以由安全增强 Linux(Security Enhanced Linux&#xff0c;简称为 SELinux)来处理。这些局限的一种情形是&am…

使用canvas实现擦玻璃效果

体验效果:http://hovertree.com/texiao/html5/25/效果图&#xff1a;代码如下&#xff1a; <!DOCTYPE html> <html> <head lang"zh"> <meta name"viewport" content"initial-scale1.0, maximum-scale1.0, user-scalableno, widt…