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