Java中的三種代理模式
Java中的三種代理模式
對代理的解釋:
通俗語言:假設我們想邀請一位明星,那麼並不是直接連線明星,而是聯絡明星的經紀人,來達到同樣的目的.明星就是一個目標物件,他只要負責活動中的節目,而其他瑣碎的事情就交給他的代理人(經紀人)來解決.這就是代理思想在現實中的一個例子
Java中的解釋:我們在呼叫一個類中的方法時,此時,可能需要對此方法的功能有所拓展,但是並不想改變原有的封裝。此時,就是代理類進行對此方法進行功能的擴充套件,之後由代理類去呼叫目標方法。
整個專案圖:
一.JDK靜態代理模式
靜態代理在使用時,需要定義介面或者父類,被代理物件與代理物件一起實現相同的介面或者是繼承相同父類.
圖解如下:
原始碼(本例項由介面實現):
1. 介面:
packageJDKProxy0;
public interfaceUserService {
public void add();
}
2. 介面實現類:
packageJDKProxy0;
/**
* 目標物件
*/
public classUserServiceImpl implements UserService{
/**
* 目標方法
*/
public void add() {
System.out.println("目標方法");
}
}
3. 代理類:
packageJDKProxy0;
public classUserServiceProxy implements UserService{
private UserService target;
public UserServiceProxy(UserService target){
this.target=target;
}
@Override
public void add() {
System.out.println("before");
target.add();
System.out.println("after");
}
}
4. 測試類:
package JDKProxy0;
public classJDKProxy0Test {
public static void main(String[] args) {
//目標物件
UserService target=newUserServiceImpl();
//代理物件
UserService userService=newUserServiceProxy(target);
userService.add();
}
}
靜態代理總結:
1.可以做到在不修改目標物件的功能前提下,對目標功能擴充套件.
2.缺點:
因為代理物件需要與目標物件實現一樣的介面,所以會有很多代理類,類太多.同時,一旦介面增加方法,目標物件與代理物件都要維護.
二.JDK動態代理模式
動態代理有以下特點:
1.代理物件,不需要實現介面
2.代理物件的生成,是利用JDK的API,動態的在記憶體中構建代理物件(需要我們指定建立代理物件/目標物件實現的介面的型別)
3.動態代理也叫做:JDK代理,介面代理
圖解:
於是,這裡原有的方法在沒有改變的情況下,對原來的方法進行了二次封裝。有時候多個物件呼叫同一物件時,有著共同的需求,但是在共同需求裡又有自己獨特的需求,這時候這一部分獨特的需求讓代理類去處理。
1.先決條件:
目標物件要實現一個介面
2.圖解:
3.原始碼:
1. 目標物件介面:
package JDKProxy;
/**
* 目標物件實現的介面,用JDK來生成代理物件一定要實現一個介面
*/
public interfaceUserService {
/**
* 目標方法
*/
voidadd();
}
2. 目標物件介面實現類:
package JDKProxy;
/**
* 目標物件
*/
public classUserServiceImpl implements UserService{
/**
* 目標方法
*/
public void add() {
System.out.println("目標方法");
}
}
3. 代理類:
package JDKProxy;
importjava.lang.reflect.InvocationHandler;
importjava.lang.reflect.Method;
importjava.lang.reflect.Proxy;
/**
* 實現自己的MyInvocationHandlder
*/
public classMyInvocationHandler implements InvocationHandler{
//目標物件
private Object target;
/**
* 構造方法
*@param target
*/
MyInvocationHandler(Object target){
super();
this.target=target;
}
/**
* 執行目標物件的方法
*/
@Override
public Object invoke(Object proxy, Methodmethod, Object[] args) throws Throwable {
//目標物件執行之前
System.out.println("before");
//執行目標物件的方法
Object result=method.invoke(target,args);
//目標物件執行之後
System.out.println("after");
return result;
}
/**
* 獲取目標物件的代理物件
*/
public Object getProxy(){
returnProxy.newProxyInstance(Thread.currentThread(). //
getContextClassLoader(),target.getClass().getInterfaces(),this);
}
}
4. 代理類測試類:
package JDKProxy;
public classJDKProxyTest {
public static void main(String[] args) {
//例項化目標物件
UserService userService=newUserServiceImpl();
//例項化代理物件
MyInvocationHandlerinvocationHandler=new MyInvocationHandler(userService);
UserService proxy=(UserService)invocationHandler.getProxy();
proxy.add();
}
}
4. 對上述代理類中用到的方法的解釋:
1.代理類所在包:java.lang.reflect.Proxy
2.JDK實現代理只需要使用newProxyInstance方法,但是該方法需要接收三個引數,完整的寫法是:
staticObject newProxyInstance(ClassLoader loader, Class<?>[]interfaces,InvocationHandler h )
其中
ClassLoader loader,:指定當前目標物件使用類載入器,獲取載入器的方法是固定的
Class<?>[]interfaces,:目標物件實現的介面的型別,使用泛型方式確認型別
InvocationHandler h:事件處理,執行目標物件的方法時,會觸發事件處理器的方法,會把當前執行目標物件的方法作為引數傳入
3. public Objectinvoke(Objectproxy, Method method, Object[] args)
此方法會被代理類自動呼叫。
Object proxy:目標類
Method method:目標方法
Object[] args:目標方法的引數
總結:
代理物件不需要實現介面,但是目標物件一定要實現介面,否則不能用動態代理
三.CGLIB代理模式
【上面的靜態代理和動態代理模式都是要求目標物件是實現一個介面的目標物件,但是有時候目標物件只是一個單獨的物件,並沒有實現任何的介面,這個時候就可以使用以目標物件子類的方式類實現代理,這種方法就叫做:Cglib代理
Cglib代理,也叫作子類代理,它是在記憶體中構建一個子類物件從而實現對目標物件功能的擴充套件.
JDK的動態代理有一個限制,就是使用動態代理的物件必須實現一個或多個介面,如果想代理沒有實現介面的類,就可以使用Cglib實現.
Cglib是一個強大的高效能的程式碼生成包,它可以在執行期擴充套件java類與實現java介面.它廣泛的被許多AOP的框架使用,例如Spring AOP,為他們提供方法的interception(攔截)
Cglib包的底層是通過使用一個小而塊的位元組碼處理框架ASM來轉換位元組碼並生成新的類.不鼓勵直接使用ASM,因為它要求你必須對JVM內部結構包括class檔案的格式和指令集都很熟悉
Cglib子類代理實現方法:
1.需要引入cglib的jar檔案,但是Spring的核心包中已經包括了Cglib功能,所以直接引入pring-core-3.2.5.jar即可.
2.引入功能包後,就可以在記憶體中動態構建子類
3.代理的類不能為final,否則報錯
4.目標物件的方法如果為final/static,那麼就不會被攔截,即不會執行目標物件額外的業務方法. 】
(【…】中內容來自網際網路)
原始碼:
1.目標類(無介面)
package CGLIB;
public class UserService {
public void add() {
System.out.println("目標類");
}
}
2.代理類
package CGLIB;
import java.lang.reflect.Method;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
public class ProxyFactory implements MethodInterceptor {
// 目標物件
private Object target;
public ProxyFactory(Objecttarget) {
this.target =target;
}
// 建立目標物件
public ObjectgetProxyInstance() {
// 核心類
Enhancer enhancer =new Enhancer();
// 確定增強的類
enhancer.setSuperclass(target.getClass());
// 添加回調函式
enhancer.setCallback(this);
returnenhancer.create();
}
@Override
public Objectintercept(Object proxy, Method method, Object[] args,
MethodProxymethodProxy) throws Throwable {
System.out.println("before");
Object value =method.invoke(target, args);
System.out.println("after");
return value;
}
}
3.測試類:
package CGLIB;
public class ProxyFactoryTest {
public static voidmain(String[] args) {
UserServicetarget=new UserService();
UserServiceproxy=(UserService) new ProxyFactory(target).getProxyInstance();
proxy.add();
}
}
總結:
其實CGLIB代理模式和JDK代理模式的思路是一致的,只是兩種不一樣的實現方法。當我們目標類有介面時,我們使用JDK代理,當沒有介面時,使用CGLIB代理模式。要深刻理解代理模式,僅僅這三個例子是不能理解的,它們這裡面這幾個引數也值得學習幾番。
本內容由安康學院”雨季”原創!