本文將經過模擬Mybatis動態代理生成Mapper代理類,講解Mybatis原理java
先寫一個簡單的UserMapper,它包含一個全表查詢的方法,代碼以下spring
public interface UserMapper { @Select("select * from user") public List<User> queryAll(); }
而後你們思考一個問題,咱們平時是怎麼使用這個UserMapper的?sql
不少時候咱們會把Mybatis和Spring整合起來一塊兒使用,因而會有相似下面的代碼:數據庫
@Service public class UserServiceImpl { @Autowired private UserMapper userMapper; public List<User> queryAll(){ return this.userMapper.queryAll(); } }
看到這段熟得不能再熟的代碼不知道你們會不會有一絲疑惑:UserMapper明明是一個接口,爲何能夠直接調用他的queryAll方法呢?數組
這個問題其實也不難解,咱們不能直接調用一個接口的方法,這背後確定是有一個對象的,至於這個對象是怎麼來的,這裏直接告訴你們是經過動態代理生成的。只要弄懂了這個動態代理對象是怎麼生成的,整個Mybatis框架原理就就說清楚了。在模擬以前咱們先驗證一下是否是使用JDK的動態代理。session
因爲上面一段代碼整合了spring,spring又爲咱們封裝了許多細節,咱們從新看一段代碼,看看沒有spring的狀況下咱們怎麼得到一個UserMappermybatis
public static void main(String[] args){ SqlSessionFactory sqlSessionFactory = .... //這裏省略,官網給了不少配置SqlSessionFactory的方法(不必定是這麼得到) SqlSession session = sqlSessionFactory.openSession(); UserMapper userMapper = session.getMapper(UserMapper.class); }
爲了驗證得到UserMapper採用的是動態代理,咱們能夠在IDE中對着session.getMapper(UserMapper.class)
一路按着Ctrl點進去,咱們會發現最終調用的代碼是這樣的:app
protected T newInstance(MapperProxy<T> mapperProxy) { return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
最後果真調用的是JDK的動態代理框架
既然知道了原理,下面咱們就動手驗證吧!ide
爲了簡單明瞭,咱們不寫SqlSessionFactory類了,直接自定義一個MySession類,在裏面給出模擬的getMapper方法:
public class MySession { public static Object getMapper(Class clazz){ //調用newProxyInstance須要傳入class數組 Class[] clazzs = new Class[]{clazz}; //把動態代理過程當中生成的代理類保留下來,有助於新手理解動態代理(此行可省略) System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true"); //經過動態代理生成Mapper Object object = Proxy.newProxyInstance(MySession.class.getClassLoader(), clazzs, new MyInvocationHandler()); return object; } }
若是不熟悉JDK的動態代理也不要緊,下面我會逐步分析。
你們能夠看到調用動態代理生成動態代理對象須要三個參數:
爲何須要類加載器?
動態代理生成類和其調用類必須經過同一個類加載器加載,不然它們之間沒法相互調用
爲何有個class數組?
JDK的動態代理是基於接口的,class數組中存放的是動態代理類須要實現的接口。好比本文中的例子生成的動態代理類須要實現UserMapper接口,因此你得把接口告訴它。
爲何會有InvocationHandler實例?
動態代理會在原有方法上實現加強,而加強的邏輯就寫在InvocationHandler類的invoke方法上,因此要有這麼個實例。
你想想,當初的這段代碼
public interface UserMapper { @Select("select * from user") public List<User> queryAll(); }
,咱們想實現一個怎樣的功能?
無非就是給它一條sql語句,但願它能去數據庫中執行這條sql語句並返回結果。這個過程能夠拆分紅兩個部分:
這裏省略JDBC的過程,給出一個簡單的invoke方法示例(Mybatis爲咱們封裝了一切JDBC的處理細節):
public class MyInvocationHandler implements InvocationHandler { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { //解析獲得sql Select annotation = method.getAnnotation(Select.class); String sql = annotation.value()[0]; //執行sql(模擬JDBC) System.out.println(sql + " executing..."); return null; } }
最終咱們執行UserMapper的queryAll()方法時,就會出現以下結果:
public class Main { public static void main(String[] args) { UserMapper userMapper = (UserMapper) MySession.getMapper(UserMapper.class); userMapper.query(); } } //打印: //select * from user executing...
最後咱們總結一下Mybatis框架的核心原理:
經過個核心原理咱們也就知道了Mybatis爲咱們作了什麼: