MyBatis 3.5版本中也广泛使用了多种设计模式,下面是其中一些主要使用的设计模式
MyBatis
一、构建器模式
- XMLConfigBuilder: 用于解析 MyBatis 配置文件
- XMLMapperBuilder: 用于解析映射文件(Mapper.xml)
- SqlSourceBuilder: 用于构建不同类型的 SqlSource 实现
- XMLConfigBuilder
XMLConfigBuilder
类用于解析 MyBatis 的全局配置文件,它的主要实现在 parse()
方法中。该方法通过 Java 的 SAX (Simple API for XML) 解析器来读取 XML 配置文件,并将配置项构建到 Configuration
对象中。
- XMLMapperBuilder
XMLMapperBuilder
类用于解析映射文件(Mapper.xml),它的主要实现在 parse()
方法中。该方法同样使用 SAX 解析器来读取映射文件,并根据不同的节点类型构建出相应的 MappedStatement
对象,最终添加到 Configuration
对象中。
- SqlSourceBuilder
SqlSourceBuilder
是一个抽象类,它定义了 parse()
方法的框架,用于解析不同类型的 SQL 节点(如 <select>
、<insert>
等)。具体的解析工作由其子类 XMLScriptBuilder
和 XMLStaticSqlSource
来完成。
XMLScriptBuilder
用于解析动态 SQL 节点,它会将节点内容构建成 SqlSource
接口的实现类 DynamicSqlSource
。
XMLStaticSqlSource
用于解析静态 SQL 节点,它会将节点内容作为字符串直接构建出 StaticSqlSource
实例。
以上这些构建器类通过组合使用,共同完成了对 MyBatis 全局配置文件和映射文件的解析和构建工作。
// 创建 XMLConfigBuilder
XMLConfigBuilder configBuilder = ...
// 解析全局配置文件,构建 Configuration 对象
Configuration config = configBuilder.parse();
// 创建 XMLMapperBuilder
XMLMapperBuilder mapperBuilder = new XMLMapperBuilder(config, ...);
// 解析映射文件,构建 MappedStatement 对象并添加到 Configuration 中
mapperBuilder.parse();
// 创建 SqlSourceBuilder
SqlSourceBuilder sqlSourceBuilder = new XMLScriptBuilder(...);
// 解析 SQL 节点,构建 SqlSource 实现
SqlSource sqlSource = sqlSourceBuilder.parse(...);
二、工厂模式
- SqlSessionFactory: 用于创建 SqlSession 实例
- ObjectFactory: 用于创建结果对象的实例
- TypeHandlerRegistry: 管理 TypeHandler 实例的注册与获取
- SqlSessionFactory
SqlSessionFactory 接口定义了 openSession() 方法,用于创建 SqlSession 对象。它的默认实现是 DefaultSqlSessionFactory 类。
在 DefaultSqlSessionFactory 中,通过构造函数或 openSessionFromDataSource() 方法创建 SqlSession 实例。以 openSession() 方法为例:
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Transaction tx = null;
try {
final Environment environment = configuration.getEnvironment();
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
final Executor executor = configuration.newExecutor(tx, execType);
return new DefaultSqlSession(configuration, executor, autoCommit);
} catch (Exception e) {
closeTransaction(tx); // may have fetched a connection so lets call close()
throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
- ObjectFactory
ObjectFactory 接口定义了创建结果对象实例的方法。默认实现是 DefaultObjectFactory 类,它使用反射机制创建对象实例。
public <T> T create(Class<T> type) {
return create(type, null, null);
}
public <T> T create(Class<T> type, List<Class<?>> constructorArgTypes, List<Object> constructorArgs) {
Class<?> classToCreate = resolveInterface(type);
// we know types are assignable
return (T) createInstance(classToCreate, constructorArgTypes, constructorArgs);
}
- TypeHandlerRegistry
TypeHandlerRegistry 类用于管理 TypeHandler 实例的注册和获取。它维护了一个 TypeHandler 的注册表,开发者也可以通过 register() 方法注册自定义的 TypeHandler。
public <T> void register(TypeInfo<T> typeInfo, TypeHandler<? extends T> typeHandler) {
register(MappedTypes.forTypeInfo(typeInfo), typeHandler);
}
private <T> void register(MappedTypes<T> mappedTypes, TypeHandler<? extends T> typeHandler) {
if (mappedTypes.hasUnknownType()) {
throw new IllegalArgumentException("Cannot add handlers for unknown types.");
}
bundlers.put(mappedTypes, typeHandler);
}
public <T> TypeHandler<T> getTypeHandler(Class<T> type) {
return getTypeHandler((Type) type, null);
}
public <T> TypeHandler<T> getTypeHandler(Type type, TypeHandlerRegistry instance) {
if (instance == null) {
instance = this;
}
final Map<TypeInfo<?>, TypeHandler<?>> typeHandlers = instance.getTypeHandlers();
TypeHandler<?> handler = typeHandlers.get(TypeParameterSetter.resolveType(type));
if (handler == null) {
handler = typeHandlers.get(MappedTypes.forType(type, null));
}
@SuppressWarnings("unchecked")
// This cast should be safe as we'll only get handlers of the expected type
TypeHandler<T> cachedHandler = (TypeHandler<T>) handler;
return cachedHandler;
}
三、代理模式
- MapperProxy: 基于 JDK 动态代理创建 Mapper 接口的代理实例
- ConnectionProxy: 为 Connection 对象创建代理
MyBatis 3.5版本中使用了 JDK 动态代理来实现代理模式,下面是关键源码分析:
- MapperProxy
MapperProxy 实现了 InvocationHandler 接口,用于创建 Mapper 接口的代理实例。关键代码如下:
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
// 如果是 Object 类中定义的方法,直接调用
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
// 忽略一些方法
} else if (isDefaultMethod(method)) {
return invokeDefaultMethod(proxy, method, args);
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
// 创建 MapperMethod 实例,它定义了如何执行映射语句
final MapperMethod mapperMethod = cachedMapperMethod(method);
return mapperMethod.execute(sqlSession, args);
}
MapperProxy 会缓存 MapperMethod 实例,在执行 Mapper 接口方法时,会调用对应 MapperMethod 的 execute 方法,该方法内部会进一步调用 SqlSession 的方法执行映射语句。
- ConnectionProxy
ConnectionProxy 实现了 InvocationHandler 接口,用于为 Connection 对象创建代理。关键代码如下:
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
// 忽略 Object 类定义的方法
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
// 忽略一些方法
} else if (CLOSE.hashCode() == method.getName().hashCode() && CLOSE.equals(method.getName())) {
return null;
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
// 如果是其他方法,执行原始 Connection 对象的对应方法
try {
return method.invoke(connection, args);
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
}
ConnectionProxy 的主要作用是拦截 Connection 的 close 方法,防止连接被过早关闭,其他方法调用都会转发给原始的 Connection 对象执行。通过代理模式,MyBatis 可以在原有功能的基础上添加一些额外的处理逻辑,如 SQL 缓存、延迟加载等,从而提高性能和扩展性。
四、 模板方法模式
- BaseExecutor: 提供了 update、query 等模板方法
- SimpleExecutor、ReuseExecutor 等继承并实现具体逻辑
下面我们来看 BaseExecutor
中 query
方法的实现:
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
// 获取缓存键
CacheKey cacheKey = createCacheKey(ms, parameter, rowBounds, boundSql);
return query(ms, parameter, rowBounds, resultHandler, cacheKey, boundSql);
}
private <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey cacheKey, BoundSql boundSql) throws SQLException {
// 从二级缓存中获取结果
List<E> list = resultHandlerCacheCtrl.getList(cacheKey, boundSql, resultHandler);
if (list != null) {
return list;
}
// 执行查询
list = delegateExecute(ms, parameter, rowBounds, resultHandler, cacheKey, boundSql);
// 存入二级缓存
resultHandlerCacheCtrl.storeList(cacheKey, list);
return list;
}
private <E> List<E> delegateExecute(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey cacheKey, BoundSql boundSql) throws SQLException {
// 由具体的执行器实现查询逻辑
return delegate.execute(ms, parameter, rowBounds, resultHandler, boundSql);
}
在 query
方法中,先根据传入的参数创建缓存键,然后尝试从二级缓存中获取结果。如果缓存中没有,就调用 delegateExecute
方法执行具体的查询逻辑。查询结果会被存入二级缓存中。
delegateExecute
方法只是一个模板方法,它将具体的查询逻辑委托给了子类的 execute
方法去实现。
下面以 SimpleExecutor
为例,看一下它是如何实现 execute
方法的:
public <E> List<E> execute(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
Configuration configuration = ms.getConfiguration();
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
stmt = handler.prepare(statement, transaction.getTimeout());
return handler.query(stmt, resultHandler);
}
SimpleExecutor
的 execute
方法首先创建了一个 StatementHandler
对象,然后调用它的 prepare
方法创建 Statement
。最后,再调用 StatementHandler
的 query
方法执行查询,并返回结果。
通过这种模板方法模式的设计,BaseExecutor
定义了一个算法的框架,而具体的执行步骤由子类去实现
五、装饰器模式
- Cache decorators: 为缓存提供装饰器扩展功能
- ResultLoaderMap: 延迟加载的实现使用了装饰器模式
在 MyBatis 3.5 版本中,装饰器模式主要应用在缓存和延迟加载功能上。下面分别对这两个部分的源码实现进行分析:
- Cache decorators: 为缓存提供装饰器扩展功能
MyBatis 使用装饰器模式为缓存功能提供了可扩展性,主要通过 CacheDecorator
抽象类和多个具体的装饰器类实现。
// CacheDecorator 是一个抽象装饰器类
public abstract class CacheDecorator implements Cache {
// 持有被装饰的缓存对象
protected final Cache delegate;
// 构造函数将被装饰对象传入
public CacheDecorator(Cache delegate) {
this.delegate = delegate;
}
// 其他方法的实现都会调用被装饰对象的相应方法
@Override
public String getId() {
return delegate.getId();
}
// ...
}
CacheDecorator
持有一个被装饰的 Cache
对象,并在其方法实现中调用该对象的相应方法。
具体的装饰器类继承自 CacheDecorator
并添加了额外的功能,例如:
LruCache
(最近最少使用缓存): 添加了维护缓存大小的功能ScheduledCache
(定时刷新缓存): 添加了定时刷新线程清理缓存的功能SerializedCache
(序列化缓存): 添加了缓存对象序列化功能LoggingCache
(日志缓存): 添加了缓存操作日志输出功能
通过装饰器模式的设计,MyBatis 可以很方便地扩展缓存的功能,只需要新增一个装饰器类就可以了。
- ResultLoaderMap: 延迟加载的实现使用了装饰器模式
MyBatis 使用 ResultLoaderMap
装饰器类为映射结果集提供了延迟加载功能。
public class ResultLoaderMap extends AbstractObjectDecorator {
// 持有被装饰对象的元数据信息
private final MetaObject metaResultObject;
// ...
public ResultLoaderMap(Object mapResult, Configuration configuration) {
// 将被装饰对象传入父类
super(mapResult);
metaResultObject = configuration.newMetaObject(mapResult);
}
// 重写 setValue 方法,实现延迟加载
@Override
public void setValue(Object prop, Object value) {
if (prop instanceof String) {
// 调用父类 setValue 设置属性值
super.setValue(prop, value);
} else {
// 通过 MetaObject 设置值,实现延迟加载
metaResultObject.setValue(prop, value);
}
}
}
ResultLoaderMap
继承自 AbstractObjectDecorator
,在构造函数中将被装饰对象传递给父类。
setValue
方法被重写,如果属性是 String 类型,则直接调用父类的 setValue
方法设置值;否则通过 MetaObject
设置值,实现延迟加载的逻辑。
六、代理模式
- 插件运行原理基于观察者模式
MyBatis 中有四大对象: Executor
、ParameterHandler
、ResultSetHandler
和 StatementHandler
。插件可以选择性地监听这四个对象的方法,并在方法执行前后进行拦截和处理。
这个过程是通过动态代理实现的,MyBatis 会为目标对象创建一个代理对象,插件可以在代理对象的方法执行前后注入自己的逻辑。
public Object plugin(Object target) {
// 生成代理对象
return Plugin.wrap(target, this);
}
public static Object wrap(Object target, Interceptor interceptor) {
// 获取目标对象的类
Class<?> type = target.getClass();
// 获取所有接口
Class<?>[] interfaces = getAllInterfaces(type, null);
if (interfaces.length > 0) {
// 创建代理对象
return Proxy.newProxyInstance(
type.getClassLoader(),
interfaces,
new Plugin(target, interceptor));
}
return target;
}
在 Plugin
的 wrap
方法中,通过 JDK 动态代理创建了一个目标对象的代理对象,代理对象的所有方法调用都会被 Plugin
实例的 invoke
方法拦截。
- 插件通过监听四大对象的方法调用来实现自身逻辑
以 Executor
插件为例,它需要实现 Interceptor
接口,并重写 intercept
方法:
@Intercepts({@Signature(type = Executor.class, method = "update", args = {...})})
public class ExamplePlugin implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
// 执行被拦截方法之前进行的处理
...
// 执行被拦截方法
Object result = invocation.proceed();
// 执行被拦截方法之后进行的处理
...
return result;
}
@Override
public Object plugin(Object target) {
// 返回目标对象的代理对象
return Plugin.wrap(target, this);
}
}
在 intercept
方法中,插件可以在执行被拦截方法之前和之后注入自己的逻辑。插件通过 invocation.proceed()
调用执行被拦截方法。
插件需要在 plugin
方法中返回目标对象的代理对象,这样 MyBatis 在使用目标对象时就会使用这个代理对象,从而实现了插件逻辑的注入。