之前写了一篇关于IOC的博客——《Spring容器IOC解析及简单实现》,今天再来聊聊AOP。大家都知道Spring的两大特性是IOC和AOP,换句话说,容器的两大特性就是IOC和AOP。

IOC负责将对象动态的注入到容器,从而达到一种需要谁就注入谁,什么时候需要就什么时候注入的效果,可谓是招之则来,挥之则去。想想都觉得爽,如果现实生活中也有这本事那就爽歪歪了,至于有多爽,各位自己脑补吧;而AOP呢,它实现的就是容器的另一大好处了,就是可以让容器中的对象都享有容器中的公共服务。那么容器是怎么做到的呢?它怎么就能让在它里面的对象自动拥有它提供的公共性服务呢?答案就是我们今天要讨论的内容——动态代理。

动态代理其实并不是什么新鲜的东西,学过设计模式的人都应该知道代理模式,代理模式是一种静态代理,而动态代理就是利用反射和动态编译将代理模式变成动态的。原理跟动态注入一样,代理模式在编译的时候就已经确定代理类将要代理谁,而动态代理在运行的时候才知道自己要代理谁。

Spring的动态代理有两种:一是JDK的动态代理;另一个是cglib动态代理(通过修改字节码来实现代理)。今天咱们主要讨论JDK动态代理的方式。JDK的代理方式主要就是通过反射跟动态编译来实现的,下面咱们就通过代码来看看它具体是怎么实现的。

假设我们要对下面这个用户管理进行代理:

  1. //用户管理接口
  2. package com.tgb.proxy;
  3.  
  4. public interface UserMgr {
  5. void addUser();
  6. void delUser();
  7. }
  8.  
  9. //用户管理的实现
  10. package com.tgb.proxy;
  11.  
  12. public class UserMgrImpl implements UserMgr {
  13.  
  14. @Override
  15. public void addUser() {
  16. System.out.println("添加用户.....");
  17. }
  18.  
  19. @Override
  20. public void delUser() {
  21. System.out.println("删除用户.....");
  22. }
  23.  
  24. }

按照代理模式的实现方式,肯定是用一个代理类,让它也实现UserMgr接口,然后在其内部声明一个UserMgrImpl,然后分别调用addUser和delUser方法,并在调用前后加上我们需要的其他操作。但是这样很显然都是写死的,我们怎么做到动态呢?别急,接着看。

我们知道,要实现代理,那么我们的代理类跟被代理类都要实现同一接口,但是动态代理的话我们根本不知道我们将要代理谁,也就不知道我们要实现哪个接口,那么要怎么办呢?我们只有知道要代理谁以后,才能给出相应的代理类,那么我们何不等知道要代理谁以后再去生成一个代理类呢?想到这里,我们好像找到了解决的办法,就是动态生成代理类!

这时候我们亲爱的反射又有了用武之地,我们可以写一个方法来接收被代理类,这样我们就可以通过反射知道它的一切信息——包括它的类型、它的方法等等(如果你不知道怎么得到,请先去看看我写的反射的博客《反射一》《反射二》)。

JDK动态代理的两个核心分别是InvocationHandler和Proxy,下面我们就用简单的代码来模拟一下它们是怎么实现的:

InvocationHandler接口:

  1. package com.tgb.proxy;
  2.  
  3. import java.lang.reflect.Method;
  4.  
  5. public interface InvocationHandler {
  6. public void invoke(Object o, Method m);
  7. }

实现动态代理的关键部分,通过Proxy动态生成我们具体的代理类:

  1. package com.tgb.proxy;
  2.  
  3. import java.io.File;
  4. import java.io.FileWriter;
  5. import java.lang.reflect.Constructor;
  6. import java.lang.reflect.Method;
  7. import java.net.URL;
  8. import java.net.URLClassLoader;
  9. import javax.tools.JavaCompiler;
  10. import javax.tools.StandardJavaFileManager;
  11. import javax.tools.ToolProvider;
  12. import javax.tools.JavaCompiler.CompilationTask;
  13.  
  14. public class Proxy {
  15. /**
  16. *
  17. * @param infce 被代理类的接口
  18. * @param h 代理类
  19. * @return
  20. * @throws Exception
  21. */
  22. public static Object newProxyInstance(Class infce, InvocationHandler h) throws Exception {
  23. String methodStr = "";
  24. String rt = "\r\n";
  25.  
  26. //利用反射得到infce的所有方法,并重新组装
  27. Method[] methods = infce.getMethods();
  28. for(Method m : methods) {
  29. methodStr += " @Override" + rt +
  30. " public "+m.getReturnType()+" " + m.getName() + "() {" + rt +
  31. " try {" + rt +
  32. " Method md = " + infce.getName() + ".class.getMethod(\"" + m.getName() + "\");" + rt +
  33. " h.invoke(this, md);" + rt +
  34. " }catch(Exception e) {e.printStackTrace();}" + rt +
  35. " }" + rt ;
  36. }
  37.  
  38. //生成Java源文件
  39. String srcCode =
  40. "package com.tgb.proxy;" + rt +
  41. "import java.lang.reflect.Method;" + rt +
  42. "public class $Proxy1 implements " + infce.getName() + "{" + rt +
  43. " public $Proxy1(InvocationHandler h) {" + rt +
  44. " this.h = h;" + rt +
  45. " }" + rt +
  46. " com.tgb.proxy.InvocationHandler h;" + rt +
  47. methodStr + rt +
  48. "}";
  49. String fileName =
  50. "d:/src/com/tgb/proxy/$Proxy1.java";
  51. File f = new File(fileName);
  52. FileWriter fw = new FileWriter(f);
  53. fw.write(srcCode);
  54. fw.flush();
  55. fw.close();
  56.  
  57. //将Java文件编译成class文件
  58. JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
  59. StandardJavaFileManager fileMgr = compiler.getStandardFileManager(null, null, null);
  60. Iterable units = fileMgr.getJavaFileObjects(fileName);
  61. CompilationTask t = compiler.getTask(null, fileMgr, null, null, null, units);
  62. t.call();
  63. fileMgr.close();
  64.  
  65. //加载到内存,并实例化
  66. URL[] urls = new URL[] {new URL("file:/" + "d:/src/")};
  67. URLClassLoader ul = new URLClassLoader(urls);
  68. Class c = ul.loadClass("com.tgb.proxy.$Proxy1");
  69.  
  70. Constructor ctr = c.getConstructor(InvocationHandler.class);
  71. Object m = ctr.newInstance(h);
  72.  
  73. return m;
  74. }
  75.  
  76. }

