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

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

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

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

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

[java] view plain copy

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


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


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

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

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

InvocationHandler接口:

[java] view plain copy

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

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

[java] view plain copy

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


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

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

[java] view plain copy

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

运行结果:

[plain] view plain copy

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

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

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

事务处理:

[java] view plain copy

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


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

时间: 2024-08-24 17:42:51

AOP的实现原理——动态代理的相关文章

菜鸟学SSH(十四)——Spring容器AOP的实现原理——动态代理

之前写了一篇关于IOC的博客--<Spring容器IOC解析及简单实现>,今天再来聊聊AOP.大家都知道Spring的两大特性是IOC和AOP. IOC负责将对象动态的注入到容器,从而达到一种需要谁就注入谁,什么时候需要就什么时候注入的效果,可谓是招之则来,挥之则去.想想都觉得爽,如果现实生活中也有这本事那就爽歪歪了,至于有多爽,各位自己脑补吧:而AOP呢,它实现的就是容器的另一大好处了,就是可以让容器中的对象都享有容器中的公共服务.那么容器是怎么做到的呢?它怎么就能让在它里面的对象自动拥有它

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

参考:http://wiki.jikexueyuan.com/project/ssh-noob-learning/dynamic-proxy.html(from极客学院) 一.介绍 Spring的动态代理有两种:一是JDK的动态代理:另一个是cglib动态代理(通过修改字节码来实现代理). 今天主要讨论JDK动态代理的方式. JDK的代理方式主要就是通过反射跟动态编译来实现的,主要搭配InvocationHandler和Proxy来实现,下面的例子中使用了两层代理(即代理上加了一层代理). 二.

细说Spring——AOP详解(动态代理实现AOP)

前言 嗯,我应该是有一段实现没有写过博客了,在写完了细说Spring——AOP详解(AOP概览)之后,我发现我不知道该怎么写AOP这一部分,所以就把写博客这件事给放下了,但是这件事情又不想就这么放弃,所以今天我仔细思考了一下,决定还是要克服困难,我仔细的想了一下怎么讲解AOP实现这一部分,然后我决定由浅入深的讲解动态代理,然后用动态代理实现一个简单的AOP,感觉这样能够让人对AOP的原理有一个比较深刻的认识,希望能帮到大家.而且最近学习又组建了ACM比赛的队伍,虽然已经要大三了,按理来说应该一心

Spring AOP基础之JDK动态代理

JDK动态代理 Jdk动态代理是装饰模式的一个典型用例,关于装饰模式这里不多解释,直接说重点吧.jdk动态代理实际上就是代替继承方案,在不破坏原始类的原则下,在运行期间为某个类动态注入一些新的方法.java.lang.reflect.Proxy提供了生成代理类的接口.进入源代码,我们可以看见关于Proxy的详细说明这里截取一些关键的部分: /** * {@code Proxy} provides static methods for creating dynamic proxy * classe

AOP学习心得&amp;jdk动态代理与cglib比较

什么是AOP AOP(Aspect-OrientedProgramming,面向方面编程),可以说是OOP(Object-Oriented Programing,面向对象编程)的补充和完善.OOP引入封装.继承和多态性等概念来建立一种对象层次结构,用以模拟公共行为的一个集合.当我们需要为分散的对象引入公共行为的时候,OOP则显得无能为力.也就是说,OOP允许你定义从上到下的关系,但并不适合定义从左到右的关系.例如日志功能.日志代码往往水平地散布在所有对象层次中,而与它所散布到的对象的核心功能毫无

Spring笔记(三)AOP前篇之动态代理

AOP思想是将程序中的业务代码与服务代码进行分离,在运行时进行结合.比较强调程序的层次结构,是一种面向切面的编程.而在AOP实现的底层主要用到了动态代理,而动态代理又分为JDK动态代理和CGLIB动态代理,两者的区别是JDK动态代理的实现中业务类必须必须定义接口,而CGLIB没有这个约束,可以说CGLIB更强大: JDK动态代理实现示例: // 业务接口定义 public interface IUnit {     void execute(String msg); } // 业务实现类 pub

Spring学习笔记-AOP前传之动态代理

假设有如下需求: 写一个计算器类,里面包含加减乘除四个方法.在每个方法开始前打印出该方法开始的消息,在每个方法结束前打印出该方法结束的消息和计算的结果. 普通方法,先写一个借口,然后在接口里实现四个方法.在每个方法里加上要打印的语句.实现代码如下. ArithmeticCalculator接口 package com.spring.aop.helloworld; public interface ArithmeticCalculator { int add(int i, int j); int 

AOP之Castle DynamicProxy 动态代理

这里主要介绍使用castle这个动态代理,在.net一些开源的框架里可以找到它的影子,就连微软的rchard也是使用这个进行方法拦截等可以基于这个进行方法拦截,在这个方面PostSharp算是比较好用的,可以跟使用属性一样使用没有代码侵入,可是这个是收费,postsharp使用的是运行时注入,这个在之前的文章已经说过这里不再重复说,这里就直接进入正题. 这里介绍先DynamicProxy的方法拦截功能先来个例子 先定义一个类 public class MyClass : IMyClass { p

Spring AOP的实现原理

原文出处: Listen AOP(Aspect Orient Programming),我们一般称为面向方面(切面)编程,作为面向对象的一种补充,用于处理系统中分布于各个模块的横切关注点,比如事务管理.日志.缓存等等.AOP实现的关键在于AOP框架自动创建的AOP代理,AOP代理主要分为静态代理和动态代理,静态代理的代表为AspectJ:而动态代理则以Spring AOP为代表.本文会分别对AspectJ和Spring AOP的实现进行分析和介绍. 使用AspectJ的编译时增强实现AOP 之前