java之靜態代理,動態代理

不知道你有沒有自己租過房子的經驗,尤其是初到一個城市,相信大家都會因爲租房問題而搞得心煩意亂,如果是自己租房的話還需要自己去聯繫房東,因爲手裏沒有資源而浪費大量的時間,當每次大費周章一番後最後卻又會發現對應的房源信息讓我們大失所望,最後搞得自己心疲力竭。

同樣的,不知道你有沒有編碼的經驗,往往在實際開發的過程中,除了完成一些核心的業務功能之外,還需要大費周章的實現一些必須完成的非業務功能。比如開發每個交易系統時,需要進行輸入參數的檢驗以及統一日誌的生成。

還有就是每個同學在進行演講的這個過程中,如果都使用自己的電腦進行控屏分享的話,會發現不僅要完成PPT的製作,除此之外每個人的電腦都需要完成一些控屏軟件之類的安裝工作。如果某個時刻,比如這些控屏軟件失效不能使用了,會發現所有人的電腦又需要重新安裝一遍,費時又費力,最後把我們忙得心煩意亂。

在今天這篇文章中了,我們講解一種全新的設計模式,這種模式就是基於以上幾種場景而出現的一種解決方式,也就是代理模式。大家一定要相信,所有的編程技術毫無疑問都是爲了解決現實生活中的問題而出現的,任何技術的出現也都是源於我們的生活場景。比如:針對租房問題,現在大家百分之九十都不會直接去尋找房東,而是直接去找對應的中介公司,只需要將需求告訴中介公司即可,就可以輕鬆的獲取到自己想要的房源。針對演講了,只需要使用一臺公用的電腦,比如只需要在老師的電腦裝上控屏軟件,哪個同學需要進行演講,只需要將自己的PPT發送給老師即可,對每個同學來說,只需要專心完成自己的PPT即可。哪怕後面即使控屏軟件失效不能用了,也不用每臺電腦都大刀闊斧的改變,只需要讓老師的這臺電腦重新安裝即可。這樣一來,效率是不是提高了很多。

接下來了,來好好認識一下代理模式。值得一提的是,代理模式是23種設計模式當中極其重要的一種設計模式,作爲Spring核心之一的AOP以及極其重要的聲明式事務,還有就是Mybatis框架中的mapper接口都是使用代理模式的思想進行完成的。下面呢,不妨好好的來認識今天的主角-代理模式。

一、代理模式三種實現

概念:

代理模式的核心就是在不改變原有代碼的基礎上對一個方法進行功能性的增強,通常是給某一個對象提供一個代理對象,並由代理對象控制對原對象的引用。說簡單點,代理模式就是設置一箇中間代理者來控制原目標對象的訪問,以達到在不改變目標對象方法的同時對目標方法進行增強與拓展。

比如租房子的這個過程,貌似是自己在找房子,其實訪問的並不是房東或者房子本身,而是直接訪問的中介公司,中介公司把房子登記以及和房東交接的一系列工作都完成了,對於我們來說,只需要交錢選擇最合適的房子進行入住即可。還有就是最常見的網絡FQ,對於一些境外的網站,每個人的電腦是沒有辦法進行直接訪問的,通常的情況一些不合法的商家會配置一臺服務器,經過一系列的技術處理,讓其能夠訪問國外的服務器,每個人的電腦只需要訪問商家配置的那一臺服務器即可,間接性的訪問到了國外的服務器。

代理模式,一共分爲兩種,一種爲靜態代理,其次就是動態代理。接下來呢,我們一起來正式認識一下,感受一下代理模式帶來的魅力。

1.1靜態代理

爲了更簡單更快速的理解靜態代理模式,我們針對一個小案例來體會一下靜態代理的思想,還是針對租房子的這個場景展開講解,在現在這個大時代下,租房子只需要尋找中介公司就可以了。

第一步:創建接口

package com.ignorance.staticProxy;

public interface HouseInter {

public void rentHouse();

}

在靜態代理模式中,需要創建一個公有的接口,並提供一個抽象的租房子rentHouse()方法,讓房東以及中介對象去進行實現就可以了。

第二步:創建房東類,並且實現rentHouse()方法。

package com.ignorance.staticProxy;

public class HouseOwner implements HouseInter {

@Override

public void rentHouse() {

System.out.println("我是房東,我正在出租房子...");

}

}

針對房東這個類,只需要實現接口中的抽象方法即可。

第三步:創建中介類,實現rentHouse()方法。

package com.ignorance.staticProxy;

public class Intermediary implements HouseInter{

private HouseInter houseInter;

public Intermediary(HouseInter houseInter) {

this.houseInter = houseInter;

}

@Override

public void rentHouse() {

System.out.println("我是中介公司,正在處理房子的前置手續...");

this.houseInter.rentHouse();

}

}

針對中介類,也就是靜態代理模式思想的核心,都說中介這個角色是幫助我們完成了目標對象的訪問並且對目標對象也就是房東這個類進行了增強。那麼是怎麼增強的呢,只需要定義一個HouseInter類型的成員變量,在創建中介對象也就是在構造器初始化的時候給房東類賦值即可。然後在實現接口方法時進行調用目標對象的rentHouse()方法即可。

第四步:創建Client類進行測試

package com.ignorance.staticProxy;

public class Client {

public static void main(String[] args) {

HouseInter houseInter = new Intermediary(new HouseOwner());

houseInter.rentHouse();

}

}

測試結果如下:

 

通過上述案例,我們可以看出靜態代理還是有一定優勢的,在一定的程度上,在沒有改變原有類方法基礎上對方法進行了增強,完全滿足對修改關閉、對拓展開放的原則,對維護代碼時還是有一定的幫助的。但是通過上述的案例也會發現靜態代理模式帶來好處的同時也會帶來一些缺陷性問題,比如完成一個功能,除了創建一個目標類,還需要額外的創建一個公用接口,以及創建一個代理對象,而且因爲在代理對象中,使用的是硬編碼,就會導致每次代理一個目標對象時都需要創建一個代理對象,並且通過硬編碼的方式實現對應的方法。優點也有,就是在維護代碼時能找到現成的代碼,更容易定位到項目中出現的問題。缺點就是類與類之間的數量會急劇暴增,比如爲了完成一個業務,需要創建三個類來協同完成,對程序來說還是有極大的負面影響。

所以說呢,針對靜態代理功能單一,且類與類之間的膨脹問題,後續也就出現了動態代理的思想。

1.2JDK動態代理