这个类的主要功能就是,根据被代理对象的信息,动态组装一个代理类,生成$Proxy1.java文件,然后将其编译成$Proxy1.class。这样我们就可以在运行的时候,根据我们具体的被代理对象生成我们想要的代理类了。这样一来,我们就不需要提前知道我们要代理谁。也就是说,你想代理谁,想要什么样的代理,我们就给你生成一个什么样的代理类。

然后,在客户端我们就可以随意的进行代理了。

  1. package com.tgb.proxy;
  2.  
  3. public class Client {
  4. public static void main(String[] args) throws Exception {
  5. UserMgr mgr = new UserMgrImpl();
  6.  
  7. //为用户管理添加事务处理
  8. InvocationHandler h = new TransactionHandler(mgr);
  9. UserMgr u = (UserMgr)Proxy.newProxyInstance(UserMgr.class,h);
  10.  
  11. //为用户管理添加显示方法执行时间的功能
  12. TimeHandler h2 = new TimeHandler(u);
  13. u = (UserMgr)Proxy.newProxyInstance(UserMgr.class,h2);
  14.  
  15. u.addUser();
  16. System.out.println("\r\n==========华丽的分割线==========\r\n");
  17. u.delUser();
  18. }
  19. }

运行结果:

  1. 开始时间:2014年-07月-15 15时:48分:54
  2. 开启事务.....
  3. 添加用户.....
  4. 提交事务.....
  5. 结束时间:2014年-07月-15 15时:48分:57
  6. 耗时:3
  7.  
  8. ==========华丽的分割线==========
  9.  
  10. 开始时间:2014年-07月-15 15时:48分:57
  11. 开启事务.....
  12. 删除用户.....
  13. 提交事务.....
  14. 结束时间:2014年-07月-15 15时:49分:00
  15. 耗时:3

这里我写了两个代理的功能,一个是事务处理,一个是显示方法执行时间的代理,当然都是非常简单的写法,只是为了说明这个原理。当然,我们可以想Spring那样将这些AOP写到配置文件,因为之前那篇已经写了怎么通过配置文件注入了,这里就不重复贴了。

到这里,你可能会有一个疑问:你上面说,只要放到容器里的对象,都会有容器的公共服务,我怎么没看出来呢?好,那我们就继续看一下我们的代理功能:

事务处理:

  1. package com.tgb.proxy;
  2.  
  3. import java.lang.reflect.Method;
  4.  
  5. public class TransactionHandler implements InvocationHandler {
  6.  
  7. private Object target;
  8.  
  9. public TransactionHandler(Object target) {
  10. super();
  11. this.target = target;
  12. }
  13.  
  14. @Override
  15. public void invoke(Object o, Method m) {
  16. System.out.println("开启事务.....");
  17. try {
  18. m.invoke(target);
  19. } catch (Exception e) {
  20. e.printStackTrace();
  21. }
  22. System.out.println("提交事务.....");
  23. }
  24.  
  25. }

从代码中不难看出,我们代理的功能里没有涉及到任何被代理对象的具体信息,这样有什么好处呢?这样的好处就是将代理要做的事情跟被代理的对象完全分开,这样一来我们就可以在代理和被代理之间随意的进行组合了。也就是说同一个功能我们只需要一个。同样的功能只有一个,那么这个功能不就是公共的功能吗?不管容器中有多少给对象,都可以享受容器提供的服务了。这就是容器的好处。

不知道我讲的够不够清楚,欢迎大家积极交流、讨论。

