天天看点

硬核!五千字带你从设计模式去解读Mybatis源码思维导图概述工厂模式单例模式建造者模式模板模式代理模式装饰器模式总结

思维导图

硬核!五千字带你从设计模式去解读Mybatis源码思维导图概述工厂模式单例模式建造者模式模板模式代理模式装饰器模式总结
文章已收录Github精选,欢迎Star: https://github.com/yehongzhi/learningSummary

概述

Mybatis是一个比较主流的ORM框架,所以在日常工作中接触得很多。我比较喜欢看优秀框架的源码,因为能写出这种框架的作者肯定有其独特之处。如果能看懂源码的一些巧妙构思,一定是受益匪浅的。

所谓万事开头难,看源码也要找到切入的点。设计模式无疑是源码分析一个很好的切入点,废话不多说,那么我们就开始吧。

工厂模式

工厂模式属于创建型模式。工厂模式的作用是把创建对象的逻辑封装起来,提供一个接口给外部创建对象,降低类与类之间的耦合。

在Mybatis中,用到工厂模式主要在DataSourceFactory。这是一个负责创建DataSource数据源的工厂。DataSourceFactory是一个接口,有不同的子类实现,根据不同的配置,生成不同的DataSourceFactory实现类。类图如下:

硬核!五千字带你从设计模式去解读Mybatis源码思维导图概述工厂模式单例模式建造者模式模板模式代理模式装饰器模式总结

接着我们看一下DataSourceFactory的源码:

public interface DataSourceFactory {
  
  void setProperties(Properties props);

  DataSource getDataSource();

}           

DataSourceFactory接口定义了两个抽象方法,怎么工作的呢,其实是跟dataSource标签的属性type有关。

<environment id="development">
    <transactionManager type="JDBC"/>
    <!-- type属性是关键属性 -->
    <dataSource type="POOLED">
        <property name="driver" value="${dataSource.driver}"/>
        <property name="url" value="${dataSource.url}"/>
        <property name="username" value="${dataSource.username}"/>
        <property name="password" value="${dataSource.password}"/>
    </dataSource>
</environment>           

Mybatis内置的type有三种配置,分别是UNPOOLED,POOLED,JNDI。

UNPOOLED,这个数据源的实现只是每次被请求时打开和关闭连接。

POOLED,这种数据源的使用“池“的思想,避免了创建新的连接实例时所必需的初始化和认证时间。

JNDI,这个数据源的实现是为了能在如 EJB 或应用服务器这类容器中使用,容器可以集中或在外部配置数据源,然后放置一个 JNDI 上下文的引用。

接着看XMLConfigBuilder的dataSourceElement()方法。

private DataSourceFactory dataSourceElement(XNode context) throws Exception {
    if (context != null) {
        //读取配置文件中dataSource标签的属性type
        String type = context.getStringAttribute("type");
        Properties props = context.getChildrenAsProperties();
        //根据type属性的值,返回不同的子类,使用接口DataSourceFactory接收,体现了面向接口编程的思想
        DataSourceFactory factory = (DataSourceFactory) resolveClass(type).newInstance();
        //设置属性值,比如数据库的url,username,password等等
        factory.setProperties(props);
        //返回factory
        return factory;
    }
    throw new BuilderException("Environment declaration requires a DataSourceFactory.");
}           

获得DataSourceFactory之后,就通过getDataSource()方法获取数据源,完事了。

private void environmentsElement(XNode context) throws Exception {
    if (context != null) {
        if (environment == null) {
            environment = context.getStringAttribute("default");
        }
        //这里for循环主要是配置文件可以配置多个数据源
        for (XNode child : context.getChildren()) {
            String id = child.getStringAttribute("id");
            if (isSpecifiedEnvironment(id)) {
                TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager"));
                DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource"));
                //dsFactory调动getDataSource()方法,创建dataSource对象
                DataSource dataSource = dsFactory.getDataSource();
                Environment.Builder environmentBuilder = new Environment.Builder(id)
                    .transactionFactory(txFactory)
                    .dataSource(dataSource);
                configuration.setEnvironment(environmentBuilder.build());
            }
        }
    }
}           