在上面呢,我們講解了靜態代理這種模式的優缺點。優點是符合開閉原則,缺點嘛不言而喻,就是功能太過單一,類的數量會急劇膨脹。所以呢在靜態代理的基礎上,出現了動態代理的思想來進行加強和補充。

通過以上的案例,我們知道靜態代理之所以會有問題,是因爲在代理類已經把目標對象以及處理代碼寫死了,比如我有個用戶類以及訂單類,需要在該方法執行之前都加入一些邏輯,需要額外的定義兩套接口,然後分別對這兩個目標類建立兩個代理類,然後通過硬編碼的方式進行加強。

這樣的設計肯定是有很大問題,對我們來說,工作都是重複而且繁瑣沒有任何技術性挑戰的,所以呢,要進行改變。

所謂動態代理,就是希望代理類可以根據實際的業務需要動態進行生成,從而避免硬編碼帶來的麻煩。在Java語言中,說起動態,那麼必不可少的就是反射技術,通過當前類的class字節碼對象,不需要知道這個類是什麼,就可以動態的獲取到當前類的各種信息。

而針對動態代理,不需要額外實現,在JDK的API中,已經爲提供了現成的方法和代碼方便在業務中進行實現,我們只需要在合適的場景下對其進行靈活使用就可以了。

下面呢,我使用動態代理模式來完成對上面案例的改造:

第一步:創建公有接口HouseInter,並且定義抽象方法rentHouse()

package com.ignorance.dynamicsProxy;

public interface HouseInter {

void rentHouse();

}

第二步:創建房東類並且實現對應的rentHouse方法()

package com.ignorance.dynamicsProxy;

public class HouseOwner implements HouseInter {

@Override

public void rentHouse() {

System.out.println("我是房東,正在出租房子...");

}

}

第三步:也就是咱們動態代理的核心,對於動態代理來說,代理類並不是通過硬編碼的方式實現的,而是調用JDK的相關API動態的幫我們生成在內存中的,接下來呢,創建一個ProxyFactory工廠類來幫助我們獲取對應的代理對象。

package com.ignorance.dynamicsProxy;

public class ProxyFactory<T> {

//目標對象

private T targetObj;

//調用構造器時對目標對象進行初始化

public ProxyFactory(T targetObj) {

this.targetObj = targetObj;

}

//返回targetObj目標對象的一個代理對象

public Object getProxyInstance() {

return null;

}

}

對代理工廠類ProxyFactoy來說,它主要是用於幫助創建對應的代理類。對代理類的生成來說,它需要知道自己代理的目標對象是誰,所以在ProxyFactory這個類中定義了一個T類型的targetObj,也就是代理對象將要代理的目標對象,只需要在創建ProxyFactory類也就是調用構造方法傳遞具體的目標對象即可。

另外就是getProxyInstance()方法,這個方法是極其重要的,它主要負責創建並且返回targetObj目標對象所對應的代理對象。那麼針對這個方法,那怎麼進行實現呢?

前面講到過,在JDK中,已經提供了開箱即用的API方法,只需要進行使用即可,如下代碼所示:

//返回targetProxy目標對象的一個代理對象

public Object getProxyInstance() {

//目標對象所對應的類加載器

ClassLoader classLoader = targetObj.getClass().getClassLoader();

//目標對象大所對應的接口

Class<?>[] interfaces = targetObj.getClass().getInterfaces();

return Proxy.newProxyInstance(classLoader, interfaces, new InvocationHandler() {

@Override

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

return null;

}

});

}

只需要調用Proxy類所對應的newProxyInstance()方法即可,這個方法就可以爲返回相應的代理對象,通過方法聲明來看,該方法需要提供三個參數。

第一個是當前目標對象所對應的類加載器,只需要通過目標對象的字節碼對象進行獲取即可,如上代碼所示;

第二個參數需要獲取目標對象所在的接口,同樣可以通過目標對象的字節碼對象進行獲取。第三個參數爲事件對象,通過參數來看;

第三個參數需要提供InvocationHandler接口的子對象,通過匿名內部類的方式傳遞一個子對象,在這個類中,同時需要實現invoke()方法。

這樣一來目標對象被調用時,就會將目標對象的方法信息以及參數信息當做實際參數傳遞到invoke()方法的形參中,也就是invoke()方法對應的第二個以及第三個參數。這樣一來,拿到目標對象的方法簽名以及參數信息後,就可以輕鬆的控制代碼邏輯,對目標類進行任意加強和處理。

如下所示:

package com.ignorance.dynamicsProxy;

import java.lang.reflect.InvocationHandler;

import java.lang.reflect.Method;

import java.lang.reflect.Proxy;

public class ProxyFactory<T> {

//目標對象

private T targetObj;

//調用構造器時對目標對象進行初始化

public ProxyFactory(T targetObj) {

this.targetObj = targetObj;

}

//返回targetObj目標對象的一個代理對象

public Object getProxyInstance() {

//目標對象所對應的類加載器

ClassLoader classLoader = targetObj.getClass().getClassLoader();

//目標對象大所對應的接口

Class<?>[] interfaces = targetObj.getClass().getInterfaces();

return Proxy.newProxyInstance(classLoader, interfaces, new InvocationHandler() {

@Override

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

System.out.println("************開始執行代理前置方法************");

System.out.println("我是中介,請將你的信息告訴我,我將全力以赴幫助你找到滿意的房子...");

System.out.println("************開始執行目標對象方法************");

Object returObj = method.invoke(targetObj, args);

return returObj;

}

});

}

}

下面呢,主要來說下第三個參數InvocationHandler,前面說過這個參數爲事件對象,在調用目標對象的方法時,這個事件對象的抽象方法invoke方法會被調用,在invoke方法中,也存在三個參數。第一個參數爲當前代理對象,第二個參數爲目標對象當前執行的方法,最後一個參數爲調用目標對象方法傳入的實參。在invoke方法能夠拿到目標方法以及其對應的實參列表後,那麼就可以通過反射的方法來動態控制咱們目標對象的調用了。

接下來呢,創建一個Client來完成動態代理的測試:

package com.ignorance.dynamicsProxy;

public class Client {

public static void main(String[] args) {

HouseInter houseInter = new HouseOwner();

ProxyFactory<HouseInter> proxyFactory = new ProxyFactory<>(houseInter);

HouseInter proxyInstance = (HouseInter)proxyFactory.getProxyInstance();

proxyInstance.rentHouse();

}

}

測試結果如下所示:

 

