【概要】
本篇文章了解一下MyBatis中运用到的设计模式,我们在实际的开发中很大程度上只是对设计模式留在一个理论的环节上,缺少实践,通过源码,我们可以学习一下这些设计模式的实践方式,有利于我们能够更深入的理解和使用设计模式。
- Builder模式
- 工厂模式
- 单例模式
- 代理模式
- 组合模式
- 模板模式
- 适配器模式
- 装饰模式
- 迭代器模型
1. 【Builder模式】
属于:创建类模式
意图:将一个复杂的构建与其表示相分离,使得同样的构建过程可以创建不同的表示。
Builder模式的定义是“将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。 抽象工厂与建造者 抽象工厂模式实现对产品家族的创建,一个产品家族是这样的一系列产品:具有不同分类维度的产品组合,采用抽象工厂模式不需要关心构建过程,只关心什么产品由什么工厂生产即可。而建造者模式则是要求按照指定的蓝图建造产品,它的主要目的是通过组装零配件而产生一个新产品。
SqlSessionFactoryBuilder类
SqlSessionFactoryBuilder通过调用build中的XMLConfigBuilder构造器,将该Configuration对象作为参数构建一个SqlSessionFactory对象。
XMLConfigBuilder继续调用mapperElement下的XMLMapperBuilder构造器,而XMLMapperBuilder会调用XMLStatementBuilder。
2. 【工厂模式】
属于:创建类模式
意图:定义一个创建对象的接口,让其子类自己决定实例化哪一个工厂类,工厂模式使其创建过程延迟到子类进行。
- 通过SqlSessionFactoryBuilder产生出来的DefaultSqlSessionFactory。调用不同的openSession方法,生成产品SqlSession。
- 简单工厂LogFactory,针对不同的日志输出
3. 【单例模式】
属于:创建类模式
意图:保证一个类仅有一个实例,并提供一个访问它的全局访问点。
ErrorContext
ErrorContext.instance().resource("SQL Mapper Configuration");
LOCAL的静态实例变量使用了ThreadLocal修饰,也就是说它属于每个线程各自的数据,而在instance()方法中,先获取本线程的该实例,如果没有就创建该线程独有的ErrorContext。
public static ErrorContext instance() { return LOCAL.get(); }
4. 【代理模式】
属于:结构型模式
意图:为其他对象提供一种代理以控制对这个对象的访问。
MapperProxyFactory
代理模式(Proxy Pattern) :给某一个对象提供一个代 理,并由代理对象控制对原对象的引用。代理模式的英 文叫做Proxy或Surrogate,它是一种对象结构型模式。
代理模式包含如下角色:
- Subject: 抽象主题角色
- Proxy: 代理主题角色
- RealSubject: 真实主题角色
经典的实现InvocationHandler接口
public class MapperProxy<T> implements InvocationHandler, Serializable { ... public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethodInvoker> methodCache) { this.sqlSession = sqlSession; this.mapperInterface = mapperInterface; this.methodCache = methodCache; @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { try { if (Object.class.equals(method.getDeclaringClass())) { return method.invoke(this, args); } else { return cachedInvoker(method).invoke(proxy, method, args, sqlSession); } } catch (Throwable t) { throw ExceptionUtil.unwrapThrowable(t); } } } ... @Override public Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable { return mapperMethod.execute(sqlSession, args); }
在实现原数据MetaObject,用到了JavassistProxyFactory反射工具类
5. 【组合模式】
属于:结构型模式
意图:将对象组合成树形结构以表示"部分-整体"的层次结构。组合模式使得用户对单个对象和组合对象的使用具有一致性。
组合模式组合多个对象形成树形结构以表示“整体-部分”的结构层次。 在使用组合模式中需要注意一点也是组合模式最关键的地方:叶子对象和组合对象实现相同的接口。这就是组合模式能够将叶子节点和对象节点进行一致处理的原因。
动态SQL就是利用组合模式。
6. 【模板模式】
属于:行为型模式
意图:将一个类的接口转换成客户希望的另外一个接口。适配器模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。
前面说到的Executor,提供固定的方法,用来实现大部分的sql
7. 【适配器模式】
属于:结构型模式
意图:定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。
适配器模式(Adapter Pattern) :将一个接口转换成客户希望的另一个接口,适配器模式使接口不兼容的那些类可以一起工作,其别名为包装器(Wrapper)。适配器模式既可以作为类结构型模式,也可以作为对象结构型模式。
Log,不同的日志框架
8. 装饰模式
属于:结构型模式
意图:动态地给一个对象添加一些额外的职责。就增加功能来说,装饰器模式相比生成子类更为灵活。
装饰模式(Decorator Pattern) :动态地给一个对象增加一些额外的职责(Responsibility),就增加对象功能来说,装饰模式比生成子类实现更为灵活。其别名也可以称为包装器(Wrapper),与适配器模式的别名相同,但它们适用于不同的场合。根据翻译的不同,装饰模式也有人称之为“油漆工模式”,它是一种对象结构型模式。
在mybatis中,缓存的功能由根接口Cache(org.apache.ibatis.cache.Cache)定义。整个体系采用装饰器设计模式,数据存储和缓存的基本功能由PerpetualCache(org.apache.ibatis.cache.impl.PerpetualCache)永久缓存实现,然后通过一系列的装饰器来对PerpetualCache永久缓存进行缓存策略等方便的控制。
接口类:
实现类:
装饰类:
在原先的实现上,做一层扩展,符合设计模式的,对外开放,对内关闭的原则, Cache对象之间的引用顺序为:SynchronizedCache–>LoggingCache–>SerializedCache–>ScheduledCache–>LruCache–>PerpetualCache
@Test void shouldRemoveFirstItemInBeyondFiveEntries() { FifoCache cache = new FifoCache(new PerpetualCache("default")); cache.setSize(5); for (int i = 0; i < 5; i++) { cache.putObject(i, i); } assertEquals(0, cache.getObject(0)); cache.putObject(5, 5); assertNull(cache.getObject(0)); assertEquals(5, cache.getSize()); }
9. 【迭代器模型】
属于:行为型模式
意图:提供一种方法顺序访问一个聚合对象中各个元素, 而又无须暴露该对象的内部表示。
实现iterator接口的就算是迭代器模型
myBatis中PropertyTokenizer就是使用迭代器模型
public class PropertyTokenizer implements Iterator<PropertyTokenizer> { private String name; private final String indexedName; private String index; private final String children; public PropertyTokenizer(String fullname) { int delim = fullname.indexOf('.'); if (delim > -1) { name = fullname.substring(0, delim); children = fullname.substring(delim + 1); } else { name = fullname; children = null; } indexedName = name; delim = name.indexOf('['); if (delim > -1) { index = name.substring(delim + 1, name.length() - 1); name = name.substring(0, delim); } } public String getName() { return name; } public String getIndex() { return index; } public String getIndexedName() { return indexedName; } public String getChildren() { return children; } @Override public boolean hasNext() { return children != null; } @Override public PropertyTokenizer next() { return new PropertyTokenizer(children); } @Override public void remove() { throw new UnsupportedOperationException("Remove is not supported, as it has no meaning in the context of properties."); }