Mybatis使用工厂模式来创建DataSourceFactory,可以做到通过配置去使用不同的DataSourceFactory创建DataSource,非常灵活。在Mybatis中使用到工厂模式还有很多地方,比如SqlSessionFactory,这里就不再展开了,有兴趣的可以自己探索一下。

单例模式

单例模式属于创建型设计模式,这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。保证在应用中只有单一对象被创建。

Mybatis中用到单例模式的地方有很多,这里举个例子是ErrorContext类。这是一个用于记录该线程的执行环境错误信息,所以是在线程范围内的单例。

public class ErrorContext {
    //使用ThreadLocal保存每个线程中的单例对象
    private static final ThreadLocal<ErrorContext> LOCAL = new ThreadLocal<ErrorContext>();
    //私有化构造器
    private ErrorContext() {
    }
    //向外提供唯一的接口获取单例对象
    public static ErrorContext instance() {
        //从LOCAL中取出context对象
        ErrorContext context = LOCAL.get();
        if (context == null) {
            //如果为null,new一个
            context = new ErrorContext();
            //放入到LOCAL中保存
            LOCAL.set(context);
        }
        //如果不为null,直接返回
        return context;
    }
}           

建造者模式

建造者模式也是属于创建型模式,主要是在创建一个复杂对象时使用,通过一步一步构造最终的对象,将一个复杂对象的构建与它的表示分离。

在Mybatis中,使用到建造者模式的地方体现在ParameterMapping类,这是用于参数映射的一个类。

public class ParameterMapping {
  private Configuration configuration;
  private String property;
  private ParameterMode mode;
  private Class<?> javaType = Object.class;
  private JdbcType jdbcType;
  private Integer numericScale;
  private TypeHandler<?> typeHandler;
  private String resultMapId;
  private String jdbcTypeName;
  private String expression;
  
  //私有化构造器
  private ParameterMapping() {
  }
  
  //通过内部类Builder创建对象
  public static class Builder {
      //初始化ParameterMapping实例
      private ParameterMapping parameterMapping = new ParameterMapping();
      //通过构造器初始化ParameterMapping的一些成员变量
      public Builder(Configuration configuration, String property, TypeHandler<?> typeHandler) {
          parameterMapping.configuration = configuration;
          parameterMapping.property = property;
          parameterMapping.typeHandler = typeHandler;
          parameterMapping.mode = ParameterMode.IN;
      }

      public Builder(Configuration configuration, String property, Class<?> javaType) {
          parameterMapping.configuration = configuration;
          parameterMapping.property = property;
          parameterMapping.javaType = javaType;
          parameterMapping.mode = ParameterMode.IN;
      }
      //设置parameterMapping的mode
      public Builder mode(ParameterMode mode) {
          parameterMapping.mode = mode;
          return this;
      }
      //设置parameterMapping的javaType
      public Builder javaType(Class<?> javaType) {
          parameterMapping.javaType = javaType;
          return this;
      }
      //设置parameterMapping的jdbcType
      public Builder jdbcType(JdbcType jdbcType) {
          parameterMapping.jdbcType = jdbcType;
          return this;
      }
      //设置parameterMapping的numericScale
      public Builder numericScale(Integer numericScale) {
          parameterMapping.numericScale = numericScale;
          return this;
      }
      //设置parameterMapping的resultMapId
      public Builder resultMapId(String resultMapId) {
          parameterMapping.resultMapId = resultMapId;
          return this;
      }
      //设置parameterMapping的typeHandler
      public Builder typeHandler(TypeHandler<?> typeHandler) {
          parameterMapping.typeHandler = typeHandler;
          return this;
      }
      //设置parameterMapping的jdbcTypeName
      public Builder jdbcTypeName(String jdbcTypeName) {
          parameterMapping.jdbcTypeName = jdbcTypeName;
          return this;
      }
      //设置parameterMapping的expression
      public Builder expression(String expression) {
          parameterMapping.expression = expression;
          return this;
      }
      //通过build()方法创建对象,返回
      public ParameterMapping build() {
          resolveTypeHandler();
          validate();
          return parameterMapping;
      }
      }
}           

在SqlSourceBuilder类的buildParameterMapping()方法中可以看到建造者模式的实战应用:

