設計模式(三):代理模式(靜態代理、動態代理)

我們都知道SpringAOP是用代理模式實現,那麼到底是如何實現的呢?我們來探一探究竟,並手寫部分代碼。

定義

代理模式(Proxy Pattern):是指爲其他對象提供一種代理、來控制這個對象的訪問。
代理對象在客戶端和目標對象之間起到一種中介作用。

目的:

1.保護目標對象:
我們不直接訪問被代理對象,而是通過中間媒介去訪問。
2.增強目標對象:
參考SpringAop的功能。

場景:

生活中場景:
租房中介、售票黃牛、婚介、經紀人、快遞、事務代理、非侵入式日誌監聽等,這些都是代理模式的實際體現。
如何理解:
租房,房屋擁有者將想出租自己多餘的房子,將房子託付給房屋中介,房屋中介就代理了這個房子,我們如果想要租房子就直接去找中介即可。

代理模式主要就是增加一箇中間層,來實現代理與被代理之間的組合。

我們來看看類圖結構:
在這裏插入圖片描述
Subject是頂層接口,RealSubject是真實的對象(被代理對象),Proxy是代理對象,代理對象擁有被代理對象的引用,客戶端調用代理對象方法,同時也調用了被代理對象方法,但是在代理對象前後增加一些處理。在代碼編寫中,我們想到代理就會理解爲是代碼增強,其實就是在原本邏輯上增加一些前後邏輯,而調用者感知不到。代理模式屬性結構型代理,有靜態代理和動態代理

靜態代理

這裏使用業務場景來幫助理解:
在分佈式業務場景中,我們通常會對數據庫進行分庫分表,分庫分表之後使用Java操作時,就可能需要配置多個數據源,我們通過設置數據源路由來動態切換數據源。

1.創建訂單實體Order:

@Data
public class Order {
    //訂單信息
    private Object orderInfo;
    //訂單創建時間
    private Long createTime;
    private String id;
}

2.創建OrderDao

//持久層,這裏就直接寫實現類了。
public class OrderDao {
    public  int insert(Order order){
        System.out.println("OrderDao 創建Order成功");
        return 1;
    }
}

3.創建IOrderService接口

public interface IOrderService {
    int createOrder(Order order);
}

4.創建OrderService實現類

public class OrderService implements IOrderService{

    private OrderDao orderDao;

    //模擬spring的注入 直接創建對象
    public OrderService(){
        orderDao = new OrderDao();
    }
    @Override
    public int createOrder(Order order) {
        System.out.println("調用OrderDao 創建訂單");

        return orderDao.insert(order);
    }
}

咱們使用靜態代理來進行切換數據源,依據開閉原則,寫好的代理邏輯不去改變,通過代理對象來完成。先創建數據源路由對象,我們使用ThreadLocal的單例實現,DynamicDataSourceEntry類
5.DynamicDataSourceEntry

//動態切換數據源
public class DynamicDataSourceEntry {
    // 默認數據源
    public final static String DEFAULT_SOURCE = null;

    private final static ThreadLocal<String> local = new ThreadLocal<String>();

    private DynamicDataSourceEntry(){}

    //清空數據源
    public static void clear() {
        local.remove();
    }
    //獲取當前正在使用的數據源名字
    public static String get() { return local.get(); }

    //還原當前切面的數據源
    public static void restore() { local.set(DEFAULT_SOURCE); }
    //設置已知名字的數據源
    public static void set(String source) { local.set(source); }
    //根據年份動態設置數據源
    public static void set(int year) { local.set("DB_" + year); }
}

6.創建代理對象OrderServiceStaticProx 實現被代理的對象。

public class OrderServiceStaticProx  implements IOrderService {

    //日期格式化
    private SimpleDateFormat yearFormat = new SimpleDateFormat("yyyy");
    //代理對象
    private IOrderService orderService;

    //模擬spring注入
    public OrderServiceStaticProx(IOrderService orderService) {
        this.orderService = orderService;
    }

    public int createOrder(Order order) {
        before();
        Long time = order.getCreateTime();
        Integer dbRouter = Integer.valueOf(yearFormat.format(new Date(time)));
        System.out.println("靜態代理類自動分配到【DB_" + dbRouter + "】數據源處理數據。");
        //設置數據源
        DynamicDataSourceEntry.set(dbRouter);
        orderService.createOrder(order);
        after();
        return 0;
    }

    private void before() {
        System.out.println("Proxy before method.");
    }

    private void after() {
        System.out.println("Proxy after method.");
    }

}

測試代碼:

 public static void main(String[] args) throws ParseException {
        Order order = new Order();
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd");
        Date date = sdf.parse("2020/03/11");
        order.setCreateTime(date.getTime());
        IOrderService orderService = new OrderServiceStaticProx(new OrderService());
        orderService.createOrder(order);
    }

