java 代理模式的實現及應用
阿新 • • 發佈:2019-01-01
java 代理模式
代理(Proxy)是一種設計模式,提供了對目標物件另外的訪問方式;即通過代理物件訪問目標物件.這樣做的好處是:可以在目標物件實現的基礎上,增強額外的功能操作,即擴充套件目標物件的功能
1.靜態代理
靜態代理在使用時,需要定義介面或者父類,被代理物件與代理物件一起實現相同的介面或者是繼承相同父類.
程式碼示例:
介面:IUserDao.java
目標物件:UserDao.java
代理物件:UserDaoProxy.java
測試類:App.java
1.2.動態代理
動態代理有以下特點:
1.代理物件,不需要實現介面
2.代理物件的生成,是利用JDK的API,動態的在記憶體中構建代理物件(需要我們指定建立代理物件/目標物件實現的介面的型別)
3.動態代理也叫做:JDK代理,介面代理
JDK中生成代理物件的API
代理類所在包:java.lang.reflect.Proxy
JDK實現代理只需要使用newProxyInstance方法,但是該方法需要接收三個引數,完整的寫法是:
static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces,InvocationHandler h );
注意該方法是在Proxy類中是靜態方法,且接收的三個引數依次為:
ClassLoader loader,:指定當前目標物件使用類載入器,獲取載入器的方法是固定的
Class<?>[] interfaces,:目標物件實現的介面的型別,使用泛型方式確認型別
InvocationHandler h:事件處理,執行目標物件的方法時,會觸發事件處理器的方法,會把當前執行目標物件的方法作為引數傳入
程式碼示例一:
介面類IUserDao.java以及介面實現類,目標物件UserDao是一樣的,沒有做修改.在這個基礎上,增加一個代理工廠類(ProxyFactory.java),將代理類寫在這個地方,然後在測試類(需要使用到代理的程式碼)中先建立目標物件和代理物件的聯絡,然後代用代理物件的中同名方法
代理工廠類:ProxyFactory.java
測試類:App.java
程式碼示例二:DynamicProxy.java
測試類:ProxyTest.java
總結:
代理物件不需要實現介面,但是目標物件一定要實現介面,否則不能用動態代理
1.3 Cglib動態代理
上面的靜態代理和動態代理模式都是要求目標物件是實現一個介面,但是有時候目標物件只是一個單獨的物件,並沒有實現任何的介面,這個時候就可以使用以目標物件子類的方式類實現代理,這種方法就叫做:Cglib代理
Cglib代理,也叫作子類代理,它是在記憶體中構建一個子類物件從而實現對目標物件功能的擴充套件.
JDK的動態代理有一個限制,就是使用動態代理的物件必須實現一個或多個介面,如果想代理沒有實現介面的類,就可以使用Cglib實現.
Cglib是一個強大的高效能的程式碼生成包,它可以在執行期擴充套件java類與實現java介面.它廣泛的被許多AOP的框架使用,例如Spring AOP和synaop,為他們提供方法的interception(攔截)
Cglib包的底層是通過使用一個小而塊的位元組碼處理框架ASM來轉換位元組碼並生成新的類.不鼓勵直接使用ASM,因為它要求你必須對JVM內部結構包括class檔案的格式和指令集都很熟悉.
Cglib子類代理實現方法:
1.需要引入cglib的jar檔案,但是Spring的核心包中已經包括了Cglib功能,所以直接引入pring-core-3.2.5.jar即可.
2.引入功能包後,就可以在記憶體中動態構建子類
3.代理的類不能為final,否則報錯
4.目標物件的方法如果為final/static,那麼就不會被攔截,即不會執行目標物件額外的業務方法.
程式碼示例:
目標物件類:UserDao.java
測試類:
在Spring的AOP程式設計中:
如果加入容器的目標物件有實現介面,用JDK代理
如果目標物件沒有實現介面,用Cglib代理
1.4 動態代理--代理介面無實現類
使用通過介面定義,或解析介面註解等完成相關功能,如mybatis的SqlSession.getMapper的實現及dubbo消費者的實現
1.4.1 介面定義
1.4.2 代理實現
1.4.3 測試類
代理(Proxy)是一種設計模式,提供了對目標物件另外的訪問方式;即通過代理物件訪問目標物件.這樣做的好處是:可以在目標物件實現的基礎上,增強額外的功能操作,即擴充套件目標物件的功能
1.靜態代理
靜態代理在使用時,需要定義介面或者父類,被代理物件與代理物件一起實現相同的介面或者是繼承相同父類.
程式碼示例:
介面:IUserDao.java
/**
* 介面
*/
public interface IUserDao {
void save();
}
目標物件:UserDao.java
/** * 介面實現 * 目標物件 */ public class UserDao implements IUserDao { public void save() { System.out.println("----已經儲存資料!----"); } }
代理物件:UserDaoProxy.java
/** * 代理物件,靜態代理 */ public class UserDaoProxy implements IUserDao{ //接收儲存目標物件 private IUserDao target; public UserDaoProxy(IUserDao target){ this.target=target; } public void save() { System.out.println("開始事務..."); target.save();//執行目標物件的方法 System.out.println("提交事務..."); } }
測試類:App.java
/**
* 測試類
*/
public class App {
public static void main(String[] args) {
//目標物件
UserDao target = new UserDao();
//代理物件,把目標物件傳給代理物件,建立代理關係
UserDaoProxy proxy = new UserDaoProxy(target);
proxy.save();//執行的是代理的方法
}
}
1.2.動態代理
動態代理有以下特點:
1.代理物件,不需要實現介面
2.代理物件的生成,是利用JDK的API,動態的在記憶體中構建代理物件(需要我們指定建立代理物件/目標物件實現的介面的型別)
3.動態代理也叫做:JDK代理,介面代理
JDK中生成代理物件的API
代理類所在包:java.lang.reflect.Proxy
JDK實現代理只需要使用newProxyInstance方法,但是該方法需要接收三個引數,完整的寫法是:
static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces,InvocationHandler h );
注意該方法是在Proxy類中是靜態方法,且接收的三個引數依次為:
ClassLoader loader,:指定當前目標物件使用類載入器,獲取載入器的方法是固定的
Class<?>[] interfaces,:目標物件實現的介面的型別,使用泛型方式確認型別
InvocationHandler h:事件處理,執行目標物件的方法時,會觸發事件處理器的方法,會把當前執行目標物件的方法作為引數傳入
程式碼示例一:
介面類IUserDao.java以及介面實現類,目標物件UserDao是一樣的,沒有做修改.在這個基礎上,增加一個代理工廠類(ProxyFactory.java),將代理類寫在這個地方,然後在測試類(需要使用到代理的程式碼)中先建立目標物件和代理物件的聯絡,然後代用代理物件的中同名方法
代理工廠類:ProxyFactory.java
/** * 建立動態代理物件 * 動態代理不需要實現介面,但是需要指定介面型別 */ 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("開始事務2"); //執行目標物件方法 Object returnValue = method.invoke(target, args); System.out.println("提交事務2"); return returnValue; } } ); } }
測試類:App.java
/**
* 測試類
*/
public class App {
public static void main(String[] args) {
// 目標物件
IUserDao target = new UserDao();
// 【原始的型別 class cn.itcast.b_dynamic.UserDao】
System.out.println(target.getClass());
// 給目標物件,建立代理物件
IUserDao proxy = (IUserDao) new ProxyFactory(target).getProxyInstance();
// class $Proxy0 記憶體中動態生成的代理物件
System.out.println(proxy.getClass());
// 執行方法 【代理物件】
proxy.save();
}
}
程式碼示例二:DynamicProxy.java
public class DynamicProxy implements InvocationHandler {
private Object target;
public DynamicProxy(Object target){
this.target=target;
}
@SuppressWarnings("unchecked")
public <T> T getProxy(){
return (T) Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
this
);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
before();
Object result = method.invoke(target,args);
after();
return result;
}
private void before(){
System.out.println("Before");
}
private void after(){
System.out.println("After");
}
}
測試類:ProxyTest.java
/**
* 測試類
*/
public class ProxyTest {
public static void main(String[] args){
Hello hello = new HelloImpl();
DynamicProxy dynamicProxy = new DynamicProxy(hello);
Hello helloProxy = (Hello)dynamicProxy.getProxy();
helloProxy.say("Jack");
}
}
總結:
代理物件不需要實現介面,但是目標物件一定要實現介面,否則不能用動態代理
1.3 Cglib動態代理
上面的靜態代理和動態代理模式都是要求目標物件是實現一個介面,但是有時候目標物件只是一個單獨的物件,並沒有實現任何的介面,這個時候就可以使用以目標物件子類的方式類實現代理,這種方法就叫做:Cglib代理
Cglib代理,也叫作子類代理,它是在記憶體中構建一個子類物件從而實現對目標物件功能的擴充套件.
JDK的動態代理有一個限制,就是使用動態代理的物件必須實現一個或多個介面,如果想代理沒有實現介面的類,就可以使用Cglib實現.
Cglib是一個強大的高效能的程式碼生成包,它可以在執行期擴充套件java類與實現java介面.它廣泛的被許多AOP的框架使用,例如Spring AOP和synaop,為他們提供方法的interception(攔截)
Cglib包的底層是通過使用一個小而塊的位元組碼處理框架ASM來轉換位元組碼並生成新的類.不鼓勵直接使用ASM,因為它要求你必須對JVM內部結構包括class檔案的格式和指令集都很熟悉.
Cglib子類代理實現方法:
1.需要引入cglib的jar檔案,但是Spring的核心包中已經包括了Cglib功能,所以直接引入pring-core-3.2.5.jar即可.
2.引入功能包後,就可以在記憶體中動態構建子類
3.代理的類不能為final,否則報錯
4.目標物件的方法如果為final/static,那麼就不會被攔截,即不會執行目標物件額外的業務方法.
程式碼示例:
目標物件類:UserDao.java
/**
* 目標物件,沒有實現任何介面
*/
public class UserDao {
public void save() {
System.out.println("----已經儲存資料!----");
}
}
Cglib代理工廠:ProxyFactory.java/**
* Cglib子類代理工廠
* 對UserDao在記憶體中動態構建一個子類物件
*/
public class ProxyFactory implements MethodInterceptor{
//維護目標物件
private Object target;
public ProxyFactory(Object target) {
this.target = target;
}
//給目標物件建立一個代理物件
public Object getProxyInstance(){
//1.工具類
Enhancer en = new Enhancer();
//2.設定父類
en.setSuperclass(target.getClass());
//3.設定回撥函式
en.setCallback(this);
//4.建立子類(代理物件)
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 returnValue;
}
}
測試類:
/**
* 測試類
*/
public class App {
@Test
public void test(){
//目標物件
UserDao target = new UserDao();
//代理物件
UserDao proxy = (UserDao)new ProxyFactory(target).getProxyInstance();
//執行代理物件的方法
proxy.save();
}
}
在Spring的AOP程式設計中:
如果加入容器的目標物件有實現介面,用JDK代理
如果目標物件沒有實現介面,用Cglib代理
1.4 動態代理--代理介面無實現類
使用通過介面定義,或解析介面註解等完成相關功能,如mybatis的SqlSession.getMapper的實現及dubbo消費者的實現
1.4.1 介面定義
package cn.proxy;
public interface IHello {
String say(String aa);
}
1.4.2 代理實現
package cn.proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
* JDK動態代理代理類
*
*/
@SuppressWarnings("unchecked")
public class FacadeProxy implements InvocationHandler {
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
System.out.println("介面方法呼叫開始");
//執行方法
System.out.println("method toGenericString:"+method.toGenericString());
System.out.println("method name:"+method.getName());
System.out.println("method args:"+(String)args[0]);
System.out.println("介面方法呼叫結束");
return "呼叫返回值";
}
public static <T> T newMapperProxy(Class<T> mapperInterface) {
ClassLoader classLoader = mapperInterface.getClassLoader();
Class<?>[] interfaces = new Class[]{mapperInterface};
FacadeProxy proxy = new FacadeProxy();
return (T) Proxy.newProxyInstance(classLoader, interfaces, proxy);
}
}
1.4.3 測試類
package cn.proxy;
public class Test {
public static void main(String[] args) {
IHello hello = FacadeProxy.newMapperProxy(IHello.class);
System.out.println(hello.say("hello world"));
}
}