Java 動態代理都不懂怎麼裝逼?

語言: CN / TW / HK

小知識,大挑戰!本文正在參與“  程式設計師必備小知識

本文同時參與 「掘力星計劃」  ,贏取創作大禮包,挑戰創作激勵金

好幾天不寫文章,今天來寫一篇,從之前的計劃表上看到還有關於java的動態代理沒寫,這個技術平常用的少,也不是特別好理解,今天補上這篇,希望能講明白,不至於像我一樣迷茫好久,開始吧

動態代理分兩部分,動態和代理,我們先說下代理模式

1、代理模式

image.png

代理模式是常用的設計模式之一,也是開發中常見的設計模式。

簡單的描述一下,代理模式就是將實現類隔離開,比如你想給你女朋友過個生日,找個明星唱生日歌,你女朋友的偶像是周杰倫,想找周杰倫給她過生日,唱歌,但是你不太能聯絡上週杰倫,即使在社交網站上聯絡,可能也不太理你,所以你可以聯絡周杰倫的經紀人進行溝通,經紀人就是周杰倫的代理。

實現過程:

定義一個唱歌的介面,代表業務 java public interface ISing { void sing(); } 周杰倫有唱歌的業務,並且業務突出,實現介面 java /** * 周杰倫 */ public class JayImp implements ISing { @Override public void sing() { System.out.println("say happy birthday to you girl friend"); } } 經紀人接受業務, 經紀人的建構函式需要和明星繫結

經紀人接收唱歌的業務,今天可能是周杰倫唱,明天可能經紀人換了明星,比如蔡依林也是可以的 ```java /* * 經紀人 / public class JayProxy implements ISing{ ISing target;

/* * 初始化的時候,和明星進行簽約 * @param target / public JayProxy(ISing target) { this.target = target; }

@Override public void sing() { target.sing(); } } 聯絡經紀人進行唱歌,周杰倫唱完歌之後,經紀人收錢,very happyjava public class MoneyOwner { public static void main(String[] args) { JayImp jay = new JayImp(); // 周杰倫和經紀人進行簽約,這一步可以放在內部實現 JayProxy jayProxy = new JayProxy(jay); jayProxy.sing(); } } ``` 看下執行結果,皆大歡喜,你女朋友很開心。

image.png

上面這一套就是代理模式的實現,

但是代理類只能代理一種類,如果為每一個服務都建立一個代理類,有點傻

而且介面如果改變的情況下代理類也需要改變,非常不方便,周杰倫又是拍電影,做綜藝,寫歌,業務很多

好了,靜態代理該說的也說了,相信看到這裡你應該沒有什麼不理解的,下面我們正式開始今天的正餐,動態代理

2、動態代理

動態代理是Java提供的一種代理方式,這個技術的核心點就是在執行期的時候對介面進行增強,生成class 物件,然後載入進虛擬機器,說簡單點就是虛擬機器幫你建立了一個實現你介面的class

廢話少說,先來實現一個動態代理

第一步定義介面,上面程式碼已經有了 ISing 就不重複定義了

第二步 實現介面,上面程式碼也已經實現了 JayImp ,也不重複定義了,這次經紀人多簽約了一個歌手,林俊杰,看下實現 ```java package org.pdool.dynamic;

/* * 林俊杰 / public class JJImp implements ISing { @Override public void sing() { System.out.println("I am JJ! happy birthday to you"); } } 第三步,經紀人可以動態派出簽約歌手,注意經紀人要實現InvocationHandler,這樣才能統一處理所有的方法呼叫java import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy;

public class JayAgentFactory implements InvocationHandler { Object target;

public JayAgentFactory(Object target) { this.target = target; }

// 生成代理類 public ISing CreatProxyedObj() { return (ISing) Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this); }

@Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { Object invoke = method.invoke(target, args); logAfter(invoke); return invoke; }

public void logAfter(Object invoke) { System.out.println("結果 " + invoke); System.out.println("收入 ++ "); } } 第四步,接收業務java package org.pdool.dynamic;

import java.lang.reflect.Proxy;

public class Aain { public static void main(String[] args) { JayImp jayImp = new JayImp(); ISing subjectProxy=(ISing) Proxy.newProxyInstance(jayImp.getClass().getClassLoader(), jayImp.getClass().getInterfaces(), new JayAgentFactory(jayImp)); subjectProxy.sing(); } } ``` 總結:動態代理是Java提供的實現方式,需要InvocationHandler 的實現類

1、為什麼編輯器可以提示介面的方法?因為強轉編輯器才會能有提示

2、生成的記憶體class是的預設建構函式是需要InvocationHandler引數

3、建立代理class的核心引數是 類載入器,介面,還有InvocationHandler 子類。

類載入器保證和目標類在同一個載入器內,可以呼叫,防止不同載入器載入的類之間不能呼叫

介面就是你要代理的介面

InvocationHandler 子類是轉發器,將所有的訊息進行攔截處理轉發

3、原理研究

實現看到了,探究下原理,動態代理的最根本的在於根據介面建立記憶體class,這一步是怎麼實現的,我們跟著原始碼瞧一瞧

image.png

1、克隆接口裡函式的資訊

2、查詢或生成指定的代理類,如果快取中有,則用快取的,沒有則建立

3、通過反射,拿到代理類的建構函式

