重新學習Mybatis(四)(JDK模式詳解)

重溫一下代理模式、以及自己在理解代理模式中存在的誤區。

記得在大學的時候學校spring的時候,spring的好多特性都是依賴於JDK的動態代理。後來慢慢寫代碼就出現了至今都流行的代碼結構。

controller->service->dao這三層,一層一層調用。在controller調用service層的時候,具體業務的實現就是serviceImpl類裏面的具體實現。

誤區一:serviceImpl就是service的動態代理的代理類,service是代理類(真的是很長一段時間,我都這麼認爲),其實serviceImpl纔是被代理類,$Proxy數字.class纔是代理類,主要死沒有搞懂代理模式的含義以及背景

解決誤區的措施:在XXXController裏@Autowired XXXService,整個項目啓動的時候,spring加載XXXController bean的時候,由於裏面注入了Service,所以要先實例化Service,這裏打斷點發現,如果在Service中沒有加類似於@Transcationl註解的時候,注入的就是普通Servcie的實現類,因爲@Transcationl等一些註解是通過JDK動態代理實現的,因爲當有了這些註解的時候,我要對當前的ServiceImpl進行事務等切面處理,所以這時候要生成ServiceImpl的代理類。在spring注入類的時候,具體邏輯可參考以下方法,可以在這裏打斷點開始debug,一步一步的就可以看到spring是如何創建代理類的。並且可以看到爲什麼@Service註解設置名字的要小寫。

org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator#postProcessAfterInitialization

org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator#wrapIfNecessary

誤區一注意1:這裏是ServiceImpl的代理類,而不是Service的代理類(這一點至關重要)

誤區一注意2:當多個Serviceimpl都實現了Service,那麼注入的時候如何告知spring注入哪個呢?可以使用@Qualifier註解指定名字即可

代理模式的背景簡介:

如果我先要從JVM A裏調用JVM B裏的某一個方法B我該如何調用?這種背景下出現了代理模式

最早期的RMI(HeadFirst中關於代理模式有說)的解決方案如下,以下代理模式爲遠程代理

1、啓動一個JVM A和JVM B都能訪問的公共倉庫

2、將要被調用JVM B啓動的時候將自己接口的實現類註冊到一個公共的倉庫裏面(Naming.rebind("ServiceB",ServiceImpl)方法)

3、在JVM A中去公共倉庫裏找到要調用的方法的類(Naming.lookup("ServiceB")),獲得了ServiceImpl的實例,JVM A中用以下方式接收。通過Java的多態,實例化接口的具體實現類

ServiceB service = Naming.lookup("ServiceB");
//上面返回的是接口的具體實現,就相當於
ServiceB service = new ServiceBImpl();

4、通過拿到的service的實例化對象,調用對應的方法即可。

是不是有點類似於現在微服務的註冊中心的味道?

代理模式爲虛擬代理

如果我的目的要取出一個對象,但是這個對象構造很耗時間,那麼在構造完畢之前我會告訴調用這個對象的那一方此時我的狀態是什麼,當我構造完畢,立刻返回此對象

1、我只對外提供一個可以取出該對象的方法

2、實際取出對象的操作和其他操作都在此方法中

3、讓調用者一方認爲就是調用此方法獲得的對象

其真正邏輯基本爲以下代碼

// 接口
public Interface A(){
   String printMessage();
}

// 提供給調用者一方的暴露的代理類
public class Aproxy implements A {
 ArealImpl aRealImpl;
 public String printMessage(){
    if(時間沒到){
       // 其他操作
    }else{ 
      aRealImpl = new ArealImpl();
      return aRealImpl.printMessage();
    }
   return "什麼都沒拿到";
  }
}

// 真正實現取出對象的實現類
public class ArealImpl implements A {
  public String printMessage(){
     return "取出對象";
  }
}

虛擬代理看着是不是和JDK的動態代理很像?

建議:閱讀HeadFirst中關於代理模式的章節,反覆仔細閱讀,直至完全理解。