接下來呢,不妨通過Debug的方式看一下通過ProxyFactory創建的這個代理對象到底是什麼?

 

注意我標註紅框的地方,在這個地方真正的暴露出了這個對象的目標地址,可以看出它存在一個$Proxy0開頭的前綴,這個前綴相信大家都不會陌生,這也就是在內存中動態生成的代理對象。

1.3Cglib代理

繼上面的內容,我們講解代理模式的最後一種,也就是Cglib代理。通過靜態代理和JDK動態代理的案例,不知道細心的你有沒有發現兩者之間的共同點,那就是兩者都需要實現公共的接口,靜態代理以及動態代理的代理對象必須是基於接口才能實現的。這也就導致在選擇使用這兩種代理模式之前,必須要讓咱們的目標對象實現約定的接口。

那麼如果覺得定義接口太麻煩,不想讓目標類實現接口,而且還想動態的生成代理類動態的來增強以及拓展目標方法,這個時候就出現了一種新的代理模式,也就是Cglib代理模式。

Cglib代理也是屬於動態代理的範圍,跟JDK代理最大的區別是否需要基於接口生成,如果需要接口那麼就是JDK代理,反之則就是Cglib代理。

Cglib代理也叫作子類代理,它主要是在內存中構建了一個目標對象的子類對象從而實現對目標對象的功能拓展,它的底層是通過字節碼處理框架ASM來轉換字節碼並生成新的類。

因爲Cglib代理走得是繼承路線,所以就要求目標類不能被final關鍵字修飾,這樣也比較容易理解,被final修飾的類爲最終類,是無法被任何類繼承的。

對於Cglib代理呢,因爲它不屬於JDK內置類,所以在使用的時候需要額外的導入關於Cglib的jar包,其中maven座標如下:

<!-- https://mvnrepository.com/artifact/cglib/cglib -->

<dependency>

<groupId>cglib</groupId>

<artifactId>cglib</artifactId>

<version>3.3.0</version>

</dependency>

接下來呢,使用Cglib代理來完成一個小案例:

第一步:創建目標類Student,並且完成progress()方法的編寫。如下所示:

package com.ignorance.cglib;

public class Student {

public String progress(){

System.out.println("我是學生,我正在演示PPT");

return "ok";

}

}

第二步:創建CglibProxyFactory,但是必須要實現MethodInterceptor接口,並重寫intercept()方法。如下代碼所示:

package com.ignorance.cglib;

import net.sf.cglib.proxy.MethodInterceptor;

import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

public class CglibProxyFactory implements MethodInterceptor {

@Override

public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {

return null;

}

}

第三步:不管是哪種代理模式,都不能缺少目標對象,所以在Cglib代理中同樣需要依賴目標對象,和JDK代理同樣的思想,在構造器初始化目標對象:

package com.ignorance.cglib;

import net.sf.cglib.proxy.MethodInterceptor;

import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

public class CglibProxyFactory<T> implements MethodInterceptor {

//目標對象

private T targetObj;

//調用構造器時爲目標對象賦值

public CglibProxyFactory(T targetObj) {

this.targetObj = targetObj;

}

@Override

public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {

return null;

}

}

第四步:和JDK代理一樣,提供代理對象的創建方法getProxyInstance():

public Object getProxyInstance(){

Enhancer enhancer = new Enhancer();

enhancer.setSuperclass(targetObj.getClass());

enhancer.setCallback(this);

return enhancer.create();

}

在JDK代理中,代理對象的生成是使用JDK爲提供的內置類,也就是Proxy類的newProxyInstance()方法幫助在內存中構建的代理對象,在Cglib代理類中,同樣提供了Enhancer工具類,可以使用該類的create()方法幫助構建代理類,值得注意的是,前面說Cglib代理類是通過繼承的方式在內存中構建的子類,所以需要setSuperclass()方法指定代理的父類爲當前目標對象,同時也需要通過setCallback()方法指定回調方法。

第五步:實現intercept()方法,這個方法同JDK代理的invoke()方法一樣,當目標方法被調用時,這個時候會觸發intercept()方法執行,並且將目標類的目標方法以及方法實參當作參數傳入過來。這個時候獲取到目標方法以及實參列表後,就可以根據業務需要靈活操作。如下代碼所示:

public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {

System.out.println("*******************開始執行Cglib代理*******************");

System.out.println("我是公共電腦,我已經安裝好了控屏以及PPT演示軟件...");

Object returnVal = method.invoke(targetObj, objects);

return returnVal;

}

接下來了,創建一個測試類對Cglib代理案例進行測試:

package com.ignorance.cglib;

public class Client {

public static void main(String[] args) {

CglibProxyFactory<Student> cglibProxyFactory = new CglibProxyFactory<>(new Student());

Student proxyInstance = (Student)cglibProxyFactory.getProxyInstance();

proxyInstance.progress();

}

}

運行結果如下:

 

以上呢,就是講解的代理模式的三種。下面呢,對代理模式整體來做一下總結:

1.代理模式的核心是在不修改原有代碼的基礎上對某個方法進行拓展和加強,從而符合開閉原則,讓程序耦合度更低,從而讓代碼更加靈活富有生命力;

2.代理模式一共存在兩個主體對象,一是被代理對象,也就是目標類對象;其次則是代理對象,在代理類中,聚合了被代理對象,從而可以在代理類中引用被代理對象,進而實現動態的加強效果。

3.靜態代理和JDK代理都需要實現公共接口,而Cglib代理則不需要實現接口,其本質是通過繼承的方式在內存中創建了一個目標類的子類。

二、代理模式使用場景以及案例

講解了代理模式的幾種實現方式後,可能比較抽象,大家理解的還是有點模糊。其次就是要想真正掌握一種編程思想,光靠空泛的理論知識是遠遠不夠的,還需要在開發場景以及案例中不斷地進行體會,從而達到融會貫通的地步。下面呢,我們通過幾個小案例來加強對代理模式的理解。

2.1日誌問題

在開發工作中,對於每個核心業務功能都需要考慮記錄日誌,以方便後期存在歷史記錄,從而解決系統引起的一系列問題。比如一個購買功能,用戶說他買了1000,只給了他900,誰都說不清楚。這個時候就可以通過查詢他的操作日誌從而知道他具體做了什麼操作,從而做到有據可查,從而避免很多麻煩,也讓維護數據更加方便。

