上一篇主要讲了properties、settings、typeAliases三个节点的解析,其实还是比较容易的
这一篇主要讲下这个几个 plugins、factory相关的、environments
话不多说,还是接着看源码
plugins
首先还是先看下使用,具体可以参考官网地址
https://mybatis.org/mybatis-3/zh/configuration.html#plugins
1. 先定义实现了Interceptor接口的实现类
@Intercepts({@Signature(
type= Executor.class,
method = "update",
args = {MappedStatement.class,Object.class})})
public class ExamplePlugin implements Interceptor {
private Properties properties = new Properties();
@Override
public Object intercept(Invocation invocation) throws Throwable {
// implement pre processing if need
Object returnObject = invocation.proceed();
// implement post processing if need
return returnObject;
}
@Override
public void setProperties(Properties properties) {
this.properties = properties;
}
}
2. 在mybatis-config.xml文件中进行配置
<plugins>
<plugin interceptor="org.mybatis.example.ExamplePlugin">
<property name="someProperty" value="100"/>
</plugin>
</plugins>
其中properties 到时mybatis会拿到你的配置设置进去
这个主要是mybatis提供给我们进行方法拦截的功能,分页自动拦截就发生在这些插件中
接着看下源码的解析
private void pluginElement(XNode parent) throws Exception {
if (parent != null) {
//循环插件列表
for (XNode child : parent.getChildren()) {
//获取拦截器全类名
String interceptor = child.getStringAttribute("interceptor");
//获取你配置的属性值,到时会调用Interceptor接口的setProperties传给你定义的拦截器
Properties properties = child.getChildrenAsProperties();
//解析拦截器,这里别名就发挥作用了,你可以定义一些简短的名字,在引用的时候就比较方便了
Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor).newInstance();
interceptorInstance.setProperties(properties);
//添加到配置类中进行统一管理
configuration.addInterceptor(interceptorInstance);
}
}
}
仍然是一目了然,其实还是挺简单的,别名注册测就发挥了作用,可以看看resolveClass方法做了什么
protected Class<?> resolveClass(String alias) {
if (alias == null) {
return null;
}
try {
//从别名注册里面解析一个类出来
return resolveAlias(alias);
} catch (Exception e) {
throw new BuilderException("Error resolving class. Cause: " + e, e);
}
}
再到具体的解析方法
protected Class<?> resolveAlias(String alias) {
//其实就是调用前面的注册map进行获取了
return typeAliasRegistry.resolveAlias(alias);
}
再稍微看下configuration里面的Interceptor是如何管理的,后面会再说到这个东西,因为这个是sql运行过程中一个比较核心的拦截点
configuration使用了InterceptorChain对象同时使用了组合模式来统一管理所有的拦截器,如下
/**
* @author Clinton Begin
* 这里使用了一个组合模式来统一管理Interceptor对象
*/
public class InterceptorChain {
/**
* 封装了拦截器链表
*/
private final List<Interceptor> interceptors = new ArrayList<Interceptor>();
/**
* 统一进行代理,所以要看哪里可以代理,直接看这里就行了
* @param target
* @return
*/
public Object pluginAll(Object target) {
for (Interceptor interceptor : interceptors) {
target = interceptor.plugin(target);
}
return target;
}
/**
* 添加一个拦截器
* @param interceptor
*/
public void addInterceptor(Interceptor interceptor) {
interceptors.add(interceptor);
}
/**
* 获取不可修改的拦截器链
* @return
*/
public List<Interceptor> getInterceptors() {
return Collections.unmodifiableList(interceptors);
}
}
一般进行代理都是使用 Plugin.wrap(target, this);即可,这是mybatis提供的插件代理生成,可以大概看看里面做了什么
public static Object wrap(Object target, Interceptor interceptor) {
//获取前面接口
Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
//获取代理类,通过责任链传输过来的,一层层进行包装
Class<?> type = target.getClass();
//获取所有的接口
Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
if (interfaces.length > 0) {
//使用jdk进行动态代理,InvocationHandler为Plugin当前这个类
return Proxy.newProxyInstance(
type.getClassLoader(),
interfaces,
new Plugin(target, interceptor, signatureMap));
}
return target;
}
比如上面的 ExamplePlugin 就是一个例子了,这个代理逻辑等后面sql执行的时候再具体分析,当然有兴趣的可以自行分析
factory相关的
这个我们一般不会用,所以只会讲两个比较简单的,如下
官网参考地址:https://mybatis.org/mybatis-3/zh/configuration.html#objectFactory
objectFactory:这个接口是mybatis用来创建对象时候使用的,他提供了一个默认实现DefaultObjectFactory,我们一般不会定义,当然了你也可以进行定义
1,先提供一个实现类
public class ExampleObjectFactory extends DefaultObjectFactory {
@Override
public <T> T create(Class<T> type) {
return super.create(type);
}
@Override
public <T> T create(Class<T> type, List<Class<?>> constructorArgTypes, List<Object> constructorArgs) {
return super.create(type, constructorArgTypes, constructorArgs);
}
@Override
public void setProperties(Properties properties) {
super.setProperties(properties);
}
@Override
public <T> boolean isCollection(Class<T> type) {
return Collection.class.isAssignableFrom(type);
}}
2. 在mybatis-config.xml进行注册
<objectFactory type="org.mybatis.example.ExampleObjectFactory">
<property name="someProperty" value="100"/>
</objectFactory>
这样写后mybatis就会用你的对象工厂来生成对象了,但是我们一般不会自己去定义,当然了如果你有需要也是可以提供的
再来看下源码的解析
if (context != null) {
//获取类型
String type = context.getStringAttribute("type");
//获取properties
Properties properties = context.getChildrenAsProperties();
//依然使用了别名处理器进行解析
ObjectFactory factory = (ObjectFactory) resolveClass(type).newInstance();
factory.setProperties(properties);
//设置给configuration对象
configuration.setObjectFactory(factory);
}
}
就这样,一目了然,没有太多可以说的
至于objectWrapperFactory reflectorFactory这里就不说了,原理都是一样的,我们一般不会去自己设置,有兴趣的话可以自己参考下官网和解析
environments
这是环境相关的一个类,主要就是数据源和事务工厂了,不得不说mybatis的抽象还是比我们普通人要好的
官网配置地址:https://mybatis.org/mybatis-3/zh/configuration.html#environments
<environments default="development">
<environment id="development">
<transactionManager type="JDBC">
<property name="..." value="..."/>
</transactionManager>
<dataSource type="POOLED">
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</dataSource>
</environment>
</environments>
这里主要是讲几个比较核心的接口
DataSourceFactory:数据源工厂,用来获取数据源的,想必大家对这个还是比较熟悉的,没错,mybatis自己提供了数据库连接池的实现,接口就这样
public interface DataSourceFactory {
/**
* 设置你配置的熟悉
* @param props
*/
void setProperties(Properties props);
/**
* 获取数据源
* @return
*/
DataSource getDataSource();
}
TransactionFactory:事务工厂,主要是用来获取Transaction事务对象的,看看接口的样子
public interface TransactionFactory {
/**
* Sets transaction factory custom properties.
* @param props
*/
void setProperties(Properties props);
/**
* Creates a {@link Transaction} out of an existing connection.
* @param conn Existing database connection
* @return Transaction
* @since 3.1.0
*/
Transaction newTransaction(Connection conn);
/**
* Creates a {@link Transaction} out of a datasource.
* @param dataSource DataSource to take the connection from
* @param level Desired isolation level
* @param autoCommit Desired autocommit
* @return Transaction
* @since 3.1.0
*/
Transaction newTransaction(DataSource dataSource, TransactionIsolationLevel level, boolean autoCommit);
}
Transaction:事务管理对象,用来处理一次事务过程中所需要的属性和对象已经操作的,看到接口的定义你就明白了
public interface Transaction {
/**
* Retrieve inner database connection
* @return DataBase connection
* @throws SQLException
*/
Connection getConnection() throws SQLException;
/**
* Commit inner database connection.
* @throws SQLException
*/
void commit() throws SQLException;
/**
* Rollback inner database connection.
* @throws SQLException
*/
void rollback() throws SQLException;
/**
* Close inner database connection.
* @throws SQLException
*/
void close() throws SQLException;
/**
* Get transaction timeout if set
* @throws SQLException
*/
Integer getTimeout() throws SQLException;
}
这些方法是不是都很熟悉,就是在一次事务过程中会进行的一些提交....
最后一个对象是 Environment : 也就是环境对象,对于mybatis来说,环境对象就是数据源和事务处理等,如下
public final class Environment {
private final String id;
private final TransactionFactory transactionFactory;
private final DataSource dataSource;
public Environment(String id, TransactionFactory transactionFactory, DataSource dataSource) {
if (id == null) {
throw new IllegalArgumentException("Parameter 'id' must not be null");
}
if (transactionFactory == null) {
throw new IllegalArgumentException("Parameter 'transactionFactory' must not be null");
}
this.id = id;
if (dataSource == null) {
throw new IllegalArgumentException("Parameter 'dataSource' must not be null");
}
this.transactionFactory = transactionFactory;
this.dataSource = dataSource;
}
上面mybatis就用了很多设计模式:比如抽象工厂、建造者模式等
最后再简单看下源码解析,其实源码解析一直都不难
private void environmentsElement(XNode context) throws Exception {
if (context != null) {
//当前环境没配置的时候拿默认的
if (environment == null) {
environment = context.getStringAttribute("default");
}
for (XNode child : context.getChildren()) {
//循环环境,每个环境都有一个id,必须保证不一样即可
String id = child.getStringAttribute("id");
//如果是不指定就是上面默认的
if (isSpecifiedEnvironment(id)) {
//解析事务工厂
TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager"));
//解析数据源工厂
DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource"));
//从数据源工厂获取数据源
DataSource dataSource = dsFactory.getDataSource();
//使用建造者模式构建环境对象
Environment.Builder environmentBuilder = new Environment.Builder(id)
.transactionFactory(txFactory)
.dataSource(dataSource);
//放到configuration进行管理
configuration.setEnvironment(environmentBuilder.build());
}
}
}
}
下面的子标签就不看了,都是比较简单的,这里主要是学习隔离的设计,mybatis抽象出了数据源工厂,事务工厂,事务对象等等
好了这一篇就到这里,内容看起来挺多,但是源码看懂并不复杂,复杂的是对mybatis的抽象进行理解,程序的美妙之处是在设计上面,而不是单纯的实现
好的设计可以提供更多的扩展性给其他开发者,而且维护起来也会更方便,基本都是在跟接口打交道进行维护的
这才是我们学习的重点