一、代理模式介紹
代理模式是一種設計模式,提供了對目標對象額外的訪問方式,即通過代理對象訪問目標對象,這樣可以在不修改原目標對象的前提下,提供額外的功能操作,擴展目標對象的功能。
簡言之,代理模式就是設置一箇中間代理來控制訪問原目標對象,以達到增強原對象的功能和簡化訪問方式。
代理模式UML類圖
舉個例子,我們生活中經常到火車站去買車票,但是人一多的話,就會非常擁擠,於是就有了代售點,我們能從代售點買車票了。這其中就是代理模式的體現,代售點代理了火車站對象,提供購買車票的方法。
二、靜態代理
這種代理方式需要代理對象和目標對象實現一樣的接口。
優點:可以在不修改目標對象的前提下擴展目標對象的功能。
缺點:
- 冗餘。由於代理對象要實現與目標對象一致的接口,會產生過多的代理類。
- 不易維護。一旦接口增加方法,目標對象與代理對象都要進行修改。
舉例:保存用戶功能的靜態代理實現
- 接口類: IUserDao
package com.proxy;
public interface IUserDao {
public void save();
}
- 目標對象:UserDao
package com.proxy;
public class UserDao implements IUserDao{
@Override
public void save() {
System.out.println("保存數據");
}
}
- 靜態代理對象:UserDapProxy 需要實現IUserDao接口!
package com.proxy;
public class UserDaoProxy implements IUserDao{
private IUserDao target;
public UserDaoProxy(IUserDao target) {
this.target = target;
}
@Override
public void save() {
System.out.println("開啓事務");//擴展了額外功能
target.save();
System.out.println("提交事務");
}
}
- 測試類:TestProxy
package com.proxy;
import org.junit.Test;
public class StaticUserProxy {
@Test
public void testStaticProxy(){
//目標對象
IUserDao target = new UserDao();
//代理對象
UserDaoProxy proxy = new UserDaoProxy(target);
proxy.save();
}
}
- 輸出結果
開啓事務
保存數據
提交事務
三、動態代理
動態代理利用了JDK API,動態地在內存中構建代理對象,從而實現對目標對象的代理功能。動態代理又被稱爲JDK代理或接口代理。
靜態代理與動態代理的區別主要在:
- 靜態代理在編譯時就已經實現,編譯完成後代理類是一個實際的class文件
- 動態代理是在運行時動態生成的,即編譯完成後沒有實際的class文件,而是在運行時動態生成類字節碼,並加載到JVM中
特點:
動態代理對象不需要實現接口,但是要求目標對象必須實現接口,否則不能使用動態代理。
JDK中生成代理對象主要涉及的類有
- java.lang.reflect Proxy,主要方法爲
static Object newProxyInstance(ClassLoader loader, //指定當前目標對象使用類加載器
Class<?>[] interfaces, //目標對象實現的接口的類型
InvocationHandler h //事件處理器
)
//返回一個指定接口的代理類實例,該接口可以將方法調用指派到指定的調用處理程序。
Object invoke(Object proxy, Method method, Object[] args)
// 在代理實例上處理方法調用並返回結果。
舉例:保存用戶功能的動態代理實現
- 接口類: IUserDao
package com.proxy;
public interface IUserDao {
public void save();
}
- 目標對象:UserDao
package com.proxy;
public class UserDao implements IUserDao{
@Override
public void save() {
System.out.println("保存數據");
}
}
- 動態代理對象:UserProxyFactory
package com.proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class ProxyFactory {
private Object target;// 維護一個目標對象
public ProxyFactory(Object target) {
this.target = target;
}
// 爲目標對象生成代理對象
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("開啓事務");
// 執行目標對象方法
Object returnValue = method.invoke(target, args);
System.out.println("提交事務");
return null;
}
});
}
}
- 測試類:TestProxy
package com.proxy;
import org.junit.Test;
public class TestProxy {
@Test
public void testDynamicProxy (){
IUserDao target = new UserDao();
System.out.println(target.getClass()); //輸出目標對象信息
IUserDao proxy = (IUserDao) new ProxyFactory(target).getProxyInstance();
System.out.println(proxy.getClass()); //輸出代理對象信息
proxy.save(); //執行代理方法
}
}
- 輸出結果
class com.proxy.UserDao
class com.sun.proxy.$Proxy4
開啓事務
保存數據
提交事務
四、cglib代理
cglib is a powerful, high performance and quality Code Generation Library. It can extend JAVA classes and implement interfaces at runtime.
cglib (Code Generation Library )是一個第三方代碼生成類庫,運行時在內存中動態生成一個子類對象從而實現對目標對象功能的擴展。
cglib特點
- JDK的動態代理有一個限制,就是使用動態代理的對象必須實現一個或多個接口。
如果想代理沒有實現接口的類,就可以使用CGLIB實現。 - CGLIB是一個強大的高性能的代碼生成包,它可以在運行期擴展Java類與實現Java接口。
它廣泛的被許多AOP的框架使用,例如Spring AOP和dynaop,爲他們提供方法的interception(攔截)。 - CGLIB包的底層是通過使用一個小而快的字節碼處理框架ASM,來轉換字節碼並生成新的類。
不鼓勵直接使用ASM,因爲它需要你對JVM內部結構包括class文件的格式和指令集都很熟悉。
cglib與動態代理最大的區別就是
- 使用動態代理的對象必須實現一個或多個接口
- 使用cglib代理的對象則無需實現接口,達到代理類無侵入。
使用cglib需要引入cglib的jar包,如果你已經有spring-core的jar包,則無需引入,因爲spring中包含了cglib。
- cglib的Maven座標
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.2.5</version>
</dependency>
舉例:保存用戶功能的動態代理實現
- 目標對象:UserDao
package com.cglib;
public class UserDao{
public void save() {
System.out.println("保存數據");
}
}
- 代理對象:ProxyFactory
package com.cglib;
import java.lang.reflect.Method;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
public class ProxyFactory implements MethodInterceptor{
private Object target;//維護一個目標對象
public ProxyFactory(Object target) {
this.target = target;
}
//爲目標對象生成代理對象
public Object getProxyInstance() {
//工具類
Enhancer en = new Enhancer();
//設置父類
en.setSuperclass(target.getClass());
//設置回調函數
en.setCallback(this);
//創建子類對象代理
return en.create();
}
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("開啓事務");
// 執行目標對象的方法
Object returnValue = method.invoke(target, args);
System.out.println("關閉事務");
return null;
}
}
- 測試類:TestProxy
package com.cglib;
import org.junit.Test;
public class TestProxy {
@Test
public void testCglibProxy(){
//目標對象
UserDao target = new UserDao();
System.out.println(target.getClass());
//代理對象
UserDao proxy = (UserDao) new ProxyFactory(target).getProxyInstance();
System.out.println(proxy.getClass());
//執行代理對象方法
proxy.save();
}
}
- 輸出結果
class com.cglib.UserDao
class com.cglib.UserDao$$EnhancerByCGLIB$$552188b6
開啓事務
保存數據
關閉事務
五、總結
- 靜態代理實現較簡單,只要代理對象對目標對象進行包裝,即可實現增強功能,但靜態代理只能爲一個目標對象服務,如果目標對象過多,則會產生很多代理類。
- JDK動態代理需要目標對象實現業務接口,代理類只需實現InvocationHandler接口。
- 動態代理生成的類爲 lass com.sun.proxy.$Proxy4,cglib代理生成的類爲class com.cglib.UserDao$$EnhancerByCGLIB$$552188b6。
- 靜態代理在編譯時產生class字節碼文件,可以直接使用,效率高。
- 動態代理必須實現InvocationHandler接口,通過反射代理方法,比較消耗系統性能,但可以減少代理類的數量,使用更靈活。
- cglib代理無需實現接口,通過生成類字節碼實現代理,比反射稍快,不存在性能問題,但cglib會繼承目標對象,需要重寫方法,所以目標對象不能爲final類。