那麼對於這個問題就完全可以使用代理模式,通過這個場景可以得出結論:對於每個功能來說,核心業務纔是真正功能,記錄日誌並不算得上是核心功能,但是每個地方又不能不加日誌。就跟之前說得演講一個道理,對學生來說核心任務就是做PPT,但是爲了完成演講工作又不得不安裝投屏以及PPT等軟件。

所以呢,可以將日誌功能作爲代理類,將業務功能抽取爲目標類,從而完成對應的功能,接下來呢,來模擬這個案例來進行設計。因爲本次主要講代理模式,所以就省略數據庫操作,使用下面的工具類進行模擬這個場景。

第一步:創建OperateLog日誌實體類

package com.ignorance.cglib.fact;

import java.io.Serializable;

import java.time.LocalDateTime;

public class OperateLog {

private Long id;

private String operateSerno;

private String operateCode;

private String operateType;

private LocalDateTime createTime;

public Long getId() {

return id;

}

public void setId(Long id) {

this.id = id;

}

public String getOperateSerno() {

return operateSerno;

}

public void setOperateSerno(String operateSerno) {

this.operateSerno = operateSerno;

}

public String getOperateCode() {

return operateCode;

}

public void setOperateCode(String operateCode) {

this.operateCode = operateCode;

}

public String getOperateType() {

return operateType;

}

public void setOperateType(String operateType) {

this.operateType = operateType;

}

public LocalDateTime getCreateTime() {

return createTime;

}

public void setCreateTime(LocalDateTime createTime) {

this.createTime = createTime;

}

@Override

public String toString() {

return "OperateLog{" +

"id=" + id +

", operateSerno='" + operateSerno + '\'' +

", operateCode='" + operateCode + '\'' +

", operateType='" + operateType + '\'' +

", createTime=" + createTime +

'}';

}

}

第二步:創建Product實體類

package com.ignorance.cglib.fact;

import java.io.Serializable;

import java.math.BigDecimal;

public class Product {

private Serializable productId;

private String productName;

private BigDecimal price;

public Serializable getProductId() {

return productId;

}

public void setProductId(Serializable productId) {

this.productId = productId;

}

public String getProductName() {

return productName;

}

public void setProductName(String productName) {

this.productName = productName;

}

public BigDecimal getPrice() {

return price;

}

public void setPrice(BigDecimal price) {

this.price = price;

}

@Override

public String toString() {

return "Product{" +

"productId=" + productId +

", productName='" + productName + '\'' +

", price=" + price +

'}';

}

}

第三步:創建DataUtils工具類模擬數據庫操作

在的DataUtils這個類,主要有兩個成員變量,一個爲operateLogList,主要用於存儲操作日誌。用於模擬數據庫的日誌插入操作;businessList爲核心交易,主要模擬核心交易的操作。

下面呢,創建這個工具類,主要模擬產品的插入以及刪除操作,同時每個功能都要插入日誌。

package com.ignorance.cglib.fact;

import java.io.Serializable;

import java.util.ArrayList;

import java.util.List;

import java.util.stream.Collectors;

public class DataUtils {

//日誌list

private static List<OperateLog> operateLogList = new ArrayList<>();

//交易

private static List<Product> bussinessList = new ArrayList<>();

public static void createOperateLog(OperateLog operateLog){

operateLogList.add(operateLog);

}

public static void createBussiness(Product business){

bussinessList.add(business);

}

public static List<OperateLog> getOperateLogList() {

return operateLogList;

}

public static List<Product> getBussinessList() {

return bussinessList;

}

public static void removeBusinessById(Serializable id){

bussinessList = bussinessList.stream().filter(o -> !id.equals(o.getProductId())).collect(Collectors.toList());

}

}

第四步:創建業務類,也就是我們代理模式的目標類:

package com.ignorance.cglib.fact;

import java.io.Serializable;

public class Business {

public void saveProduct(Product product){

System.out.println("**************開始執行產品的添加**************");

DataUtils.createBussiness(product);

System.out.println("**************產品添加完成**************");

}

public void deleteById(Serializable id){

System.out.println("開始執行產品的刪除");

DataUtils.removeBusinessById(id);

System.out.println("**************產品刪除完成**************");

}

}

第五步:創建操作類型枚舉類

package com.ignorance.cglib.fact;

import java.util.Arrays;

import java.util.stream.Collectors;

public enum OperateType {

OPERATE_TYPE_ADD("01","新增產品操作"),

OPERATE_TYPE_DELETE("02","刪除產品操作");

private String operateCode;

private String operateDescr;

OperateType(String operateCode, String operateDescr) {

this.operateCode = operateCode;

this.operateDescr = operateDescr;

}

public String getOperateCode() {

return operateCode;

}

public void setOperateCode(String operateCode) {

this.operateCode = operateCode;

}

public String getOperateDescr() {

return operateDescr;

}

public void setOperateDescr(String operateDescr) {

this.operateDescr = operateDescr;

}

public static OperateType getTargetOperateTypeByCode(String code){

return Arrays.stream(OperateType.values()).

filter(o -> code.compareTo(o.getOperateCode()) == 0).

collect(Collectors.toList()).get(0);

}

}

第六步:創建註解類

package com.ignorance.cglib.fact;

import java.lang.annotation.*;

@Target(ElementType.METHOD)

@Retention(RetentionPolicy.RUNTIME)

@Documented

public @interface OperateAnnotation {

OperateType value() ;

}

第七步:操作註解標記在業務方法上

package com.ignorance.cglib.fact;

import java.io.Serializable;

public class Business {

@OperateAnnotation(value = OperateType.OPERATE_TYPE_ADD)

public void saveProduct(Product product){

System.out.println("**************開始執行產品的添加**************");

DataUtils.createBussiness(product);

System.out.println("**************產品添加完成**************");

}

@OperateAnnotation(value = OperateType.OPERATE_TYPE_DELETE)

public void deleteById(Serializable id){

System.out.println("開始執行產品的刪除");

DataUtils.removeBusinessById(id);

System.out.println("**************產品刪除完成**************");

}

}

第八步:創建Cglib代理類,因爲目標類Business沒有實現接口,所以代理模式只能使用Cglib代理:

package com.ignorance.cglib.fact;

import net.sf.cglib.proxy.Enhancer;

import net.sf.cglib.proxy.MethodInterceptor;

import net.sf.cglib.proxy.MethodProxy;

import java.io.Serializable;

import java.lang.reflect.Method;

import java.time.LocalDateTime;

import java.util.UUID;

