代理模式在java中的使用,包括靜態代理和動態代理,對retrofit進行模擬 ...
代理模式
在某些情況下,一個客戶不想或者不能直接引用一個對象,此時可以通過一個稱之為“代理”的第三者來實現間接引用。代理對象可以在客戶端和目標對象之間起到 中介的作用,並且可以通過代理對象去掉客戶不能看到 的內容和服務或者添加客戶需要的額外服務。
簡單來說代理模式就是通過一個代理對象去訪問一個實際對象,並且可以像裝飾模式一樣給對象添加一些功能。
靜態代理
所謂靜態代理即在程式運行前代理類就已經存在,也就是說我們編寫代碼的時候就已經把代理類的代碼寫好了,而動態代理則是在程式運行時自動生成代理類。
描述起來太過抽象,看一下代碼就明白是怎麼回事了
- main
public class Main {
public static void main(String[] args) {
Water water = new Water();
WaterProxy waterProxy = new WaterProxy(water);
waterProxy.drink();
}
}
- 介面
//代理類與被代理類共同實現的介面
public interface Drink {
void drink();
}
- 被代理類
//被代理的類
public class Water implements Drink {
@Override
public void drink() {
System.out.println("drink water");
}
}
- 代理類
//代理類
//與被代理類實現同一個介面
public class DrinkProxy implements Drink {
private Drink drinkImpl;
//通過構造函數傳入Water對象
public DrinkProxy(Drink drinkImpl) {
this.drinkImpl = drinkImpl;
}
@Override
public void drink() {
//在執行被代理對象的方法前做一些事情
System.out.println("before drink");
//執行被代理對象的方法
drinkImpl.drink();
//在執行被代理對象的方法後做一些事
System.out.println("after drink");
}
}
執行結果
before drink
drink water
after drink
動態代理
有時候我們只想改變代理類所代理的類,但是代理對象執行實際對象的方法前後所做的事情是一樣的,正所謂鐵打的代理類,流水的被代理類。而採用靜態代理就只能代理實現了同一介面的類,如果要代理任意類則必須寫很多重覆的代理類。此時我們可以採用動態代理,java已經為實現動態代理提供了一套比較方便的工具。
- java.lang.reflect.Proxy類中可以動態生成代理對象的方法
/**
*返回實現了指定介面的對象,調用代理對象的方法會調用
*InvocationHandler的invoke方法
*
* @param loader 獲取代理類所使用的類載入器
* @param interfaces 代理類所要實現的介面
* @param h 實現了InvocationHandler介面的對象
* @return 代理對象
*/
public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces, InvocationHandler h)
- InvocationHandler介面
/**
*每個代理類都有一個關聯的InvocationHandler
*當代理對象執行一個方法的時候會直接執行invoke方法
*/
public interface InvocationHandler {
/**
* @param 調用該方法的代理對象
* @param method 代理對象所調用的方法
* @param args 調用的方法的參數
* @return 調用的方法的返回值
*/
public Object invoke(Object proxy, Method method, Object[] args)
}
描述總是比較抽象,還是看實際例子比較好理解
例子
- InvocationHandler介面的實現類
public class CommonInvocationHandler implements InvocationHandler {
//被代理的對象
private Object proxied;
public CommonInvocationHandler(Object proxied) {
this.proxied = proxied;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//在調用被代理對象的方法前做一些事情
System.out.println("before doing something");
//調用被代理對象的方法
Object result = method.invoke(proxied, args);
//在調用被代理對象的方法後做一些事情
System.out.println("after doing something");;
return result;
}
}
- Main
public class Main {
public static void main(String[] args) {
//被代理的對象
Water water = new Water();
//動態獲取代理對象
Drink waterProxy =
(Drink) Proxy.newProxyInstance(water.getClass().getClassLoader(),
water.getClass().getInterfaces(),
new CommonInvocationHandler(water));
//通過代理對象調用方法
waterProxy.drink();
}
}
- 輸出結果
before doing something
drink water
after doing something
也可以不要具體的被代理對象,但是必須有相應的介面(沒有實現介面的類可以使用cglib實現動態代理)才可以動態獲取代理對象。像最近比較火的Retrofit就直接通過聲明好的介面使用動態代理進行網路請求。
例子
簡單的模擬一下retrofit
- POST註解
//Post請求註解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface POST {
String value() default "";
}
- Query註解
//Post請求註解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface POST {
String value() default "";
}
- Service介面
public interface Service {
//用POST註解聲明請求的方式和相對路徑
@POST("/login")
//@Query註解聲明請求的參數名
void login(@Query("username")String username,
@Query("password")String password);
}
- Main
public class Main {
public static void main(String[] args) {
// 動態獲取Service介面的代理
Service service = (Service) Proxy.newProxyInstance(Service.class.getClassLoader(),
new Class[] { Service.class }, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 通過註解獲取請求的相對路徑
String retativePath = ((POST) method.getAnnotations()[0]).value();
System.out.println("relative path: " + retativePath);
// 獲取參數的註解
Annotation[][] parameterAnnotations = method.getParameterAnnotations();
// 通過參數的註解獲取請求參數
for (int i = 0; i < parameterAnnotations.length; i++) {
if (parameterAnnotations[i].length != 0) {
for (int j = 0; j < parameterAnnotations[i].length; j++) {
Query query = (Query) parameterAnnotations[i][j];
System.out.println(query.value() + ": " + args[i].toString());
}
}
}
return null;
}
});
// 調用代理對象的方法
service.login("hello", "world");
}
}