測試結果:

Proxy before method.
靜態代理類自動分配到【DB_2020】數據源處理數據。
調用OrderDao 創建訂單
OrderDao 創建Order成功
Proxy after method.

符合我們預期的效果,通過代理來達到切換數據源的效果,並增加了before和after方法。
我們看一看類圖是否符合我們的上文中寫到的:
在這裏插入圖片描述

動態代理

動態代理實際上思路是與靜態代理一致的,只不過動態代理的功能更強大,擴展性更強:
我們來看一看JDK動態代理的實現原理:
1、拿到被代理類的引用,並且獲取它的所有的接口(反射獲取)。
2、JDK Proxy類重新生成一個新的類,實現了被代理類所有 接口的方法。
3、動態生成Java代碼,把增強邏輯加入到新生成代碼中。
4、編譯生成新的Java代碼的class文件。
5、加載並重新運行新的class,得到類就是全新類。

我們按照以上思路來對上文靜態代理做一些改進:修改OrderServiceStaticProx類

public class OrderServiceDynamicProx implements InvocationHandler {
    private SimpleDateFormat yearFormat = new SimpleDateFormat("yyyy");
    private Object target;

    public Object getInstance(Object target) {
        this.target = target;
        //獲得對象
        Class<?> clazz = target.getClass();
        //獲得所有接口和方法
        return Proxy.newProxyInstance(clazz.getClassLoader(), clazz.getInterfaces(), this);
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //執行前邏輯
        before(args[0]);
        //被代理對象方法
        Object object = method.invoke(target, args);
        //執行後邏輯
        after();
        return object;

    }

    private void before(Object target) {
        try {
            System.out.println("Proxy before method.");
            Long time = (Long) target.getClass().getMethod("getCreateTime").invoke(target);
            Integer dbRouter = Integer.valueOf(yearFormat.format(new Date(time)));
            System.out.println("動態代理類自動分配到【DB_" + dbRouter + "】數據源處理數據。");
            DynamicDataSourceEntry.set(dbRouter);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    private void after() {
        System.out.println("Proxy after method.");
    }
}

測試代碼:

public static void main(String[] args) {
        try {
            Order order = new Order();
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd");
            Date date = sdf.parse("2020/03/31");
            order.setCreateTime(date.getTime());
            IOrderService orderService = (IOrderService) new OrderServiceDynamicProx().getInstance(new OrderService());
            orderService.createOrder(order);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

測試結果

Proxy before method.
動態代理類自動分配到【DB_2020】數據源處理數據。
調用OrderDao 創建訂單
OrderDao 創建Order成功
Proxy after method.

達到了與靜態代理一樣的功能。但是,動態代理實現之後,我們不僅能實現Order的數據源動態路由,還可以實現其他任何類的數據源路由。

代理模式與Spring

Spring中代理核心類ProxyFactoryBean,核心方法getObject:我們首先來看看源碼:

@Override
	@Nullable
	public Object getObject() throws BeansException {
		initializeAdvisorChain();
		if (isSingleton()) {
			return getSingletonInstance();
		}
		else {
			if (this.targetName == null) {
				logger.warn("Using non-singleton proxies with singleton targets is often undesirable. " +
						"Enable prototype proxies by setting the 'targetName' property.");
			}
			return newPrototypeInstance();
		}
	}

在getObject()方法中,主要調用getSingletonInstance()和newPrototypeInstance(),在Spring的配置中,如果不做任何設置,那麼Spring代理生成的Bean都是單例對象。如果修改scope則每次創建一個新的原型對象.
Spring利用動態代理實現AOP有兩個非常重要的類,一個是JdkDynamicAopProxy類和CglibAopProxy類,來看一下類圖:
在這裏插入圖片描述
Spring中的代理選擇原則
1、當Bean有實現接口時,Spring就會用JDK的動態代理
2、當Bean沒有實現接口時,Spring選擇CGLib。
3、Spring可以通過配置強制使用CGLib,只需在Spring的配置文件中加入如下代碼:

<aop:aspectj-autoproxy proxy-target-class="true"/> 

關與JDK的代理和CGLib我會另外整理單獨記錄,到時候會及時補上,還請見諒。

代理模式優缺點:
優點:
1、代理模式能將代理對象與真實被調用的目標對象分離。
2、一定程度上降低了系統的耦合度,擴展性好。
3、可以起到保護目標對象的作用。
4、可以對目標對象的功能增強。
缺點:
1、代理模式會造成系統設計中類的數量增加。
2、在客戶端和目標對象增加一個代理對象,會造成請求處理速度變慢。
3、增加了系統的複雜度。

望各位看過的夥伴們如果發現了問題能夠及時批評指正,在此感謝。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章