public class BusinessProxyFactory<T> implements MethodInterceptor {

private T targetObj;

public BusinessProxyFactory(T targetObj) {

this.targetObj = targetObj;

}

public Object getProxyInstance(){

Enhancer enhancer = new Enhancer();

enhancer.setSuperclass(targetObj.getClass());

enhancer.setCallback(this);

return enhancer.create();

}

@Override

public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {

System.out.println("*******************開始執行日誌操作*******************");

OperateAnnotation operateAnnotation = method.getDeclaredAnnotation(OperateAnnotation.class);

OperateType operateType = operateAnnotation.value();

OperateLog operateLog = new OperateLog();

Long id = DataUtils.getOperateLogList().size() == 0 ? 1L

: DataUtils.getOperateLogList().stream().map(OperateLog::getId).max(Long::compareTo).get() + 1;

operateLog.setId(id);

operateLog.setOperateCode(operateType.getOperateCode());

operateLog.setOperateType(operateType.getOperateDescr());

operateLog.setOperateSerno(UUID.randomUUID().toString().replaceAll("\\-",""));

operateLog.setCreateTime(LocalDateTime.now());

DataUtils.createOperateLog(operateLog);

System.out.println("代理對象完成日誌的添加");

Object returnVal = method.invoke(targetObj, objects);

return returnVal;

}

}

在代理類中,主要用於獲取目標方法上的操作註解,然後在目標對象方法執行之前將日誌完成創建,這樣一來,在執行業務方法時,代理對象都會對目標方法進行拓展和加強,所有的業務在不進行修改代碼的情況都會進行日誌添加操作。

值得一提的是,這個日誌功能相對比較簡單,之所以會寫給大家是想讓大家更深一步的理解到代理模式的設計魅力以及使用場景,在代理類中,可以盡情的展示反射操作,從而在不改變核心代碼的基礎上實現出更爲強大的功能。

接下來呢,對這個案例進行測試。測試代碼如下所示:

package com.ignorance.cglib.fact;

import com.ignorance.cglib.CglibProxyFactory;

import java.math.BigDecimal;

public class Client {

public static void main(String[] args) {

BusinessProxyFactory<Business> businessBusinessProxyFactory = new BusinessProxyFactory<>(new Business());

Business proxyInstance = (Business)businessBusinessProxyFactory.getProxyInstance();

Product product = new Product();

product.setProductId(1001);

product.setProductName("MacBook Pro");

product.setPrice(new BigDecimal("24999"));

proxyInstance.saveProduct(product);

Product product1 = new Product();

product1.setProductId(1002);

product1.setProductName("iphone14 Pro");

product1.setPrice(new BigDecimal("7999"));

proxyInstance.saveProduct(product1);

proxyInstance.deleteById(1001);

System.out.println("日誌信息:");

DataUtils.getOperateLogList().forEach(System.out::println);

System.out.println("產品信息:");

DataUtils.getBussinessList().forEach(System.out::println);

}

}

運行結果如下圖所示:

 

從上面的運行結果來看,邏輯是沒有任何問題的,通過使用代理模式的設計思想對業務方法進行了動態的增強與拓展,讓其在不改變原有代碼的基礎上無縫的融合了日誌功能,並且類與類之間的耦合度也是相對比較低的,維護起來也遵從開閉原則。

2.2數據庫事務案例

接觸過數據庫的同學們都應該知道,數據安全是一個軟件必須需要保證的前提,在數據庫中,提供了事務機制來確保數據的一致性。有個業務涉及到多個寫操作,而且這幾個單獨的寫操作又有着數據的強一致性。比如訂單總表以及訂單明細表的寫操作,再比如支付成功用戶餘額的扣減、庫存的更新以及訂單狀態修改等操作。整體的業務功能都是由各個小步驟組合而成的,而且每個步驟要求數據保證強一致性,不能出現一個步驟成功一個步驟失敗的情況,要不全部失敗回滾,要不全部成功提交。對於這些場景,就必須使用到事務機制來保證數據的強一致性。

下面呢,使用訂單以及訂單明細這一模型來完成接下來的講解。

因爲此案例涉及到數據庫的操作,所以需要導入數據庫的驅動包,maven座標如下:

<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->

<dependency>

<groupId>mysql</groupId>

<artifactId>mysql-connector-java</artifactId>

<version>8.0.33</version>

</dependency>

接下來呢,需要創建一系列跟數據庫以及事務相關的類,因爲這些不是要講解的核心,所以就不用再花太多的時間去講解這些類。

第一步:前置工作一,創建數據庫配置文件jdbc.properties

jdbc.driver=com.mysql.cj.jdbc.Driver

jdbc.url=jdbc:mysql:///order_db?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=UTC

jdbc.username=root

jdbc.password=123123

第二步:前置工作二,創建數據庫連接類ConnUtil

package com.ignorance.cglib.trans;

import java.io.IOException;

import java.io.InputStream;

import java.sql.Connection;

import java.sql.DriverManager;

import java.sql.SQLException;

import java.util.Properties;

public class ConnUtil {

private static String DRIVER = "";

private static String URL = "";

private static String USER = "";

private static String PWD = "";

private static ThreadLocal<Connection> threadLocal = new ThreadLocal<>();

static {

loadJDBCProperties();

}

public static Connection getconn() {

Connection conn = threadLocal.get();

if (conn == null) {

conn = createConn();

threadLocal.set(conn);

}

return threadLocal.get();

}

private static void loadJDBCProperties(){

try {

InputStream inputStream = ConnUtil.class.getClassLoader().getResourceAsStream("jdbc.properties");

Properties properties = new Properties();

properties.load(inputStream);

DRIVER = properties.getProperty("jdbc.driver");

URL = properties.getProperty("jdbc.url");

USER = properties.getProperty("jdbc.username");

PWD = properties.getProperty("jdbc.password");

} catch (IOException e) {

e.printStackTrace();

}

}

private static Connection createConn(){

try {

Class.forName(DRIVER);

return DriverManager.getConnection(URL,USER,PWD);

} catch (ClassNotFoundException | SQLException e) {

e.printStackTrace();

}

return null ;

}

public static void close() throws SQLException {

Connection conn = threadLocal.get();

if (conn!=null){

if (!conn.isClosed()){

conn.close();

threadLocal.set(null);

}

}

}

}

第三步:前置工作三,創建數據庫基礎操作類BaseDao

package com.ignorance.cglib.trans;

import java.lang.reflect.Field;

import java.lang.reflect.ParameterizedType;

import java.lang.reflect.Type;

