mybatis运行原理03-mapper代理对象的获取

上一篇文章中讲述了DefaultSqlSession的创建过程。它可以利用executor来完成crud操作、管理数据库连接和事务,也可以根据mapper类型来获取mapper代理对象。sql的执行放到后面讲,本文先记录一下mapper代理对象是如何生成的

    MemberDao mapper = sqlSession.getMapper(MemberDao.class);

当我们写下getMapper(mapper.class)的时候,框架在后台做了些什么事?实际上,sqlSession本身是对mapper无感知的,所有关于mapper的信息,都在configuration属性中。所以getMapper先交给了configuration来完成。

    public <T> T getMapper(Class<T> type) {
        return this.configuration.getMapper(type, this);
    }

在第一篇文章中说过,对mapper的解析完成后,以type:MapperProxyFactory(type)的形式将mapper的类型和产生对应代理对象的工厂存放到了mapperRegistry中的knowMapppers内,并将mapper的parameterMap、resultMap、statement等属性以namespace: val的形式存到了对应的HashMap里。然后在这里configuration.getMapper就将工作交给了mapperRegistry.同时,为了完成mapper与sqlSession的绑定,还将sqlSession作为参数传递了进来。

    //委派给mapperRegistry完成
    public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
        return this.mapperRegistry.getMapper(type, sqlSession);
    }

看看mapperRegistry是如何获取到mapper代理对象的吧。

// mapperRegistry.getMapper(...)
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
        MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory)this.knownMappers.get(type);
        if (mapperProxyFactory == null) {
            throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
        } else {
            try {
                return mapperProxyFactory.newInstance(sqlSession);
            } catch (Exception var5) {
                throw new BindingException("Error getting mapper instance. Cause: " + var5, var5);
            }
        }
    }

实际上,mapperRegistry并不承担创建代理对象的职责,它的责任就是完成mapper的注册。创建代理对象交给mapperProxyFactory来完成。mapperProxyFactory内部mapperInterface用于封装mapper的类型对象,methodCache则存放方法缓存。

public class MapperProxyFactory<T> {
    private final Class<T> mapperInterface;
    private final Map<Method, MapperMethodInvoker> methodCache = new ConcurrentHashMap();
    // ······
    protected T newInstance(MapperProxy<T> mapperProxy) {
        return Proxy.newProxyInstance(this.mapperInterface.getClassLoader(), new Class[]{this.mapperInterface}, mapperProxy);
    }

    public T newInstance(SqlSession sqlSession) {
        MapperProxy<T> mapperProxy = new MapperProxy(sqlSession, this.mapperInterface, this.methodCache);
        return this.newInstance(mapperProxy);
    }

重点关注代理对象的创建,在这里首先调用的是 newInstance(SqlSession sqlSession)方法,然后调newInstance(MapperProxy<T> mapperProxy),最后由Proxy.newProxyInstance()生成代理对象。至于这中间做了什么,我们想想动态代理Proxy.newProxyInstance(...)是如何创建代理对象的吧。

public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces,  InvocationHandler h)  throws IllegalArgumentException

Proxy.newProxyInstance(loader, interfaces, ih)需要三个参数: 被代理对象的类加载器loader、被代理对象的接口类型数组Class<?> [] interfaces、定义了方法调用逻辑的InvocationHandler h.

先思考一下:假设现在我们有一个类对象,需要创建一个能够对它的方法进行增强的代理实例对象,该对象进行方法调用的逻辑在invocationHandler中,该如何创建这个代理对象呢?
咱也不知道哇,咱只知道动态代理...这里的invocationHandler不就有方法的实现逻辑吗?我们只需要一个对象,把该对象方法的调用关联给这个invocationHandler去做不就行了吗?
//todo : 动态代理ref:https://zhuanlan.zhihu.com/p/60805342

//invocationHandler.invoke(proxy, method, args)
public Object invoke(Object proxy, Method method, Object[] args)
        throws Throwable;
}

所以Proxy类就实现了这么一个功能:动态创建一个代理对象proxyObj,将对象与InvocationHandler ih关联。然后当我们在程序中使用proxyObj.method(args)的时候,实际上就交给了ih.invoke(proxyObj, method, args)去处理。

我们现在有了前两个参数,但是当方法被调用时怎么进行处理的逻辑还没有啊。因此需要先创建一个InvocationHandler对象来,这个对象就是mapperProxy.他的构造方法很简单,就是将sqlSession, 接口和方法缓存关联进来。没什么好讲的

public class MapperProxy<T> implements InvocationHandler, Serializable {
    private static final long serialVersionUID = -4724728412955527868L;
    private static final int ALLOWED_MODES = 15;
    private static final Constructor<Lookup> lookupConstructor;
    private static final Method privateLookupInMethod;
    private final SqlSession sqlSession;
    private final Class<T> mapperInterface;
    private final Map<Method, MapperProxy.MapperMethodInvoker> methodCache;

    public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperProxy.MapperMethodInvoker> methodCache) {
        this.sqlSession = sqlSession;
        this.mapperInterface = mapperInterface;
        this.methodCache = methodCache;
    }

    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        try {
            return Object.class.equals(method.getDeclaringClass()) ? method.invoke(this, args) : this.cachedInvoker(method).invoke(proxy, method, args, this.sqlSession);
        } catch (Throwable var5) {
            throw ExceptionUtil.unwrapThrowable(var5);
        }
    }
  //......
}

在newInstance(SqlSession sqlSession)时创建了mapperProxy,然后再使用Proxy.newInstance(interface.getClassLoader, new Class[]{interface}, mapperProxy)创建了一个对象,并将该对象与mapperProxy进行了关联,最后返回代理对象。如下。

public class MapperProxyFactory<T> {
    // ······
    protected T newInstance(MapperProxy<T> mapperProxy) {
        return Proxy.newProxyInstance(this.mapperInterface.getClassLoader(), new Class[]{this.mapperInterface}, mapperProxy);
    }

    public T newInstance(SqlSession sqlSession) {
        MapperProxy<T> mapperProxy = new MapperProxy(sqlSession, this.mapperInterface, this.methodCache);
        return this.newInstance(mapperProxy);
    }

至此,xxxMapper = getMapper(xxxMapper.class)就结束了。我们拿到了代理对象,接下来的问题就是如何使用这个代理对象了,也就是当我们调用xxxMapper .xxxMethod()时,框架在后台做了些什么。
to be continued.

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,830评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,992评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,875评论 0 331
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,837评论 1 271
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,734评论 5 360
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,091评论 1 277
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,550评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,217评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,368评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,298评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,350评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,027评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,623评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,706评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,940评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,349评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,936评论 2 341