mybatis之動態代理的應用
在前文(https://www.cnblogs.com/NYfor2018/p/9093472.html)我們知道了,Mybatis的使用需要用到Mapper映射文件,一個是映射接口,另一個是映射XML文件(此處不詳談映射文件XML),在應用中我們可以感覺到,映射接口似乎對接着XML文件中的實現命令,可是我們在運行程序是時候調用的往往是Mapper接口,而不是一個包含邏輯的實現類。很顯然Mapper產生了代理類。
首先,什么是代理模式?代理模式的定義:為其他對象提供一種代理以控制對這個對象的訪問。在某些情況下,一個對象不適合或者不能直接引用另一個對象,而代理對象可以在客戶端和目標對象之間起到中介的作用。(取自百度百科:https://baike.baidu.com/item/%E4%BB%A3%E7%90%86%E6%A8%A1%E5%BC%8F/8374046?fr=aladdin )
舉個栗子,如圖:
我們租房的時候一般是去找中介,而不是直接去找包租婆。放在代理模式這里來說,就是,我們在訪問真實的對象的時候,往往不是直接去訪問真實對象,而是通過代理對象,來對真實對象進行訪問。
為什么要使用代理模式?通過代理,一方面可以控制如何訪問真正的服務對象,提供額外的服務。另外一方面有機會通過重寫一些類來滿足特定的需要。就像是,有時候租客反映的一些問題,中介可以直接解決而不需要麻煩到包租婆。
一般來說,動態代理分為兩種:一種是JDK反射機制提供的代理;另一種是CGLB代理。在JDK提供的代理,我們必須要提供接口;而在CGLIB中則不需要提供接口。
在學習動態代理之前,先了解一下反射的基礎。
反射簡單應用
import java.lang.reflect.Method; public class ReflectService { public void sayHello(String name) { System.out.println("hello"+name); } public static void main(String[] args) throws Exception, IllegalAccessException, ClassNotFoundException { //通過反射創建ReflectService對象 Object service = Class.forName(ReflectService.class.getName()).newInstance(); //獲取服務方法 Method method = service.getClass().getMethod("sayHello", String.class); //反射調用方法 method.invoke(service, "zhangsan"); } }
① 先根據類名,來創建實例對象,所以就找了ReflectService類的類名。
② 然后再根據實例對象來找回它的方法,參數是方法名及其參數。
③ 利用反射機制來調用ReflectService類的sayHello方法。
JDK動態代理
JDK的動態代理,是由JDK的java.lang.reflect.*包提供支持的,我們需要完成以下步驟:
① 編寫服務類和接口,服務類是真正的服務提供者,而JDK代理中接口是必要的。
② 編寫代理類,提供綁定和代理方法。
首先,先編寫動態代理的接口:
public interface HelloService { public void sayHello(String name); }
接着,寫一個這個接口的實現類:
public class HelloServiceImpl implements HelloService{ @Override public void sayHello(String name) { System.out.println("hello "+name); } }
然后,寫一個代理類,提供真實對象的綁定和代理方法。代理類的要求是實現InvocationHandler接口的代理方法,當一個對象被綁定后,執行其方法的時候就會進入到代理方法里。
import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; public class HelloServiceProxy implements InvocationHandler{ /** * 真實的服務對象 */ private Object target; /** * * @param target * @return 綁定委托對象並返回一個代理類 */ public Object bind(Object target) { this.target = target; //取得代理對象 //public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)throws IllegalArgumentException //loader服務對象的類加載器,interfaces是加載服務對象的接口,h是執行這個代理類的方法的執行者 return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this); } @Override /** * 通過代理對象調用方法首先進入這個方法 * invoke方法的參數分別是:proxy是代理對象,method是被調用的方法,args是方法的參數 */ public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.err.println("#########我是JDK動態代理##########"); Object result = null; //反射方法前調用 System.err.println("我准備說hello"); result = method.invoke(target, args); //反射方法后調用 System.err.println("我說過hello了"); return result; } }
最后,編寫一個測試類:
public class HelloServiceMain { public static void main(String[] args) { HelloServiceProxy HelloHandler = new HelloServiceProxy(); HelloService proxy = (HelloService)HelloHandler.bind(new HelloServiceImpl()); proxy.sayHello("張三"); } }
運行出來的結果是:
整個過程可以這樣理解:
先創建一個代理類對象a,然后用代理類對象綁定真正提供服務對象b,返回一個接口b+,再用這個接口b+來進行服務。
如果我們把proxy.sayHello(“張三”);注釋掉:
我們會發現控制台什么東西都沒有輸出。
所以我們可以知道,代理類的invoke方法,只有在代理的真正提供服務的對象被調用的時候,才會起作用。
所以這時候我們可以這樣理解:
挑選的人相當於訪問者,相親交流平台相當於代理,被挑選的人相當於真正提供服務的對象。當挑選的人在向相親交流平台要求得到被挑選的人的信息,實際上,提供信息的人不是平台,是被挑選的人。挑選的人在對被挑選的人打招呼,而不是對相親交流平台打招呼。而且,相親交流平台不只是為一個獨特的挑選的人提供被挑選的人的信息,只要有忍看上被挑選的人,相親交流平台就可以提供ta的信息。
CGLIB動態代理
此處僅突出CGLIB動態代理跟JDK動態代理在代理類上的區別:
import java.lang.reflect.Method; import org.springframework.cglib.proxy.Enhancer; import org.springframework.cglib.proxy.MethodInterceptor; import org.springframework.cglib.proxy.MethodProxy; public class HelloServiceCgLib implements MethodInterceptor{ private Object target; public Object getInstance(Object target) { this.target = target; Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(this.target.getClass()); //回調方法 enhancer.setCallback(this); return enhancer.create(); } @Override public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { System.err.println("##########我是CGLIB的動態代理##########"); //反射方法前調用 System.err.println("我准備說hello"); Object returnObj = proxy.invokeSuper(obj, args); //反射方法后調用 System.err.println("我說過hello了"); return returnObj; } }
可以看到CGLIB的代理類是實現MethodInterceptor的代理方法。在mybatis中通常在延遲加載的時候才會用到CGLIB的動態代理。