import java.sql.*;

import java.util.ArrayList;

import java.util.List;

public abstract class BaseDao<T> {

//定義一個實體類的Class變量

private Class<T> entityClass ;

public BaseDao() {

//this : 當前實例對象 , 不是BaseDao!!

//將來我們是執行如下代碼:FruitDao fruitDao = new FruitDaoImpl();

//因此,this代表當前new出的實例,也就是FruitDaoImpl的實例

//getGenericSuperclass : 獲取帶有泛型的父類Class -> BaseDao<Fruit>

//Type是父接口;parameterizedType是子接口,翻譯過來叫:參數化類型

Type genericSuperclass = this.getClass().getGenericSuperclass();

ParameterizedType parameterizedType = (ParameterizedType)genericSuperclass ;

try {

//獲取實際傳入的類型參數

// FruitDaoImpl extends BaseDao<Fruit> 那麼此時參數化類型就是Fruit

// MemberDaoImpl extends BaseDao<Member> 那麼此時參數化類型就是Member

// 返回的是數組,因爲在定義的時候,語法上可以這麼去定義:BaseDao<T, K , V , M > 表示我們可以定義多個泛型類型

Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();

//因爲當前Base後面的尖括號中我們只定義了<Fruit>,表示只有一個類型,因此我們獲取數組的第一個元素

Type type = actualTypeArguments[0];

//獲取Fruit的全類名

String className = type.getTypeName();

//通過反射技術獲取指定全類名對應的Class對象

entityClass = (Class<T>) Class.forName(className);

} catch (ClassNotFoundException e) {

e.printStackTrace();

System.out.println("T類型沒有指定!或者指定錯誤!");

}

}

protected Connection conn ;

protected PreparedStatement psmt ;

protected ResultSet rs ;

//執行更新操作

protected int executeUpdate(String sql , Object... params){

boolean insertFlag = sql.trim().toUpperCase().startsWith("INSERT");

try {

conn = ConnUtil.getconn() ;

if(!insertFlag) {

psmt = conn.prepareStatement(sql);

}else {

psmt = conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS);

}

setParams(psmt,params);

int count = psmt.executeUpdate();

if(insertFlag){

rs = psmt.getGeneratedKeys();

if(rs.next()){

Long genId = rs.getLong(1);

return genId.intValue() ;

}

}else{

return count ;

}

} catch (SQLException e) {

e.printStackTrace();

} finally {

}

return 0 ;

}

//填充參數

private void setParams(PreparedStatement psmt , Object... params) throws SQLException {

if(params!=null && params.length>0){

for (int i = 0; i < params.length; i++) {

psmt.setObject(i+1,params[i]);

}

}

}

//執行查詢,返回列表

protected List<T> executeQuery(String sql , Object... params){

List<T> list = new ArrayList<>();

try {

conn = ConnUtil.getconn();

psmt = conn.prepareStatement(sql);

setParams(psmt,params);

rs = psmt.executeQuery();

//結果集元數據:說明結果集結構的數據(例如,有多少列,列名叫什麼,什麼類型等等)

ResultSetMetaData rsmd = rs.getMetaData();

//獲取結果集的列數

int colCount = rsmd.getColumnCount();

//6.解析結果集

while(rs.next()){

//通過反射技術創建出entityClass的一個實例對象(底層仍然是調用的無參構造方法)

T entity = entityClass.newInstance() ;

for(int i = 1; i<=colCount ; i++){

//獲取當前列的列名 getColumnLabel獲取當前列的別名

String columnName = rsmd.getColumnName(i);

//獲取結果集中當前行的每一列數值

Object columnValue = rs.getObject(i);

//給entity實例的columnName對應的屬性賦columnValue值

//通過反射技術獲取指定名稱的屬性

Field field = entityClass.getDeclaredField(columnName);

//強制訪問,即使是private,也可以訪問

field.setAccessible(true);

//通過反射給entity的field屬性賦columnValue值

field.set(entity , columnValue);

}

list.add(entity);

}

return list ;

} catch (SQLException e) {

e.printStackTrace();

} catch (IllegalAccessException e) {

e.printStackTrace();

} catch (InstantiationException e) {

e.printStackTrace();

} catch (NoSuchFieldException e) {

e.printStackTrace();

} finally {

}

return null ;

}

//執行查詢,返回單個實體

protected T load(String sql , Object... params){

List<T> list = executeQuery(sql,params);

return list!=null && list.size()>0 ? list.get(0) : null ;

}

//執行復雜查詢,返回數組

//select count(*) , avg(age) as avg_age from t_person -> 得到兩個數據,返回一個數組

protected Object[] executeComplexQuery(String sql , Object... params){

conn = ConnUtil.getconn();

try {

psmt = conn.prepareStatement(sql);

setParams(psmt,params);

rs = psmt.executeQuery();

ResultSetMetaData rsmd = rs.getMetaData();

Object[] values = new Object[rsmd.getColumnCount()];

if(rs.next()){

for(int i = 0 ; i<values.length;i++){

values[i] = rs.getObject(i+1);

}

return values ;

}

} catch (SQLException e) {

e.printStackTrace();

}finally {

}

return null ;

}

}

第四步:前置工作四,創建事務管理類TransactionManager

package com.ignorance.cglib.trans;

import java.sql.SQLException;

public class TransactionManager {

//開啓事務

public static void begin() throws SQLException {

ConnUtil.getconn().setAutoCommit(false);

}

//提交事務

public static void commit() throws SQLException {

ConnUtil.getconn().commit();

ConnUtil.close();

}

//回滾事務

public static void rollback() throws SQLException {

System.out.println(444444);

ConnUtil.getconn().rollback();

ConnUtil.close();

}

}

第五步:創建訂單實體類

package com.ignorance.cglib.trans;

import java.math.BigDecimal;

import java.util.List;

public class Order {

private String orderId;

private BigDecimal balance;

private Integer count;

private List<OrderItem> orderItemList;

public Order(String orderId, BigDecimal balance, Integer count) {

this.orderId = orderId;

this.balance = balance;

this.count = count;

}

public String getOrderId() {

return orderId;

}

public List<OrderItem> getOrderItemList() {

return orderItemList;

}

public void setOrderItemList(List<OrderItem> orderItemList) {

this.orderItemList = orderItemList;

}

public void setOrderId(String orderId) {

this.orderId = orderId;

}

public BigDecimal getBalance() {

return balance;

}

public void setBalance(BigDecimal balance) {

this.balance = balance;

}

public Integer getCount() {

return count;

}

public void setCount(Integer count) {

this.count = count;

}

}

