1. 程式人生 > >aop 實現原理(jdk動態代理和動態生成class)

aop 實現原理(jdk動態代理和動態生成class)

java中可以通過jdk提供的 Proxy.newProxyInstance靜態方法來建立動態代理物件,下面先來看看這個方法的實現

public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)  
           throws IllegalArgumentException {  
       //InvocationHandler不能為空,因為對代理物件的所有方法呼叫實際上都會委託到InvocationHandler的invoke方法,  
       //這個我們後面通過檢視產生的代理類的原始碼便會一目瞭然  
       if (h == null) {  
           throw new NullPointerException();  
       }  
  
       //這個是核心的地方,通過提供的ClassLoader和interface列表來產生代理類,具體的實現可以參考getProxyClass這個方法的實現,  
       //真正的工作是由sun.misc.ProxyGenerator這個類來完成的,可以google檢視具體的邏輯.在我們的程式中通過設定  
       //System.setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles", "true")可以檢視產生的類檔案  
       Class cl = getProxyClass(loader, interfaces);  
  
       //因為代理類繼承了Proxy類.而Proxy中定義了建構函式protected Proxy(InvocationHandler h),所以可以反射得到Constructer例項  
       //建立代理物件  
       try {  
           Constructor cons = cl.getConstructor(constructorParams);  
           return (Object) cons.newInstance(new Object[] { h });  
       } catch (NoSuchMethodException e) {  
           throw new InternalError(e.toString());  
       } catch (IllegalAccessException e) {  
           throw new InternalError(e.toString());  
       } catch (InstantiationException e) {  
           throw new InternalError(e.toString());  
       } catch (InvocationTargetException e) {  
           throw new InternalError(e.toString());  
       }  
   }  

下面通過個例子來說明下:

先來定義一個介面,jdk的動態代理基於介面來建立代理類,不能基於類的原因是java不支援多重繼承,而代理類都會繼承Proxy類(個人理解).

/** 
 * Subject 
 *  
 * @author Kevin Fan 
 * @since 2013-9-13 下午2:43:33 
 */  
public interface Subject {  
    void pub(String key, String content);  
  
    String sub(String key);  
}  


再來一個具體的實現,在代理模式中可以叫它的例項可以叫target,這個是真正執行操作的物件
/** 
 * SimpleSubject 
 *  
 * @author Kevin Fan 
 * @since 2013-9-13 下午2:45:03 
 */  
public class SimpleSubject implements Subject {  
    private Map<String, String> msg = new ConcurrentHashMap<String, String>();  
  
    public void pub(String key, String content) {  
        System.out.println("pub msg: key is " + key + ", content is " + content);  
        msg.put(key, content);  
    }  
  
    public String sub(String key) {  
        if (msg.containsKey(key)) {  
            String ret = msg.get(key);  
            System.out.println("sub msg: key is " + key + ", result is " + ret);  
            return ret;  
        }  
  
        return null;  
    }  
  
}  


好,接下來我們來寫個動態代理工廠,根據 不同的target來建立動態代理物件[java] view plain copy print?
/** 
 * SubjectProxyFactory 
 *  
 * @author Kevin Fan 
 * @since 2013-9-13 下午2:47:24 
 */  
public class SubjectProxyFactory {  
    //TODO: cache   
    public static Subject getSubject(final Subject realSubject) {  
        return (Subject) Proxy.newProxyInstance(realSubject.getClass().getClassLoader(), new Class[] { Subject.class },  
                new InvocationHandler() {  
  
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {  
                        System.out.println("\naction before method invocation....");  
                        Object retVal = method.invoke(realSubject, args);  
                        System.out.println("action after method invocation....\n");  
                        return retVal;  
                    }  
                });  
    }  
}  

可以看到這是一個簡單的實現,只是在真實物件執行前後各打一句資訊..

接下來用一個 main函式來把這些結合起來 
/** 
 * Demo 
 *  
 * @author Kevin Fan 
 * @since 2013-9-13 下午2:50:28 
 */  
public class Demo {  
    public static void main(String[] args) {  
        //設定此係統屬性,以檢視代理類檔案  
        System.setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");  
          
        //建立真實物件  
        Subject subj = new SimpleSubject();  
        subj.pub("name", "kevin.fan");  
        subj.sub("name");  
          
        //建立代理物件  
        Subject proxy = SubjectProxyFactory.getSubject(subj);  
        proxy.pub("hobby", "r&b music");  
        proxy.sub("name");  
    }  
}  


pub msg: key is name, content is kevin.fan  
sub msg: key is name, result is kevin.fan  
  
  
action before method invocation....  
pub msg: key is hobby, content is r&b music  
action after method invocation....  
  
  
  
  
action before method invocation....  
sub msg: key is name, result is kevin.fan  
action after method invocation....  



可以看到在呼叫代理物件的方法時,新增的額外動作已經生效,接下來我們看下生成的代理類的程式碼..[java] view plain copy print?
import com.aliyun.demo.kevin.coder.lang.proxy.Subject;  
import java.lang.reflect.InvocationHandler;  
import java.lang.reflect.Method;  
import java.lang.reflect.Proxy;  
import java.lang.reflect.UndeclaredThrowableException;  
  
