Mybatis源碼剖析 -- Mapper代理方式

一、回顧 Mapper 代理寫法

思考?個問題,通常的Mapper接口我們都沒有實現(xiàn)的方法卻可以使用蔼水,是為什么呢?
答案很簡單:動態(tài)代理
開始之前介紹?下 MyBatis 初始化時對接口的處理:MapperRegistry 是 Configuration 中的?個屬性录肯,它內部維護?個 HashMap 用于存放 mapper 接口的工廠類趴腋,每個接口對應?個工廠類

/**
 * mapper代理方式
 */
@Test
public void test2() throws IOException {
    InputStream inputStream = Resources.getResourceAsStream("sqlMapConfig.xml");
    SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(inputStream);
    SqlSession sqlSession = factory.openSession();
    // 使用JDK動態(tài)代理對mapper接口產生代理對象
    IUserMapper mapper = sqlSession.getMapper(IUserMapper.class);
    //代理對象調用接口中的任意方法,執(zhí)行的都是動態(tài)代理中的invoke方法
    List<User> all = mapper.findAll();
    for (User user : all) {
        System.out.println(user);
    }
}

mappers 標簽中可以配置接口的包路徑嘁信,或者某個具體的接口類

<!--引入映射配置文件-->
<mappers>
    <package name="com.wujun.mapper"/>
</mappers>