第六步:創建訂單明細類

package com.ignorance.cglib.trans;

import java.math.BigDecimal;

public class OrderItem {

private String id;

private Long productId;

private String productName;

private BigDecimal balance;

private Integer count;

private String orderId;

public OrderItem(String id, Long productId, String productName, BigDecimal balance, Integer count, String orderId) {

this.id = id;

this.productId = productId;

this.productName = productName;

this.balance = balance;

this.count = count;

this.orderId = orderId;

}

public String getId() {

return id;

}

public void setId(String id) {

this.id = id;

}

public Long getProductId() {

return productId;

}

public void setProductId(Long productId) {

this.productId = productId;

}

public String getProductName() {

return productName;

}

public void setProductName(String productName) {

this.productName = productName;

}

public BigDecimal getBalance() {

return balance;

}

public void setBalance(BigDecimal balance) {

this.balance = balance;

}

public Integer getCount() {

return count;

}

public void setCount(Integer count) {

this.count = count;

}

public String getOrderId() {

return orderId;

}

public void setOrderId(String orderId) {

this.orderId = orderId;

}

}

這些字段相信不用多跟大家解釋都能秒懂,重點說下具體的業務邏輯,模擬的邏輯是用戶提交訂單需要同時向訂單表以及訂單明細表插入數據,訂單表是當前交易的總計,訂單明細表則是當前訂單的詳情。訂單表與明細表的關係爲一對多,在訂單明細表中,我門設置了orderId爲邏輯外鍵,用於關聯訂單總表的訂單Id。

數據庫表結構如下圖所示:

訂單總表:t_order:

 

訂單明細表:t_order_item

 

第七步:創建OrderDao繼承BaseDao,用於操作t_order表

package com.ignorance.cglib.trans;

public class OrderDao extends BaseDao<Order> {

}

第八步:創建OrderItemDao繼承BaseDao,用於操作t_order_item表

package com.ignorance.cglib.trans;

public class OrderItemDao extends BaseDao<OrderItem> {

}

第九步:創建業務接口OrderService類,並且定義保存訂單方法

package com.ignorance.cglib.trans;

public interface OrderService {

public void saveOrder(Order order);

}

第十步:創建業務層實現類OrderServiceImpl

package com.ignorance.cglib.trans;

public class OrderServiceImpl implements OrderService {

private OrderDao orderDao = new OrderDao();

private OrderItemDao orderItemDao = new OrderItemDao();

@Override

public void saveOrder(Order order) {

String sql = "insert into t_order values (?,?,?)";

orderDao.executeUpdate(sql,order.getOrderId(),order.getBalance(),order.getCount());

order.getOrderItemList().forEach(o -> {

String sql1 = "insert into t_order_item values(?,?,?,?,?,?)";

orderItemDao.executeUpdate(sql1,o.getId(),o.getProductId(),o.getProductName(),o.getBalance(),o.getCount(),o.getOrderId());

});

}

}

最後呢,創建測試類模擬客戶下單的過程:

package com.ignorance.cglib.trans;

import java.math.BigDecimal;

import java.util.ArrayList;

import java.util.List;

import java.util.UUID;

public class Client {

private static OrderService orderService = new OrderServiceImpl();

public static void main(String[] args) {

String orderId = UUID.randomUUID().toString().replaceAll("\\-","");

Order order = new Order(orderId,new BigDecimal("22000"),5);

List<OrderItem> orderItemList = new ArrayList<>();

String itemId1 = UUID.randomUUID().toString().replaceAll("\\-","");

OrderItem orderItem1 = new OrderItem(itemId1,1001L,"iphone14",new BigDecimal("5000"),2,orderId);

String itemId2 = UUID.randomUUID().toString().replaceAll("\\-","");

OrderItem orderItem2 = new OrderItem(itemId2,1002L,"小米13",new BigDecimal("4000"),3,orderId);

orderItemList.add(orderItem1);

orderItemList.add(orderItem2);

order.setOrderItemList(orderItemList);

orderService.saveOrder(order);

}

}

所謂下訂單,其實數據庫操作就是向訂單表寫入一條合計數據,然後再把詳細數據插入到訂單明細表,執行代碼後數據庫變化如下圖所示:

t_order表數據變化:

 

t_order_item表數據變化:

 

此時通過數據庫的變化,發現完全符合業務邏輯,執行代碼後和預期結果一致。但是呢,這是在代碼沒有任何異常的情況下,知道訂單表和訂單明細表雖然是兩個獨立的表,但是在保存訂單的過程,兩者是不可分割的,要不同時成功要麼同時失敗,絕對不允許出現一個步驟成功另外一個步驟失敗的結果。比如此時在第一個步驟執行成功後,然後手動產生一個異常。如下所示:

public void saveOrder(Order order) {

String sql = "insert into t_order values (?,?,?)";

orderDao.executeUpdate(sql,order.getOrderId(),order.getBalance(),order.getCount());

int a = 10 / 0;

order.getOrderItemList().forEach(o -> {

String sql1 = "insert into t_order_item values(?,?,?,?,?,?)";

orderItemDao.executeUpdate(sql1,o.getId(),o.getProductId(),o.getProductName(),o.getBalance(),o.getCount(),o.getOrderId());

});

}

在第4行代碼中,人爲製造了一個算數異常,此時重置數據庫,重新運行一下代碼,結果如下:

 

此時程序的運行結果我相信大家都不會意外,主要觀測一下數據庫的數據變化情況:

 

對訂單表t_order來說,它的執行是在異常之前,所以它成功的完成了數據插入肯定是沒什麼問題的。

接下來再觀察一下t_order_item表,通過代碼可以發現,明細表的邏輯是在異常之後,所以它沒有機會執行,通過數據庫來論證結論。

 

可以看出此時明細表沒有一條記錄,但是呢訂單表卻插入成功了,這肯定是有極大的安全問題的,前面說訂單表和明細表在下單時應該保持強一致性,此時卻出現了一個成功一個失敗的情況,這顯然是絕對不允許的。所以呢,需要引入事務來解決這個問題,將這兩個步驟看作一個不可分割的整體,要麼同時成功要麼同時失敗,接下來使用事務來修改代碼:

