Mybatis学习(二)——动态代理源码分析


Mybatis 之所以不用我们自己实现接口的实例,根本原因就在于它采用了反射机制以及动态代理来代理我们的接口。

一、什么是动态代理

代理是一种模式,提供了对目标对象的间接访问方式,即通过代理访问目标对象。如此便于在目标实现的基础上增加额外的功能操作,前拦截,后拦截等,以满足自身的业务需求,同时代理模式便于扩展目标对象功能的特点也为多人所用。

JDK 本身提供的动态代理只能代理接口。java 中的动态代理位于java.lang.reflect 包下,主要涉及两个类:
1. Interface InvocationHandler
该接口中仅定义了一个方法
public object invoke(Object obj,Method method, Object[] args)。这个抽象方法在代理类中动态实现。

参数 功能
Object obj 被代理的对象
Method method 是被代理的方法
Object[] args 为该方法的参数数组

2.Proxy
该类即为动态代理类,其中主要包含以下内容:
protected Proxy(InvocationHandler h)
构造函数,用于给内部的h赋值。
static Class getProxyClass (ClassLoaderloader, Class[] interfaces)
获得一个代理类,其中loader是类装载器,interfaces是真实类所拥有的全部接口的数组。
static Object newProxyInstance(ClassLoad load,Class[] interfaces, InvocationHandler h)
返回代理类的一个实例,返回后的代理类可以当作被代理类使用(可使用被代理类的在Subject接口中声明过的方法)

实现步骤
1、通过实现 InvocationHandler 接口创建自己的调用处理器;
2、通过为 Proxy 类指定 ClassLoader 对象和一组 interface 来创建动态代理类;
3、通过反射机制获得动态代理类的构造函数,其唯一参数类型是调用处理器接口类型;
4、通过构造函数创建动态代理类实例,构造时调用处理器对象作为参数被传入。

public class Test{
interface Subject {
public void sayHi();
public void sayHello();
}

static class SubjectImpl implements Subject{

public void sayHi() {
System.out.println("hi");
}

public void sayHello() {
System.out.println("hello");
}
}
static class ProxyInvocationHandler implements InvocationHandler {
private Subject target;
public ProxyInvocationHandler(Subject target) {
this.target=target;
}

// @Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.print("say:");
return method.invoke(target, args);
}
}

public static void main(String[] args) {
Subject subject=new SubjectImpl();
Subject subjectProxy=(Subject) Proxy.newProxyInstance(subject.getClass().getClassLoader(), subject.getClass().getInterfaces(), new ProxyInvocationHandler(subject));
subjectProxy.sayHi();
subjectProxy.sayHello();
/**
* 结果
* say:hi
* say:hello
*/
}
}

JDK 的代理类

/**   
*    
* JDK动态代理类   
*    
*   
*/    
public class JDKProxy implements InvocationHandler {    
    
    private Object targetObject;//需要代理的目标对象    
    
    public Object newProxy(Object targetObject) {//将目标对象传入进行代理    
        this.targetObject = targetObject;     
        return Proxy.newProxyInstance(targetObject.getClass().getClassLoader(),    
                targetObject.getClass().getInterfaces(), this);//返回代理对象    
    }    
    
    public Object invoke(Object proxy, Method method, Object[] args)//invoke方法    
            throws Throwable {    
        before();
        Object ret = null;      // 设置方法的返回值    
        ret  = method.invoke(targetObject, args);       //invoke调用需要代理的方法
        after();
        return ret;    
    }    
    
    private void before() {//方法执行前   
        System.out.println("方法执行前 !");    
    }    
    private void after() {//方法执行后    
        System.out.println("方法执行后");    
    }    
}

newProxyInstance方法执行了以下几种操作。

  • 生成一个实现了参数interfaces里所有接口且继承了Proxy的代理类的字节码,然后用参数里的classLoader加载这个代理类。
  • 使用代理类父类的构造函数 Proxy(InvocationHandler h)来创造一个代理类的实例,将我们自定义的InvocationHandler的子类传入。
  • 返回这个代理类实例。
