幾個月沒寫博客了。前些日子換了工作,把事情調整好了,又可以繼續寫博客了~
學習了下代理模式,本篇文章講動態代理與靜態代理模式怎么寫,后續文章會講動態代理(JDK動態代理)原理,講講怎么使用CGLIB實現沒有接口的類的動態代理
一、代理在生活中的例子
代理, 代表授權方處理事務(wiki 的解釋)。 舉個例,顧客(client)想買一個u盤; 顧客想去西部數據工廠買一個u盤, 保安都直接把他攔住叫他去京東買; 沒辦法只能去被授權了的京東那去買,被可惡的中間商賺了差價。
雖然京東和西部數據都是賣u盤的,但是京東只是個代理商,最終需要的u盤還是要去西部數據進貨,京東但是可以發優惠券在顧客買u盤前。
生活中的代理例子很多, 找中介租房子。自己不想做的事,可以交給代理方做,但是代理方也能力有限,最終的事還是需要交給本人去做。
代理模式有啥用?
- 方法增強, 代理方不僅僅是賣u盤了,還可以發放優惠券,賺取差價
- 控制訪問,顧客去找西部數據工廠買個u盤,保安直接把他攔住了
二、 靜態代理模式實現
下面我們先看看靜態代理在Java中的實現
以買賣u盤為例子,
步驟一,定義一個接口(serviceInterface), 定義廠家與代理商的共同目標(賣u盤)
public interface UpanSell {
float sell(int amount);
}
步驟二,創建廠家類(serive),實現步驟一定義的接口
public class WesternDigitalUpanFactory implements UpanSell {
@Override
public float sell(int amount) {
float price = 85.0f * amount;
System.out.println("------西部數據賣出" + amount + "個u盤, 價格: " + price);
return price;
}
}
步驟三,創建一個商家類(proxy),實現相同的接口
public class jd implements UpanSell {
private final UpanSell upanSell = new WesternDigitalUpanFactory();
@Override
public float sell(int amount) {
float price = upanSell.sell(amount);
// -------增強功能 ↓↓↓↓↓↓↓↓↓↓
// 中間商賺差價
price += 25.f * amount;
System.out.println("------京東賣出" + amount + "個u盤, 價格: " + price);
System.out.println("------給你一個優惠券, 0.01分");
return price;
}
}
步驟四, 創建一個客戶端(client)調用商家買U盤
public class Shop {
public static void main(String[] args) {
UpanSell upanSell = new jd();
upanSell.sell(2);
}
}
輸出結果
------西部數據賣出2個u盤, 價格: 170.0
------京東賣出2個u盤, 價格: 220.0
------給你一個優惠券, 0.01分
上面的代理類(jd), 做了兩件事,1. 中間商加了價格 2. 給了我們一張優惠券
我們能看到靜態代理的實現還是挺簡單的,也是多態的一個運用,我們是由商家類調用到的廠家類。
簡單是簡單,但是
- 如果多一個商家,比如淘寶,也要賣了,是不是需要多一個代理類。
- 如果serviceInterface,是不是廠家類與商家類都要修改。
那么,我們來看看動態代理是怎么把這些缺點規避掉的
運用JDK實現動態代理👇
三、動態代理模式的實現
動態代理,其實就是不用我們手動實現代理類,而是根據我們傳入的參數在程序運行時,動態生成代理類。
3.1 JDK動態代理
我們在看JDK動態代理的代碼前,不要問為什么要這么寫,因為我們可以把它理解成一個模板,規定就是要這么寫(不用背,寫多了,就自然記住了)。
步驟一、二都跟上面一樣
步驟一,定義一個接口(serviceInterface), 定義廠家與代理商的共同目標(賣u盤)
public interface UpanSell {
float sell(int amount);
}
步驟二,創建廠家類(serive),實現步驟一定義的接口
public class WesternDigitalUpanFactory implements UpanSell {
@Override
public float sell(int amount) {
float price = 85.0f * amount;
System.out.println("------西部數據賣出" + amount + "個u盤, 價格: " + price);
return price;
}
}
步驟三,實現InvocationHandler接口中的invoke() (定義代理類要干的事情) !!!
public class MySellHandler implements InvocationHandler {
/**
* 目標對象
*/
private final Object target;
public MySellHandler(Object target){
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 執行目標方法
Object res = method.invoke(target, args);
int amount = (int) args[0];
if(res != null)
{
float price = (Float) res;
price += 25.f * amount;
System.out.println("------京東賣出" + amount + "個u盤, 價格: " + price);
System.out.println("------給你一個優惠券, 0.01分");
res = price;
}
return res;
}
}
步驟四,獲取代理實例,購買U盤 !!!
public class Shop {
public static void main(String[] args) {
// 1. 創建目標對象
UpanSell factory = new WesternDigitalUpanFactory();
// 2. 創建InvocationHandler對象
InvocationHandler handler = new MySellHandler(factory);
// 3. 生成一個代理實體類
UpanSell proxy = (UpanSell) Proxy.newProxyInstance(factory.getClass().getClassLoader(),
factory.getClass().getInterfaces(),
handler
);
float price = proxy.sell(2);
}
}
輸出結果
------西部數據賣出2個u盤, 價格: 170.0
------京東賣出2個u盤, 價格: 220.0
------給你一個優惠券, 0.01分
輸出結果當然是一樣的。步驟三、步驟四是最重要的,可以依葫蘆畫瓢,寫一個新的JDK動態代理(中介租房...),不用在意細節!
我們能看見,如果增加或減少serviceInterface的方法,修改的地方沒有靜態代理的多;
用起來更加靈活,代理類與service是解耦的。唯一惱火,可能就是不太容易理解。
我們可以看到,我們並沒有寫代理的類,卻實現了代理功能,如何看到這個生成的代理類?
在main方法的第一行加下面的屬性
...
// 加一個這個玩意兒,就可以了
System.getProperties().setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
...
生成的代理class文件
package com.sun.proxy;
import com.ukyu.dynamicproxy.service.UpanSell;
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 UpanSell {
private static Method m1;
private static Method m2;
private static Method m0;
private static Method m3;
public $Proxy0(InvocationHandler var1) throws {
super(var1);
}
public final boolean equals(Object var1) throws {
try {
return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
} catch (RuntimeException | Error var3) {
throw var3;
} catch (Throwable var4) {
throw new UndeclaredThrowableException(var4);
}
}
public final String toString() throws {
try {
return (String)super.h.invoke(this, m2, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final int hashCode() throws {
try {
return (Integer)super.h.invoke(this, m0, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final float sell(int var1) throws {
try {
return (Float)super.h.invoke(this, m3, new Object[]{var1});
} catch (RuntimeException | Error var3) {
throw var3;
} catch (Throwable var4) {
throw new UndeclaredThrowableException(var4);
}
}
static {
try {
m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
m2 = Class.forName("java.lang.Object").getMethod("toString");
m0 = Class.forName("java.lang.Object").getMethod("hashCode");
m3 = Class.forName("com.ukyu.dynamicproxy.service.UpanSell").getMethod("sell", Integer.TYPE);
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
}
}
先對這個class文件混個眼熟吧,下一篇文章開始去了解JDK動態代理的原理。