動態代理由淺入深, 源碼詳解

實際生活中,我們經常聽說中介、代理商、加盟等關鍵名詞。試想一下,爲什麼需要這些?
就說說中介:就我本人身在上海來說,如果我要賣房子,我想找中介。原因:我每天工作很忙,賣房子也不是一天兩天能找到買家,除了我的工作外,我沒有時間浪費在上面,給我減輕負擔。
再說一些品牌商,他們的核心就是打響了品牌,使用技術早就了這些好的商品,但是這些商品需要賣出去,自己去賣?人力、精力都不夠用。所以他們就需要找代理商幫他去賣。
基於上述的問題,其實體現了專業分工的思想。實際開發中,一個類中,方法除了當前需要的核心操作,其他次要的可以丟給代理類去做。這樣分工明確,業務分明。這就是我們接下來要說的動態代理。

動態代理的作用:可以在不改變原有類的基礎上對方法進行增強。
什麼是動態代理?這裏2個關鍵字:動態和代理。
首先說下代理:這裏涉及到一種設計模式–代理模式。我先不解釋代理模式,我先說個詞語:代理商。那麼這個詞語相信大家都聽過,這個代理商和我們這裏的代理是一樣的意思。
所以動態代理,就是java虛擬就動態的生成了這個代理,而不是我們開發者手寫java代碼生成的。

一:快速入門-動態代理

需求:賣的奶茶和製造的奶茶需要加上品牌名“一點點”
首先我們直接先看動態代理的實現:只要記住動態代理的實現就是一種格式,固定步驟。
首先需要一個接口:奶茶

public interface Milk {
	//奶茶接口,有賣奶茶的方法
	public void sell(String milkName);
	//製造奶茶
	public void make();
}

然後一個品牌商:一點點

public class YiDianDian implements Milk {

	@Override
	public void sell(String milkName) {
		System.out.println("賣奶茶");
	}

	@Override
	public void make() {
		System.out.println("製造奶茶");
	}
}

動態代理生成 一點點 的代理商

public class Test {

	public static void main(String[] args) {
		System.setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
		//這是一點點 奶茶
		final Milk yiDianDian = new YiDianDian();
		//獲取一點點的類加載器
		ClassLoader classLoader = yiDianDian.getClass().getClassLoader();
		//獲取一點點實現的接口
		Class<?>[] interfaces = yiDianDian.getClass().getInterfaces();
		/*
		 * 動態代理創建一點點 的代理商
		 * 我們需要記住這種創建代理對象的格式:
		 * 	第一個參數:類加載器,一般寫被代理對象的類加載器
		 * 	第二個參數:被代理對象實現的接口
		 * 	第三個參數:處理器     InvocationHandler接口的實現類。實現invoke方法,用來指定代理做的事情。
		 * 
		 */
		Milk proxyYiDianDian =(Milk)Proxy.newProxyInstance(classLoader, interfaces,new InvocationHandler() {	
			/*
			 * 
			 * 代理商做的事情就在這個invoke方法裏書寫,
			 * 這個方法3個參數:
			 * 第一個參數:代理商本身,invoke方法中不能使用,使用就報錯。原因後面介紹。
			 * 第二個參數:正在執行的方法。這裏可以理解成一點點這個品牌需要執行的方法。
			 * 第三個參數:正在執行的方法需要的參數。
			*/
			@Override
			public Object invoke(Object proxy, Method method, Object[] args)
					throws Throwable {
				//輸出執行的方法的名稱
				System.out.println("一點點的代理商代替 一點點品牌執行:"+method.getName()+"方法");
				//輸出執行的方法下需要的參數
				System.out.println("一點點品牌傳遞過來的的參數:"+Arrays.toString(args));
				//執行被代理對象本身方法原有的功能。
				System.out.println("**********一點點品牌**********");
				method.invoke(yiDianDian, args);
				return null;
			}
		});
		
		proxyYiDianDian.sell("紅茶");
		proxyYiDianDian.make();
	}
}

結果:
這裏寫圖片描述

二:代理模式-靜態代理

代理模式:創建一個對象的代理,以控制對這個對象的訪問。通俗來講,就是創建了一箇中介(代理)。

首先需要一個接口:奶茶

public interface Milk {
	//奶茶接口,有賣奶茶的方法
	public void sell(String milkName);
	//製造奶茶
	public void make();
}

然後一個品牌商:一點點

public class YiDianDian implements Milk {

	@Override
	public void sell(String milkName) {
		System.out.println("賣奶茶");
	}

	@Override
	public void make() {
		System.out.println("製造奶茶");
	}
}

代理類

public class ProxyYiDianDian implements Milk {
	//代理商  擁有   被代理商的引用
	private Milk yiDianDain;
	public ProxyYiDianDian(Milk milk) {
		this.yiDianDain = milk;
	}
	@Override
	public void sell(String milkName) {
		System.out.println("***********一點點品牌*****************");
		yiDianDain.sell(milkName);
	}
	@Override
	public void make() {
		System.out.println("***********一點點品牌*****************");
		yiDianDain.make();	
	}
}

