MyBatis原理(四):MyBatis中代理模式的运用

问题

先来思考几个问题:

  1. 为什么我们在mybatis中只需要写一个dao层接口就可以直接调用方法,返回数据库查询结果呢?

一、JDK动态代理

先说出答案,MyBatis是使用JDK动态代理来设计的:

利用java.lang.reflect.Proxy对象实现,底层用到其实是反射

  • 被代理对象必须实现接口(因为java类是单继承的,动态代理类在被创建后继承了Proxy类,所以目标对象必须实现接口,其实跟静态代理的条件差不多,只不过通过反射,很多东西可以动态执行,不需要手写)
  • static Object newProxyInstance(ClassLoader loader, Class[] interfaces,InvocationHandler h )Proxy类的静态方法,ClassLoader ,指定类加载器,interfaces目标类实现的接口,InvocationHandler 这个类会对目标对象进行拦截,然后代理类使用反射来进行调用。例如下边的代码。

 

// jdk动态代理
public class ProxyFactory{
    //维护一个目标对象
    private Object target;
    public ProxyFactory(Object target){
        this.target=target;
    }
   //给目标对象生成代理对象
    public Object getProxyInstance(){
        return Proxy.newProxyInstance(
                target.getClass().getClassLoader(),
                target.getClass().getInterfaces(),
                new InvocationHandler() {
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        System.out.println("开始事务2");
                        //执行目标对象方法
                        Object returnValue = method.invoke(target, args);
                        System.out.println("提交事务2");
                        return returnValue;
                    }
                }
        );
    }
}

 

 

上一篇我们已经知道了怎么样用SqlSession.selectList()来执行查询了,但是我们在做项目时,用的都是xxxMapper.selectByPriMary()来执行查询的,这个过程mybatis是怎么转换到使用sqlsession呢?

// 获取UserMapper
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);

我们就从getMapper去追源码就可以了。

// 1.可以看到是从configuration获取的
public  T getMapper(Class type) {
    return configuration.getMapper(type, this);
  }
// 2.configuration类的获取方法,mapperRegistry顾名思义是一个注册mapper的对象
public  T getMapper(Class type, SqlSession sqlSession) {
    return mapperRegistry.getMapper(type, sqlSession);
  }
/* 3.mapperRegistry类的获取方法
 knownMappers是一个HashMap,在项目启动时会从配置文件中(配置的包扫描)
 将所有mapper全部放到这个map,key是Mapper的Class对象,value就是Mapper对应的MapperProxyFactory,
一个Mapper对应一个MapperProxyFactory*/
public  T getMapper(Class type, SqlSession sqlSession) {
    final MapperProxyFactory mapperProxyFactory = (MapperProxyFactory) knownMappers.get(type);
    if (mapperProxyFactory == null) {
      throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
    }
    try {
      return mapperProxyFactory.newInstance(sqlSession);
    } catch (Exception e) {
      throw new BindingException("Error getting mapper instance. Cause: " + e, e);
    }
  }

再看MapperProxyFactory对象的newInstance():

public T newInstance(SqlSession sqlSession) {
// MapperProxy对象是jdk代理的newProxyInstance()方法中用到的第三个参数,继承了InvocationHandler,会对mapper方法进行拦截,然后使用反射执行代理方法
    final MapperProxy mapperProxy = new MapperProxy(sqlSession, mapperInterface, methodCache);
// 调用重载方法
    return newInstance(mapperProxy);
  }
protected T newInstance(MapperProxy mapperProxy) {
// 重点来了,看到jdk动态代理所用到的Proxy对象了么
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
  }

再看MapperProxy类:

// 实现了InvocationHandler
public class MapperProxy implements InvocationHandler, Serializable {

  private static final long serialVersionUID = -6424540398559729838L;
  private final SqlSession sqlSession;
  private final Class mapperInterface;
  private final Map methodCache;

  public MapperProxy(SqlSession sqlSession, Class mapperInterface, Map methodCache) {
    this.sqlSession = sqlSession;
    this.mapperInterface = mapperInterface;
    this.methodCache = methodCache;
  }

  @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 如果两者类型相同,那么直接用原对象执行
    if (Object.class.equals(method.getDeclaringClass())) {
      try {
// 反射执行方法
        return method.invoke(this, args);
      } catch (Throwable t) {
        throw ExceptionUtil.unwrapThrowable(t);
      }
    }
// 甚至对执行过的方法进行了缓存,下次就不用再创建新的了
    final MapperMethod mapperMethod = cachedMapperMethod(method);
//  最后在execute()里边开始使用sqlSession调用查询、插入、删除等等了。
    return mapperMethod.execute(sqlSession, args);
  }

  private MapperMethod cachedMapperMethod(Method method) {
    MapperMethod mapperMethod = methodCache.get(method);
    if (mapperMethod == null) {
      mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration());
      methodCache.put(method, mapperMethod);
    }
    return mapperMethod;
  }

到此就追完了,但是我在写的过程中发现一个问题:

说好的JDK动态代理,目标类需要实现一个接口呢,为什么MyBatis的mapper没有实现呢,mapper本身就是接口还实现个啥。自己就是顶级接口,所以就不需要实现了,哈哈哈

你可能感兴趣的:(MyBatis,mybatis)