//這裡很清楚了,代理類繼承了Proxy類,並且實現了Proxy.newProxyInstance這個方法中傳入的介面  
public final class $Proxy0 extends Proxy  
  implements Subject  
{  
    
  //這些方法在下面的static init block中進行初始化  
  private static Method m4;  
  private static Method m1;  
  private static Method m3;  
  private static Method m0;  
  private static Method m2;  
    
  static  
  {  
    try  
    {  
      m4 = Class.forName("com.aliyun.demo.kevin.coder.lang.proxy.Subject").getMethod("sub", new Class[] { Class.forName("java.lang.String") });  
      m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") });  
      m3 = Class.forName("com.aliyun.demo.kevin.coder.lang.proxy.Subject").getMethod("pub", new Class[] { Class.forName("java.lang.String"), Class.forName("java.lang.String") });  
      m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);  
      m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);  
      return;  
    }  
    catch (NoSuchMethodException localNoSuchMethodException)  
    {  
      throw new NoSuchMethodError(localNoSuchMethodException.getMessage());  
    }  
    catch (ClassNotFoundException localClassNotFoundException)  
    {  
      throw new NoClassDefFoundError(localClassNotFoundException.getMessage());  
    }  
  }  
  
  //建構函式,接收一個 InvocationHandler作為引數,這就是為什麼Proxy.newProxyInstance方法裡可以  
  //通過InvocationHandler例項作為引數來反射獲取Constructer例項   
  public $Proxy0 paramInvocationHandler)  
    throws   
  {  
    super(paramInvocationHandler);  
  }  
  
  //下面通過這個來看下代理物件中方法是怎樣呼叫的  
  public final String sub(String paramString)  
    throws   
  {  
    try  
    {  
       //全部是通過呼叫InvocationHandler的invoke方法,傳入對應的方法和引數  
      return (String)this.h.invoke(this, m4, new Object[] { paramString });  
    }  
    catch (Error|RuntimeException localError)  
    {  
      throw localError;  
    }  
    catch (Throwable localThrowable)  
    {  
      throw new UndeclaredThrowableException(localThrowable);  
    }  
  }  
  
  public final boolean equals(Object paramObject)  
    throws   
  {  
    try  
    {  
      return ((Boolean)this.h.invoke(this, m1, new Object[] { paramObject })).booleanValue();  
    }  
    catch (Error|RuntimeException localError)  
    {  
      throw localError;  
    }  
    catch (Throwable localThrowable)  
    {  
      throw new UndeclaredThrowableException(localThrowable);  
    }  
  }  
  
  public final void pub(String paramString1, String paramString2)  
    throws   
  {  
    try  
    {  
      this.h.invoke(this, m3, new Object[] { paramString1, paramString2 });  
      return;  
    }  
    catch (Error|RuntimeException localError)  
    {  
      throw localError;  
    }  
    catch (Throwable localThrowable)  
    {  
      throw new UndeclaredThrowableException(localThrowable);  
    }  
  }  
  
  public final int hashCode()  
    throws   
  {  
    try  
    {  
      return ((Integer)this.h.invoke(this, m0, null)).intValue();  
    }  
    catch (Error|RuntimeException localError)  
    {  
      throw localError;  
    }  
    catch (Throwable localThrowable)  
    {  
      throw new UndeclaredThrowableException(localThrowable);  
    }  
  }  
  
  public final String toString()  
    throws   
  {  
    try  
    {  
      return (String)this.h.invoke(this, m2, null);  
    }  
    catch (Error|RuntimeException localError)  
    {  
      throw localError;  
    }  
    catch (Throwable localThrowable)  
    {  
      throw new UndeclaredThrowableException(localThrowable);  
    }  
  }  
  
}  


二 動態生成class檔案,newProxyInstance核心工具的一個示例。

import java.io.File;  
import java.util.Arrays;  
  
import javax.tools.JavaCompiler;  
import javax.tools.JavaFileObject;  
import javax.tools.StandardJavaFileManager;  
import javax.tools.ToolProvider;  
  
public class JavaCompilerUtil {  
    private static JavaCompiler javaCompiler;  
  
    private JavaCompilerUtil() {  
    };  
  
    private static JavaCompiler getJavaCompiler() {  
        if (javaCompiler == null) {  
            synchronized (JavaCompilerUtil.class) {  
                if (javaCompiler == null) {  
                    // 講道理,根據JavaCompiler 的獲取方式來看,應該是採用了單例模式的,但是這裡為了順便複習一下單例模式,以及確保一下單例吧  
                    javaCompiler = ToolProvider.getSystemJavaCompiler();  
                }  
            }  
        }  
  
        return javaCompiler;  
    }  
  //傳入真實的java檔案,需要替換成複製實現類的格式(反射原生的介面方法作為變數儲存,注入InvocationHandler介面型別物件方法,實現類方法替換成呼叫invoke
    public static boolean CompilerJavaFile(String sourceFileInputPath,  
            String classFileOutputPath) {  
        // 設定編譯選項,配置class檔案輸出路徑  
        Iterable<String> options = Arrays.asList("-d", classFileOutputPath);  
        StandardJavaFileManager fileManager = getJavaCompiler()  
                .getStandardFileManager(null, null, null);  
  
        Iterable<? extends JavaFileObject> compilationUnits = fileManager  
                .getJavaFileObjectsFromFiles(Arrays.asList(new File(  
                        sourceFileInputPath)));  
  
        return getJavaCompiler().getTask(null, fileManager, null, options,  
                null, compilationUnits).call();  
    }  
}  

總結: 1.將介面通過反射生成一份方法簽名相同類中和實現原類介面並例項化(子轉父需要強轉-- (Subject) Proxy.newProxyInstance)
            2.複製物件注入自己實現的InvocationHandler介面型別物件
            3.複製物件各個方法呼叫自己傳入InvocationHandler現實類的invoke方法,方法引數傳遞this、當前匹配的原生方法、引數值三個引數。