# Java - 動態代理
###### tags: `Java` `Advanced Java`
## 什麼是代理?
幫物件的方法(行為)做一些輔助操作的行為,就稱為代理
可以想像成明星身邊的經紀人
以下用明星跟經紀人的關係說明代理
## 動態代理的意思:
白話來說,就是把物件交給代理,代理除了同時可以執行該物件的功能外,還可以返回更多新的功能可供使用,不需要新增或修改原本的功能的程式
## Java示範代理(Proxy)
**注意:使用代理的前提一定要有介面**
我們先創建一個技能介面,裡面有唱歌跟跳舞
```
public interface Skill {
void dance();
void sing();
}
```
建立明星類別,並實裝該介面,重寫裡面的方法
```
public class Celebrity implements Skill {
private String name;
public Celebrity(String name) {
this.name = name;
}
@Override
public void dance() {
System.out.println(this.name + " dances.");
}
@Override
public void sing() {
System.out.println(this.name + " sings.");
}
}
```
另外建立一個代理類別
這邊要注意的是,雖然代理類別要跟他代理的原類別做一樣的事情(執行一樣的方法,ie唱歌跳舞),但卻不是用implement的方式
而是要寫一個代理方法,傳入參數為其代理的類別物件,返回值為proxy class的實現
程式碼如下:
```
public class AgentProxy {
public static Skill getProxy(Celebrity c) {
return (Skill) Proxy.newProxyInstance(c.getClass().getClassLoader(), c.getClass().getInterfaces(), new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 調用被代理物件的方法,也就是代理人也要執行相同的方法(args為方法傳入的參數)
// 通常會建立一個object物件去接返回值,然後返回
Object obj = method.invoke(c, args);
return obj;
}
});
}
}
```
代理關係都建立之後,需要在主程式把代理實裝
然後調用代理的方法,即可獲得與實例化原物件相同的結果
```
public static void main(String[] args) {
Celebrity c = new Celebrity("John");
Skill s = AgentProxy.getProxy(c);
s.sing();
s.dance();
}
```
> John sings.
> John dances.
另一種寫法:
代理類別:
```
public class AgentProxy2 implements InvocationHandler {
private Celebrity c;
public AgentProxy2(Celebrity c) {
this.c = c;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object object = method.invoke(c, args);
return object;
}
public Skill getProxy() {
return (Skill) Proxy.newProxyInstance(c.getClass().getClassLoader(), c.getClass().getInterfaces(), this);
}
}
```
主方法
```
AgentProxy2 proxy2 = new AgentProxy2(c);
Skill s2 = proxy2.getProxy();
s2.sing();
s2.dance();
```
概念就是:
1. 建立一個代理物件,實作InvocationHandler介面
2. 設定被代理物件的變數
3. 重寫InvocationHandler中invoke的方法,讓這個方法能夠使用被代理物件的方法
4. 建立一個"建立代理"的方法,回傳實例化的interface物件
5. 實例化代理物件,調用"建立代理"的方法,然後操作裡面的方法
## 應用場景舉例:
若需要統計不同功能區塊的執行時間,原本的寫法就是在實作介面後,於程式碼頭尾加上 long startTime = System.currentTimeMillis(); 和
long endTime = System.currentTimeMillis();
但這樣做的話程式碼還是有重複,且不好管理
此時透過動態代理就可以解決這件事情
優點:新增功能的時候,不需要改程式即可直接進行時間性能分析
Sample Code:
```
public class ProxyUtility {
public static UserService getProxy(UserService obj) {
return (UserService) Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj.getClass().getInterfaces(), new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
long startTime = System.currentTimeMillis();
Object result = method.invoke(obj, args);
long endTime = System.currentTimeMillis();
System.out.println(method.getName() + (endTime - startTime));
return result;
}
});
}
}
```
用泛型增加擴充性,讓用途更廣
```
public class ProxyUtility {
public static <T> T getProxy(T obj) {
return (T) Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj.getClass().getInterfaces(), new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
long startTime = System.currentTimeMillis();
Object result = method.invoke(obj, args);
long endTime = System.currentTimeMillis();
System.out.println(method.getName() + (endTime - startTime));
return result;
}
});
}
}
```
T可以接受任意物件的傳入