MyBatis 启动之 XMLConfigBuilder 解析配置文件(二)
前言
BaseBuilder所有子类
XMLConfigBuilder
是BaseBuilder
(解析中会涉及到讲解)的其中一个子类,它的作用是把MyBatis的XML及相关配置解析出来,然后保存到Configuration
中。本文就解析过程按照执行顺序进行分析,掌握常用配置的解析原理。
使用
调用XMLConfigBuilder
进行解析,要进行两步操作,上篇文章中【MyBatis之启动分析(一)】有提到。
实例化XMLConfigBuilder
对象。
private XMLConfigBuilder(XPathParser parser, String environment, Properties props) { // 调用父类的构造方法 super(new Configuration()); ErrorContext.instance().resource("SQL Mapper Configuration"); this.configuration.setVariables(props); this.parsed = false; this.environment = environment; this.parser = parser; }
实例化Configuration
通过new Configuration()
的方式实例化:
typeAliasRegistry
是一个类型别名注册器,实现原理就是维护一份HashMap
,别名作为key
,类的全限定名作为value
。这里将框架中使用的类注册到类型别名注册器中。TypeAliasRegistry.registerAlias
代码如下:
public void registerAlias(String alias, Class<?> value) { if (alias == null) { throw new TypeException("The parameter alias cannot be null"); } // issue #748 // 在验证是否存在key和保存kv前,统一将key转换成小写 String key = alias.toLowerCase(Locale.ENGLISH); if (TYPE_ALIASES.containsKey(key) && TYPE_ALIASES.get(key) != null && !TYPE_ALIASES.get(key).equals(value)) { // 当注册的类型已存在时,抛出异常 throw new TypeException("The alias '" + alias + "' is already mapped to the value '" + TYPE_ALIASES.get(key).getName() + "'."); } // TYPE_ALIASES 为定义的一个HashMap TYPE_ALIASES.put(key, value); }
在实例化Configuration
类过程中,在该类里除了实例化了TypeAliasRegistry
还实例化了另外一个下面用到的的类:
// 类型处理器注册器 protected final TypeAliasRegistry typeAliasRegistry = new TypeAliasRegistry();
TypeHandlerRegistry
和TypeAliasRegistry
实例化逻辑相似,里面注册了一些常用类型和处理器,代码易懂。TypeHandlerRegistry
的属性
// jdbc类型和TypeHandler的映射关系,key必须是JdbcType的枚举类型,读取结果集数据时,将jdbc类型转换成java类型 private final Map<JdbcType, TypeHandler<?>> JDBC_TYPE_HANDLER_MAP = new EnumMap<JdbcType, TypeHandler<?>>(JdbcType.class); // Java类型与JdbcType类型的键值对,存在一对多的映射关系 private final Map<Type, Map<JdbcType, TypeHandler<?>>> TYPE_HANDLER_MAP = new ConcurrentHashMap<Type, Map<JdbcType, TypeHandler<?>>>(); // 没有相应的类型处理器时,使用的处理器 private final TypeHandler<Object> UNKNOWN_TYPE_HANDLER = new UnknownTypeHandler(this); // 类型处理器类类型和类型处理器的映射关系 private final Map<Class<?>, TypeHandler<?>> ALL_TYPE_HANDLERS_MAP = new HashMap<Class<?>, TypeHandler<?>>(); // 空处理器的值,用来做校验 private static final Map<JdbcType, TypeHandler<?>> NULL_TYPE_HANDLER_MAP = Collections.emptyMap(); // 默认枚举类型处理器 private Class<? extends TypeHandler> defaultEnumTypeHandler = EnumTypeHandler.class;
TypeHandlerRegistry
构造函数:
public TypeHandlerRegistry() { register(Boolean.class, new BooleanTypeHandler()); register(boolean.class, new BooleanTypeHandler()); register(JdbcType.BOOLEAN, new BooleanTypeHandler()); register(JdbcType.BIT, new BooleanTypeHandler()); register(Byte.class, new ByteTypeHandler()); register(byte.class, new ByteTypeHandler()); register(JdbcType.TINYINT, new ByteTypeHandler()); register(Short.class, new ShortTypeHandler()); register(short.class, new ShortTypeHandler()); register(JdbcType.SMALLINT, new ShortTypeHandler()); register(Integer.class, new IntegerTypeHandler()); register(int.class, new IntegerTypeHandler()); register(JdbcType.INTEGER, new IntegerTypeHandler()); register(Long.class, new LongTypeHandler()); register(long.class, new LongTypeHandler()); register(Float.class, new FloatTypeHandler()); register(float.class, new FloatTypeHandler()); register(JdbcType.FLOAT, new FloatTypeHandler()); register(Double.class, new DoubleTypeHandler()); register(double.class, new DoubleTypeHandler()); register(JdbcType.DOUBLE, new DoubleTypeHandler()); register(Reader.class, new ClobReaderTypeHandler()); register(String.class, new StringTypeHandler()); register(String.class, JdbcType.CHAR, new StringTypeHandler()); register(String.class, JdbcType.CLOB, new ClobTypeHandler()); register(String.class, JdbcType.VARCHAR, new StringTypeHandler()); register(String.class, JdbcType.LONGVARCHAR, new ClobTypeHandler()); register(String.class, JdbcType.NVARCHAR, new NStringTypeHandler()); register(String.class, JdbcType.NCHAR, new NStringTypeHandler()); register(String.class, JdbcType.NCLOB, new NClobTypeHandler()); register(JdbcType.CHAR, new StringTypeHandler()); register(JdbcType.VARCHAR, new StringTypeHandler()); register(JdbcType.CLOB, new ClobTypeHandler()); register(JdbcType.LONGVARCHAR, new ClobTypeHandler()); register(JdbcType.NVARCHAR, new NStringTypeHandler()); register(JdbcType.NCHAR, new NStringTypeHandler()); register(JdbcType.NCLOB, new NClobTypeHandler()); register(Object.class, JdbcType.ARRAY, new ArrayTypeHandler()); register(JdbcType.ARRAY, new ArrayTypeHandler()); register(BigInteger.class, new BigIntegerTypeHandler()); register(JdbcType.BIGINT, new LongTypeHandler()); register(BigDecimal.class, new BigDecimalTypeHandler()); register(JdbcType.REAL, new BigDecimalTypeHandler()); register(JdbcType.DECIMAL, new BigDecimalTypeHandler()); register(JdbcType.NUMERIC, new BigDecimalTypeHandler()); register(InputStream.class, new BlobInputStreamTypeHandler()); register(Byte[].class, new ByteObjectArrayTypeHandler()); register(Byte[].class, JdbcType.BLOB, new BlobByteObjectArrayTypeHandler()); register(Byte[].class, JdbcType.LONGVARBINARY, new BlobByteObjectArrayTypeHandler()); register(byte[].class, new ByteArrayTypeHandler()); register(byte[].class, JdbcType.BLOB, new BlobTypeHandler()); register(byte[].class, JdbcType.LONGVARBINARY, new BlobTypeHandler()); register(JdbcType.LONGVARBINARY, new BlobTypeHandler()); register(JdbcType.BLOB, new BlobTypeHandler()); register(Object.class, UNKNOWN_TYPE_HANDLER); register(Object.class, JdbcType.OTHER, UNKNOWN_TYPE_HANDLER); register(JdbcType.OTHER, UNKNOWN_TYPE_HANDLER); register(Date.class, new DateTypeHandler()); register(Date.class, JdbcType.DATE, new DateOnlyTypeHandler()); register(Date.class, JdbcType.TIME, new TimeOnlyTypeHandler()); register(JdbcType.TIMESTAMP, new DateTypeHandler()); register(JdbcType.DATE, new DateOnlyTypeHandler()); register(JdbcType.TIME, new TimeOnlyTypeHandler()); register(java.sql.Date.class, new SqlDateTypeHandler()); register(java.sql.Time.class, new SqlTimeTypeHandler()); register(java.sql.Timestamp.class, new SqlTimestampTypeHandler()); // mybatis-typehandlers-jsr310 // 是否包含日期,时间相关的Api,通过判断是否加载java.time.Clock作为依据 if (Jdk.dateAndTimeApiExists) { this.register(Instant.class, InstantTypeHandler.class); this.register(LocalDateTime.class, LocalDateTimeTypeHandler.class); this.register(LocalDate.class, LocalDateTypeHandler.class); this.register(LocalTime.class, LocalTimeTypeHandler.class); this.register(OffsetDateTime.class, OffsetDateTimeTypeHandler.class); this.register(OffsetTime.class, OffsetTimeTypeHandler.class); this.register(ZonedDateTime.class, ZonedDateTimeTypeHandler.class); this.register(Month.class, MonthTypeHandler.class); this.register(Year.class, YearTypeHandler.class); this.register(YearMonth.class, YearMonthTypeHandler.class); this.register(JapaneseDate.class, JapaneseDateTypeHandler.class); } // issue #273 register(Character.class, new CharacterTypeHandler()); register(char.class, new CharacterTypeHandler()); }
里面调用了两个register()
重载方法, type + handler
参的TypeHandlerRegistry.register(Class<T> javaType, TypeHandler<? extends T> typeHandler)
和 type + jdbc type + handler
参的TypeHandlerRegistry.register(Class<T> type, JdbcType jdbcType, TypeHandler<? extends T> handler)
// java type + handler public <T> void register(Class<T> javaType, TypeHandler<? extends T> typeHandler) { register((Type) javaType, typeHandler); } private <T> void register(Type javaType, TypeHandler<? extends T> typeHandler) { MappedJdbcTypes mappedJdbcTypes = typeHandler.getClass().getAnnotation(MappedJdbcTypes.class); if (mappedJdbcTypes != null) { for (JdbcType handledJdbcType : mappedJdbcTypes.value()) { register(javaType, handledJdbcType, typeHandler); } if (mappedJdbcTypes.includeNullJdbcType()) { register(javaType, null, typeHandler); } } else { register(javaType, null, typeHandler); } } // java type + jdbc type + handler public <T> void register(Class<T> type, JdbcType jdbcType, TypeHandler<? extends T> handler) { register((Type) type, jdbcType, handler); } // type + handler 和 type + jdbc type + handler 最终都调用此方法 private void register(Type javaType, JdbcType jdbcType, TypeHandler<?> handler) { if (javaType != null) { // 当 javaType 不为空时, 获取 java 类型的的映射 Map<JdbcType, TypeHandler<?>> map = TYPE_HANDLER_MAP.get(javaType); if (map == null || map == NULL_TYPE_HANDLER_MAP) { // 若映射为空,新建一个映射关系 map = new HashMap<JdbcType, TypeHandler<?>>(); // 保存至类型处理器映射关系中 TYPE_HANDLER_MAP.put(javaType, map); } // 保存jdbcType和处理器关系,完成 java类型,jdbc类型,处理器三者之间的注册 map.put(jdbcType, handler); } // 保存处理器信息中 ALL_TYPE_HANDLERS_MAP.put(handler.getClass(), handler); } // MappedJdbcTypes 注解 @Documented @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) public @interface MappedJdbcTypes { JdbcType[] value(); boolean includeNullJdbcType() default false; }
type + handler
方法:先获取处理器的MappedJdbcTypes
注解(自定义处理器注解),若注解的value
值不为空时,由于该值为JdbcType[]
类型,所以for
循环javaType+jdbcType+TypeHandler
注册,若includeNullJdbcType
(jdbcType
是否包含null
)为true
,默认值为false
,注册到相应映射中。若注解的value
为null
,直接调用注册操作,里面不会注册type + jdbc type + handler
关系。type + jdbc type + handler
方法:该方法将java类强制转换为java.lang.reflect.Type
类型,然后调用最终注册的方法。
调用父类BaseBuilder
的构造方法
BaseBuilder
定义有三个属性
protected final Configuration configuration; // 类型别名注册器 protected final TypeAliasRegistry typeAliasRegistry; // 类型处理器注册器 protected final TypeHandlerRegistry typeHandlerRegistry;
BaseBuilder
构造方法
public BaseBuilder(Configuration configuration) { this.configuration = configuration; this.typeAliasRegistry = this.configuration.getTypeAliasRegistry(); this.typeHandlerRegistry = this.configuration.getTypeHandlerRegistry(); }
这里属性,就是上面讲解到的。
调用 XMLConfigBuilder.parse()
作为解析入口。
parse()
实现配置文件是否解析过
public Configuration parse() { // 若parsed为true,配置文件解析过 if (parsed) { throw new BuilderException("Each XMLConfigBuilder can only be used once."); } // 标志已解析过 parsed = true; // 从根节点 configuration 开始解析 parseConfiguration(parser.evalNode("/configuration")); return configuration; }
解析/configuration
里的配置
private void parseConfiguration(XNode root) { try { //issue #117 read properties first propertiesElement(root.evalNode("properties")); Properties settings = settingsAsProperties(root.evalNode("settings")); loadCustomVfs(settings); typeAliasesElement(root.evalNode("typeAliases")); pluginElement(root.evalNode("plugins")); objectFactoryElement(root.evalNode("objectFactory")); objectWrapperFactoryElement(root.evalNode("objectWrapperFactory")); reflectorFactoryElement(root.evalNode("reflectorFactory")); settingsElement(settings); // read it after objectFactory and objectWrapperFactory issue #631 environmentsElement(root.evalNode("environments")); databaseIdProviderElement(root.evalNode("databaseIdProvider")); typeHandlerElement(root.evalNode("typeHandlers")); mapperElement(root.evalNode("mappers")); } catch (Exception e) { throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e); } }
从上面源码中,不难看出这里是解析/configuration
中的各个子节点。
properties 节点解析
properties
配置方式
<!-- 方法一 --> <properties> <property name="username" value="${jdbc.username}" /> </properties> <!-- 方法二 --> <properties resource="xxxConfig.properties"> </properties> <!-- 方法三 --> <properties url="file:///D:/xxxConfig.properties"> </properties>
propertiesElement()
方法
private void propertiesElement(XNode context) throws Exception { if (context != null) { // 获取 propertie 节点,并保存 Properties 中 Properties defaults = context.getChildrenAsProperties(); // 获取 resource 的值 String resource = context.getStringAttribute("resource"); // 获取 url 的值 String url = context.getStringAttribute("url"); if (resource != null && url != null) { throw new BuilderException("The properties element cannot specify both a URL and a resource based property file reference. Please specify one or the other."); } if (resource != null) { defaults.putAll(Resources.getResourceAsProperties(resource)); } else if (url != null) { defaults.putAll(Resources.getUrlAsProperties(url)); } Properties vars = configuration.getVariables(); if (vars != null) { defaults.putAll(vars); } // 将解析的值保存到 XPathParser 中 parser.setVariables(defaults); // 将解析的值保存到 Configuration 中 configuration.setVariables(defaults); } }
从上面源码中,resource
和url
的配置形式不允许同时存在,否则抛出BuilderException
异常。先解析propertie
的配置值,再解析resource
或url
的值。当propertie
存在与resource
或url
相同的key
时,propertie
的配置会被覆盖,应为Properties
实现的原理就是继承的Hashtable
类来实现的。
settings 节点解析
settings
配置方式
<settings> <setting name="cacheEnabled" value="true" /> ...... </settings>
设置中各项的意图、默认值
settingsAsProperties()
方法
private Properties settingsAsProperties(XNode context) { if (context == null) { return new Properties(); } // 获取setting节点的name和value,并保存至Properties返回 Properties props = context.getChildrenAsProperties(); // Check that all settings are known to the configuration class // 创建Configuration的MetaClass MetaClass metaConfig = MetaClass.forClass(Configuration.class, localReflectorFactory); // 校验Configuration中是否有setting设置的name值 for (Object key : props.keySet()) { if (!metaConfig.hasSetter(String.valueOf(key))) { throw new BuilderException("The setting " + key + " is not known. Make sure you spelled it correctly (case sensitive)."); } } return props; }
这里获取到setting
的值,并返回Properties
对象。然后做配置的name
是否合法。org.apache.ibatis.reflection.MetaClass
类是保存着一个利用反射获取到的类信息,metaConfig.hasSetter(String.valueOf(key))
是判断metaConfig
对象中是否包含key
属性。
vfsImpl()
方法
private void loadCustomVfs(Properties props) throws ClassNotFoundException { String value = props.getProperty("vfsImpl"); if (value != null) { String[] clazzes = value.split(","); for (String clazz : clazzes) { if (!clazz.isEmpty()) { @SuppressWarnings("unchecked") Class<? extends VFS> vfsImpl = (Class<? extends VFS>)Resources.classForName(clazz); configuration.setVfsImpl(vfsImpl); } } } }
该方法是解析虚拟文件系统配置,用来加载自定义虚拟文件系统的资源。类保存在Configuration.vfsImpl
中。
settingsElement()
方法
这个方法的作用就是将解析的settings
设置到 configuration
中
private void settingsElement(Properties props) throws Exception { 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"))); configuration.setLazyLoadingEnabled(booleanValueOf(props.getProperty("lazyLoadingEnabled"), false)); configuration.setAggressiveLazyLoading(booleanValueOf(props.getProperty("aggressiveLazyLoading"), false)); configuration.setMultipleResultSetsEnabled(booleanValueOf(props.getProperty("multipleResultSetsEnabled"), true)); configuration.setUseColumnLabel(booleanValueOf(props.getProperty("useColumnLabel"), true)); configuration.setUseGeneratedKeys(booleanValueOf(props.getProperty("useGeneratedKeys"), false)); configuration.setDefaultExecutorType(ExecutorType.valueOf(props.getProperty("defaultExecutorType", "SIMPLE"))); configuration.setDefaultStatementTimeout(integerValueOf(props.getProperty("defaultStatementTimeout"), null)); configuration.setDefaultFetchSize(integerValueOf(props.getProperty("defaultFetchSize"), null)); configuration.setMapUnderscoreToCamelCase(booleanValueOf(props.getProperty("mapUnderscoreToCamelCase"), false)); configuration.setSafeRowBoundsEnabled(booleanValueOf(props.getProperty("safeRowBoundsEnabled"), false)); configuration.setLocalCacheScope(LocalCacheScope.valueOf(props.getProperty("localCacheScope", "SESSION"))); configuration.setJdbcTypeForNull(JdbcType.valueOf(props.getProperty("jdbcTypeForNull", "OTHER"))); configuration.setLazyLoadTriggerMethods(stringSetValueOf(props.getProperty("lazyLoadTriggerMethods"), "equals,clone,hashCode,toString")); configuration.setSafeResultHandlerEnabled(booleanValueOf(props.getProperty("safeResultHandlerEnabled"), true)); configuration.setDefaultScriptingLanguage(resolveClass(props.getProperty("defaultScriptingLanguage"))); @SuppressWarnings("unchecked") Class<? extends TypeHandler> typeHandler = (Class<? extends TypeHandler>)resolveClass(props.getProperty("defaultEnumTypeHandler")); configuration.setDefaultEnumTypeHandler(typeHandler); configuration.setCallSettersOnNulls(booleanValueOf(props.getProperty("callSettersOnNulls"), false)); configuration.setUseActualParamName(booleanValueOf(props.getProperty("useActualParamName"), true)); configuration.setReturnInstanceForEmptyRow(booleanValueOf(props.getProperty("returnInstanceForEmptyRow"), false)); configuration.setLogPrefix(props.getProperty("logPrefix")); @SuppressWarnings("unchecked") Class<? extends Log> logImpl = (Class<? extends Log>)resolveClass(props.getProperty("logImpl")); configuration.setLogImpl(logImpl); configuration.setConfigurationFactory(resolveClass(props.getProperty("configurationFactory"))); }
typeAliases 节点解析
typeAliases
配置方式
<typeAliases> <package name="com.ytao.main.model"/> // 或 <typeAlias type="com.ytao.main.model.Student" alias="student"/> <typeAlias type="com.ytao.main.model.Person"/> </typeAliases>
该节点是配置类和别名的关系
package
节点是配置整个包下的类typeAlias
节点是指定配置单个类,type
为必填值且为类全限定名,alias
为选填。配置后,是该类时,可直接使用别名。
typeAliasesElement()
方法
private void typeAliasesElement(XNode parent) { if (parent != null) { for (XNode child : parent.getChildren()) { if ("package".equals(child.getName())) { // 以 package 方式配置 String typeAliasPackage = child.getStringAttribute("name"); configuration.getTypeAliasRegistry().registerAliases(typeAliasPackage); } else { // 以 alias 方式配置 String alias = child.getStringAttribute("alias"); String type = child.getStringAttribute("type"); try { Class<?> clazz = Resources.classForName(type); if (alias == null) { typeAliasRegistry.registerAlias(clazz); } else { typeAliasRegistry.registerAlias(alias, clazz); } } catch (ClassNotFoundException e) { throw new BuilderException("Error registering typeAlias for '" + alias + "'. Cause: " + e, e); } } } } }
使用 package 配置
当扫描package
时,获取到包名后TypeAliasRegistry.registerAliases(typeAliasPackage)
public void registerAliases(String packageName){ registerAliases(packageName, Object.class); } public void registerAliases(String packageName, Class<?> superType){ ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<Class<?>>(); // 获取 package 下所有已 .class 结尾的文件 resolverUtil.find(new ResolverUtil.IsA(superType), packageName); // 获取扫描出来的类 Set<Class<? extends Class<?>>> typeSet = resolverUtil.getClasses(); for(Class<?> type : typeSet){ // Ignore inner classes and interfaces (including package-info.java) // Skip also inner classes. See issue #6 // 过滤类 if (!type.isAnonymousClass() && !type.isInterface() && !type.isMemberClass()) { registerAlias(type); } } }
扫描到指定package
下所有以.class
结尾文件的类,并保存至Set集合中,然后遍历集合,过滤掉没有名称,接口,和底层特定类。最后TypeAliasRegistry.registerAlias(Class<?> type)
注册到别名注册器中。
public void registerAlias(Class<?> type) { // 使用类的 simpleName 作为别名,也就是默认的别名命名规则 String alias = type.getSimpleName(); Alias aliasAnnotation = type.getAnnotation(Alias.class); if (aliasAnnotation != null) { alias = aliasAnnotation.value(); } // 上面分析的最终注册的方法 registerAlias(alias, type); }
通过类注册到注册器中时,如果该注册类有使用@Alias
(org.apache.ibatis.type.Alias
)注解,那么XML配置中配置的别名会被注解配置覆盖。
使用 typeAlias 配置
如果typeAlias
的alias
有设置值,使用自定名称方式注册,否则使用默认方式注册,即类的simpleName作为别名。
plugins 节点解析
plugins
配置方式
<plugins> // 配置自定义插件,可指定在某个点进行拦截 <plugin interceptor="com.ytao.main.plugin.DemoInterceptor"> // 当前插件属性 <property name="name" value="100"/> </plugin> </plugins>
自定义插件需要实现org.apache.ibatis.plugin.Interceptor
接口,同时在注解上指定拦截的方法。
pluginElement()
方法
private void pluginElement(XNode parent) throws Exception { if (parent != null) { for (XNode child : parent.getChildren()) { // 获取自定插件的类名 String interceptor = child.getStringAttribute("interceptor"); // 获取插件属性 Properties properties = child.getChildrenAsProperties(); // 实例化 Interceptor Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor).newInstance(); // 设置插件属性到插件中 interceptorInstance.setProperties(properties); // 将插件保存在 configuration 中 configuration.addInterceptor(interceptorInstance); } } }
这里取<plugin>
节点的interceptor
可以使用别名设置。从源码中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) { return typeAliasRegistry.resolveAlias(alias); } // public <T> Class<T> resolveAlias(String string) { try { if (string == null) { return null; } // issue #748 // 将传入的 类 名称统一转换 String key = string.toLowerCase(Locale.ENGLISH); Class<T> value; // 验证别名中是否有当前传入的key if (TYPE_ALIASES.containsKey(key)) { value = (Class<T>) TYPE_ALIASES.get(key); } else { value = (Class<T>) Resources.classForName(string); } return value; } catch (ClassNotFoundException e) { throw new TypeException("Could not resolve type alias '" + string + "'. Cause: " + e, e); } }
以上源码为别名解析过程,其他别名的解析也是调用此方法,先去保存的别名中去找,是否有别名,如果没有就通过Resources.classForName
生成实例。
objectFactory,objectWrapperFactory,reflectorFactory 节点解析
以上都是对实现类都是对MyBatis进行扩展。解析方法也类似,最后都是保存在configuration
。
// objectFactory 解析 private void objectFactoryElement(XNode context) throws Exception { if (context != null) { String type = context.getStringAttribute("type"); Properties properties = context.getChildrenAsProperties(); ObjectFactory factory = (ObjectFactory) resolveClass(type).newInstance(); factory.setProperties(properties); configuration.setObjectFactory(factory); } } // objectWrapperFactory 解析 private void objectWrapperFactoryElement(XNode context) throws Exception { if (context != null) { String type = context.getStringAttribute("type"); ObjectWrapperFactory factory = (ObjectWrapperFactory) resolveClass(type).newInstance(); configuration.setObjectWrapperFactory(factory); } } // reflectorFactory 解析 private void reflectorFactoryElement(XNode context) throws Exception { if (context != null) { String type = context.getStringAttribute("type"); ReflectorFactory factory = (ReflectorFactory) resolveClass(type).newInstance(); configuration.setReflectorFactory(factory); } }
以上为解析objectFactory,objectWrapperFactory,reflectorFactory
源码,经过前面的分析后,这里比较容易看懂。
environments 节点解析
environments
配置方式
<environments default="development"> <environment id="development"> <!-- 事务管理 --> <transactionManager type="JDBC"> <property name="prop" value="100"/> </transactionManager> <!-- 数据源 --> <dataSource type="UNPOOLED"> <!-- JDBC 驱动 --> <property name="driver" value="com.mysql.jdbc.Driver"/> <!-- 数据库的 url --> <property name="url" value="${jdbc.url}"/> <!-- 数据库登录名 --> <property name="username" value="${jdbc.username}"/> <!-- 数据库登录密码 --> <property name="password" value="${jdbc.password}"/> </dataSource> </environment> <!-- 一个环境,对应一个environment --> ...... </environments>
该节点可设置多个环境,针对不同的环境单独配置。environments
的属性default
是默认环境,该值对应一个environment
的属性id
的值。
transactionManager
为事务管理,属性type
为事务管理类型,上面的介绍的new Configuration()
有定义类型有:JDBC 和 MANAGED事务管理类型。dataSource
是数据源,type
为数据源类型,与transactionManager
同理,可知内建的数据源类型有:JNDI,POOLED,UNPOOLED数据源类型。
environmentsElement()
方法
private void environmentsElement(XNode context) throws Exception { if (context != null) { if (environment == null) { environment = context.getStringAttribute("default"); } for (XNode child : context.getChildren()) { String id = child.getStringAttribute("id"); // 验证 id if (isSpecifiedEnvironment(id)) { // 解析 transactionManager, 并实例化 TransactionFactory TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager")); // 解析 dataSource,并实例化 DataSourceFactory DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource")); // 获取 dataSource DataSource dataSource = dsFactory.getDataSource(); Environment.Builder environmentBuilder = new Environment.Builder(id) .transactionFactory(txFactory) .dataSource(dataSource); configuration.setEnvironment(environmentBuilder.build()); } } } } private boolean isSpecifiedEnvironment(String id) { if (environment == null) { throw new BuilderException("No environment specified."); } else if (id == null) { throw new BuilderException("Environment requires an id attribute."); } else if (environment.equals(id)) { return true; } return false; }
若没有配置environment
环境或环境没有给id
属性,则会抛出异常,若当前id
是要使用的就返回true
,否则返回false
。TransactionFactory
实例化过程比较简单,与创建DataSourceFactory
类似。
数据源的获取
获取数据源,首先得创建DataSourceFactory
,上面使用DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource"))
创建
private DataSourceFactory dataSourceElement(XNode context) throws Exception { if (context != null) { String type = context.getStringAttribute("type"); Properties props = context.getChildrenAsProperties(); DataSourceFactory factory = (DataSourceFactory) resolveClass(type).newInstance(); factory.setProperties(props); return factory; } throw new BuilderException("Environment declaration requires a DataSourceFactory."); }
这里就是获取到数据源得type
后,利用上面所讲到得resolveClass()
方法获取到DataSourceFactory
。以UNPOOLED
为例,对应的DataSourceFactory
实现类为UnpooledDataSourceFactory
。实例化过程中就给该类的属性dataSource
数据源赋值了
/** * UnpooledDataSourceFactory 类 */ protected DataSource dataSource; public UnpooledDataSourceFactory() { this.dataSource = new UnpooledDataSource(); } @Override public DataSource getDataSource() { return dataSource; }
UnpooledDataSource
类里面有静态代码块所以数据源被加载
/** * UnpooledDataSource 类 */ static { Enumeration<Driver> drivers = DriverManager.getDrivers(); while (drivers.hasMoreElements()) { Driver driver = drivers.nextElement(); registeredDrivers.put(driver.getClass().getName(), driver); } }
databaseIdProvider 节点解析
databaseIdProvider
配置方式
<databaseIdProvider type="DB_VENDOR"> <property name="SQL Server" value="sqlserver"/> <property name="DB2" value="db2"/> <property name="Oracle" value="oracle" /> <property name="MySQL" value="mysql"/> </databaseIdProvider> <select id="select" resultType="com.ytao.main.model.Student" databaseId="mysql"> select * from student </select>
基于映射语句中的databaseId
属性,可以根据不同数据库厂商执行不同的sql。
databaseIdProviderElement()
方法
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).newInstance(); databaseIdProvider.setProperties(properties); } Environment environment = configuration.getEnvironment(); if (environment != null && databaseIdProvider != null) { String databaseId = databaseIdProvider.getDatabaseId(environment.getDataSource()); configuration.setDatabaseId(databaseId); } }
根据匹配的数据库厂商类型匹配数据源databaseIdProvider.getDatabaseId(environment.getDataSource())
@Override public String getDatabaseId(DataSource dataSource) { if (dataSource == null) { throw new NullPointerException("dataSource cannot be null"); } try { return getDatabaseName(dataSource); } catch (Exception e) { log.error("Could not get a databaseId from dataSource", e); } return null; } private String getDatabaseName(DataSource dataSource) throws SQLException { // 根据数据源获取数据库产品名称 String productName = getDatabaseProductName(dataSource); if (this.properties != null) { for (Map.Entry<Object, Object> property : properties.entrySet()) { // 判断是否包含,选择使用的数据库产品 if (productName.contains((String) property.getKey())) { return (String) property.getValue(); } } // no match, return null return null; } return productName; } private String getDatabaseProductName(DataSource dataSource) throws SQLException { Connection con = null; try { // 数据库连接 con = dataSource.getConnection(); // 获取连接元数据 DatabaseMetaData metaData = con.getMetaData(); // 获取数据库产品名称 return metaData.getDatabaseProductName(); } finally { if (con != null) { try { con.close(); } catch (SQLException e) { // ignored } } } }
这里需要注意的是配置:比如使用mysql
,我踩过这里的坑,这里Name为MySQL
,我把y
写成大写,结果匹配不上。另外这里写个My
也能匹配上,应为是使用的String.contains
方法,只要包含就会符合,这里代码应该不够严谨。
typeHandlers 节点解析
typeHandlers
配置方式
<typeHandlers> <package name="com.ytao.main.handler"/> // 或 <typeHandler javaType="java.util.Date" jdbcType="TIMESTAMP" handler="com.ytao.main.handler.DemoDateHandler" /> </typeHandlers>
扫描整个包或者指定类型之间的映射,javaType
, jdbcType
非必需,handler
必填项
typeHandlerElement()
方法
private void typeHandlerElement(XNode parent) throws Exception { if (parent != null) { for (XNode child : parent.getChildren()) { if ("package".equals(child.getName())) { // 获取包名 String typeHandlerPackage = child.getStringAttribute("name"); // 注册包下所有的类型处理器 typeHandlerRegistry.register(typeHandlerPackage); } else { String javaTypeName = child.getStringAttribute("javaType"); String jdbcTypeName = child.getStringAttribute("jdbcType"); String handlerTypeName = child.getStringAttribute("handler"); Class<?> javaTypeClass = resolveClass(javaTypeName); JdbcType jdbcType = resolveJdbcType(jdbcTypeName); Class<?> typeHandlerClass = resolveClass(handlerTypeName); if (javaTypeClass != null) { if (jdbcType == null) { typeHandlerRegistry.register(javaTypeClass, typeHandlerClass); } else { typeHandlerRegistry.register(javaTypeClass, jdbcType, typeHandlerClass); } } else { typeHandlerRegistry.register(typeHandlerClass); } } } } }
源码分析会根据包下所有处理器或者指定处理器进行解析,最后会根据上面分析到的type + handler
和type + jdbc type + handler
不同情况注册。另外这里还有个TypeHandlerRegistry.register(Class<?> typeHandlerClass)
注册类
public void register(Class<?> typeHandlerClass) { // 标志是否从 MappedTypes 注解中获取 javaType 注册 boolean mappedTypeFound = false; // 获取 MappedTypes 的值 MappedTypes mappedTypes = typeHandlerClass.getAnnotation(MappedTypes.class); if (mappedTypes != null) { for (Class<?> javaTypeClass : mappedTypes.value()) { // 已 type + handler 的方式注册 register(javaTypeClass, typeHandlerClass); // 标志已通过注解注册类型 mappedTypeFound = true; } } if (!mappedTypeFound) { // 通过 TypeHandler 注册 register(getInstance(null, typeHandlerClass)); } } // 实例化 public <T> TypeHandler<T> getInstance(Class<?> javaTypeClass, Class<?> typeHandlerClass) { if (javaTypeClass != null) { try { // 获取有参构造函数 Constructor<?> c = typeHandlerClass.getConstructor(Class.class); // 实例化对象 return (TypeHandler<T>) c.newInstance(javaTypeClass); } catch (NoSuchMethodException ignored) { // ignored } catch (Exception e) { throw new TypeException("Failed invoking constructor for handler " + typeHandlerClass, e); } } try { // 获取无参构造函数 Constructor<?> c = typeHandlerClass.getConstructor(); return (TypeHandler<T>) c.newInstance(); } catch (Exception e) { throw new TypeException("Unable to find a usable constructor for " + typeHandlerClass, e); } } // 注册实例 public <T> void register(TypeHandler<T> typeHandler) { boolean mappedTypeFound = false; MappedTypes mappedTypes = typeHandler.getClass().getAnnotation(MappedTypes.class); if (mappedTypes != null) { for (Class<?> handledType : mappedTypes.value()) { register(handledType, typeHandler); mappedTypeFound = true; } } // @since 3.1.0 - try to auto-discover the mapped type if (!mappedTypeFound && typeHandler instanceof TypeReference) { try { TypeReference<T> typeReference = (TypeReference<T>) typeHandler; register(typeReference.getRawType(), typeHandler); mappedTypeFound = true; } catch (Throwable t) { // maybe users define the TypeReference with a different type and are not assignable, so just ignore it } } if (!mappedTypeFound) { register((Class<T>) null, typeHandler); } }
以上的register
方法中,了解type + jdbc type + handler
后,其他的register
重载方法比较容易理解,其他的都是基于它上面的封装。
mappers 节点解析
mappers
配置方式
<mappers> <package name="com.ytao.main.mapper"/> // 或 <mapper resource="mapper/studentMapper.xml"/> // 或 <mapper url="file:///D:/mybatis-3-mybatis-3.4.6/src/main/resources/mapper/studentMapper.xml"/> // 或 <mapper class="com.ytao.main.mapper.StudentMapper"/> </mappers>
可通过以上四种形式配置mappers
节点,<package>
和<mapper>
为互斥节点。
mapperElement()
方法
该方法是负责解析<mappers>
节点
private void mapperElement(XNode parent) throws Exception { if (parent != null) { for (XNode child : parent.getChildren()) { // 如果配置 package 节点,则扫描 if ("package".equals(child.getName())) { String mapperPackage = child.getStringAttribute("name"); // 解析包下类Mapper接口,并注册到configuration的mapperRegistry中 configuration.addMappers(mapperPackage); } else { // 获取mapper节点的resource,url,class属性 String resource = child.getStringAttribute("resource"); String url = child.getStringAttribute("url"); String mapperClass = child.getStringAttribute("class"); // 根据resource解析,并且url,class值必须为空,也就不能配置值。url,class同理,其它两个属性也不能配置值 if (resource != null && url == null && mapperClass == null) { ErrorContext.instance().resource(resource); // 通过resource获取流 InputStream inputStream = Resources.getResourceAsStream(resource); // 创建XMLMapperBuilder对象 XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments()); // 解析映射配置文件 mapperParser.parse(); } else if (resource == null && url != null && mapperClass == null) { ErrorContext.instance().resource(url); // 通过url获取流 InputStream inputStream = Resources.getUrlAsStream(url); // 和resource解析方式一样,创建XMLMapperBuilder对象,然后解析映射配置文件 XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments()); mapperParser.parse(); } else if (resource == null && url == null && mapperClass != null) { // 加载class属性的接口 Class<?> mapperInterface = Resources.classForName(mapperClass); // 将接口注册到configuration的mapperRegistry中 configuration.addMapper(mapperInterface); } else { throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one."); } } } } }
<package>
的包扫描到的类,然后单个单个注册到configuration的mapperRegistry中,这里和<mapper>
使用class
属性是一样逻辑。解析package
方式
// Configuration 中定义了 protected final MapperRegistry mapperRegistry = new MapperRegistry(this); /** * 步骤一 * 该函数于 Configuration 中 */ public void addMappers(String packageName) { // mapperRegistry定义在Configuration中的一个属性 mapperRegistry.addMappers(packageName); } /** * 步骤二 * 该函数于 MapperRegistry 中 */ public void addMappers(String packageName) { addMappers(packageName, Object.class); } /** * 步骤三 * 该函数于 MapperRegistry 中 */ public void addMappers(String packageName, Class<?> superType) { // 通过 ResolverUtil 获取包下的类 ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<Class<?>>(); resolverUtil.find(new ResolverUtil.IsA(superType), packageName); Set<Class<? extends Class<?>>> mapperSet = resolverUtil.getClasses(); for (Class<?> mapperClass : mapperSet) { // 遍历获取到的类,注册到 MapperRegistry addMapper(mapperClass); } } /** * 步骤四 * 该函数于 MapperRegistry 中 */ public <T> void addMapper(Class<T> type) { // mapper 类为 interface 接口 if (type.isInterface()) { // 判断当前class是否已经注册过 if (hasMapper(type)) { throw new BindingException("Type " + type + " is already known to the MapperRegistry."); } // 校验是否加载完成 boolean loadCompleted = false; try { // 保存 mapper 接口和 MapperProxyFactory 之间的映射 knownMappers.put(type, new MapperProxyFactory<T>(type)); // It's important that the type is added before the parser is run // otherwise the binding may automatically be attempted by the // mapper parser. If the type is already known, it won't try. // 解析xml和注解 MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type); parser.parse(); // 标志加载完成 loadCompleted = true; } finally { if (!loadCompleted) { knownMappers.remove(type); } } } }
解析mapper
的class
属性
// 该函数于 Configuration 中 public <T> void addMapper(Class<T> type) { mapperRegistry.addMapper(type); } // ... 这里调用上面的【步骤四】
这两中方式是直接注册接口到mapperRegistry
,另外两种是解析xml
的方式就是获取映射文件的namespace
,再注册进来,XMLMapperBuilder
是负责解析映射配置文件的类,今后会单独详细分析这个类,这里不展开讲。
这里对XMLConfigBuilder解析配置文件到此分析完,本文对配置文件解析的流程大致了解流程和原理。相信遇到配置问题异常,大致能排查到根本原因。
推荐阅读
版权声明: 本文为 InfoQ 作者【ytao】的原创文章。
原文链接:【http://xie.infoq.cn/article/54ed88045a65d546ba89ec464】。文章转载请联系作者。
ytao
Java 开发 2018.05.03 加入
公众号:『ytao』❤ 个人博客:https://ytao.top 一个技术人的成长过程中技术,观点分享。专注但不限于java相关技术。
评论