菜鸟学SSH(十四)——Spring容器AOP的实现原理——动态代理的更多相关文章

  1. Spring 容器AOP的实现原理——动态代理

    参考:http://wiki.jikexueyuan.com/project/ssh-noob-learning/dynamic-proxy.html(from极客学院) 一.介绍 Spring的动态 ...

  2. Spring容器AOP的实现原理——动态代理(转)

    文章转自http://blog.csdn.net/liushuijinger/article/details/37829049#comments

  3. 浅析Spring中AOP的实现原理——动态代理

    一.前言   最近在复习Spring的相关内容,刚刚大致研究了一下Spring中,AOP的实现原理.这篇博客就来简单地聊一聊Spring的AOP是如何实现的,并通过一个简单的测试用例来验证一下.废话不 ...

  4. 十四 Spring的AOP的基于AspectJ的注解开发

    Spring的AOP的基于AspectJ的注解开发 创建项目,引入jar包 编写目标类.切面类 配置目标类.切面类 在注解文件里开启AOP的开发 <?xml version="1.0& ...

  5. AOP的实现原理——动态代理

    IOC负责将对象动态的 注入到容器,从而达到一种需要谁就注入谁,什么时候需要就什么时候注入的效果,可谓是招之则来,挥之则去.想想都觉得爽,如果现实生活中也有这本事那就爽 歪歪了,至于有多爽,各位自己脑 ...

  6. 菜鸟学SSH(四)——Struts2拦截器

    什么是拦截器 拦截器(Interceptor)是Struts 2的一个强有力的工具,有许多功能都是构建于它之上,如国际化(前两篇博客介绍过).转换器,校验等. 拦截器是动态拦截Action调用的对象. ...

  7. 七 MyBatis整合Spring,DAO开发(传统DAO&动态代理DAO)

    整合思路: 1.SQLSessionFactory对象应该放到Spring中作为单例存在 2.传统dao开发方式中,应该从Spring容器中获得SqlSession对象 3.Mapper代理行驶中,应 ...

  8. HDU 6467 简单数学题 【递推公式 && O(1)优化乘法】(广东工业大学第十四届程序设计竞赛)

    传送门:http://acm.hdu.edu.cn/showproblem.php?pid=6467 简单数学题 Time Limit: 4000/2000 MS (Java/Others)    M ...

  9. HDU 6464 免费送气球 【权值线段树】(广东工业大学第十四届程序设计竞赛)

    传送门:http://acm.hdu.edu.cn/showproblem.php?pid=6464 免费送气球 Time Limit: 2000/1000 MS (Java/Others)    M ...

随机推荐

  1. django之创建第8个项目-数据库配置及同步研究

    1.sqlitestudio-2.1.5数据库可视化工具--百度云盘下载 2.编写C:\djangoweb\helloworld\blog\models.py文件 # Create your mode ...

  2. Jquery 获取对象的几种方式(转载)

    1.先讲讲JQuery的概念 JQuery首先是由一个 America 的叫什么 John Resig的人创建的,后来又很多的JS高手也加入了这个团队.其实 JQuery是一个JavaScript的类 ...

  3. 彻底抛弃脚本录制,LR脚本之使用web_custom_request函数自定义

    原文  http://www.cnblogs.com/Bonnie83/p/3525200.html 初学性能测试时候,第一步必学脚本录制,但一路下来各种录制失败.回放脚本失败的问题层出不穷,究其原因 ...

  4. Light OJ 1406 Assassin`s Creed 状态压缩DP+强连通缩点+最小路径覆盖

    题目来源:Light OJ 1406 Assassin`s Creed 题意:有向图 派出最少的人经过全部的城市 而且每一个人不能走别人走过的地方 思路:最少的的人能够走全然图 明显是最小路径覆盖问题 ...

  5. Eclipse 语言包下载

    1.登陆http://www.eclipse.org/babel/downloads.php 选择你的eclipse版本   2.找到IDE中文补丁包 INDIGO的地址如下:http://downl ...

  6. 从html加载json文件想起

    原文来自:https://www.cnblogs.com/dibaosong/p/4572274.html#top 文中给出了data.json文件内容 还给出了html文件内容 ok. 1.新建工程 ...

  7. windows修改环境变量

    windows的环境变量有两套: 系统的 当前用户的 不同的用户可以拥有不同的环境变量,当前用户的环境变量优先级比系统的环境变量优先级高,PATH环境变量比较特殊,它不是替换而是拼接. 在命令行下也可 ...

  8. Maven学习--- 搭建多模块企业级项目

    我们先在eclipse中新建一个maven项目,pom.xml的文件如下: 搭建多模块项目,必须要有一个packaging为pom的根目录.创建好这个maven项目后,我们对着项目右键-->ne ...

  9. hibernate 注解 boolean问题解决方案

    1.JPA本身是不支持boolean.可以用Hibernater自带的标签.修改如下. @Column(name = "manager_log") @org.hibernate.a ...

  10. ASP.NET Core Linux环境安装并运行项目

    原文地址:https://blog.csdn.net/u014368040/article/details/79192622 一 安装环境 1.  从微软官网下载 Linux版本的.NetCoreSd ...