public void saveOrder(Order order) {

try {

TransactionManager.begin();

String sql = "insert into t_order values (?,?,?)";

orderDao.executeUpdate(sql,order.getOrderId(),order.getBalance(),order.getCount());

int a = 10 / 0;

order.getOrderItemList().forEach(o -> {

String sql1 = "insert into t_order_item values(?,?,?,?,?,?)";

orderItemDao.executeUpdate(sql1,o.getId(),o.getProductId(),o.getProductName(),o.getBalance(),o.getCount(),o.getOrderId());

});

TransactionManager.commit();

}catch (Exception e){

try {

TransactionManager.rollback();

} catch (SQLException ex) {

ex.printStackTrace();

}

}

}

在代碼的開始使用try...catch異常機制加上事務機制來完善安全隱患,在第一行開啓事務,如果所有代碼或者說一個事務中的所有子步驟都沒有異常,則正常提交,否則則回滾事務。我相信這些基本操作大家都很熟悉,所以也就不一一贅述了。

接下來重新執行程序,並重置數據庫數據,結果如下:

對訂單表t_order來說,數據變化如下:

 

對訂單明細表t_order_item來說,數據變化如下;

 

可以看出加了事務以後,這個時候我訂單表以及訂單明細表的寫操作已經成爲了一個無法分割的整體,它們在數據上保持着強一致性,要不全部成功,要不全部失敗,安全性問題得以解決。

而這還沒開始重點,這個案例的目的是引用代理模式,前面的一堆問題都是爲了代理模式做準備的,那麼事務跟代理模式有什麼聯繫呢?其實一點關係都沒有,只是在程序開發中,並不僅僅只有這個業務會用到事務,在一個程序應用中,存在着大量使用事務的情況。而像這樣直接硬編碼將事務的代碼硬編碼在業務類中顯然是很不明智的,一是代碼耦合性太嚴重了,其次是每個業務都要寫事務開啓、提交以及回滾的代碼,這顯然是不合適的。

都說事務跟代理模式無關,只是加事務這個步驟正好完美的符合了代理模式的思想,還是拿訂單案例來講,核心代碼應該就是訂單表以及明細表的添加邏輯,而完成這個功能又不得不添加非邏輯性代碼,也就是事務機制,不然程序是有很大安全問題的。

所以添加事務的這個步驟可以使用代理模式的思想進行改造,可以根據業務場景構建出代理模式的模型,即將業務類OrderServiceImp看作目標類,將事務添加看作代理類,需要對業務類動態拓展事務的功能。因爲業務類實現了對應的接口,所以可以考慮使用JDK動態代理的方法改造程序。

1.創建TransactionProxyFactory類,用於幫助構建事務代理類:

package com.ignorance.cglib.trans;

import java.lang.reflect.InvocationHandler;

import java.lang.reflect.Method;

import java.lang.reflect.Proxy;

public class TransactionProxyFactory<T> {

//目標對象

private T targetObj;

//調用構造器時對目標對象進行初始化

public TransactionProxyFactory(T targetObj) {

this.targetObj = targetObj;

}

//返回targetObj目標對象的一個代理對象

public Object getProxyInstance() {

//目標對象所對應的類加載器

ClassLoader classLoader = targetObj.getClass().getClassLoader();

//目標對象大所對應的接口

Class<?>[] interfaces = targetObj.getClass().getInterfaces();

return Proxy.newProxyInstance(classLoader, interfaces, new InvocationHandler() {

@Override

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

try {

TransactionManager.begin();

Object returObj = method.invoke(targetObj, args);

TransactionManager.commit();

}catch (Exception e){

TransactionManager.rollback();

}

return null;

}

});

}

}

2.刪除目標方法上的事務代碼

public void saveOrder(Order order){

String sql = "insert into t_order values (?,?,?)";

orderDao.executeUpdate(sql,order.getOrderId(),order.getBalance(),order.getCount());

int a = 10 / 0;

order.getOrderItemList().forEach(o -> {

String sql1 = "insert into t_order_item values(?,?,?,?,?,?)";

orderItemDao.executeUpdate(sql1,o.getId(),o.getProductId(),o.getProductName(),o.getBalance(),o.getCount(),o.getOrderId());

});

}

3.進行測試:

package com.ignorance.cglib.trans;

import java.math.BigDecimal;

import java.sql.SQLException;

import java.util.ArrayList;

import java.util.List;

import java.util.UUID;

public class Client {

public static void main(String[] args) throws SQLException {

TransactionProxyFactory<OrderService> transactionProxyFactory = new TransactionProxyFactory<>(new OrderServiceImpl());

OrderService proxyInstance = (OrderService)transactionProxyFactory.getProxyInstance();

String orderId = UUID.randomUUID().toString().replaceAll("\\-","");

Order order = new Order(orderId,new BigDecimal("22000"),5);

List<OrderItem> orderItemList = new ArrayList<>();

String itemId1 = UUID.randomUUID().toString().replaceAll("\\-","");

OrderItem orderItem1 = new OrderItem(itemId1,1001L,"iphone14",new BigDecimal("5000"),2,orderId);

String itemId2 = UUID.randomUUID().toString().replaceAll("\\-","");

OrderItem orderItem2 = new OrderItem(itemId2,1002L,"小米13",new BigDecimal("4000"),3,orderId);

orderItemList.add(orderItem1);

orderItemList.add(orderItem2);

order.setOrderItemList(orderItemList);

proxyInstance.saveOrder(order);

}

}

至於運行結果,同樣和上面一樣。會發現這種寫法比直接耦合在業務類中優雅太多了,一是業務類只需要專注業務,不需要編寫業務無關的代碼,對來說還是提高了很多效率的。其次是解耦,讓我項目維護起來更加輕鬆松容易。

總結

在本篇文章中,我們講解了一種全新的設計模式,也就是代理模式。不管是在面試還是在開發中,代理模式都毋庸置疑是一種極其重要的設計模式,所以針對代理模式花了很長的篇幅進行講解。

在文章剛開始呢,通過一些生活的案例引入了代理模式,並對代理模式的三種思想進行了詳細的講解。即靜態代理、JDK動態代理以及通過繼承機制實現的Cglib代理,大家一定要重點去理解和掌握,不管是面試,還是提高我們的設計思想都是極其重要有意義的。

在最後呢,講解了兩種涉及到使用代理模式的案例,第一種是日誌問題,第二種則是事務機制。之所以會給大家舉這個例子,一是這兩種場景大家比較常見,其次就是向通過案例的方式讓大家進一步的領悟代理模式的魅力。學習嘛,肯定要學會舉一反三的能力,這樣我們的技術纔會更上一個臺階!

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