二于样、初始化 Mapper

  1. 之前在說 Mybatis源碼剖析 -- 初始化過程(傳統(tǒng)方式)的時候,提到過 XMLConfigBuilder.parseConfiguration(XNode root)這個方法里面會對xml進行解析潘靖,那么 mappers 的解析也在里面
    // 解析 <mappers /> 標簽
    mapperElement(root.evalNode("mappers"));
    
    private void mapperElement(XNode parent) throws Exception {
            if (parent != null) {
                // 遍歷子節(jié)點
                for (XNode child : parent.getChildren()) {
                    // 如果是 package 標簽穿剖,則掃描該包
                    if ("package".equals(child.getName())) {
                        // 獲取 <package> 節(jié)點中的 name 屬性
                        String mapperPackage = child.getStringAttribute("name");
                        // 從指定包中查找 mapper 接口,并根據 mapper 接口解析映射配置
                        configuration.addMappers(mapperPackage);
                    // 如果是 mapper 標簽卦溢,
                    } else {
                        // 獲得 resource糊余、url、class 屬性
                        String resource = child.getStringAttribute("resource");
                        String url = child.getStringAttribute("url");
                        String mapperClass = child.getStringAttribute("class");
    
                        // resource 不為空单寂,且其他兩者為空贬芥,則從指定路徑中加載配置
                        if (resource != null && url == null && mapperClass == null) {
                            ErrorContext.instance().resource(resource);
                            // 獲得 resource 的 InputStream 對象
                            InputStream inputStream = Resources.getResourceAsStream(resource);
                            // 創(chuàng)建 XMLMapperBuilder 對象
                            XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
                            // 執(zhí)行解析
                            mapperParser.parse();
                            // url 不為空,且其他兩者為空宣决,則通過 url 加載配置
                        } else if (resource == null && url != null && mapperClass == null) {
                            ErrorContext.instance().resource(url);
                            // 獲得 url 的 InputStream 對象
                            InputStream inputStream = Resources.getUrlAsStream(url);
                            // 創(chuàng)建 XMLMapperBuilder 對象
                            XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
                            // 執(zhí)行解析
                            mapperParser.parse();
                            // mapperClass 不為空蘸劈,且其他兩者為空,則通過 mapperClass 解析映射配置
                        } else if (resource == null && url == null && mapperClass != null) {
                            // 獲得 Mapper 接口
                            Class<?> mapperInterface = Resources.classForName(mapperClass);
                            // 添加到 configuration 中
                            configuration.addMapper(mapperInterface);
                            // 以上條件不滿足尊沸,則拋出異常
                        } else {
                            throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
                        }
                    }
                }
            }
        }
    
  2. 威沫,調用configuration.addMapper(mapperPackage)方法將包名或者接口傳入 mapper 注冊表贤惯,存入一個名叫 knownMappers 的 HashMap 中,key就是接口的類型棒掠,value就是針對這個接口所生成的代理對象
    /**
     * Mapper 注冊表
     *
     * @author Clinton Begin
     * @author Eduardo Macarron
     * @author Lasse Voss
     */
    public class MapperRegistry {
    
        /**
         * MyBatis Configuration 對象
         */
        private final Configuration config;
        /**
         * MapperProxyFactory 的映射
         *
         * KEY:Mapper 接口
         */
        //這個類中維護一個HashMap存放MapperProxyFactory
        private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<>();
    
  3. 接著看getMapper()的源碼
    public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
        // 獲得 MapperProxyFactory 對象
        final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
        // 不存在孵构,則拋出 BindingException 異常
        if (mapperProxyFactory == null) {
            throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
        }
        /// 通過動態(tài)代理工廠生成實例。
        try {
            return mapperProxyFactory.newInstance(sqlSession);
        } catch (Exception e) {
            throw new BindingException("Error getting mapper instance. Cause: " + e, e);
        }
    }
    
    public T newInstance(SqlSession sqlSession) {
        // 創(chuàng)建了JDK動態(tài)代理的invocationHandler接口的實現(xiàn)類mapperProxy
        final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
        // 調用了重載方法
        return newInstance(mapperProxy);
    }
    
    protected T newInstance(MapperProxy<T> mapperProxy) {
        return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[]{mapperInterface}, mapperProxy);
    }
    
  4. invoke()方法源碼烟很,其實本質上還是調用了 sqlSession 里面的增刪改查方法
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        try {
            // 如果是 Object 定義的方法颈墅,直接調用
            if (Object.class.equals(method.getDeclaringClass())) {
                return method.invoke(this, args);
    
            } else if (isDefaultMethod(method)) {
                return invokeDefaultMethod(proxy, method, args);
            }
        } catch (Throwable t) {
            throw ExceptionUtil.unwrapThrowable(t);
        }
        // 獲得 MapperMethod 對象
        final MapperMethod mapperMethod = cachedMapperMethod(method);
        // 重點在這:MapperMethod最終調用了執(zhí)行的方法
        return mapperMethod.execute(sqlSession, args);
    }
    
    public Object execute(SqlSession sqlSession, Object[] args) {
        Object result;
        //判斷mapper中的方法類型,最終調用的還是SqlSession中的方法
        switch (command.getType()) {
            case INSERT: {
                // 轉換參數
                Object param = method.convertArgsToSqlCommandParam(args);
                // 執(zhí)行 INSERT 操作
                // 轉換 rowCount
                result = rowCountResult(sqlSession.insert(command.getName(), param));
                break;
            }
            case UPDATE: {
                // 轉換參數
                Object param = method.convertArgsToSqlCommandParam(args);
                // 轉換 rowCount
                result = rowCountResult(sqlSession.update(command.getName(), param));
                break;
            }
            case DELETE: {
                // 轉換參數
                Object param = method.convertArgsToSqlCommandParam(args);
                // 轉換 rowCount
                result = rowCountResult(sqlSession.delete(command.getName(), param));
                break;
            }
            case SELECT:
                // 無返回雾袱,并且有 ResultHandler 方法參數恤筛,則將查詢的結果,提交給 ResultHandler 進行處理
                if (method.returnsVoid() && method.hasResultHandler()) {
                    executeWithResultHandler(sqlSession, args);
                    result = null;
                // 執(zhí)行查詢谜酒,返回列表
                } else if (method.returnsMany()) {
                    result = executeForMany(sqlSession, args);
                // 執(zhí)行查詢叹俏,返回 Map
                } else if (method.returnsMap()) {
                    result = executeForMap(sqlSession, args);
                // 執(zhí)行查詢,返回 Cursor
                } else if (method.returnsCursor()) {
                    result = executeForCursor(sqlSession, args);
                // 執(zhí)行查詢僻族,返回單個對象
                } else {
                    // 轉換參數
                    Object param = method.convertArgsToSqlCommandParam(args);
                    // 查詢單條
                    result = sqlSession.selectOne(command.getName(), param);
                    if (method.returnsOptional() &&
                            (result == null || !method.getReturnType().equals(result.getClass()))) {
                        result = Optional.ofNullable(result);
                    }
                }
                break;
            case FLUSH:
                result = sqlSession.flushStatements();
                break;
            default:
                throw new BindingException("Unknown execution method for: " + command.getName());
        }
        // 返回結果為 null ,并且返回類型為基本類型屡谐,則拋出 BindingException 異常
        if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
            throw new BindingException("Mapper method '" + command.getName()
                    + " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
        }
        // 返回結果
        return result;
    }
    
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末述么,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子愕掏,更是在濱河造成了極大的恐慌度秘,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,602評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件饵撑,死亡現(xiàn)場離奇詭異剑梳,居然都是意外死亡,警方通過查閱死者的電腦和手機滑潘,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,442評論 2 382
  • 文/潘曉璐 我一進店門垢乙,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人语卤,你說我怎么就攤上這事追逮。” “怎么了粹舵?”我有些...
    開封第一講書人閱讀 152,878評論 0 344
  • 文/不壞的土叔 我叫張陵钮孵,是天一觀的道長。 經常有香客問我眼滤,道長巴席,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,306評論 1 279
  • 正文 為了忘掉前任诅需,我火速辦了婚禮漾唉,結果婚禮上睬关,老公的妹妹穿的比我還像新娘。我一直安慰自己毡证,他們只是感情好电爹,可當我...
    茶點故事閱讀 64,330評論 5 373
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著料睛,像睡著了一般丐箩。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上恤煞,一...
    開封第一講書人閱讀 49,071評論 1 285
  • 那天屎勘,我揣著相機與錄音,去河邊找鬼居扒。 笑死概漱,一個胖子當著我的面吹牛,可吹牛的內容都是我干的喜喂。 我是一名探鬼主播瓤摧,決...
    沈念sama閱讀 38,382評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼玉吁!你這毒婦竟也來了照弥?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 37,006評論 0 259
  • 序言:老撾萬榮一對情侶失蹤进副,失蹤者是張志新(化名)和其女友劉穎这揣,沒想到半個月后,有當地人在樹林里發(fā)現(xiàn)了一具尸體影斑,經...
    沈念sama閱讀 43,512評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡给赞,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 35,965評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了矫户。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片片迅。...
    茶點故事閱讀 38,094評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖吏垮,靈堂內的尸體忽然破棺而出障涯,到底是詐尸還是另有隱情,我是刑警寧澤膳汪,帶...
    沈念sama閱讀 33,732評論 4 323
  • 正文 年R本政府宣布唯蝶,位于F島的核電站,受9級特大地震影響遗嗽,放射性物質發(fā)生泄漏粘我。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,283評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望征字。 院中可真熱鬧都弹,春花似錦、人聲如沸匙姜。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,286評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽氮昧。三九已至框杜,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間袖肥,已是汗流浹背咪辱。 一陣腳步聲響...
    開封第一講書人閱讀 31,512評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留椎组,地道東北人油狂。 一個月前我還...
    沈念sama閱讀 45,536評論 2 354
  • 正文 我出身青樓,卻偏偏與公主長得像寸癌,于是被迫代替她去往敵國和親专筷。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 42,828評論 2 345

推薦閱讀更多精彩內容