//根据参数content,构建parameterMapping实例
private ParameterMapping buildParameterMapping(String content) {
    //属性值的Map集合
    Map<String, String> propertiesMap = parseParameterMapping(content);
    String property = propertiesMap.get("property");
    Class<?> propertyType;
    //省略...
    //创建一个ParameterMapping.Builder对象
    ParameterMapping.Builder builder = new ParameterMapping.Builder(configuration, property, propertyType);
    //省略...
    //这里遍历Map集合,把属性值设置到ParameterMapping对象中,并创建
    for (Map.Entry<String, String> entry : propertiesMap.entrySet()) {
        String name = entry.getKey();
        String value = entry.getValue();
        if ("javaType".equals(name)) {
            javaType = resolveClass(value);
            builder.javaType(javaType);
        } else if ("jdbcType".equals(name)) {
            builder.jdbcType(resolveJdbcType(value));
        } else if ("mode".equals(name)) {
            builder.mode(resolveParameterMode(value));
        } else if ("numericScale".equals(name)) {
            builder.numericScale(Integer.valueOf(value));
        } else if ("resultMap".equals(name)) {
            builder.resultMapId(value);
        } else if ("typeHandler".equals(name)) {
            typeHandlerAlias = value;
        } else if ("jdbcTypeName".equals(name)) {
            builder.jdbcTypeName(value);
        } else if ("property".equals(name)) {
            // Do Nothing
        } else if ("expression".equals(name)) {
            //抛出异常Expression based parameters are not supported yet
        } else {
            //抛出异常
        }
    }
    //省略...
    //创建ParameterMapping对象,并返回
    return builder.build();
}           

模板模式

模板模式是一种行为型模式,一般用在一些比较通用的方法中,定义一个抽象类,编写一个算法的骨架,将一些步骤延迟到子类。就像是请假条一样,开头和结尾都是写好的模板,中间的请假的原因(内容)由请假人(子类)去补充完整,这样可以提高代码的复用。

在Mybatis中,模板模式体现在Executor和BaseExecutor这两个类中。首先看张类图:

硬核!五千字带你从设计模式去解读Mybatis源码思维导图概述工厂模式单例模式建造者模式模板模式代理模式装饰器模式总结

Executor是一个接口,从命名上可以看出是用来执行SQL语句的对象。下面有一个BaseExecutor的抽象类,这就是用来定义模板方法的。再下面有三个实现类,SimpleExecutor(简单执行器),ReuseExecutor(重用执行器),BatchExecutor(批量执行器)。实现类就是用来填充模板中间的内容的。

执行器在执行JDBC操作的前后往往有很多需要处理的工作都是相同的,比如查询的时候使用缓存,更新时需要清除缓存等等,所以就很适合使用模板模式。

接着我们看BaseExecutor抽象类的源码,一看就明白了,其实就定义了一个骨架。

public abstract class BaseExecutor implements Executor {
    //查询操作
    private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
        List<E> list;
        localCache.putObject(key, EXECUTION_PLACEHOLDER);
        //上面的代码是固定的
        try {
            //这段代码不同的子类有不同的实现,所以是调用抽象方法doQuery()
            list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
        //下面的代码也是固定的
        } finally {
            //清除缓存
            localCache.removeObject(key);
        }
        //添加到缓存中
        localCache.putObject(key, list);
        if (ms.getStatementType() == StatementType.CALLABLE) {
            localOutputParameterCache.putObject(key, parameter);
        }
        //返回结果
        return list;
    }
    
    //抽象方法,由子类去实现
    protected abstract <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql)
        throws SQLException;
}           

那么就有疑问了,一开始如果都不设置的话,默认使用哪个子类的实现。很简单,直接跟着源码去顺藤摸瓜,我们就看到了。

public class Configuration {
    //默认是SIMPLE,也就是SimpleExecutor
    protected ExecutorType defaultExecutorType = ExecutorType.SIMPLE;
}           

其实模板模式很简单就能“认出来”,我的理解就是,抽象类里定义具(具体的方法),具体方法再调抽(抽象方法)。那十有八九就是模板模式了。

代理模式

代理模式属于结构型模式,代理模式的定义说的很抽象,为其他对象提供一种代理以控制对这个对象的访问。在某些情况下,一个对象不适合或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用。

其实代理模式可以简单理解为中介的作用,比如一手房东只关心收租,其他的水电费结算,带人看房这些杂七杂八的东西他不想关心,就交给中介(二手房东),租客要租房就给钱中介,一手房东收钱就找中介,这个中介就是所谓的代理者。

