為什麼要用代理
我們先來看一個案例。
有一個介面IService,如下:
package com.javacode2018.lesson001.demo15;public interface IService {
void m1();
void m2();
void m3();}1234567
介面有2個實現類ServiceA和ServiceB,如下:
package com.javacode2018.lesson001.demo15;public class ServiceA implements IService {
@Override
public void m1() {
System.out.println("我是ServiceA中的m1方法!");
}
@Override
public void m2() {
System.out.println("我是ServiceA中的m2方法!");
}
@Override
public void m3() {
System.out.println("我是ServiceA中的m3方法!");
}}package com.javacode2018.lesson001.demo15;public class ServiceB implements IService {
@Override
public void m1() {
System.out.println("我是ServiceB中的m1方法!");
}
@Override
public void m2() {
System.out.println("我是ServiceB中的m2方法!");
}
@Override
public void m3() {
System.out.println("我是ServiceB中的m3方法!");
}}123456789101112131415161718192021222324252627282930313233343536
來個測試用例來呼叫上面類的方法,如下:
package com.javacode2018.lesson001.demo15;import org.junit.Test;public class ProxyTest {
@Test
public void m1() {
IService serviceA = new ServiceA();
IService serviceB = new ServiceB();
serviceA.m1();
serviceA.m2();
serviceA.m3();
serviceB.m1();
serviceB.m2();
serviceB.m3();
}}123456789101112131415161718
上面的程式碼很簡單,就不解釋了,我們執行一下m1()方法,輸出:
我是ServiceA中的m1方法!我是ServiceA中的m2方法!我是ServiceA中的m3方法!我是ServiceA中的m1方法!我是ServiceA中的m2方法!我是ServiceA中的m3方法!123456
上面是我們原本的程式,突然領導有個需求:呼叫IService介面中的任何方法的時候,需要記錄方法的耗時。
此時你會怎麼做呢?
IService介面有2個實現類ServiceA和ServiceB,我們可以在這兩個類的所有方法中加上統計耗時的程式碼,如果IService介面有幾十個實現,是不是要修改很多程式碼,所有被修改的方法需重新測試?是不是非常痛苦,不過上面這種修改程式碼的方式倒是可以解決問題,只是增加了很多工作量(編碼 & 測試)。
突然有一天,領導又說,要將這些耗時統計傳送到監控系統用來做監控報警使用。
此時是不是又要去一個修改上面的程式碼?又要去測試?此時的系統是難以維護。
還有假如上面這些類都是第三方以jar包的方式提供給我們的,此時這些類都是class檔案,此時我們無法去修改原始碼。
比較好的方式:可以為IService介面建立一個代理類,透過這個代理類來間接訪問IService介面的實現類,在這個代理類中去做耗時及傳送至監控的程式碼,程式碼如下:
package com.javacode2018.lesson001.demo15;// IService的代理類public class ServiceProxy implements IService {
//目標物件,被代理的物件
private IService target;
public ServiceProxy(IService target) {
this.target = target;
}
@Override
public void m1() {
long starTime = System.nanoTime();
this.target.m1();
long endTime = System.nanoTime();
System.out.println(this.target.getClass() + ".m1()方法耗時(納秒):" + (endTime - starTime));
}
@Override
public void m2() {
long starTime = System.nanoTime();
this.target.m1();
long endTime = System.nanoTime();
System.out.println(this.target.getClass() + ".m1()方法耗時(納秒):" + (endTime - starTime));
}
@Override
public void m3() {
long starTime = System.nanoTime();
this.target.m1();
long endTime = System.nanoTime();
System.out.println(this.target.getClass() + ".m1()方法耗時(納秒):" + (endTime - starTime));
}}1234567891011121314151617181920212223242526272829303132333435
ServiceProxy是IService介面的代理類,target為被代理的物件,即實際需要訪問的物件,也實現了IService介面,上面的3個方法中加了統計耗時的程式碼,當我們需要訪問IService的其他實現類的時候,可以透過ServiceProxy來間接的進行訪問,用法如下:
@Testpublic void serviceProxy() {
IService serviceA = new ServiceProxy(new ServiceA());//@1
IService serviceB = new ServiceProxy(new ServiceB()); //@2
serviceA.m1();
serviceA.m2();
serviceA.m3();
serviceB.m1();
serviceB.m2();
serviceB.m3();}123456789101112
上面程式碼重點在於@1和@2,建立的是代理物件ServiceProxy,ServiceProxy構造方法中傳入了被代理訪問的物件,現在我們訪問ServiceA或者ServiceB,都需要經過ServiceProxy,執行輸出:
我是ServiceA中的m1方法!class com.javacode2018.lesson001.demo15.ServiceA.m1()方法耗時(納秒):90100我是ServiceA中的m1方法!class com.javacode2018.lesson001.demo15.ServiceA.m1()方法耗時(納秒):31600我是ServiceA中的m1方法!class com.javacode2018.lesson001.demo15.ServiceA.m1()方法耗時(納秒):25800我是ServiceB中的m1方法!class com.javacode2018.lesson001.demo15.ServiceB.m1()方法耗時(納秒):142100我是ServiceB中的m1方法!class com.javacode2018.lesson001.demo15.ServiceB.m1()方法耗時(納秒):35000我是ServiceB中的m1方法!class com.javacode2018.lesson001.demo15.ServiceB.m1()方法耗時(納秒):32900123456789101112
上面實現中我們沒有去修改ServiceA和ServiceB中的方法,只是給IService介面建立了一個代理類,透過代理類去訪問目標物件,需要新增的一些共有的功能都放在代理中,當領導有其他需求的時候,我們只需修改ServiceProxy的程式碼,方便系統的擴充套件和測試。
假如現在我們需要給系統中所有介面都加上統計耗時的功能,若按照上面的方式,我們需要給每個介面建立一個代理類,此時程式碼量和測試的工作量也是巨大的,那麼我們能不能寫一個通用的代理類,來滿足上面的功能呢?
通用代理的2種實現:
jdk動態代理
cglib代理
jdk動態代理詳解
jdk中為實現代理提供了支援,主要用到2個類:
zhengzhou/
java.lang.reflect.Proxy
java.lang.reflect.InvocationHandler12
jdk自帶的代理使用上面有個限制,只能為介面建立代理類,如果需要給具體的類建立代理類,需要用後面要說的cglib
java.lang.reflect.Proxy
這是jdk動態代理中主要的一個類,裡面有一些靜態方法會經常用到,我們來熟悉一下:
getProxyClass方法
為指定的介面建立代理類,返回代理類的Class物件
public static Class<?> getProxyClass(ClassLoader loader,
Class<?>... interfaces)12
引數說明:
loader:定義代理類的類載入器
interfaces:指定需要實現的介面列表,建立的代理預設會按順序實現interfaces指定的介面
newProxyInstance方法
建立代理類的例項物件
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)123
這個方法先為指定的介面建立代理類,然後會生成代理類的一個例項,最後一個引數比較特殊,是InvocationHandler型別的,這個是個藉口如下:
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable;12
上面方法會返回一個代理物件,當呼叫代理物件的任何方法的時候,會就被InvocationHandler介面的invoke方法處理,所以主要程式碼需要解除安裝invoke方法中,稍後會有案例細說。
isProxy方法
判斷指定的類是否是一個代理類
public static boolean isProxyClass(Class<?> cl)1
getInvocationHandler方法
獲取代理物件的InvocationHandler物件
public static InvocationHandler getInvocationHandler(Object proxy)
throws IllegalArgumentException12
上面幾個方法大家熟悉一下,下面我們來看建立代理具體的2種方式。
建立代理:方式一
步驟
1.呼叫Proxy.getProxyClass方法獲取代理類的Class物件2.使用InvocationHandler介面建立代理類的處理器3.透過代理類和InvocationHandler建立代理物件4.上面已經建立好代理物件了,接著我們就可以使用代理物件了1234
案例
先來個介面IService
package com.javacode2018.lesson001.demo16;public interface IService {
void m1();
void m2();
void m3();}1234567
建立IService介面的代理物件
@Testpublic void m1() throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
// 1. 獲取介面對應的代理類
Class<IService> proxyClass = (Class<IService>) Proxy.getProxyClass(IService.class.getClassLoader(), IService.class);
// 2. 建立代理類的處理器
InvocationHandler invocationHandler = new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("我是InvocationHandler,被呼叫的方法是:" + method.getName());
return null;
}
};
// 3. 建立代理例項
IService proxyService = proxyClass.getConstructor(InvocationHandler.class).newInstance(invocationHandler);
// 4. 呼叫代理的方法
proxyService.m1();
proxyService.m2();
proxyService.m3();}12345678910111213141516171819
執行輸出
zzdxb.baikezh.com/xinyang/
我是InvocationHandler,被呼叫的方法是:m1
我是InvocationHandler,被呼叫的方法是:m2
我是InvocationHandler,被呼叫的方法是:m3123
建立代理:方式二
建立代理物件有更簡單的方式。
1.使用InvocationHandler介面建立代理類的處理器2.使用Proxy類的靜態方法newProxyInstance直接建立代理物件3.使用代理物件123
案例
@Testpublic void m2() throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
// 1. 建立代理類的處理器
InvocationHandler invocationHandler = new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("我是InvocationHandler,被呼叫的方法是:" + method.getName());
return null;
}
};
// 2. 建立代理例項
IService proxyService = (IService) Proxy.newProxyInstance(IService.class.getClassLoader(), new Class[]{IService.class}, invocationHandler);
// 3. 呼叫代理的方法
proxyService.m1();
proxyService.m2();
proxyService.m3();}1234567891011121314151617
執行輸出:
我是InvocationHandler,被呼叫的方法是:m1
我是InvocationHandler,被呼叫的方法是:m2
我是InvocationHandler,被呼叫的方法是:m3123
案例:任意介面中的方法耗時統計
下面我們透過jdk動態代理實現一個通用的代理,解決統計所有介面方法耗時的問題。
主要的程式碼在代理處理器InvocationHandler實現上面,如下:
package com.javacode2018.lesson001.demo16;import java.lang.reflect.InvocationHandler;import java.lang.reflect.Method;import java.lang.reflect.Proxy;public class CostTimeInvocationHandler implements InvocationHandler {
private Object target;
public CostTimeInvocationHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
long starTime = System.nanoTime();
Object result = method.invoke(this.target, args);//@1
long endTime = System.nanoTime();
System.out.println(this.target.getClass() + ".m1()方法耗時(納秒):" + (endTime - starTime));
return result;
}
/**
* 用來建立targetInterface介面的代理物件
*
* @param target 需要被代理的物件
* @param targetInterface 被代理的介面
* @param <T>
* @return
*/
public static <T> T createProxy(Object target, Class<T> targetInterface) {
if (!targetInterface.isInterface()) {
throw new IllegalStateException("targetInterface必須是介面型別!");
} else if (!targetInterface.isAssignableFrom(target.getClass())) {
throw new IllegalStateException("target必須是targetInterface介面的實現類!");
}
return (T) Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), new CostTimeInvocationHandler(target));
}}12345678910111213141516171819202122232425262728293031323334353637383940
上面主要是createProxy方法用來建立代理物件,2個引數:
target:目標物件,需要實現targetInterface介面
targetInterface:需要建立代理的介面
invoke方法中透過method.invoke(this.target, args)呼叫目標方法,然後統計方法的耗時。
測試用例
@Testpublic void costTimeProxy() {
IService serviceA = CostTimeInvocationHandler.createProxy(new ServiceA(), IService.class);
IService serviceB = CostTimeInvocationHandler.createProxy(new ServiceB(), IService.class);
serviceA.m1();
serviceA.m2();
serviceA.m3();
serviceB.m1();
serviceB.m2();
serviceB.m3();}123456789101112
執行輸出
我是ServiceA中的m1方法!class com.javacode2018.lesson001.demo16.ServiceA.m1()方法耗時(納秒):61300我是ServiceA中的m2方法!class com.javacode2018.lesson001.demo16.ServiceA.m1()方法耗時(納秒):22300我是ServiceA中的m3方法!class com.javacode2018.lesson001.demo16.ServiceA.m1()方法耗時(納秒):18700我是ServiceB中的m1方法!class com.javacode2018.lesson001.demo16.ServiceB.m1()方法耗時(納秒):54700我是ServiceB中的m2方法!class com.javacode2018.lesson001.demo16.ServiceB.m1()方法耗時(納秒):27200我是ServiceB中的m3方法!class com.javacode2018.lesson001.demo16.ServiceB.m1()方法耗時(納秒):19800123456789101112
我們再來的介面,也需要統計耗時的功能,此時我們無需去建立新的代理類即可實現同樣的功能,如下:
IUserService介面
package com.javacode2018.lesson001.demo16;public interface IUserService {
/**
* 插入使用者資訊
* @param name
*/
void insert(String name);}123456789
IUserService介面實現類:
package com.javacode2018.lesson001.demo16;public class UserService implements IUserService {
@Override
public void insert(String name) {
System.out.println(String.format("使用者[name:%s]插入成功!", name));
}}12345678
測試用例
@Testpublic void userService() {
IUserService userService = CostTimeInvocationHandler.createProxy(new UserService(), IUserService.class);
userService.insert("路人甲Java");}12345
執行輸出:
使用者[name:路人甲Java]插入成功!class com.javacode2018.lesson001.demo16.UserService.m1()方法耗時(納秒):19300012
上面當我們建立一個新的介面的時候,不需要再去新建一個代理類了,只需要使用CostTimeInvocationHandler.createProxy建立一個新的代理物件就可以了,方便了很多。
Proxy使用注意
ask.baikezh.com/hefei/
-
jdk中的Proxy只能為介面生成代理類,如果你想給某個類建立代理類,那麼Proxy是無能為力的,此時需要我們用到下面要說的cglib了。
-
Proxy類中提供的幾個常用的靜態方法大家需要掌握
-
透過Proxy建立代理物件,當呼叫代理物件任意方法時候,會被InvocationHandler介面中的invoke方法進行處理,這個介面內容是關鍵
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/30239065/viewspace-2732119/,如需轉載,請註明出處,否則將追究法律責任。