博客列表 >重学设计模式之跟Mybatis学动态代理应用

重学设计模式之跟Mybatis学动态代理应用

yeeloow
yeeloow原创
2019年11月12日 09:36:12615浏览

抛出问题

1、Mybatis 开发为何通过接口就可以执行sql?

首先、有一点我们很清楚,在Java中 接口是无法被实例化使用的。
既然我们没有提供实现类,那么我们可以很自然的猜测是Mybatis帮我们生成了实现类。

直接上源码看下,Mapper接口动态代理的处理器类

1、MapperProxy

public class MapperProxy<T> implements InvocationHandler, Serializable {

    private static final long serialVersionUID = -6424540398559729838L;
    //sql执行的顶层接口
    private final SqlSession sqlSession;
    //mapper接口
    private final Class<T> mapperInterface;
    //mapperMethod缓存
    private final Map<Method, MapperMethod> methodCache;
    //动态代理的构造函数、同时代理多个对象,持有多个真实对象
    public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethod> methodCache) {
      this.sqlSession = sqlSession;
      this.mapperInterface = mapperInterface;
      this.methodCache = methodCache;
    }
  
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
      //判断如果是调用Object的方法,如:toString()、equals()等执行反射执行。
      if (Object.class.equals(method.getDeclaringClass())) {
        try {
          return method.invoke(this, args);
        } catch (Throwable t) {
          throw ExceptionUtil.unwrapThrowable(t);
        }
      }
      //缓存mapperMethod
      final MapperMethod mapperMethod = cachedMapperMethod(method);
      //通过mapperMethod,执行sql
      return mapperMethod.execute(sqlSession, args);
    }
  
    private MapperMethod cachedMapperMethod(Method method) {
      MapperMethod mapperMethod = methodCache.get(method);
      //当前mapperMethod被缓存,则设置缓存。
      if (mapperMethod == null) {
        mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration());
        methodCache.put(method, mapperMethod);
      }
      return mapperMethod;
    }}123456789101112131415161718192021222324252627282930313233343536373839404142

观察MapperProxy,我们可以看出这是一个典型的jdk动态代理实现,与上篇示例不同的是,MapperProxy是mapper接口的处理器类,内部持有多个真实对象,也就是说它基于多个对象进行了增强。查看invoke方法,可以认为它进行对增强操作就是mapperMethod缓存和sql执行。

它是被谁调用,在什么时候使用呢?

2、MapperProxyFactory

 public class MapperProxyFactory<T> {

    private final Class<T> mapperInterface;
    private final Map<Method, MapperMethod> methodCache = new ConcurrentHashMap<Method, MapperMethod>();
   
    //构造函数接收一个mapper接口
    public MapperProxyFactory(Class<T> mapperInterface) {
      this.mapperInterface = mapperInterface;
    }
   
    public Class<T> getMapperInterface() {
      return mapperInterface;
    }
   
    public Map<Method, MapperMethod> getMethodCache() {
      return methodCache;
    }
     //通过Proxy#newProxyInstance创建代理对象
    @SuppressWarnings("unchecked")
    protected T newInstance(MapperProxy<T> mapperProxy) {
     //最终返回一个动态代理生成的代理对象
      return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
 }

     public T newInstance(SqlSession sqlSession) {
     //创建mapperProxy对象,这个就是我们的真实对象,也就是mapper接口的实现类
     final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface,  methodCache);
     return newInstance(mapperProxy);
 }}123456789101112131415161718192021222324252627282930

MapperProxyFactory类,见名知意,它就是MapperProxy的工厂类,负责生成Mapper接口的动态代理类,通过代理类执行sql。可以这样理解: 执行过程中生成的MapperProxy实例就是具体Mapper接口的实现类。因此,我们才可以通过接口直接执行sql.

2、SqlSessionTemplate 是如何实现线程安全的?

首先、mybatis原生提供的DefaultSqlSession是线程不安全的。
在Mybatis-Spring整合包中,为我们提供了线程安全的SqlSessionTemplate.
3.png

查看SqlSessionTemplate源码类注释可以看到,明确说明,它是被Spring管理的、线程安全的、单例类,可以被多个DAO共享。这是如何实现的呢?

这个问题在MapperFactoryBean源码解析查看,这次我们看下它关于动态代理的实现。
在SqlSessionTemplate中通过内部类,SqlSessionInterceptor实现InvocationHandler接口作为一个动态代理处理器类。

 private class SqlSessionInterceptor implements InvocationHandler {
   //没有内部对象,但是可以调用外部SqlSessionTemplate的。成员变量
   @Override
   public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
     //进行了spring事物相关的增强。
     SqlSession sqlSession = getSqlSession(
         SqlSessionTemplate.this.sqlSessionFactory,
         SqlSessionTemplate.this.executorType,
         SqlSessionTemplate.this.exceptionTranslator);
     try {
       Object result = method.invoke(sqlSession, args);
       if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
         // force commit even on non-dirty sessions because some databases require
         // a commit/rollback before calling close()
         sqlSession.commit(true);
       }
       return result;
     } catch (Throwable t) {
        //异常处理、释放资源。
       Throwable unwrapped = unwrapThrowable(t);
       if (SqlSessionTemplate.this.exceptionTranslator != null && unwrapped instanceof PersistenceException) {
         // release the connection to avoid a deadlock if the translator is no loaded. See issue #22
         closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
         sqlSession = null;
         Throwable translated = SqlSessionTemplate.this.exceptionTranslator.translateExceptionIfPossible((PersistenceException) unwrapped);
         if (translated != null) {
           unwrapped = translated;
         }
       }
       throw unwrapped;
     } finally {
       if (sqlSession != null) {
         closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
       }
     }
   }
 }12345678910111213141516171819202122232425262728293031323334353637

我们知道它只是动态代理的处理器类,必然有调用者调用传入代理对象接口才能生效。
查看SqlSessionInterceptor的调用者:

public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType,
     PersistenceExceptionTranslator exceptionTranslator) {

   notNull(sqlSessionFactory, "Property 'sqlSessionFactory' is required");
   notNull(executorType, "Property 'executorType' is required");

   this.sqlSessionFactory = sqlSessionFactory;
   this.executorType = executorType;
   this.exceptionTranslator = exceptionTranslator;
   //Proxy#newProxyInstance创建类代理对象,对SqlSessionTemplate进行增强。
   this.sqlSessionProxy = (SqlSession) newProxyInstance(
       SqlSessionFactory.class.getClassLoader(),
       new Class[] { SqlSession.class },
       new SqlSessionInterceptor());
 }123456789101112131415

当SqlSessionTemplate相关方法被执行时,会被处理器进行增强处理,在这里即是对Spring事物相关的增强。

三、本篇总结

学习了在Mybatis中,两处关于动态代理的实现收获:
1、动态代理的使用:处理器类可以持有多个真实对象,进行增强。
2、动态代理的处理器类可以通过内部类方式实现。
3、为使用动态代理提供很好的范例,除了单一的直接创建对象,既可以通过工厂方式生成、也可以通过构造器使用。

声明:本文内容转载自脚本之家,由网友自发贡献,版权归原作者所有,如您发现涉嫌抄袭侵权,请联系admin@php.cn 核实处理。
全部评论
文明上网理性发言,请遵守新闻评论服务协议