回到Mybatis框架中,SqlSession类就用到代理模式,SqlSession是操作数据库一个会话对象,我们用户一般通过SqlSession做增删改查,但是如果每次做增删改都开启事务,关闭事务,显然是很麻烦,所以就可以交给代理类来完成这个工作,如果没有开启事务,由代理类自动开启事务。

Mybatis在这里是使用JDK动态代理,所以SqlSession是一个接口。

public interface SqlSession extends Closeable {
    
    <T> T selectOne(String statement);

    <E> List<E> selectList(String statement);

    <E> List<E> selectList(String statement, Object parameter);

    <K, V> Map<K, V> selectMap(String statement, String mapKey);

    <T> Cursor<T> selectCursor(String statement);

    void select(String statement, Object parameter, ResultHandler handler);

    int insert(String statement);

    int update(String statement);

    int delete(String statement);

    void commit();

    void rollback();
    //省略...
}           

我们知道动态代理要有一个实现InvocationHandler接口的类,这个类在SqlSessionManager里,是一个内部类,叫做SqlSessionInterceptor,在这个类里做相关的处理。

private class SqlSessionInterceptor implements InvocationHandler {
    public SqlSessionInterceptor() {
        // Prevent Synthetic Access
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //获取SqlSession对象,localSqlSession是一个ThreadLocal,所以是每个线程有自己的sqlSession
        final SqlSession sqlSession = SqlSessionManager.this.localSqlSession.get();
        //如果不为null
        if (sqlSession != null) {
            try {
                //执行方法,返回结果  
                return method.invoke(sqlSession, args);
            } catch (Throwable t) {
                throw ExceptionUtil.unwrapThrowable(t);
            }
            //如果sqlsession为null
        } else {
            //打开session  
            final SqlSession autoSqlSession = openSession();
            try {
                //执行Sqlsession的方法,获得结果  
                final Object result = method.invoke(autoSqlSession, args);
                //commit提交事务
                autoSqlSession.commit();
                //返回结果
                return result;
            } catch (Throwable t) {
                //rollback回滚事务
                autoSqlSession.rollback();
                //抛出异常
                throw ExceptionUtil.unwrapThrowable(t);
            } finally {
                //关闭sqlsession
                autoSqlSession.close();
            }
        }
    }
}           

然后通过构造器去初始化,提供静态方法返回这个SqlSessionManager实例,请看源码。

//实现了SqlSessionFactory,SqlSession接口
public class SqlSessionManager implements SqlSessionFactory, SqlSession {

    private final SqlSessionFactory sqlSessionFactory;
    //代理类sqlSessionProxy
    private final SqlSession sqlSessionProxy;

    private SqlSessionManager(SqlSessionFactory sqlSessionFactory) {
        this.sqlSessionFactory = sqlSessionFactory;
        //初始化代理类
        this.sqlSessionProxy = (SqlSession) Proxy.newProxyInstance(
            SqlSessionFactory.class.getClassLoader(),
            new Class[]{SqlSession.class},
            new SqlSessionInterceptor());
    }
    //提供一个静态方法给外部获取SqlSessionManager类,可以用SqlSession接收
    public static SqlSessionManager newInstance(SqlSessionFactory sqlSessionFactory) {
        return new SqlSessionManager(sqlSessionFactory);
    }
    
    //重写selectOne方法,使用代理类去执行
    @Override
    public <T> T selectOne(String statement) {
        return sqlSessionProxy.<T> selectOne(statement);
    }
    
    //重写insert方法,使用代理类去执行
    @Override
    public int insert(String statement) {
        return sqlSessionProxy.insert(statement);
    }
    //省略...
}           

这就是Mybatis使用代理模式的一个例子,其实也不是很复杂,还是能看懂的。