瞭解了代理模式的產生背景之後,使用JDK的動態代理

JDK動態代理詳解:

JDK動態代理有兩種用法,基於某個接口的實現類和基於接口

1、基於接口實現類(Impl)

public interface ProxyService {

    String print(String message);
}
public class ProxyServiceImpl implements ProxyService {
    @Override
    public String print(String message) {
        return message;
    }

 實現自定義的Handler

public class CommonProxyHandler implements InvocationHandler {

    private Object target;

    public CommonProxyHandler(Object tarject) {
        this.target = tarject;
    }

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

接下來使用

@SpringBootTest
public class CommonProxyTest {

    @Test
    public void testCommonProxy(){
        // 利用java的多態,接收其實例化的實例ProxyServiceImpl
        ProxyService proxyService = new ProxyServiceImpl();
        // handler中傳入目標接口的實例化的對象ProxyServiceImpl
        CommonProxyHandler commonProxyHandler = new CommonProxyHandler(proxyService);
        // 第一個參數:類加載器
        // 第二個參數:被代理類要實現的目標接口
        // 第三個參數:handler的對象
        // 返回:實現 該接口 的代理類
        ProxyService proxyInstance = (ProxyService) Proxy.newProxyInstance(ProxyServiceImpl.class.getClassLoader(),
                proxyService.getClass().getInterfaces(), commonProxyHandler);
        String s = proxyInstance.print("我是一個小代理");
        System.out.println(s);
    }
}

 上面的例子,是JDK代理的大多數入門例子(基本類似)

基於上面的例子能否想到幾個問題

1、生成的代理類的類型是什麼?是ProxyService?還是ProxyServiceImpl?

2、自定義的CommonProxyHandler爲什麼要傳入ProxyServiceImpl?傳入ProxyService(接口類型的可不可以)?

3、Proxy.newProxyInstance傳入的類加載器、接口數組、和自定義的handler作用是什麼?

4、invoke中只執行method.invoke嗎?

從第三個問題開始說,要知道爲什麼傳這幾個參數,就要看看newProxyInstance是如何實現的

兩大步:創建代理類和創建代理類的對象

核心過程

首先JDK會現在緩存中查找,是否存在目標接口的實現類,如果存在則返回,如果不存在則創建,debug進入getProxyClass0(我們傳的類加載器,我們傳入的接口數組),在這裏用到了。

調用factory.get()方法 

 接着調用factory.get()方法中的ProxyClassFactory.apply()方法生成代理類的class文件、並加載進JVM內

生成代理類

generateProxyClass方法是生成代理類的class文件

到這裏,getPrxyClass0就執行完畢,利用我們傳入的接口、和classloader,生成了一個實現該接口的類,並加載到JVM中 。

本地使用ProxyGenerator.generateProxyClass方法,看看生成的代理類長什麼樣?

@SpringBootTest
public class GenerateProxyClassTest {

