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