1.何為代理
代理,即代替主角完成一些額外的事情。例如,明星都有經紀人,明星參演電影之前,經紀人作為明星的代理人和出資方洽談片酬、排期等,而真正參與拍戲的還是明星本人,明星拍完戲後,由經紀人代理明星去清算片酬等。Java中的代理機制就是在目標方法執行前後執行一些額外的操作,如安全檢查、記錄日志等,Java中的代理分為靜態代理和動態代理。
2.靜態代理
首先看一下靜態代理,直接上代碼,代碼模擬了登錄操作。
public interface LoginService { void login(); } public class LoginServiceImpl implements LoginService { @Override public void login() { System.out.println("login"); } } public class LoginServiceProxy implements LoginService { private LoginService loginService; public LoginServiceProxy(LoginService loginService) { this.loginService = loginService; } @Override public void login() { beforeLogin(); loginService.login(); afterLogin(); } private void beforeLogin() { System.out.println("before login"); } private void afterLogin() { System.out.println("after login"); } } public class Client { @Test public void test() { LoginService loginService = new LoginServiceImpl(); LoginService loginServiceProxy = new LoginServiceProxy(loginService); loginServiceProxy.login(); } }
輸出結果如下:
before login login after login
上面代碼實現的靜態代理很容易理解,使用聚合方式,在登錄操作前後執行額外的操作。靜態代理方式可以看得到具體代理類的代碼,且代碼由程序員編寫,在編譯之後會生成相應的class文件。使用靜態代理方式的缺點,如果需要對LoginService接口中有N個方法都代理,則需要在代理類中創建N個代理方法,並且需要編寫重復的代理操作代碼。
3.概念解釋
目標接口,即對目標操作的抽象,如LoginService。
目標類,即目標接口的實現類,如LoginServiceImpl。
目標對象,即目標類的實例。
代理類,即目標類的代理,如LoginServiceProxy。
代理對象,即代理類的實例。
4.動態代理
動態代理,即在運行時根據目標接口動態生成的代理類。動態代理方式生成的代理類在編譯後不會生成實際的class文件,而是在運行時動態生成類字節碼,並加載到JVM中使用。下面使用JDK的動態代理機制模擬登錄操作,具體代碼如下。
public interface LoginService { void login(); } public class LoginServiceImpl implements LoginService { @Override public void login() { System.out.println("login"); } } public class ProxyInvocationHandler implements InvocationHandler { private LoginService loginService; public ProxyInvocationHandler(LoginService loginService) { this.loginService = loginService; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { beforeLogin(); Object invokeResult = method.invoke(loginService, args); afterLogin(); return invokeResult; } private void beforeLogin() { System.out.println("before login"); } private void afterLogin() { System.out.println("after login"); } } public class Client { @Test public void test() { LoginService loginService = new LoginServiceImpl(); ProxyInvocationHandler proxyInvocationHandler = new ProxyInvocationHandler(loginService); LoginService loginServiceProxy = (LoginService) Proxy.newProxyInstance(loginService.getClass().getClassLoader(), loginService.getClass().getInterfaces(), proxyInvocationHandler); loginServiceProxy.login(); createProxyClassFile(); } public static void createProxyClassFile() { String name = "LoginServiceProxy"; byte[] data = ProxyGenerator.generateProxyClass(name, new Class[]{LoginService.class}); try { FileOutputStream out = new FileOutputStream("/Users/" + name + ".class"); out.write(data); out.close(); } catch (Exception e) { e.printStackTrace(); } } }
輸出結果如下。
before login login after login
JDK動態代理方式實現代理的步驟如下:
1.編寫目標接口;
2.編寫目標類實現目標接口,實現目標方法的具體邏輯;
3.編寫一個代理處理器類實現InvocationHandler接口,重寫invoke方法,用於指定運行時將生成的代理類需要完成的具體操作,包括beforeLogin和afterLogin。代理對象調用任何代理方法時都會調用這個invoke方法;
4.創建代理對象,使用代理對象調用代理方法。
上面的步驟中主要涉及以下兩個類:java.lang.reflect.InvocationHandler和java.lang.reflect.Proxy。InvocationHandler是一個接口,代理類的調用處理器,每個代理對象都具有一個關聯的調用處理器,用於指定動態生成的代理類需要完成的具體操作。該接口中有一個invoke方法,代理對象調用任何目標接口的方法時都會調用這個invoke方法,在這個方法中進行目標類的目標方法的調用。Proxy提供靜態方法用於創建動態代理類和代理類實例,同時,使用它提供的方法創建的代理類都是它的子類。這個類中主要關注newProxyInstance方法,該方法用於創建代理類對象,方法聲明如下:
public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)
loader參數用於指示使用哪個類加載器加載這個代理類;interfaces表示代理類實現的接口列表;h表示使用哪個調用處理器。
後續文章《深入淺出JDK動態代理(二)》會深入源碼分析JDK動態代理生成的代理類是什麼樣,為什麼調用代理類的任何方法時都一定會調用invoke方法,值得期待!