public final class $Proxy0 extends Proxy
implements Test.Subject
{
private static Method m4;
private static Method m1;
private static Method m3;
private static Method m0;
private static Method m2;
  
static
{
   try {
     m4 = Class.forName("Test$Subject").getMethod("sayHello", new Class[0]);
     m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") });
     m3 = Class.forName("Test$Subject").getMethod("sayHi", new Class[0]);
     m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
     m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
  } catch (Exception e) {
    throw new RuntimeException(e);
  }
}
public $Proxy0(InvocationHandler paramInvocationHandler)
{
  super(paramInvocationHandler);
}
public final void sayHello()
{
  try
  {
   this.h.invoke(this, m4, null);
   return;
  }
  catch (RuntimeException localRuntimeException)
  {
   throw localRuntimeException;
  }
  catch (Throwable localThrowable)
  {
    throw new UndeclaredThrowableException(localThrowable);
  }
}
public final boolean equals(Object paramObject)
{
  try
  {
   return ((Boolean)this.h.invoke(this, m1, new Object[] { paramObject })).booleanValue();
  }
  catch (RuntimeException localRuntimeException)
  {
   throw localRuntimeException;
  }
  catch (Throwable localThrowable)
  {
    throw new UndeclaredThrowableException(localThrowable);
  }
}
public final void sayHi()
{
  try
  {
   this.h.invoke(this, m3, null);
   return;
  }
  catch (RuntimeException localRuntimeException)
  {
   throw localRuntimeException;
  }
  catch (Throwable localThrowable)
  {
    throw new UndeclaredThrowableException(localThrowable);
  }
}
public final int hashCode()
{
  try
  {
   return ((Integer)this.h.invoke(this, m0, null)).intValue();
  }
  catch (RuntimeException localRuntimeException)
  {
   throw localRuntimeException;
  }
  catch (Throwable localThrowable)
  {
    throw new UndeclaredThrowableException(localThrowable);
  }
}
public final String toString()
{
  try
  {
   return (String)this.h.invoke(this, m2, null);
  }
  catch (RuntimeException localRuntimeException)
  {
   throw localRuntimeException;
  }
  catch (Throwable localThrowable)
  {
    throw new UndeclaredThrowableException(localThrowable);
  }
}
}

我们可以看到代理类内部实现比较简单,在调用每个代理类每个方法的时候,都用反射去调newProxyInstance方法中传来的h的invoke方法(也就是我们自定义的InvocationHandler的子类中重写的invoke方法),用参数传递了代理类实例、接口方法、调用参数列表,这样我们在重写的invoke方法中就可以实现对所有方法的统一包装了。

二、Mybatis 的动态代理源码分析

1.Mapper 是怎么添加进去的呢

DataSource dataSource = BlogDataSourceFactory.getBlogDataSource();
TransactionFactory transactionFactory = new JdbcTransactionFactory();
Environment environment = new Environment("development", transactionFactory,dataSource);
Configuration configuration = new Configuration(environment);
configuration.addMapper(OrdersMapper1.class);// 添加Mapper接口SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(configuration);

等价于

String resource = "mybatis-config.xml"; // xml内容就不贴了
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

我们后面的 sqlSessionFactory采用以上方式上面生成sqlSessionFactory 的过程中有这么一行 configuration.addMapper(OrderMapper.class); 添加了一个Mapper 接口
addMapper()方法的实现

Configuration类:  
public <T> void addMapper(Class<T> type) {    
	mapperRegistry.addMapper(type);  
}

从代码中我们可以看出mapper 实际是被添加进mapperRegistry 中去了,我们继续研究mapperRegistry 类。

final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);

public <T> void addMapper(Class<T> type) {
    if (type.isInterface()) {// 只添加接口
      if (hasMapper(type)) {// 不允许重复添加
        throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
      }
      boolean loadCompleted = false;
      try {
        knownMappers.put(type, new MapperProxyFactory<T>(type));// 注意这里
        MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
        parser.parse();
        loadCompleted = true;
      } finally {
        if (!loadCompleted) {
          knownMappers.remove(type);
        }
      }
    }
  }

mapperRegistry里面有一个私有的HashMap 就是knownMappers,它的key 为当前的class 对象,value 为一个MapperProxyFactory()实例

总结:Mapper 接口被添加进了MapperReqister 里面的一个HashMap 中去了,并且以Mapper接口的class 对象作为key,以一个携带mepper 接口作为属性的MapperProxyFactory 作为value,MapperProxyFactory 从名字来看好像是一个工厂,用来创建mapper proxy 的工厂。

2.mapper 是怎样获取的呢
从源码来看依然是调用configuration 的getMapper()方法

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

configuration 类里面存放了所有关于xml 文件对的配置信息,从参数上看它要我们传入一个class 类型,这已经能看到后面一定要用到反射机制和动态代理

