定義
代理模式是對象的結(jié)構(gòu)模式。代理模式給某一個對象提供代理對象球及,并由代理對象控制對源對象的引用芋忿。
代理模式的結(jié)構(gòu)
所謂的代理,就是一個人或者一個機構(gòu)代表另外一個人或者另外一個機構(gòu)采取行動绒北。在一些情況下,一個客戶不想或者不能夠直接引用一個對象察署,而代理對象可以在客戶端和目標(biāo)對象中間起到中介的作用闷游。
代理模式類圖如下:
代理模式中的角色有:
- 抽象對象角色(AbstractObject):聲明了目標(biāo)對象和代理對象的共同接口,這樣依賴在任何可以使用目標(biāo)對象的地方都可以使用代理對象。
- 目標(biāo)對象角色(RealObject):定義了代理對象所代表的目標(biāo)對象脐往。
- 代理對象角色(ProxyObject):代理對象內(nèi)部含有目標(biāo)對象的引用休吠,從而可以在任何時候操作目標(biāo)對象;代理對象提供一個與目標(biāo)對象相同的接口业簿,以便可以在任何時候替代目標(biāo)對象瘤礁。代理對象通常在客戶端調(diào)用傳遞給目標(biāo)對象之前或者之后,執(zhí)行某個操作辖源,而不是單純的將調(diào)用傳遞給目標(biāo)對象蔚携。
示例代碼
抽象對象角色
public abstract class AbstractObject {
/**
* 定義操作
*/
public abstract void operation();
}
目標(biāo)對象角色
public class RealObject extends AbstractObject {
public void operation() {
System.out.println("Do Something!");
}
}
代理對象角色
public class ProxyObject extends AbstractObject {
RealObject realObject = new RealObject();
public void operation() {
//在調(diào)用目標(biāo)對象之前,完成一些操作
System.out.println("Before Do Something");
realObject.operation();
//在調(diào)用目標(biāo)對象之后克饶,完成一些操作
System.out.println("After Do Something");
}
}
客戶端
public class Client {
public static void main(String[] args) {
AbstractObject abstractObject = new ProxyObject();
abstractObject.operation();
}
}
從上面的例子可以看出代理對象將客戶端的調(diào)用委派給目標(biāo)對象酝蜒,在調(diào)用目標(biāo)對象的方法之前跟之后都可以執(zhí)行特定的操作。
這就是靜態(tài)代理的實現(xiàn)矾湃,靜態(tài)代理中亡脑,一個目標(biāo)對象對應(yīng)一個代理對象,代理類在編譯時期就已經(jīng)確定了邀跃。
靜態(tài)代理方式總結(jié)
- 可以做到在不修改目標(biāo)對象的前提下霉咨,拓展目標(biāo)對象的功能。
- 缺點是:因為代理對象需要同目標(biāo)對象實現(xiàn)同樣的接口拍屑,所以會有很多的代理類途戒,造成類過多;并且僵驰,一旦接口中增加方法喷斋,目標(biāo)對象同代理對象都需要進行維護。
解決這個缺點的方式就是使用動態(tài)代理蒜茴。
動態(tài)代理
動態(tài)代理主要有如下特點:
- 代理對象不需要實現(xiàn)目標(biāo)對象的接口星爪。
- 代理對象的生成,使用的是Java的API粉私,動態(tài)的在內(nèi)存中構(gòu)件代理對象(這需要我們指定創(chuàng)建代理對象/目標(biāo)對象的接口的類型)顽腾。
- 動態(tài)代理也叫做JDK代理、接口代理诺核。
JDK中生成代理對象的API
代理類所在的包為:java.lang.reflect.Proxy
抄肖。
JDK實現(xiàn)代理只需要使用newProxyInstance
方法,但是該方法需要接收三個參數(shù)窖杀,源碼中的方法定義為:
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException
{
//......
}
注意漓摩,該方法在Proxy
類中是靜態(tài)方法,且接收的三個參數(shù)依次為:
-
ClassLoader loader
:指定當(dāng)前目標(biāo)對象使用類加載器陈瘦,獲取加載器的方法是固定的。 -
Class<?>[] interfaces
:目標(biāo)對象實現(xiàn)的接口類型,使用泛型方式確認(rèn)類型痊项。 -
InvocationHandler h
:事件處理锅风。執(zhí)行目標(biāo)對象的方法時,會觸發(fā)事件處理器的方法鞍泉,會把當(dāng)前執(zhí)行目標(biāo)對象的方法作為參數(shù)傳入皱埠。
示例代碼
目標(biāo)對象接口
public interface IUserDao {
void save();
}
目標(biāo)對象類
public class UserDao implements IUserDao {
@Override
public void save() {
System.out.println("---------已經(jīng)保存數(shù)據(jù)----------");
}
}
動態(tài)代理對象
/**
* 創(chuàng)建動態(tài)代理對象
* 動態(tài)代理對象不需要實現(xiàn)接口,但是需要指定接口類型
*/
public class ProxyFactory {
//維護一個目標(biāo)對象
private Object target;
//對象構(gòu)造時咖驮,提供目標(biāo)對象
public ProxyFactory(Object target) {
this.target = target;
}
//給目標(biāo)對象生成代理對象
public Object getProxyInstance() {
return Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
new InvocationHandler() {
@Override
public Object invoke(
Object proxy,
Method method,
Object[] args)
throws Throwable {
System.out.println("Begin Transaction");
//執(zhí)行目標(biāo)對象方法
Object returnValue = method.invoke(target, args);
System.out.println("Commit Transaction");
return returnValue;
}
});
}
}
測試類
public class TestProxyFactory {
public static void main(String[] args) {
//目標(biāo)對象
IUserDao userDao = new UserDao();
//原始類型 class com.sschen.proxy.UserDao
System.out.println(userDao.getClass());
//給定目標(biāo)對象边器,動態(tài)創(chuàng)建代理對象
IUserDao proxy = (IUserDao) new ProxyFactory(userDao).getProxyInstance();
//代理對象類型 class com.sun.proxy.$Proxy0
System.out.println(proxy.getClass());
proxy.save();
}
}
從上面的代碼可以看出,動態(tài)代理對象不需要實現(xiàn)目標(biāo)對象接口托修,但是目標(biāo)對象一定要實現(xiàn)接口忘巧,否則不能使用動態(tài)代理。
Cglib代理
上面的靜態(tài)代理和動態(tài)代理模式都需要目標(biāo)對象是一個實現(xiàn)了接口的目標(biāo)對象睦刃,但是有的時候砚嘴,目標(biāo)對象可能只是一個單獨的對象,并沒有實現(xiàn)任何的接口涩拙,這個時候际长,我們就可以使用目標(biāo)對象子類的方式實現(xiàn)代理,這種代理方式就是:Cglib代理
定義
Cglib代理兴泥,也叫做子類代理工育,它是在內(nèi)存中構(gòu)件一個子類對象,從而實現(xiàn)對目標(biāo)對象的功能拓展搓彻。
- JDK的動態(tài)代理有個限制如绸,就是使用動態(tài)代理的目標(biāo)對象必須實現(xiàn)至少一個接口,由此好唯,沒有實現(xiàn)接口但是想要使用代理的目標(biāo)對象竭沫,就可以使用Cglib代理。
- Cglib是強大的高性能的代碼生成包骑篙,它可以在運行期間拓展Java類與實現(xiàn)Java接口蜕提。它廣泛的被許多AOP的框架使用,例如Spring AOP和synaop靶端,為他們提供方法的
interception
(攔截)谎势。 - Cglib包的底層是通過使用一個小而快的字節(jié)碼處理框架ASM來轉(zhuǎn)換字節(jié)碼并生成新的類,不鼓勵直接只使用ASM杨名,因為它要求你必須對JVM內(nèi)部結(jié)構(gòu)脏榆,包括class文件的格式和指令集都很熟悉。
Cglib子類代理的實現(xiàn)方法
- 需要引入Cglib的jar文件台谍,在Maven中可以直接在POM.xml中添加下列引用即可须喂。
<!-- https://mvnrepository.com/artifact/cglib/cglib -->
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.2.5</version>
</dependency>
- 引入包后,就可以在內(nèi)存中動態(tài)構(gòu)建子類。
- 代理的對象不能為final的坞生,否則會報錯仔役。
- 目標(biāo)對象的方法如果為final/static修飾的,那么就不會被攔截是己,即不會執(zhí)行目標(biāo)對象額外的方法又兵。
代碼示例
目標(biāo)對象類
public class UserDao {
public void save() {
System.out.println("--------已經(jīng)保存數(shù)據(jù)--------");
}
}
Cglib代理工廠類
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
/**
* Cglib子類代理工廠
* 對UserDao對象在內(nèi)存中動態(tài)構(gòu)建出一個子類對象
*/
public class ProxyFactory implements MethodInterceptor {
//維護目標(biāo)對象
private Object target;
public ProxyFactory(Object target) {
this.target = target;
}
//獲取目標(biāo)對象的代理對象
public Object getProxyInstance() {
//1. 實例化工具類
Enhancer en = new Enhancer();
//2. 設(shè)置父類對象
en.setSuperclass(this.target.getClass());
//3. 設(shè)置回調(diào)函數(shù)
en.setCallback(this);
//4. 創(chuàng)建子類,也就是代理對象
return en.create();
}
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("Begin Transaction");
//執(zhí)行目標(biāo)對象的方法
Object returnValue = method.invoke(target, objects);
System.out.println("End Transaction");
return returnValue;
}
}
測試類
public class TestProxyFactory {
public static void main (String[] args) {
//目標(biāo)對象
UserDao userDao = new UserDao();
//生成代理對象
UserDao userDaoProxy = (UserDao) new ProxyFactory(userDao).getProxyInstance();
//調(diào)用對象方法
userDaoProxy.save();
}
}
在Spring的AOP編程中:
- 如果加入容器的目標(biāo)對象有實現(xiàn)接口卒废,就使用JDK代理
- 如果目標(biāo)對象沒有實現(xiàn)接口沛厨,就使用Cglib代理。