文章目錄
一、簡介
Proxy代理模式是一種結構型設計模式,主要解決的問題是:在直接訪問對象時帶來的問題,
代理是一種常用的設計模式,其目的就是爲其他對象提供一個代理以控制對某個對象的訪問。
Java中使用代理技術主要用於擴展原功能又不侵入(修改)源代碼。
主要應用: 比如想在某個類的某個方法執行之前打印日誌或者記錄下開始時間,但是又不好將打印日誌和時間的邏輯寫入原來的方法裏。這時就可以創建一個代理類實現和原方法相同的方法,通過讓代理類持有真實對象,然後代碼調用的時候直接調用代理類的方法,來達到增強業務邏輯的目的。
1、代理分類:
靜態代理: 由程序員創建代理類或特定工具自動生成源代碼再對其編譯。在程序運行前代理類的.class文件就已經存在了。
Java中的靜態代理要求代理類(ProxySubject)和委託類(RealSubject)都實現同一個接口(Subject)。靜態代理中代理類在編譯期就已經確定,而動態代理則是JVM運行時動態生成,靜態代理的效率相對動態代理來說相對高一些,但是靜態代理代碼冗餘大,一旦需要修改接口,代理類和委託類都需要修改。
動態代理: 在程序運行時運用反射機制動態創建而成。
Java中的動態代理依靠反射來實現,代理類和委託類不需要實現同一個接口。委託類需要實現接口,否則無法創建動態代理。代理類在JVM運行時動態生成,而不是編譯期就能確定。
Java動態代理主要涉及到兩個類:java.lang.reflect.Proxy
和java.lang.reflect.InvocationHandler
。代理類需要實現InvocationHandler接口或者創建匿名內部類,而Proxy用於創建動態動態。
2、代碼區別:
靜態代理:
- 接口類。
- 具體用戶管理實現類。
- 代理類(業務增強類)
- 客戶端調用。
動態代理:(少用戶自己實現類)
- 接口類。
- 代理類(業務增強類)
- 客戶端調用。
二、靜態代理
1、接口定義要做的事情
public interface IBuyHouse {
// 定義要做的事情
public void buyHouse();
}
2、具體用戶實現類
/**
* 用戶實現類(也就是自己做,需要乾的事情)
*/
public class HouseDelegation implements IBuyHouse{
@Override
public void buyHouse() {
System.out.println("去選房,購房.");
}
}
3、代理類(業務增強類)
/**
* 代理類(代替你完成工作,可以附加功能)
*/
public class HouseAgent implements IBuyHouse{
private IBuyHouse iBuyHouse;
public HouseAgent(IBuyHouse iBuyHouse) {
this.iBuyHouse = iBuyHouse;
}
@Override
public void buyHouse() {
// 1、代替你完成工作
iBuyHouse.buyHouse();
// 2、代理類新增的功能
System.out.println("倒賣你的信息");
}
}
4、測試類
/**
* 測試類
*/
public class Test {
public static void main(String[] args) {
HouseDelegation delegation = new HouseDelegation();
// 1、將你要做的事情,傳遞給代理對象
HouseAgent agent = new HouseAgent(delegation);
// 2、執行代理類方法
agent.buyHouse();
}
}
優點:
代理使客戶端不需要知道實現類是什麼,怎麼做的,而客戶端只需知道代理即可(解耦合),對於如上的客戶端代碼,newUserManagerImpl()可以應用工廠將它隱藏,如上只是舉個例子而已。
缺點:
1)代理類和委託類實現了相同的接口,代理類通過委託類實現了相同的方法。這樣就出現了大量的代碼重複。如果接口增加一個方法,除了所有實現類需要實現這個方法外,所有代理類也需要實現此方法。增加了代碼維護的複雜度。
2)代理對象只服務於一種類型的對象,如果要服務多類型的對象。勢必要爲每一種對象都進行代理,靜態代理在程序規模稍大時就無法勝任了。如上的代碼是隻爲UserManager類的訪問提供了代理,但是如果還要爲其他類如Department類提供代理的話,就需要我們再次添加代理Department的代理類。
通俗點的解釋:
優點:
1、實現解耦,不需要知道怎麼做,只知道代理即可。
缺點:
1、大量的重複代碼,改代碼,需要維護的量大。
2、每個類都寫代碼,程序太大時,無法實現。
三、動態代理
使用動態代理,我們最大的改變就是不需要定義一個個的代理類了。最重要的是獲取到代理對象,有了代理對象,我們就可以直接調用代理對象了。
1、JDK動態代理類
JDK動態代理不僅可以代理有接口有實現類的情況,也可以代理只有接口沒有實現類的情況。
使用JDK動態代理無需引入任何外部的jar包,JDK已經給我們提供了一種獲取代理對象的API,只需要我們傳入相關信息,它就可以返回我們需要的代理對象。
java.lang.reflect.Proxy類的定義如下:
//CLassLoader loader:類的加載器
//Class<?> interfaces:得到全部的接口(代理的全部接口)
//InvocationHandler h:得到InvocationHandler接口的子類的實例(增強業務邏輯的,也就是說增加額外功能)
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces,InvocationHandler h) throws IllegalArgumentException
java.lang.reflect.InvocationHandler接口的定義如下:
//Object proxy:被代理的對象
//Method method:要調用的方法
//Object[] args:方法調用時所需要參數
public interface InvocationHandler {
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
}
1.1、有接口有委託類的情況
1、接口定義要做的事情
public interface IBuyHouse {
// 定義要做的事情
public void buyHouse();
}
2、具體用戶實現類
/**
* 用戶實現類(也就是自己做,需要乾的事情)
*/
public class HouseDelegation implements IBuyHouse{
@Override
public void buyHouse() {
System.out.println("去選房,購房.");
}
}
3、代理類(業務增強類)
package com.lydms.testPro;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
/**
* 功能增強類
*/
public class MyProxyPlus implements InvocationHandler {
// 把委託對象傳遞進來進行增強
private Object object;
public MyProxyPlus(Object object) {
this.object = object;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 1、執行原來的業務邏輯
Object result = method.invoke(object, args);
// 2、執行增強邏輯
System.out.println("對原有的功能進行增強");
return result; // 如果原有業務邏輯有返回值別忘了返回
}
}
4、測試類
import java.lang.reflect.Proxy;
/**
* 測試類
*/
public class test {
public static void main(String[] args) {
// 獲取IBuyHouse的代理對象
HouseDelegation houseDelegation = new HouseDelegation();
// 1、將你要做的事情,傳遞給代理對象(並做功能增強MyProxyPlus)
IBuyHouse iBuyHouse = (IBuyHouse) Proxy.newProxyInstance(houseDelegation.getClass().getClassLoader(),
houseDelegation.getClass().getInterfaces(), new MyProxyPlus(houseDelegation));
// 2、執行代理類方法
iBuyHouse.buyHouse();
}
}
總結:
同樣進行了增強,是不是代理類不見了呢!!!!這就是動態代理的好處,不需要你定義代理類了,你只需要能拿到代理對象就可以
1.2、僅有接口的情況
假如說上面我們只定義了IBuyCar接口和IBuyHouse接口,沒有委託類(實現類),也是可以玩的。定義一個InvocationHandler接口的實現,用於寫業務邏輯,你把所有的業務邏輯寫在invoke方法中就行了
1、接口定義要做的事情
public interface IBuyHouse {
// 定義要做的事情
public void buyHouse();
}
2、代理類(業務增強類)
/**
* 功能增強類
*/
public class MyProxyPlus implements InvocationHandler {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("對原有的功能進行增強");
return null;
}
}
3、測試類
import java.lang.reflect.Proxy;
/**
* 測試類
*/
public class Test {
public static void main(String[] args) {
// 1、將你要做的事情,傳遞給代理對象(並做功能增強MyProxyPlus)
IBuyHouse iBuyHouse = (IBuyHouse) Proxy.newProxyInstance(Test.class.getClassLoader(),
new Class[]{IBuyHouse.class}, new MyProxyPlus());
// 2、執行代理類方法
iBuyHouse.buyHouse();
}
}
總結:
業務邏輯從無到有不也是一種增強嘛!是不是代理類不見了而且連實現類都不需要了呢!!!!這就是我們Mapper動態代理的底層原理(只要定義接口,不需要寫實現類)
2、CGLIB動態代理
Java中的動態代理包括JDK動態代理和CGLIB動態代理。使用這兩種代理方式我們都可以不用定義代理類,區別在於使用JDK動態代理必須有一個接口類,使用CGLIB動態代理不需要接口類。
所以如果你要對一個實現了接口的類進行業務增強就用JDK動態代理,如果就對一個普通類進行業務增強就用CGLIB動態代理。如下
1、cglib是第三方jar,因此需要引入jar包
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>2.2.2</version>
</dependency>
2、具體用戶實現類
public class Book {
public void addBook() {
System.out.println("新增圖書...");
}
}
3、測試類
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
public class Test {
public static void main(String[] args) {
Book book = new Book();
// 1、獲取book對象的代理對象,
Book bookProxy = (Book) Enhancer.create(book.getClass(), new MethodInterceptor() {
Object obj = null;
// 2、都是對業務邏輯的增強
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("日誌開始........................");
obj = method.invoke(book, objects);
System.out.println("日誌結束........................");
return obj;
}
});
// 3、執行方法
bookProxy.addBook();
}
}
結果:
總結:
cglib動態代理其實就是把原有對象傳進去進行方法攔截,攔截到之後進行邏輯增強
三、總結
- 使用代理技術就是爲了幫我們在不入侵原有代碼的情況下增強業務邏輯。
- 你完全可以使用靜態代理一個一個去定義代理類,但是這樣的話太過於繁瑣,而且有些情況下你不知道未來會有什麼接口(比如咱們的Mybatis,你現在有個UserMapper.java,以後還可能有更多其他的Mapper接口,這些都是不確定的),所以最好採用動態代理生成代理對象吧。
- 有接口就用JDK動態代理。