MyBatis学习——动态代理(源码)

1,动态代理

MyBatis使用Proxy动态代理实现

2,MyBatis怎么实现动态代理的呢?

我们获取到SQLSession后,会调用getMapper()方法来返回对象实例,那么这块到底是干了什么?

我们跟进源码看一看:

DefaultSqlSession类:

可以看到它是调用了configuration类的getMapper方法。configuration类主要是存放了所有有关的配置信息

它又调用了mapperRegistry的getMapper()方法,我们现在还不知道mapperRegistry这是干什么的,现在我们继续跟进

knownMappers是一个字典类型。从Key的类型上我们可以判断出来是一个类一个动态代理工厂。

可以看到这个方法刚开始就是从这个集合中获取mapper,那么我们什么时候将mapper注册进去的呢?

在解析xml文件的时候就已经处理了<mappers>标签。使用XMLMapperBuilder类解析的时候有一个方法parse()进行处理。会将所有的mapper注册到MapperRegistry里面。代码逻辑如下。实现方式很简单就是将mapper标签里面的namespace属性添加到集合里面

 public void parse() {
        if (!configuration.isResourceLoaded(resource)) {
            //<mapper namespace="org.apache.ibatis.domain.blog.mappers.AuthorMapper"></mapper>
            //解析mapper节点
            configurationElement(parser.evalNode("/mapper"));
            configuration.addLoadedResource(resource);
            //这里就是绑定mapper和class的地方
            bindMapperForNamespace();
        }

        parsePendingResultMaps();
        parsePendingChacheRefs();
        parsePendingStatements();
    }
    private void bindMapperForNamespace() {
        String namespace = builderAssistant.getCurrentNamespace();
        if (namespace != null) {
            Class<?> boundType = null;
            try {
                boundType = Resources.classForName(namespace);
            } catch (ClassNotFoundException e) {
                //ignore, bound type is not required
            }
            if (boundType != null) {
                if (!configuration.hasMapper(boundType)) {
                    // Spring may not know the real resource name so we set a flag
                    // to prevent loading again this resource from the mapper interface
                    // look at MapperAnnotationBuilder#loadXmlResource
                    configuration.addLoadedResource("namespace:" + namespace);
                    configuration.addMapper(boundType);
                }
            }
        }
    }    

通过前面注册了class的全路径,使用mapperProxyFactory来创建mapper接口实现类。这里的mapperProxyFactory会为每一个mapper都对应一个mapperProxyFactory。因为configuration.addMapper(boundType);里面使用map接口为每一个mapper都创建了一个工厂

向knownMappers这个集合添加了这个Mapper

我们再回到getMapper()方法:

进入这个newInstance方法

可以看到它创建了一个mapper ProXy对象,看到这个是不是就能想到我们的动态代理那个继承Invocation Handler类的那个XXXProXy类是吧。

调用它 的invoke方法

上面代码中可以看到如果是执行Object类的方式那么直接调用method.invoke。如果不是Object的方法,那么执行的是MapperMethod方法。我们知道一个接口没有实现是不能够被实例化的,并且我们在写接口时,确实没有给任何实现,那么Mybatis是怎么帮我们做事的呢?就是上面这段代码。首先通过代理类生成代理对象。当执行接口中的方法时,都是调用这个invoke方法,当你不是调用Object下面的方法,那么统一都执行MapperMethod方法来执行。

从缓存中获得执行方法对应的MapperMethod类实例。如果MapperMethod类实例不存在的情况,创建加入缓存并返回相关的实例。最后调用MapperMethod类的execute方法。

 

mapperMethod持有接口名,方法名,configuration。而接口名对应mapper标签的namespace;方法名对应<select id="method">里面的id。configuration持有所有配置信息例如TypeHandler,TypeAlias等等。然后调用execute()方法执行

这个类有两个成员变量:1,SqlCommand  2,MethodSignature

我们都会知道节点上有id属性值。那么MyBatis框架会把每一个节点(如:select节点、delete节点)生成一个MappedStatement类。要找到MappedStatement类就必须通过id来获得。有一个细节要注意:代码用到的id = 当前接口类 + XML文件的节点的ID属性。

我们现在查看一下mapper.execute()方法

public Object execute(SqlSession sqlSession, Object[] args) {
        Object param;
        Object result;
        if (SqlCommandType.INSERT == this.command.getType()) {
            param = this.method.convertArgsToSqlCommandParam(args);
            result = this.rowCountResult(sqlSession.insert(this.command.getName(), param));
        } else if (SqlCommandType.UPDATE == this.command.getType()) {
            param = this.method.convertArgsToSqlCommandParam(args);
            result = this.rowCountResult(sqlSession.update(this.command.getName(), param));
        } else if (SqlCommandType.DELETE == this.command.getType()) {
            param = this.method.convertArgsToSqlCommandParam(args);
            result = this.rowCountResult(sqlSession.delete(this.command.getName(), param));
        } else {
            if (SqlCommandType.SELECT != this.command.getType()) {
                throw new BindingException("Unknown execution method for: " + this.command.getName());
            }

            if (this.method.returnsVoid() && this.method.hasResultHandler()) {
                this.executeWithResultHandler(sqlSession, args);
                result = null;
            } else if (this.method.returnsMany()) {
                result = this.executeForMany(sqlSession, args);
            } else if (this.method.returnsMap()) {
                result = this.executeForMap(sqlSession, args);
            } else {
                param = this.method.convertArgsToSqlCommandParam(args);
                result = sqlSession.selectOne(this.command.getName(), param);
            }
        }

        if (result == null && this.method.getReturnType().isPrimitive() && !this.method.returnsVoid()) {
            throw new BindingException("Mapper method '" + this.command.getName() + " attempted to return null from a method with a primitive return type (" + this.method.getReturnType() + ").");
        } else {
            return result;
        }
    }

从上面的代码可以看出所有的所有的mapper对象实际都是MapperMethod对象,然后MapperMethod持有方法名『全类名.方法名』还有执行的操作『select;update;insert;delete』还有sql的各种参数方法签名。然后执行sql语句

跟进去每个Sqlsession.方法()例如update方法:

发现最终调用的是executor执行器对应的方法。

跟进去这个方法

最终就是调用statement的执行方法。执行了sql语句。

 

主要就是这几个点:

1. Mapper 接口在初始SqlSessionFactory 注册的。

2. Mapper 接口注册在了名为 MapperRegistry 类的 HashMap中, key = Mapper class value = 创建当前Mapper的工厂。

3. Mapper 注册之后,可以从SqlSession中get

4. SqlSession.getMapper 运用了 JDK动态代理,产生了目标Mapper接口的代理对象。

5. 动态代理的 代理类是 MapperProxy ,这里边最终完成了增删改查方法的调用。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值