但是上面这种方式一般很少用,我们一般都是使用Mapper接口的方式,其实Mapper接口的方式也是使用了代理模式,接下来再继续看。直接看MapperProxy类。

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

    private static final long serialVersionUID = -6424540398559729838L;
    private final SqlSession sqlSession;
    private final Class<T> mapperInterface;
    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 {
        //省略...
        //获取mapperMethod对象
        final MapperMethod mapperMethod = cachedMapperMethod(method);
        //执行方法。根据Mapper.xml配置文件的配置进行执行,返回结果
        return mapperMethod.execute(sqlSession, args);
    }

    private MapperMethod cachedMapperMethod(Method method) {
        //从methodCache取出mapperMethod
        MapperMethod mapperMethod = methodCache.get(method);
        //为null
        if (mapperMethod == null) {
            //new一个。这里已经把Mapper.xml的一些配置都封装好了
            mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration());
            //然后put进methodCache
            methodCache.put(method, mapperMethod);
        }
        //返回mapperMethod
        return mapperMethod;
    }
}           

mapperMethod的execute()方法的作用就是根据Mapper接口的方法名找到Mapper.xml文件的sql的Id,然后执行相应的操作,返回结果。内部的代码比较长,但是思路就是这样,这里就不展开了。

所以我们平时用的时候,TbCommodityInfoMapper接口假设是这样定义了一个list()方法。

public interface TbCommodityInfoMapper {
    //查询TbCommodityInfo列表
    List<TbCommodityInfo> list();
}           

那个在TbCommodityInfoMapper.xml就要对应有一个id为list的配置。

<!-- 命名空间也要和接口的全限定名一致 -->
<mapper namespace="io.github.yehongzhi.commodity.mapper.TbCommodityInfoMapper">
    <!-- 必须要有一个对应的属性id为list的sql配置 -->
    <select id="list" resultType="tbCommodityInfo">
        select `id`, `commodity_name`, `commodity_price`, `description`, `number` from tb_commodity_info
    </select>
</mapper>           

然后在MapperProxyFactory类,使用工厂模式提供获取代理类。

public class MapperProxyFactory<T> {
    private final Class<T> mapperInterface;
    private final Map<Method, MapperMethod> methodCache = new ConcurrentHashMap<Method, MapperMethod>();
    //通过构造器初始化mapperInterface
    public MapperProxyFactory(Class<T> mapperInterface) {
        this.mapperInterface = mapperInterface;
    }

    @SuppressWarnings("unchecked")
    protected T newInstance(MapperProxy<T> mapperProxy) {
        return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
    }
    
    //通过这个方法,获取Mapper接口的代理类
    public T newInstance(SqlSession sqlSession) {
        final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
        return newInstance(mapperProxy);
    }

}           

那个这个getMapper的方法就在SqlSession的子类中被调用。

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

最终用户就是这样使用,这里是纯Mybatis,没有集成Spring的写法。

public class Test {
    public static void main(String[] args) {
        //获取SqlSession对象
        SqlSession sqlSession = SqlSessionFactoryUtils.openSqlSession();
        //再获取Mapper接口的代理类
        TbCommodityInfoMapper tbCommodityInfoMapper = sqlSession.getMapper(TbCommodityInfoMapper.class);
        //通过代理类去执行相应的方法
        List<TbCommodityInfo> commodityInfoList = tbCommodityInfoMapper.list();
    }
}           

所以代理模式可以说是Mybatis核心的设计模式,用的是非常巧妙。

装饰器模式

装饰器模式属于结构型模式,使用装饰类包装对象,动态地给对象添加一些额外的职责。那么在Mybatis中,哪个地方会使用到装饰器模式呢?

没错了,就是缓存。Mybatis有一级缓存和二级缓存的功能,一级缓存默认是打开的,范围在SqlSession中生效,二级缓存需要手动配置打开,范围在全局Configuration,在每个namespace中配置。二级缓存的类型有以下几种,请看配置。

<mapper namespace="io.github.yehongzhi.commodity.mapper.TbCommodityInfoMapper">
    <!--
        eviction:代表的是缓存回收策略,目前MyBatis提供以下策略。
        (1) LRU,最近最少使用的,一处最长时间不用的对象
        (2) FIFO,先进先出,按对象进入缓存的顺序来移除他们
        (3) SOFT,软引用,移除基于垃圾回收器状态和软引用规则的对象
        (4) WEAK,弱引用,更积极的移除基于垃圾收集器状态和弱引用规则的对象。这里采用的是LRU,移除最长时间不用的对形象
        flushInterval:刷新间隔时间,单位为毫秒,这里配置的是100秒刷新,如果你不配置它,那么当SQL被执行的时候才会去刷新缓存。
        size:引用数目,一个正整数,代表缓存最多可以存储多少个对象,不宜设置过大。设置过大会导致内存溢出。这里配置的是1024个对象
        readOnly:只读,意味着缓存数据只能读取而不能修改,这样设置的好处是我们可以快速读取缓存,缺点是我们没有办法修改缓存
    -->
    <cache eviction="LRU" flushInterval="100000" readOnly="true" size="1024"/>
    
    <select id="list" resultMap="base_column">
        select `id`, `commodity_name`, `commodity_price`, `description`, `number` from tb_commodity_info
    </select>