    @Test
    public static void main(String[] args) throws Exception{
        String path = "/Users/jacksparrow414/Downloads/$Proxy1.class";
        ProxyService proxyService = new ProxyServiceImpl();
        CommonProxyHandler commonProxyHandler = new CommonProxyHandler(proxyService,path);
        ProxyService proxyInstance = (ProxyService) Proxy.newProxyInstance(ProxyServiceImpl.class.getClassLoader(), ProxyServiceImpl.class.getInterfaces(), commonProxyHandler);
        byte[] proxies = ProxyGenerator.generateProxyClass("$Proxy1", ProxyServiceImpl.class.getInterfaces());
        FileOutputStream outputStream = null;
        outputStream = new FileOutputStream(path);
        outputStream.write(proxies);
        outputStream.flush();
        outputStream.close();
    }
}

在本地找到生成的$Proxy1的class文件,idea打開

// 新生成的代理類實現了我們傳入的ProxyService接口,它的類型就是一個$Proxy+數字
public final class $Proxy1 extends Proxy implements ProxyService {
    private static Method m1;
    private static Method m2;
    private static Method m0;
    private static Method m3;

// 通過自身的構造器,傳入handler對象來進行創建對象
    public $Proxy1(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 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);
        }
    }

// 實現了接口中的方法
    public final String print(String var1) throws  {
        try {
// 調用代理類的時候,就是調用的自定義的handler對象裏的invoke方法
            return (String)super.h.invoke(this, m3, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }

    static {
        try {
            m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
            m3 = Class.forName("com.example.mybatis.demomybatis.service.ProxyService").getMethod("print", Class.forName("java.lang.String"));
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}

 可以看到,生成的代理類實現了目標接口,因爲接下里我們還要用代理類來執行方法,需要代理類的一個對象,可以看到,在代理類中,通過一個構造器來創建對象,這個構造器的參數就是Invocationhandler類型的對象。

所以這也就解釋了我們爲什麼還要傳一個自定義的handler,因爲代理類創建對象是通過自身的構造器創建對象,構造器的參數就是我們自定義的handler對象。

也可以在上面代理類的方法裏看到,當代理類調用接口裏的方法時,實際上是調用的就是handler對象裏的invoke方法,這也是爲什麼重寫invoke方法的原因

以上第三個問題解決了,第一個問題也解決了(既不是ProxyService類型,又不是ProxyServiceImpl類型)是一個實現了ProxyService接口的$Proxy+數字類型。

第四個問題,可以看到,method.invoke()裏面傳的是proxyServiceImpl的對象,所以會直接執行其對象的方法,如果只有一行method.invoke(),那麼和直接調用proxyServiceImpl對象.方法()沒有任何區別,所以一般我們不像上面那樣寫,因爲那樣寫毫無意義,我們要加一些與業務無關的公共的操作,正確姿勢

public class CommonProxyHandler implements InvocationHandler {

    private Object target;
    public CommonProxyHandler(Object target) {
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        開啓事務管理器
        Object invoke = method.invoke(target, args);
        提交事務/回滾事務
        return invoke;
    }
}

在業務操作前後,加一些所有業務都用到的操作,這就是類似於spring的AOP 

因爲實現了接口,所以根據Java的多態,向下轉型,代理類的對象可以使用對應的接口類型來接收,代理類對象當然也能調用接口的方法。

第二個問題最後說。

接下來是第二種方式

2、基於接口實現(Interface)

public interface SchoolService {

    String getString(String message);
}
public class ProxyHandler<T> implements InvocationHandler {

    private  Class<T> proxyInterface;

    public ProxyHandler(Class<T> proxyInterface) {
        this.proxyInterface = proxyInterface;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) {
        String s = null;
        System.out.println(proxyInterface.getName());
        if ("getString".equals(method.getName())) {
            s = args[0].toString();
            System.out.println(s);
        }
        return s;
    }
}

可以看到,在這個handler裏的沒有method.invoke()方法,上面已經說過,如果要使用這個方法,就要傳入接口的實現類的對象,可是現在除了代理類沒有其他實現該接口的類 ,所以,這裏就不能使用,如果使用會報錯,因爲接口是必須要有實現的。

那麼在這個invoke方法里加了一些操作,這些操作可以簡單的認爲,被當做接口的實現類要做的事情。

 

使用

@SpringBootTest
public class NoImplProxyTest {

    @Test
    public static void main(String[] args) {
        ProxyHandler proxyHandler = new ProxyHandler(SchoolService.class);
        SchoolService proxyInstance = (SchoolService) Proxy
                .newProxyInstance(SchoolService.class.getClassLoader(),
                        new Class[]{SchoolService.class}, proxyHandler);
        proxyInstance.getString("測試接口沒有實現類的代理");
    }
}

還可以測試一下,傳入接口,生成的代理類是什麼樣的

@SpringBootTest
public class GenreateProxyOnlyInterfaceTest {

    @Test
    public static void main(String[] args) throws Exception{
        String path = "/Users/jacksparrow414/Downloads/$Proxy2.class";
        ProxyHandler proxyHandler = new ProxyHandler(SchoolService.class);
        SchoolService proxyInstance = (SchoolService) Proxy
                .newProxyInstance(SchoolService.class.getClassLoader(),
                        new Class[]{SchoolService.class}, proxyHandler);
        byte[] proxies = ProxyGenerator.generateProxyClass("$Proxy2",new Class[]{SchoolService.class});
        FileOutputStream outputStream;
        outputStream = new FileOutputStream(path);
        outputStream.write(proxies);
        outputStream.flush();
        outputStream.close();
    }
}

基於接口生成的代理類

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

import com.example.mybatis.demomybatis.service.SchoolService;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;

public final class $Proxy2 extends Proxy implements SchoolService {
    private static Method m1;
    private static Method m2;
    private static Method m3;
    private static Method m0;

    public $Proxy2(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 String getString(String var1) throws  {
        try {
            return (String)super.h.invoke(this, m3, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }

    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("com.example.mybatis.demomybatis.service.SchoolService").getMethod("getString", Class.forName("java.lang.String"));
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}

可以看到,無論是傳入接口、還是接口的實現類,生成的代理類都是一樣的,都是實現了接口,具體的調用取決於自定義的handler裏的操作

所以第二個問題就是都可以傳,只不過,傳接口的話,invoke方法裏的操作就不能用method.invoke了,就要自己手寫了。

 還有一點很重要,自定義的handler的構造函數可以傳多個,具體要根據實際場景來決定

 

那麼基於接口實現類和基於接口的JDK動態代理使用場景有什麼不同

1、跟人覺得,基於接口實現類的是跟業務操作有很大的關係,是把除了業務操作之外的公共操作行爲抽象出來,在業務操作前後進行,這樣使得公共的操作代碼就一份,卻能在每一個業務操作上都起到作用(例如:spring的AOP,spring的事務註解,日誌的記錄)

利用method.invoke()執行實際業務操作方法

2、而基於接口的是不會和業務操作掛鉤,抽象的是一套規範化的流程,典型的就是mybatis的mapperProxy、feign中的feignInvocationHandler這些經典的框架,它們都是爲了解決一個流程而高度抽象出來的。

最後說明幾點

1、由Java動態產生的代理類,一般名字爲$Proxy數字,debug的時候可以輕易發現。實現InvocationHandler的接口的類,裏面的invoke的方法中method.invoke()是真正執行ServiceImpl的地方,這個地方Java會自動去真正的實現該邏輯的實習類中的方法中執行,在該方法之前或者之後的操作,就是你想要添加額外操作的位置

2、method.invoke()中接收的參數類型,method.invoke(" 要調用的方法的名字所隸屬的對象實體",方法的參數值);而不是接口,

千萬記得,你傳接口根本沒用,並且會報錯,因爲接口是抽象的,你沒辦法讓一個抽象的東西去具體化你的動作,除非你給一個該抽象化的實例

3、method.invoke方法建議使用try-catch,因爲Java有的時候會拋出異常,導致你調用失敗,錯誤信息類似於下面這樣

Method threw 'java.lang.reflect.UndeclaredThrowableException' exception. Cannot evaluate com.sun.proxy.$Proxy數字....

4、當你在method.invoke()方法之前和之後都沒有加自定義的操作,你的Handler中invoke()方法中僅僅有一句method.invoke()方法,那麼此時和你直接調用ServiceImpl(被代理類)沒有任何區別

可以看到,除了第一種遠程代理特別像代理模式之外,其他的所謂的代理模式都不過是在執行真正的業務操作之前、之後進行其他額外操作罷了,但是前輩們也是把這種方式統一歸檔爲代理模式。

一點點小感悟:Spring的AOP是真正把代理模式玩的爐火純青的地步啊、AOP內核就是一個代理模式,由此衍生出的什麼事務管理啊、異步操作啊,都是換湯不換藥。

把複雜的東西簡單化,在把簡單東西用到極致!啥都不說了,NB就完事了。

下一篇說說Mybatis中利用JDK的代理方式是如何用的?

 

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