測試:

public class Test2 {

	public static void main(String[] args) {
		//被代理對象   一點點品牌
		Milk yiDianDian = new YiDianDian();
		
		//代理對象  代理商
		Milk proxyYiDianDian = new ProxyYiDianDian(yiDianDian);
		
		//執行sell方法
		proxyYiDianDian.sell("紅茶");
		//執行make方法
		proxyYiDianDian.make();
		
	}
}

結果:
這裏寫圖片描述

爲什麼需要動態代理:

就需求而言,製造和售出方法都添加打印品牌的名字,代理的原因就是因爲我們所關注的售賣和製造的主要代碼保持不變,創建代理類來在原有的功能上添加核心操作之外的步驟:打印一點點的品牌名。這樣實現了每個方法的核心操作和打印品牌的業務分離。代碼業務清晰。但是這個方法存在很明顯缺點,當我們需要添加打印品牌的方法很多的時候,我們需要每一個方法都要添加這個輸出語句,很是麻煩。而且後期一點點品牌發生變化,比如將一點點改成兩點點,這是候每個方法都要修改,作爲一個程序員真是心中十萬只羊駝飄過。所以爲了維護方便,我們需要使用動態代理。
其實這也就是說靜態代理的缺點:
1.如果需要代理的方法很多的話,那麼我們需要對每一個方法做實現,非常的麻煩。
2.後期如果接口增加新的方法,我們需要對java文件做改寫。維護麻煩。
而動態代理完全避免了這些。因爲這是java虛擬機做的事情。

動態代理和靜態代理的區別:
通過上面的案例,我們發現,我們靜態代理是需要我們手動的書寫一個java文件,而動態代理是不需要手動書寫一個java文件的。
我們學習java的都知道,我們創建一個對象,首先需要書寫一個java文件,然後將java文件編譯成class文件,類加載器將class文件加載到內存中。然後我們可以創建此對象的實例。而上述的代理商proxyYiDianDain這個對象是沒有java文件的,也沒有class文件,是java虛擬機直接在內存中生成了class對象.從而創建這個class對象的實例,當然這個實例可以按照我們開發者的需求做任何功能的改寫,代替原有的對象。這就是動態代理含義的本質。
這裏寫圖片描述

動態代理引用場景介紹:

通過代理模式,我們發現我們使用代理模式的目的是爲了實現專業分工。所以在開發中當我們除了原始核心類以外的其他功能,比如說事務的管理,權限等,業務邏輯方法每個都要添加事務的管控和權限的判斷是非常麻煩的,而這些東西,和我們方法的核心操作關係不大,所以我們可以將這些操作用專門的動態代理來處理。

三:動態代理深入理解-源碼解析

1.Proxy.newProxyInstance靜態方法解析

 public static Object newProxyInstance(ClassLoader loader,
             Class<?>[] interfaces,InvocationHandler h)throws IllegalArgumentException{
		if (h == null) {
				throw new NullPointerException();
		}
		
		/*
		* 1.核心操作,java虛擬機根據接口和類加載器,在內存中生成代理類的class對象。
		*/
		Class<?> cl = getProxyClass0(loader, interfaces); 
		
		try {
			//2.獲取代理類的構造方法
			final Constructor<?> cons = cl.getConstructor(constructorParams);
			//3.獲取我們調用這個方法傳遞過來的處理器。
			final InvocationHandler ih = h;
			SecurityManager sm = System.getSecurityManager();
			if (sm != null && ProxyAccessHelper.needsNewInstanceCheck(cl)) {
				return AccessController.doPrivileged(new PrivilegedAction<Object>() {
				public Object run() {
					return newInstance(cons, ih);
					}
				});
			} else {
			//4.這裏可以發現,通過代理類的構造方法和處理器來創建這個代理類的實例。也就是創建動態代理對象
			return newInstance(cons, ih);
			}
		} catch (NoSuchMethodException e) {
			throw new InternalError(e.toString());
		}
	 }

通過上面的4步我們可以發現,其實動態代理,就是java虛擬機動態生成了一個class對象,然後創建了一個對象。
所以接下來我們看看java虛擬機動態生成的這個class對象的字節碼文件

2.動態代理類的字節碼文件
在測試類的最上面加上如下的一句代碼,讓java虛擬機生成的字節碼文件輸出到硬盤。
System.setProperty(“sun.misc.ProxyGenerator.saveGeneratedFiles”, “true”);
然後再項目下添加如下目錄:com/sun/proxy
這裏寫圖片描述

執行代碼:

