上一篇博客中我们分析到了加载用户的别名,今天继续往下分析。
//调用各个方法进行解析成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); } }
// <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
即可,对于真正的使用后续我们会有详细分析。
// <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."); }
配置反射工厂的的代码和上面的一样,这里就不贴出来了。
这里主要说说反射工厂的功能:
反射工作的作用主要是用来做缓存使用,前面说过, MyBatis
会将用户的 Bean
通过反射产生一个 ObjectWrapper
类,这个类包含了 Bean
的所有信息以及方法,比如 hashSetter
, setBeanProperty()
等功能,但是如果已经产生了一个这样的类的信息,就可以直接拿去就行,因此 ReflectorFactory
主要作用就是一个简单工厂,有缓存则取缓存,没缓存则新建
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
// <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
几乎是不能使用的,因此这里应该有一个检查机制才对,但是这里貌似没有
//<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
的模块划分的非常细,真正的在奉行一个类只做一件事,这样对于大型项目来说,后期维护非常方便,应该学习