---
# System prepended metadata

title: Java - 動態代理
tags: [Advanced Java, Java]

---

# 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可以接受任意物件的傳入