public static void main(String[] args) {
		System.setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
		//這是一點點 奶茶
		final Milk yiDianDian = new YiDianDian();
		//獲取一點點的類加載器
		ClassLoader classLoader = yiDianDian.getClass().getClassLoader();
		//獲取一點點實現的接口
		Class<?>[] interfaces = yiDianDian.getClass().getInterfaces();
		/*
		 * 動態代理創建一點點 的代理商
		 * 我們需要記住這種創建代理對象的格式:
		 * 	第一個參數:類加載器,一般寫被代理對象的類加載器
		 * 	第二個參數:被代理對象實現的接口
		 * 	第三個參數:處理器     InvocationHandler接口的實現類。實現invoke方法,用來指定代理做的事情。
		 * 
		 */
		Milk proxyYiDianDian =(Milk)Proxy.newProxyInstance(classLoader, interfaces,new InvocationHandler() {	
			/*
			 * 
			 * 代理商做的事情就在這個invoke方法裏書寫,
			 * 這個方法3個參數:
			 * 第一個參數:代理商本身,invoke方法中不能使用,使用就報錯。原因後面介紹。
			 * 第二個參數:被代理對象本身所執行的方法。這裏可以理解成一點點這個品牌需要執行的方法。
			 * 第三個參數:被代理對象本身所執行的方法需要的參數。
			*/
			@Override
			public Object invoke(Object proxy, Method method, Object[] args)
					throws Throwable {
				//輸出執行的方法的名稱
				System.out.println("一點點的代理商代替 一點點品牌執行:"+method.getName()+"方法");
				//輸出執行的方法下需要的參數
				System.out.println("一點點品牌傳遞過來的的參數:"+Arrays.toString(args));
				//執行被代理對象本身方法原有的功能。
				System.out.println("**********一點點品牌**********");
				method.invoke(yiDianDian, args);
				return null;
			}
		});
		proxyYiDianDian.sell("紅茶");
	}

可以發現,com/sun/proxy目錄下生成了一個class文件,然後使用反編譯工具打開,生成的代理類源碼如下:

package com.sun.proxy;

import com.itheima.proxy.Milk;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;

public final class $Proxy0 extends Proxy
  implements Milk
{
  private static Method m1;
  private static Method m3;
  private static Method m0;
  private static Method m2;

  public $Proxy0(InvocationHandler paramInvocationHandler)
    throws 
  {
    super(paramInvocationHandler);
  }

  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);
    }
  }

  /**
   * 可以發現,代理類的所有的方法都執行了this.h.invoke方法
   */
  public final void sell(String paramString)
    throws 
  {
    try
    {
      this.h.invoke(this, m3, new Object[] { paramString });
      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);
    }
  }

  static
  {
    try
    {
      m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") });
      m3 = Class.forName("com.xu.proxy.Milk").getMethod("sell", new Class[] { 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());
    }
  }
}

通過上述的源碼我們可以發現,代理類的所有的方法都執行了**this.h.invoke()**方法
我們將sell方法抽出來理解一下:

public final void sell(String paramString)
    throws 
  {
    try
    {
      /*
       * sell方法的核心操作就是這個
       * this代表當前對象,
       * h 代表我們傳遞過來的 invacationHandler的實現類對象
       * invoke就是處理器 的invoke方法
       * 
       * 也就是說,最終的實現全部丟給了invacationHandler的invoke()方法處理了。
       * 再來看看調用invoke()方法傳遞的參數,
       * 	this:代表當前對象,當前類就是代理類,所以是代理對象本身
       * 	m3: m3 = Class.forName("com.itheima.proxy.Milk").getMethod("sell", new Class[] 						 																		{ Class.forName("java.lang.String") });
       * 		最下面有如上代碼,可以發現m3就是sell方法,執行的方法
       *   new Object[] { paramString }:就是調用sell方法時傳遞的參數。
      */
      this.h.invoke(this, m3, new Object[] { paramString });
      return;
    }
    catch (Error|RuntimeException localError)
    {
      throw localError;
    }
    catch (Throwable localThrowable)
    {
      throw new UndeclaredThrowableException(localThrowable);
    }
  }

結合上面生成的動態代理類的sell方法的源碼,結合我們一開始實現動態代理類的代碼來看一下

Milk proxyYiDianDian =(Milk)Proxy.newProxyInstance(classLoader, interfaces,new InvocationHandler() {	
			/*
		    	第一個參數:代理商本身,invoke方法中不能使用,使用就報錯。原因後面介紹。
			 * 第二個參數:當前正在執行的方法。這裏可以理解成一點點這個品牌需要執行的方法。
			 * 第三個參數:當前正在執行的方法需要的參數。
		 */
@Override
			public Object invoke(Object proxy, Method method, Object[] args)
					throws Throwable {
				//輸出執行的方法的名稱
				System.out.println("一點點的代理商代替 一點點品牌執行:"+method.getName()+"方法");
				//輸出執行的方法下需要的參數
				System.out.println("一點點品牌傳遞過來的的參數:"+Arrays.toString(args));
				//執行被代理對象本身方法原有的功能。
				method.invoke(yiDianDian, args);
				return null;
			}
		});

所以,執行代理對象的任何方法,就需要執行invocationHandler的invoke方法。所以在invoke方法中調用proxy對象的方法,就會調用代理對象本身的方法,而又回到了invoke()方法中。所以陷入死循環。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章