Spring AOP原理

Posted 自在时刻

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Spring AOP原理相关的知识,希望对你有一定的参考价值。

[原文地址](http://blog.csdn.net/liushuijinger/article/details/37829049)

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


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


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


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


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

[java] view plain copy print ?
  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.   
//用户管理接口
package com.tgb.proxy;

public interface UserMgr 
    void addUser();
    void delUser();


//用户管理的实现
package com.tgb.proxy;

public class UserMgrImpl implements UserMgr 

    @Override
    public void addUser() 
        System.out.println("添加用户.....");
    

    @Override
    public void delUser() 
        System.out.println("删除用户.....");
    


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

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


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


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


InvocationHandler接口:

[java] view plain copy print ?
  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.   
package com.tgb.proxy;

import java.lang.reflect.Method;

public interface InvocationHandler 
    public void invoke(Object o, Method m);


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

[java] view plain copy print ?
  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&nbsp;implements&nbsp;"</span><span>&nbsp;+&nbsp;infce.getName()&nbsp;+&nbsp;</span><span class="string">""</span><span>&nbsp;+&nbsp;rt&nbsp;+&nbsp;&nbsp;</span></span></li><li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="string">"&nbsp;&nbsp;&nbsp;&nbsp;public&nbsp;$Proxy1(InvocationHandler&nbsp;h)&nbsp;"</span><span>&nbsp;+&nbsp;rt&nbsp;+&nbsp;&nbsp;</span></span></li><li class=""><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="string">"&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;this.h&nbsp;=&nbsp;h;"</span><span>&nbsp;+&nbsp;rt&nbsp;+&nbsp;&nbsp;</span></span></li><li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="string">"&nbsp;&nbsp;&nbsp;&nbsp;"</span><span>&nbsp;+&nbsp;rt&nbsp;+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span></span></li><li class=""><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="string">"&nbsp;&nbsp;&nbsp;&nbsp;com.tgb.proxy.InvocationHandler&nbsp;h;"</span><span>&nbsp;+&nbsp;rt&nbsp;+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span></span></li><li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;methodStr&nbsp;+&nbsp;rt&nbsp;+&nbsp;&nbsp;</span></li><li class=""><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="string">""</span><span>;&nbsp;&nbsp;</span></span></li><li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;String&nbsp;fileName&nbsp;=&nbsp;&nbsp;&nbsp;</span></li><li class=""><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="string">"d:/src/com/tgb/proxy/Proxy1.java”;  
  43.         File f = new File(fileName);  
  44.         FileWriter fw = new FileWriter(f);  
  45.         fw.write(srcCode);  
  46.         fw.flush();  
  47.         fw.close();  
  48.           
  49.         //将Java文件编译成class文件  
  50.         JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();  
  51.         StandardJavaFileManager fileMgr = compiler.getStandardFileManager(nullnullnull);  
  52.         Iterable units = fileMgr.getJavaFileObjects(fileName);  
  53.         CompilationTask t = compiler.getTask(null, fileMgr, nullnullnull, units);  
  54.         t.call();  
  55.         fileMgr.close();  
  56.           
  57.         //加载到内存,并实例化  
  58.         URL[] urls = new URL[] new URL(“file:/” + “d:/src/”);  
  59.         URLClassLoader ul = new URLClassLoader(urls);  
  60.         Class c = ul.loadClass(”com.tgb.proxy.$Proxy1”);  
  61.           
  62.         Constructor ctr = c.getConstructor(InvocationHandler.class);  
  63.         Object m = ctr.newInstance(h);  
  64.   
  65.         return m;  
  66.       
  67.       
  68.   
package com.tgb.proxy;

import java.io.File;
import java.io.FileWriter;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;
import javax.tools.JavaCompiler;
import javax.tools.StandardJavaFileManager;
import javax.tools.ToolProvider;
import javax.tools.JavaCompiler.CompilationTask;

public class Proxy 
    /**
     * 
     * @param infce 被代理类的接口
     * @param h 代理类
     * @return
     * @throws Exception
     */
    public static Object newProxyInstance(Class infce, InvocationHandler h) throws Exception  
        String methodStr = "";
        String rt = "\\r\\n";

        //利用反射得到infce的所有方法,并重新组装
        Method[] methods = infce.getMethods();  
        for(Method m : methods) 
            methodStr += "    @Override" + rt + 
                         "    public  "+m.getReturnType()+" " + m.getName() + "() " + rt +
                         "        try " + rt +
                         "        Method md = " + infce.getName() + ".class.getMethod(\\"" + m.getName() + "\\");" + rt +
                         "        h.invoke(this, md);" + rt +
                         "        catch(Exception e) e.printStackTrace();" + rt +                        
                         "    " + rt ;
        

        //生成Java源文件
        String srcCode = 
            "package com.tgb.proxy;" +  rt +
            "import java.lang.reflect.Method;" + rt +
            "public class $Proxy1 implements " + infce.getName() + "" + rt +
            "    public $Proxy1(InvocationHandler h) " + rt +
            "        this.h = h;" + rt +
            "    " + rt +          
            "    com.tgb.proxy.InvocationHandler h;" + rt +                         
            methodStr + rt +
            "";
        String fileName = 
            "d:/src/com/tgb/proxy/$Proxy1.java";
        File f = new File(fileName);
        FileWriter fw = new FileWriter(f);
        fw.write(srcCode);
        fw.flush();
        fw.close();

        //将Java文件编译成class文件
        JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
        StandardJavaFileManager fileMgr = compiler.getStandardFileManager(null, null, null);
        Iterable units = fileMgr.getJavaFileObjects(fileName);
        CompilationTask t = compiler.getTask(null, fileMgr, null, null, null, units);
        t.call();
        fileMgr.close();

        //加载到内存,并实例化
        URL[] urls = new URL[] new URL("file:/" + "d:/src/");
        URLClassLoader ul = new URLClassLoader(urls);
        Class c = ul.loadClass("com.tgb.proxy.$Proxy1");

        Constructor ctr = c.getConstructor(InvocationHandler.class);
        Object m = ctr.newInstance(h);

        return m;
    



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


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

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


public class Client 
    public static void main(String[] args) throws Exception 
        UserMgr mgr = new UserMgrImpl();

        //为用户管理添加事务处理
        InvocationHandler h = new TransactionHandler(mgr);
        UserMgr u = (UserMgr)Proxy.newProxyInstance(UserMgr.class,h);

        //为用户管理添加显示方法执行时间的功能
        TimeHandler h2 = new TimeHandler(u);
        u = (UserMgr)Proxy.newProxyInstance(UserMgr.class,h2);

        u.addUser();
        System.out.println("\\r\\n==========华丽的分割线==========\\r\\n");
        u.delUser();
    


运行结果:

[plain] view plain copy print ?
  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秒  
开始时间:2014年-07月-15日 15时:48分:54秒
开启事务.....
添加用户.....
提交事务.....
结束时间:2014年-07月-15日 15时:48分:57秒
耗时:3秒

==========华丽的分割线==========

开始时间:2014年-07月-15日 15时:48分:57秒
开启事务.....
删除用户.....
提交事务.....
结束时间:2014年-07月-15日 15时:49分:00秒
耗时:3秒

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

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


事务处理:

[java] view plain copy print ?
  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.   
package com.tgb.proxy;

import java.lang.reflect.Method;

public class TransactionHandler implements InvocationHandler 

    private Object target;

    public TransactionHandler(Object target) 
        super();
        this.target = target;
    

    @Override
    public void invoke(Object o, Method m) 
        System.out.println("开启事务.....");
        try 
            m.invoke(target);
         catch (Exception e) 
            e.printStackTrace();
        
        System.out.println("提交事务.....");
    



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


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


以上是关于Spring AOP原理的主要内容,如果未能解决你的问题,请参考以下文章

spring-aop代理的生效原理

是什么导致了Spring Aop失效了?

spring aop无法拦截类内部的方法调用

spring ioc的详解?

spring aop 深入分析

Spring AOPAOP概念组成应用场景实现及实现原理剖析