Spring系列第十五講
- 為什么要用代理
- jdk動態代理詳解
- java.lang.reflect.Proxy
- getProxyClass方法
- newProxyInstance方法
- isProxy方法
- getInvocationHandler方法
- 創建代理:方式一
- 創建代理:方式二
- 案例:任意介面中的方法耗時統計
- Proxy使用注意
為什么要用代理
我們先來看一個案例,
有一個介面IService,如下:
package com.javacode2018.lesson001.demo15;
public interface IService {
void m1();
void m2();
void m3();
}
介面有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方法!");
}
}
來個測驗用例來呼叫上面類的方法,如下:
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();
}
}
上面的代碼很簡單,就不解釋了,我們運行一下m1()方法,輸出:
我是ServiceA中的m1方法!
我是ServiceA中的m2方法!
我是ServiceA中的m3方法!
我是ServiceA中的m1方法!
我是ServiceA中的m2方法!
我是ServiceA中的m3方法!
上面是我們原本的程式,突然領導有個需求:呼叫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));
}
}
ServiceProxy是IService介面的代理類,target為被代理的物件,即實際需要訪問的物件,也實作了IService介面,上面的3個方法中加了統計耗時的代碼,當我們需要訪問IService的其他實作類的時候,可以通過ServiceProxy來間接的進行訪問,用法如下:
@Test
public 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();
}
上面代碼重點在于@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()方法耗時(納秒):32900
上面實作中我們沒有去修改ServiceA和ServiceB中的方法,只是給IService介面創建了一個代理類,通過代理類去訪問目標物件,需要添加的一些共有的功能都放在代理中,當領導有其他需求的時候,我們只需修改ServiceProxy的代碼,方便系統的擴展和測驗,
假如現在我們需要給系統中所有介面都加上統計耗時的功能,若按照上面的方式,我們需要給每個介面創建一個代理類,此時代碼量和測驗的作業量也是巨大的,那么我們能不能寫一個通用的代理類,來滿足上面的功能呢?
通用代理的2種實作:
jdk動態代理
cglib代理
jdk動態代理詳解
jdk中為實作代理提供了支持,主要用到2個類:
java.lang.reflect.Proxy
java.lang.reflect.InvocationHandler
jdk自帶的代理使用上面有個限制,只能為介面創建代理類,如果需要給具體的類創建代理類,需要用后面要說的cglib
java.lang.reflect.Proxy
這是jdk動態代理中主要的一個類,里面有一些靜態方法會經常用到,我們來熟悉一下:
getProxyClass方法
為指定的介面創建代理類,回傳代理類的Class物件
public static Class<?> getProxyClass(ClassLoader loader,
Class<?>... interfaces)
引數說明:
loader:定義代理類的類加載器
interfaces:指定需要實作的介面串列,創建的代理默認會按順序實作interfaces指定的介面
newProxyInstance方法
創建代理類的實體物件
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
這個方法先為指定的介面創建代理類,然后會生成代理類的一個實體,最后一個引數比較特殊,是InvocationHandler型別的,這個是個借口如下:
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable;
上面方法會回傳一個代理物件,當呼叫代理物件的任何方法的時候,會就被InvocationHandler介面的invoke方法處理,所以主要代碼需要卸載invoke方法中,稍后會有案例細說,
isProxy方法
判斷指定的類是否是一個代理類
public static boolean isProxyClass(Class<?> cl)
getInvocationHandler方法
獲取代理物件的InvocationHandler物件
public static InvocationHandler getInvocationHandler(Object proxy)
throws IllegalArgumentException
上面幾個方法大家熟悉一下,下面我們來看創建代理具體的2種方式,
創建代理:方式一
步驟
1.呼叫Proxy.getProxyClass方法獲取代理類的Class物件
2.使用InvocationHandler介面創建代理類的處理器
3.通過代理類和InvocationHandler創建代理物件
4.上面已經創建好代理物件了,接著我們就可以使用代理物件了
案例
先來個介面IService
package com.javacode2018.lesson001.demo16;
public interface IService {
void m1();
void m2();
void m3();
}
創建IService介面的代理物件
@Test
public 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();
}
運行輸出
我是InvocationHandler,被呼叫的方法是:m1
我是InvocationHandler,被呼叫的方法是:m2
我是InvocationHandler,被呼叫的方法是:m3
創建代理:方式二
創建代理物件有更簡單的方式,
1.使用InvocationHandler介面創建代理類的處理器
2.使用Proxy類的靜態方法newProxyInstance直接創建代理物件
3.使用代理物件
案例
@Test
public 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();
}
運行輸出:
我是InvocationHandler,被呼叫的方法是:m1
我是InvocationHandler,被呼叫的方法是:m2
我是InvocationHandler,被呼叫的方法是:m3
案例:任意介面中的方法耗時統計
下面我們通過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));
}
}
上面主要是createProxy方法用來創建代理物件,2個引數:
target:目標物件,需要實作targetInterface介面
targetInterface:需要創建代理的介面
invoke方法中通過method.invoke(this.target, args)呼叫目標方法,然后統計方法的耗時,
測驗用例
@Test
public 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();
}
運行輸出
我是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()方法耗時(納秒):19800
我們再來的介面,也需要統計耗時的功能,此時我們無需去創建新的代理類即可實作同樣的功能,如下:
IUserService介面
package com.javacode2018.lesson001.demo16;
public interface IUserService {
/**
* 插入用戶資訊
* @param name
*/
void insert(String name);
}
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));
}
}
測驗用例
@Test
public void userService() {
IUserService userService = CostTimeInvocationHandler.createProxy(new UserService(), IUserService.class);
userService.insert("路人甲Java");
}
運行輸出:
用戶[name:路人甲Java]插入成功!
class com.javacode2018.lesson001.demo16.UserService.m1()方法耗時(納秒):193000
上面當我們創建一個新的介面的時候,不需要再去新建一個代理類了,只需要使用CostTimeInvocationHandler.createProxy創建一個新的代理物件就可以了,方便了很多,
Proxy使用注意
-
jdk中的Proxy只能為介面生成代理類,如果你想給某個類創建代理類,那么Proxy是無能為力的,此時需要我們用到下面要說的cglib了,
-
Proxy類中提供的幾個常用的靜態方法大家需要掌握
-
通過Proxy創建代理物件,當呼叫代理物件任意方法時候,會被InvocationHandler介面中的invoke方法進行處理,這個介面內容是關鍵
Java
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/195036.html
標籤:其他
上一篇:JVM是如何作業的?