4、通過建構函式建立一個代理物件,並關聯InvocationHandler 的物件 ```

/* parameter types of a proxy class constructor / private static final Class<?>[] constructorParams = { InvocationHandler.class }; 看到了流程,我們看下代理class 到底是什麼樣子的, import sun.misc.ProxyGenerator;

public class Test { public static void main(String[] args) { //開啟儲存程式碼class屬性 System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true"); ProxyGenerator.generateProxyClass("Xiangcai", JayImp.class.getInterfaces()); } } ``` 執行上面的函式,可以看到在專案的路徑下生成Xiangcai.class

image.png

接著看看xiangcai.class 到底有哪些東西,直接拖到編輯器就可以了 ``` import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.lang.reflect.UndeclaredThrowableException; import org.pdool.dynamic.ISing;

public final class xiangcai extends Proxy implements ISing { private static Method m1; private static Method m2; private static Method m3; private static Method m0;

public xiangcai(InvocationHandler var1) throws { super(var1); }

public final boolean equals(Object var1) throws { try { return (Boolean)super.h.invoke(this, m1, new Object[]{var1}); } catch (RuntimeException | Error var3) { throw var3; } catch (Throwable var4) { throw new UndeclaredThrowableException(var4); } }

public final String toString() throws { try { return (String)super.h.invoke(this, m2, (Object[])null); } catch (RuntimeException | Error var2) { throw var2; } catch (Throwable var3) { throw new UndeclaredThrowableException(var3); } } //注意看這裡!!!其他的都不重要 public final void sing() throws { try { super.h.invoke(this, m3, (Object[])null); } catch (RuntimeException | Error var2) { throw var2; } catch (Throwable var3) { throw new UndeclaredThrowableException(var3); } }

public final int hashCode() throws { try { return (Integer)super.h.invoke(this, m0, (Object[])null); } catch (RuntimeException | Error var2) { throw var2; } catch (Throwable var3) { throw new UndeclaredThrowableException(var3); } }

static { try { m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object")); m2 = Class.forName("java.lang.Object").getMethod("toString"); m3 = Class.forName("org.pdool.dynamic.ISing").getMethod("sing"); m0 = Class.forName("java.lang.Object").getMethod("hashCode"); } catch (NoSuchMethodException var2) { throw new NoSuchMethodError(var2.getMessage()); } catch (ClassNotFoundException var3) { throw new NoClassDefFoundError(var3.getMessage()); } } } ``` 可以看到實現了sing的介面,並且呼叫了invokehandler的方法invoke.好了,真相大白了,你明白了嗎?

有人會說,道理我都懂,可是不會用啊,但是沒看到好的應用場景,所以有段時間是沒掌握這些的,下面我們就具體一下應用場景

4、應用

在切面程式設計(AOP)中,需要攔截特定的方法,通常,會選擇動態代理方式。看個具體的例子 spring-data-jpa 的實現

具體的使用:

spring中訪問資料庫的使用 ``` import com.tao.springboot.hibernate.entity.Customer; import org.springframework.data.jpa.repository.JpaRepository;

public interface CustomerRepository extends JpaRepository {

} ``` 只要實現上面的介面就可以直接操作資料庫,是不是很簡單?

有幾個問題,你稍微思考下:

1、兩個泛型什麼意思?

2、資料庫連線在哪?是怎麼注入的?

3、只實現介面是怎麼操作資料庫的?

第一個問題答案:

Customer 為表物件對應的entity實體。

Long 是表的主鍵型別,

第二個答案:

資料庫連線是在spring啟動的時候自動注入到spring 容器中的,在JpaRepository 的實現類自動注入的

第三個答案:

所有的的介面在spring啟動的時候會生成代理類,目標類target就是實現類SimpleJpaRepository

看下類圖

image.png

看下JpaRepository的定義,都是一些常用方法 ```java public interface JpaRepository extends PagingAndSortingRepository, QueryByExampleExecutor { List findAll();

List findAll(Sort var1);

List findAllById(Iterable var1);

List saveAll(Iterable var1);

void flush();

S saveAndFlush(S var1);

void deleteInBatch(Iterable var1);

void deleteAllInBatch();

T getOne(ID var1);

List findAll(Example var1);

List findAll(Example var1, Sort var2); } ``` 看下SimpleJpaRepository 的定義:

```java public class SimpleJpaRepository implements JpaRepositoryImplementation { private static final String ID_MUST_NOT_BE_NULL = "The given id must not be null!"; private final JpaEntityInformation entityInformation; private final EntityManager em;//看這裡!!! private final PersistenceProvider provider; @Nullable private CrudMethodMetadata metadata;

//具體的實現方法 @Transactional public void delete(T entity) { Assert.notNull(entity, "The entity must not be null!"); this.em.remove(this.em.contains(entity) ? entity : this.em.merge(entity)); } ```

類似下面的程式碼呼叫:

Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), JpaRepository.class.getInterfaces(), new SimpleJpaRepository(());

注:只是表達一下意思,具體的實現應該不是這樣的

5、總結

所有的事情都解開了,下面進行總結下:

1、靜態代理是代理模式的實現,是針對某一個具體的介面的實現

2、動態代理的是jdk提供的一種方式,必須要介面,還有其他的實現方式cglib,javassit 等等

3、動態代理是在執行的時候生成class 檔案然後自動載入的class

4、動態代理是基於反射呼叫的技術

5、動態代理會生成class 到 metaspace

6、多應用在框架中

7、解密了spring data jpa的實現