</mapper>           

所以这个场景已经很清晰了,也就是在一级缓存的基础上,再添加二级缓存,也就符合装饰器模式的那句话,动态给对象添加职责功能。怎么做呢,我们不妨先看Cache类的类图。

硬核!五千字带你从设计模式去解读Mybatis源码思维导图概述工厂模式单例模式建造者模式模板模式代理模式装饰器模式总结

先看Cache接口,其实就定义了一些接口方法。

public interface Cache {

    String getId();

    void putObject(Object key, Object value);

    Object getObject(Object key);

    Object removeObject(Object key);

    void clear();

    int getSize();

    ReadWriteLock getReadWriteLock();
}           

然后再看PerpetualCache类,这是Cache接口最基本的实现,二级缓存要扩展就在这个类上面再去包装来实现扩展。其实就是一个HashMap,再简单包装一下。

public class PerpetualCache implements Cache {

    private final String id;
    //成员变量cache,创建一个HashMap
    private Map<Object, Object> cache = new HashMap<Object, Object>();

    public PerpetualCache(String id) {
        this.id = id;
    }

    @Override
    public String getId() {
        return id;
    }

    @Override
    public int getSize() {
        return cache.size();
    }

    @Override
    public void putObject(Object key, Object value) {
        cache.put(key, value);
    }

    @Override
    public Object getObject(Object key) {
        return cache.get(key);
    }

    @Override
    public Object removeObject(Object key) {
        return cache.remove(key);
    }

    @Override
    public void clear() {
        cache.clear();
    }

    @Override
    public ReadWriteLock getReadWriteLock() {
        return null;
    }
    //省略...
}           

那么我们再看二级缓存的一个代表性的类LruCache。

public class LruCache implements Cache {
    //一级缓存,保存在这个成员变量中
    private final Cache delegate;
    //实际上这是一个LinkedHashMap,利用LinkedHashMap的LRU算法实现缓存的LRU
    private Map<Object, Object> keyMap;
    private Object eldestKey;
    //构造器缓存对象,初始化
    public LruCache(Cache delegate) {
        this.delegate = delegate;
        setSize(1024);
    }

    //初始化keyMap,重写removeEldestEntry方法,实现LUR算法
    public void setSize(final int size) {
        keyMap = new LinkedHashMap<Object, Object>(size, .75F, true) {
            private static final long serialVersionUID = 4267176411845948333L;

            @Override
            protected boolean removeEldestEntry(Map.Entry<Object, Object> eldest) {
                boolean tooBig = size() > size;
                //每次put进来时,eldestKey都是最老的key
                if (tooBig) {
                    eldestKey = eldest.getKey();
                }
                return tooBig;
            }
        };
    }

    @Override
    public void putObject(Object key, Object value) {
        //保存进缓存
        delegate.putObject(key, value);
        //这里就是删除掉不常用的值
        cycleKeyList(key);
    }

    @Override
    public Object getObject(Object key) {
        keyMap.get(key); //touch
        return delegate.getObject(key);
    }
    
    private void cycleKeyList(Object key) {
        keyMap.put(key, key);
        if (eldestKey != null) {
            //删除掉Map中最老的key
            delegate.removeObject(eldestKey);
            eldestKey = null;
        }
    }

}           

关键在于成员变量delegate,这在其他的二级缓存装饰类中都定义了。这个是为了保存PerpetualCache这个基础缓存类的。所以这也就是说二级缓存是在PerpetualCache为基础扩展的。再继续看就更加明白了。

直接看创建SqlSessionFactory的builder方法,一直追踪下去,就可以找到MapperBuilderAssistant类的useNewCache方法。