Configuration类:
  public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    return mapperRegistry.getMapper(type, sqlSession);
  }

MapperRegister 类

private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<Class<?>, MapperProxyFactory<?>>();
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
  final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) 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);
  }
}

我们调用的session.getMapper(order.class)最终会到这里,这个方法根据order的class对象,以它为key在knowMappers 中找到了对应的value——MapperProxyFactory(BlogMapper) 对象,然后调用这个对象的newInstance()方法。根据这个名字,我们就能猜到这个方法是创建了一个对象
MapperProxyFactory()

public class MapperProxyFactory<T> { 
	//映射器代理工厂  
	private final Class<T> mapperInterface; 
	private final Map<Method, MapperMethod> methodCache = new ConcurrentHashMap<Method, 	MapperMethod>();  //删除不必要代码 
	
	@SuppressWarnings("unchecked")  
	protected T newInstance(MapperProxy<T> mapperProxy) {   
	//使用了JDK自带的动态代理生成映射器代理类的对象    
		return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { 					mapperInterface }, mapperProxy);  
	} 
	public T newInstance(SqlSession sqlSession) {    
			final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, 	methodCache);    
		return newInstance(mapperProxy);  
	}
}

最终是通过Proxy.newProxyInstance产生了一个OrdersMapper1的代理对象。Mybatis 为了完成 Mapper 接口的实现,运用了代理模式。具体是使用了JDK动态代理,这个Proxy.newProxyInstance方法生成代理类的三个要素是:

  • ClassLoader —— 指定当前接口的加载器即可
  • Order —— 当前被代理的接口
  • MapperProxy—— 代理类是什么

代理模式中,代理类(MapperProxy)中才真正的完成了方法调用的逻辑。我们贴出MapperProxy的代码

public class MapperProxy<T> implements InvocationHandler, Serializable {
//实现了InvocationHandler接口

  @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    if (Object.class.equals(method.getDeclaringClass())) {
    //代理以后,所有Mapper的方法调用时,都会调用这个invoke方法
      try {
        return method.invoke(this, args);// 注意1
      } catch (Throwable t) {
        throw ExceptionUtil.unwrapThrowable(t);
      }
    }
    final MapperMethod mapperMethod = cachedMapperMethod(method); // 使用了缓存
    //执行CURD
    return mapperMethod.execute(sqlSession, args);// 注意2
  }

  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;
  }
}

Blog blog =mapper.getOrdersById(3); 实际上最后是会调用这个MapperProxy的invoke方法。这段代码中,if 语句先判断,我们想要调用的方法是否来自Object类,这里的意思就是,如果我们调用toString()方法,那么是不需要做代理增强的,直接还调用原来的method.invoke()就行了。只有调用getOrdersById之类的方法的时候,才执行增强的调用——即mapperMethod.execute(sqlSession, args);

而mapperMethod.execute(sqlSession, args);这句最终就会执行增删改查了

public Object execute(SqlSession sqlSession, Object[] args) {
    Object result;
    switch (command.getType()) {
      case INSERT: {
        Object param = method.convertArgsToSqlCommandParam(args);
        result = rowCountResult(sqlSession.insert(command.getName(), param));
        break;
      }
      case UPDATE: {
        Object param = method.convertArgsToSqlCommandParam(args);
        result = rowCountResult(sqlSession.update(command.getName(), param));
        break;
      }
      case DELETE: {
        Object param = method.convertArgsToSqlCommandParam(args);
        result = rowCountResult(sqlSession.delete(command.getName(), param));
        break;
      }
      case SELECT:
        if (method.returnsVoid() && method.hasResultHandler()) {
          executeWithResultHandler(sqlSession, args);
          result = null;
        }
       // 删除部分代码      
      }
  // 删除部分代码
    return result;
  }

我们分析了 Mapper接口是如何注册的,Mapper接口是如何产生动态代理对象的,Maper接口方法最终是如何执行的。总结起来主要就是这几个点:

  • Mapper 接口在初始SqlSessionFactory 注册的
  • Mapper 接口注册在了名为 MapperRegistry 类的 HashMap中, key = Mapper class value = 创建当前Mapper的工厂
  • Mapper 注册之后,可以从SqlSession中get
  • SqlSession.getMapper 运用了 JDK动态代理,产生了目标Mapper接口的代理对象
  • 动态代理的 代理类是 MapperProxy ,这里边最终完成了增删改查方法的调用

猜你喜欢

转载自blog.csdn.net/Alyson_jm/article/details/91899047