上一篇博客中我们分析到了加载用户的别名,今天继续往下分析。
XMLConfiguration###parseConfiguration()
//调用各个方法进行解析成Configuration对象
private void parseConfiguration(XNode root) {
try {
//读取用户自定义属性
propertiesElement(root.evalNode("properties"));
//读取用户的设置
Properties settings = settingsAsProperties(root.evalNode("settings"));
//加载用户自定义的VFS实现
loadCustomVfs(settings);
//加载日志设置
loadCustomLogImpl(settings);
//加载用户定义的别名
typeAliasesElement(root.evalNode("typeAliases"));
//加载用户定义的插件
pluginElement(root.evalNode("plugins"));
//加载用户定义的对象工厂
objectFactoryElement(root.evalNode("objectFactory"));
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
//加载用户定义的反射对象工厂
reflectorFactoryElement(root.evalNode("reflectorFactory"));
//加载用户定义的其他设置
settingsElement(settings);
//加载用户定义的环境变量
environmentsElement(root.evalNode("environments"));
//读取databaseIdProvider
databaseIdProviderElement(root.evalNode("databaseIdProvider"));
//处理类型处理器
typeHandlerElement(root.evalNode("typeHandlers"));、
//处理mapper
mapperElement(root.evalNode("mappers"));
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}
XMLConfigBuilder###pluginElement()
// <plugins>
// <plugin interceptor="com.plugins.ExamplePlugin">
// <property name="someProperty" value="100"/>
// </plugin>
//</plugins>
private void pluginElement(XNode parent) throws Exception {
if (parent != null) {
//找到所有父节点为`plugins`的子节点
for (XNode child : parent.getChildren()) {
//获取其`interceptor`节点
String interceptor = child.getStringAttribute("interceptor");
//获取属性
Properties properties = child.getChildrenAsProperties();
//通过反射加载对应的类,然后通过`newInstance`产生对象
Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor).getDeclaredConstructor().newInstance();
//给这个对象注入属性
interceptorInstance.setProperties(properties);
//将对象放入`configuration`中
configuration.addInterceptor(interceptorInstance);
}
}
}
这里可以看到,逻辑还是很简单的,先加载配置的类,然后设置属性即可
这里的插件,其实就是
MyBatis
为了实现可插拔的其他功能,分别在:
Executor
ParameterHandler
ResultSetHandler
StatementHandler
进行一些指定操作之前/之后所执行的一些定制化操作,具体的实现方式便是责任链模式,因此这里只用简单的将其
add
即可,对于真正的使用后续我们会有详细分析。
XMLConfigBuilder###objectFactoryElement()
// <objectFactory type="cn.com.mybatis.test.CartObjectFactory">
// <property name="someProperty" value="100"/>
// </objectFactory>
private void objectFactoryElement(XNode context) throws Exception {
if (context != null) {
//获取type元素对应的值
String type = context.getStringAttribute("type");
//获取properties
Properties properties = context.getChildrenAsProperties();
//通过反射新建对象
ObjectFactory factory = (ObjectFactory) resolveClass(type).getDeclaredConstructor().newInstance();
//设置属性
factory.setProperties(properties);
//将对象赋值到configuration里面
configuration.setObjectFactory(factory);
}
}
这里看到,初始化的方法和
plugins
简直一模一样。那这个objectFactory
又是干嘛的呢?我们知道,
Mybatis
是一个半ORM
框架,那这里面就必然会涉及到新建对象,而MyBatis
的配置都是通过XML
配置,那么就必须通过反射来新建对象,然后某些时候我们可以希望在新建对象的时候加入一些其他操作,这个时候就可以通过修改ObjectFactory
来实现,因此可以知道ObjectFactory
是用来产生对象的。这里可以更加深入的看看
ObjectFactory
protected ObjectFactory objectFactory = new DefaultObjectFactory();
这是
Configuration
对象的初始化语句,可以看到如果我们不进行配置,则默认使用的是DefaultObjectFactory
而
DefaultObjectFactory
实现了ObjectFactory
接口,接口就4个方法://设置属性 default void setProperties(Properties properties) { // NOP } //创建对象 <T> T create(Class<T> type); //创建带参数的对象 <T> T create(Class<T> type, List<Class<?>> constructorArgTypes, List<Object> constructorArgs); //是否是集合类型 <T> boolean isCollection(Class<T> type);
可以验证我们的猜想,
ObjectFactory
是用来反射实现对象的
这里跳过objectWrapperFactoryElement
的分析,估计是MyBatis
后续的版本的功能,
MyBatis
官方文档中并没有提及到它,并且MyBatis
中的源码实现也就一句话,调用这个方法就抛出异常。。。
@Override
public ObjectWrapper getWrapperFor(MetaObject metaObject, Object object) {
throw new ReflectionException("The DefaultObjectWrapperFactory should never be called to provide an ObjectWrapper.");
}
XMLConfigBuilder###reflectorFactoryElement()
配置反射工厂的的代码和上面的一样,这里就不贴出来了。
这里主要说说反射工厂的功能:
反射工作的作用主要是用来做缓存使用,前面说过,
MyBatis
会将用户的Bean
通过反射产生一个ObjectWrapper
类,这个类包含了Bean
的所有信息以及方法,比如hashSetter
,setBeanProperty()
等功能,但是如果已经产生了一个这样的类的信息,就可以直接拿去就行,因此ReflectorFactory
主要作用就是一个简单工厂,有缓存则取缓存,没缓存则新建
XMLConfigBuilder###settingsElement()
private void settingsElement(Properties props) {
configuration.setAutoMappingBehavior(AutoMappingBehavior.valueOf(props.getProperty("autoMappingBehavior", "PARTIAL")));
configuration.setAutoMappingUnknownColumnBehavior(AutoMappingUnknownColumnBehavior.valueOf(props.getProperty("autoMappingUnknownColumnBehavior", "NONE")));
configuration.setCacheEnabled(booleanValueOf(props.getProperty("cacheEnabled"), true));
configuration.setProxyFactory((ProxyFactory) createInstance(props.getProperty("proxyFactory")));
//.....
}
这里代码比较多,主要就是通过
setter
设置各个属性,这些属性的类型基本都是boolean
XMLConfigBuilder###environmentsElement()
// <environments default="development">
// <environment id="development">
// <transactionManager type="JDBC">
// <property name="" value=""/>
// </transactionManager>
// <dataSource type="POOLED">
// <property name="driver" value="com.mysql.jdbc.Driver"/>
// <property name="username" value="xxx"/>
// <property name="password" value="xxx"/>
// </dataSource>
// </environment>
//</environments>
private void environmentsElement(XNode context) throws Exception {
if (context != null) {
//如果没有通过参数指定`environment`则使用默认的环境
if (environment == null) {
environment = context.getStringAttribute("default");
}
//读取子节点
for (XNode child : context.getChildren()) {
//获取id
String id = child.getStringAttribute("id");
//判断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.setEnvironment(environmentBuilder.build());
}
}
}
}
这里有一点比较奇怪的是,如果没有配置环境信息按道理来说应该报错的,因为对于数据库没有账号密码信息,
MyBatis
几乎是不能使用的,因此这里应该有一个检查机制才对,但是这里貌似没有
XMLConfigBuilder###databaseIdProviderElement()
//<databaseIdProvider type="DB_VENDOR">
// <property name="DB2" value="db2" />
// <property name="Oracle" value="oracle" />
// <property name="Adaptive Server Enterprise" value="sybase" />
// <property name="MySQL" value="mysql" />
//</databaseIdProvider>
private void databaseIdProviderElement(XNode context) throws Exception {
DatabaseIdProvider databaseIdProvider = null;
if (context != null) {
//获取类型信息
String type = context.getStringAttribute("type");
//和老版本兼容
if ("VENDOR".equals(type)) {
type = "DB_VENDOR";
}
//获取属性
Properties properties = context.getChildrenAsProperties();
//新建对象以及设置属性
databaseIdProvider = (DatabaseIdProvider) resolveClass(type).getDeclaredConstructor().newInstance();
databaseIdProvider.setProperties(properties);
}
//通过DataSource获取当前环境的databaseId
//调用方法为:DatabaseMetaData.getDatabaseProductName()
//返回结果为:Oracle (DataDirect)
Environment environment = configuration.getEnvironment();
if (environment != null && databaseIdProvider != null) {
String databaseId = databaseIdProvider.getDatabaseId(environment.getDataSource());
//设置databaseId
configuration.setDatabaseId(databaseId);
}
}
这里要说两点:
- 第一个是可以看到这里的通过反射新建对象都是通过
resolveClass()
实现的,而resolveClass()
都是先通过TypeAliasHandler
查找了对象,因此所有的type
字段都可以通过别名指定第二个是
databaseIdProvider
的主要作用是获取数据库的名称,主要代码为:private String getDatabaseProductName(DataSource dataSource) throws SQLException { Connection con = null; try { con = dataSource.getConnection(); DatabaseMetaData metaData = con.getMetaData(); return metaData.getDatabaseProductName(); //.... } }
便是获取数据库的名字,以及为名字设置简写。
本章暂时到这里,下一节分析MyBatis
配置中比较关键的TypeHandler
以及Mapper
配置的代码。
这里简单总结下:
- 从
MyBatis
代码中学排版,在看MyBatis
代码的时候,就觉得非常舒服,之前在看《代码整洁之道》这本书的时候,里面提到代码排版应该和报纸一样,从上到下,从左往右像看文章一样顺序看下来,而MyBatis
的排版便是这样,当一个方法调用另外一个方法的时候,一般被调用这个方法的定义就应该放在这个方法的后面,让人一看就能明白,就像看一篇小说一样。以后开发应该注意 MyBatis
的模块划分的非常细,真正的在奉行一个类只做一件事,这样对于大型项目来说,后期维护非常方便,应该学习