public class MapperBuilderAssistant extends BaseBuilder {
    //当前Mapper.xml的命名空间
    private String currentNamespace;

    public Cache useNewCache(Class<? extends Cache> typeClass,
                             Class<? extends Cache> evictionClass,
                             Long flushInterval,
                             Integer size,
                             boolean readWrite,
                             boolean blocking,
                             Properties props) {
        Cache cache = new CacheBuilder(currentNamespace)
            //设置基础缓存
            .implementation(valueOrDefault(typeClass, PerpetualCache.class))
            //添加缓存装饰类
            .addDecorator(valueOrDefault(evictionClass, LruCache.class))
            .clearInterval(flushInterval)
            .size(size)
            .readWrite(readWrite)
            .blocking(blocking)
            .properties(props)
            //创建缓存
            .build();
        //把缓存添加到configuration,所以二级缓存是configuration范围的
        configuration.addCache(cache);
        currentCache = cache;
        return cache;
    }
}           

再看CacheBuilder的build方法,更加清晰了。

public class CacheBuilder {
    
    private Class<? extends Cache> implementation;
    private final List<Class<? extends Cache>> decorators;
    
    public Cache build() {
        setDefaultImplementations();
        Cache cache = newBaseCacheInstance(implementation, id);
        setCacheProperties(cache);
        // issue #352, do not apply decorators to custom caches
        if (PerpetualCache.class.equals(cache.getClass())) {
            //遍历装饰器类
            for (Class<? extends Cache> decorator : decorators) {
                //在cache上添加二级缓存
                cache = newCacheDecoratorInstance(decorator, cache);
                setCacheProperties(cache);
            }
            cache = setStandardDecorators(cache);
        } else if (!LoggingCache.class.isAssignableFrom(cache.getClass())) {
            cache = new LoggingCache(cache);
        }
        //返回缓存
        return cache;
    }

    private void setDefaultImplementations() {
        if (implementation == null) {
            //如果为空,初始化为PerpetualCache
            implementation = PerpetualCache.class;
            //如果装饰器类为空,默认用LruCache
            if (decorators.isEmpty()) {
                decorators.add(LruCache.class);
            }
        }
    }
}           

所以我们大概可以想到二级缓存就像千层饼一样,一层一层地包装起来。最后debug模式验证一下。

硬核!五千字带你从设计模式去解读Mybatis源码思维导图概述工厂模式单例模式建造者模式模板模式代理模式装饰器模式总结

在执行查询的时候你就会看到真的是千层饼一样,一层一层的。看CachingExecutor类的query方法。

硬核!五千字带你从设计模式去解读Mybatis源码思维导图概述工厂模式单例模式建造者模式模板模式代理模式装饰器模式总结

最后在调用Cache的putObject方法时就会一层一层从外到内地调用,实现为对象动态扩展功能的装饰器模式。

硬核!五千字带你从设计模式去解读Mybatis源码思维导图概述工厂模式单例模式建造者模式模板模式代理模式装饰器模式总结

装饰器模式在Mybatis中的应用就讲到这里了,有什么不懂的,可以关注公众号

java技术爱好者

,加我微信提问。

总结

这篇文章就介绍了Mybatis中用到的6种设计模式,分别是工厂模式,单例模式,模板模式,建造者模式,代理模式,还有装饰器模式。实际上Mybatis除了我讲的这些之外,还有很多我没有提到的,比如组合模式,适配器模式等等,有兴趣自己去研究一下吧。

因为现在很多面试动不动就问有看过什么框架的源码,实际上看源码是好的,但是不能盲目入手,因为很多框架是运用了大量的设计模式,如果对设计模式没有一定的认识,很容易看不懂,看懵。所以对于还没有设计模式基础的同学,建议先看设计模式,然后再去学习源码,这样才能循序渐进地提升自身实力。

这篇文章就讲到这里了,感谢大家的阅读。

觉得有用就点个赞吧,你的点赞是我创作的最大动力~

我是一个努力让大家记住的程序员。我们下期再见!!!

硬核!五千字带你从设计模式去解读Mybatis源码思维导图概述工厂模式单例模式建造者模式模板模式代理模式装饰器模式总结
能力有限,如果有什么错误或者不当之处,请大家批评指正,一起学习交流!