查找配置文件
Resources
在使用Mybatis的过程中,通常使用Mybatis
的Resources
类读取配置文件。在Mybatis 3.5.1
的API
文档中,对Resources
类的描述如下:
A class to simplify access to resources through the classloader.
即Resources
通过ClassLoader
访问资源。
Resources
类有两个静态属性:classLoaderWrapper
和Charset
。
ClassLoaderWrapper
类是ClassLoader
类的包装类。ClassLoaderWrapper
类有两个属aaaaaaaa性defaultClassLoader
和systemClassLoader
分别存储默认的类加载器和系统类加载器。系统类加载器systemClassLoader
在ClassLoaderWrapper
的构造方法中通过ClassLoader.getSystemClassLoader
方法获取:
ClassLoaderWrapper() {
try {
systemClassLoader = ClassLoader.getSystemClassLoader();
} catch (SecurityException ignored) {
// AccessControlException on Google App Engine
}
}
而defaultClassLoader
值则通过Resources
类的静态方法setDefaultClassLoader
和getDefaultClassLoader
设置和获取。
ClassLoaderWrapper
类中的方法有:
getResourceAsURL
getResourceAsStream
classForName
每个方法都被重载,参数分别为资源路径;资源路径,类加载器;资源路径,类加载器数组。
而前两种重载都调用了第三种重载,以getResourceAsURL
为例:
public URL getResourceAsURL(String resource) {
return getResourceAsURL(resource, getClassLoaders(null));
}
public URL getResourceAsURL(String resource, ClassLoader classLoader) {
return getResourceAsURL(resource, getClassLoaders(classLoader));
}
URL getResourceAsURL(String resource, ClassLoader[] classLoader) {
URL url;
for (ClassLoader cl : classLoader) {
if (null != cl) {
// look for the resource as passed in...
url = cl.getResource(resource);
// ...but some class loaders want this leading "/", so we'll add it
// and try again if we didn't find the resource
if (null == url) {
url = cl.getResource("/" + resource);
}
// "It's always in the last place I look for it!"
// ... because only an idiot would keep looking for it after finding it, so stop looking already.
if (null != url) {
return url;
}
}
}
// didn't find it anywhere.
return null;
}
而getClassLoaders
方法则规定了使用类加载器查找资源/类的顺序:
ClassLoader[] getClassLoaders(ClassLoader classLoader) {
return new ClassLoader[]{
classLoader,
defaultClassLoader,
Thread.currentThread().getContextClassLoader(),
getClass().getClassLoader(),
systemClassLoader};
}
读取配置文件
SqlSessionFactoryBuilder
类SqlSessionFactoryBuilder
创建SqlSessionFactory
对象,类中所有方法全部为build
方法及其重载。其中最重要的两个方法为:
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
try {
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
return build(parser.parse());
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error building SqlSession.", e);
} finally {
ErrorContext.instance().reset();
try {
inputStream.close();
} catch (IOException e) {
// Intentionally ignore. Prefer previous error.
}
}
}
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}
可以看到builder
方法首先创建了XMLConfigBuilder
类的实例,接着使用该实例创建了DefaultSqlSessionFactory
实例。
XMLConfigBuilder
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;
}
public XMLConfigBuilder(InputStream inputStream, String environment, Properties props) {
this(new XPathParser(inputStream, true, props, new XMLMapperEntityResolver()), environment, props);
}
SqlSessionFactoryBuilder
类调用的构造方法是第二个构造方法,由此可见传入的inputStram
被处啊入到了XPathParser
对象中,而XMLConfigBuilder
类继承了BaseBuilder
类。BaseBuilder
类是抽象类,有三个属性:Configuration
类对象configuration
,TypeAliasRegistry
类对象typeAliasRegistry
,TypeHandlerRegistry
类对象typeHandlerRegistry
。其中configuration
对象为默认构造方法创建,而typeAliasRegistry
对象和typeHandlerRegistry
对象泽分别由configuration
对象的getTypeAliasRegistry()
方法和getTypeHandlerRegistry()
方法创建。
XPathParser
根据调用关系,XPathParser
对象的构造方法的执行顺序为:
public XPathParser(InputStream inputStream) {
commonConstructor(false, null, null);
this.document = createDocument(new InputSource(inputStream));
}
private void commonConstructor(boolean validation, Properties variables, EntityResolver entityResolver) {
this.validation = validation;
this.entityResolver = entityResolver;
this.variables = variables;
XPathFactory factory = XPathFactory.newInstance();
this.xpath = factory.newXPath();
}
private Document createDocument(InputSource inputSource) {
// important: this must only be called AFTER common constructor
try {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
factory.setValidating(validation);
factory.setNamespaceAware(false);
factory.setIgnoringComments(true);
factory.setIgnoringElementContentWhitespace(false);
factory.setCoalescing(false);
factory.setExpandEntityReferences(true);
DocumentBuilder builder = factory.newDocumentBuilder();
builder.setEntityResolver(entityResolver);
builder.setErrorHandler(new ErrorHandler() {
@Override
public void error(SAXParseException exception) throws SAXException {
throw exception;
}
@Override
public void fatalError(SAXParseException exception) throws SAXException {
throw exception;
}
@Override
public void warning(SAXParseException exception) throws SAXException {
// NOP
}
});
return builder.parse(inputSource);
} catch (Exception e) {
throw new BuilderException("Error creating document instance. Cause: " + e, e);
}
}
可以看到在XPathParser
的构造函数中就对配置进行了读取,对配置读取时使用的是DocumentBuilder
创建Document
对象。对XML
文件读取完毕之后,XPathParser
通过parseConfiguration
方法读取配置文件中的配置。parseConfiguration
方法以及其调用的方法将配置文件中的内容通过调用Configuration
中的相应方法保存到Configuration
对象中。
TypeAliasRegistry
TypeAliasRegistry
类的主要功能是注册和查询别名。在TypeAliasRegistry.java
中,TypeAliasRegistry
类的属性有typeAliases
这一HashMap
对象,而TypeAliasRegistry
类中的主要方法resolveAlias
,registerAlias
和getTypeAliases
的行为则是对typeAliases
进行操作。
private final Map<String, Class<?>> typeAliases = new HashMap<>();
typeAliases
主要提供字符串和类的映射。方法registerAlias
将一个名称和一个类相关联起来,getTypeAlias
主要从HashMap
中获取根据名称获取对应类的Class
对象,而resolveAlias
方法的源代码如下:
public <T> Class<T> resolveAlias(String string) {
try {
if (string == null) {
return null;
}
// issue #748
String key = string.toLowerCase(Locale.ENGLISH);
Class<T> value;
if (typeAliases.containsKey(key)) {
value = (Class<T>) typeAliases.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);
}
}
可以看到, resolveAlias
方法对传入的字符串参数string
,首先将其转换成全部小写,接着如果之前没有建立由改string
到某一类的映射,则使用Resources.classForName
方法获取string
代表的类的Class
对象并建立映射关系。例如,如果有一类名为org.glassyyang.User
,则该方法获取org.glassyyang.User
类对应的Class
对象,接着建立org.glassyyang.user
与Class
对象的映射关系。
分析registryAlias
源码:
public void registerAliases(String packageName) {
registerAliases(packageName, Object.class);
}
public void registerAliases(String packageName, Class<?> superType) {
ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<>();
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);
}
}
}
public void registerAlias(Class<?> type) {
String alias = type.getSimpleName();
Alias aliasAnnotation = type.getAnnotation(Alias.class);
if (aliasAnnotation != null) {
alias = aliasAnnotation.value();
}
registerAlias(alias, type);
}
可以看到方法在建立映射关系时,首先会将字符串转换成小写,这也是Mybatis
的别名不区分大小写的原因。
在TypeAliasRegistry
的构造函数中,对Java
中常见的基本类型,包装类型以及对应的数组类型都建立了映射关系;对Date
,BigDecimal
,BigInteger
,Object
以及对应数组对象也建立了映射关系,同时对Map
,HashMap
,List
,ArrayList
,Collection
,Iterator
和ResultSet
对象建立了映射关系。同时在Configuration
对象的构造函数中创建了如下的对应关系:
public Configuration() {
typeAliasRegistry.registerAlias("JDBC", JdbcTransactionFactory.class);
typeAliasRegistry.registerAlias("MANAGED", ManagedTransactionFactory.class);
typeAliasRegistry.registerAlias("JNDI", JndiDataSourceFactory.class);
typeAliasRegistry.registerAlias("POOLED", PooledDataSourceFactory.class);
typeAliasRegistry.registerAlias("UNPOOLED", UnpooledDataSourceFactory.class);
typeAliasRegistry.registerAlias("PERPETUAL", PerpetualCache.class);
typeAliasRegistry.registerAlias("FIFO", FifoCache.class);
typeAliasRegistry.registerAlias("LRU", LruCache.class);
typeAliasRegistry.registerAlias("SOFT", SoftCache.class);
typeAliasRegistry.registerAlias("WEAK", WeakCache.class);
typeAliasRegistry.registerAlias("DB_VENDOR", VendorDatabaseIdProvider.class);
typeAliasRegistry.registerAlias("XML", XMLLanguageDriver.class);
typeAliasRegistry.registerAlias("RAW", RawLanguageDriver.class);
typeAliasRegistry.registerAlias("SLF4J", Slf4jImpl.class);
typeAliasRegistry.registerAlias("COMMONS_LOGGING", JakartaCommonsLoggingImpl.class);
typeAliasRegistry.registerAlias("LOG4J", Log4jImpl.class);
typeAliasRegistry.registerAlias("LOG4J2", Log4j2Impl.class);
typeAliasRegistry.registerAlias("JDK_LOGGING", Jdk14LoggingImpl.class);
typeAliasRegistry.registerAlias("STDOUT_LOGGING", StdOutImpl.class);
typeAliasRegistry.registerAlias("NO_LOGGING", NoLoggingImpl.class);
typeAliasRegistry.registerAlias("CGLIB", CglibProxyFactory.class);
typeAliasRegistry.registerAlias("JAVASSIST", JavassistProxyFactory.class);
languageRegistry.setDefaultDriverClass(XMLLanguageDriver.class);
languageRegistry.register(RawLanguageDriver.class);
}
TypeHandlerRegistry
TypeHandlerRegistry
类的主要功能是提供Java
类型与对应的Jdbc
类型之间进行转换的处理句柄。
其中的属性jdbcTypeHandlerMap
提供JdbcType
与相应的处理句柄之间的映射关系,而属性typeHandlerMap
是一个ConcurrentMap
,提供java
类型与Jdbc
类型之间转换的处理句柄TypeHandler
。
TypeHandlerRegistry
对象的属性如下:
// EnumMap,存储JdbcType枚举与TypeHandler<?>之间的映射关系,即保存了JdbcType的处理句柄
private final Map<JdbcType, TypeHandler<?>> jdbcTypeHandlerMap = new EnumMap<>(JdbcType.class);
// ConcurrentHashMap对象,存储Type类与Map<JdbcTYpe, TYpeHandler<?>>之间的映射关系
private final Map<Type, Map<JdbcType, TypeHandler<?>>> typeHandlerMap = new ConcurrentHashMap<>();
// 特殊的TypeHandler,专门处理未知类型
private final TypeHandler<Object> unknownTypeHandler;
//定义类Class对象与TypeHandler之间的映射关系
private final Map<Class<?>, TypeHandler<?>> allTypeHandlersMap = new HashMap<>();
// 定义一个空的Map作为null值的Map
private static final Map<JdbcType, TypeHandler<?>> NULL_TYPE_HANDLER_MAP = Collections.emptyMap();
// 暂时不知道是用来干嘛的
private Class<? extends TypeHandler> defaultEnumTypeHandler = EnumTypeHandler.class;
TypeHandlerRegistry
类的构造函数中注册了基本Java
类型和JDBC
类型相应的处理句柄。其中registry
方法用于注册句柄,提供注册JdbcType
对应处理句柄和Java
类型转换为JdbcType
类型的护理句柄。Mybatis
中默认实现了一些jdbc
类型和java
类型之间转换的处理句柄,有BooleanTypeHandler
,ByteTypeHandler
,ShortTypeHandler
等。这些Handler
提供了jdbc
类型和java
类型的互相转换。
回到BaseBuilder
。可以看到XMLConfigBuilder
继承了BaseBuilder
,并在其基础上新增了parsed
,parser
,environment
和localReflectorFactory
四个属性:
// 是否已解析,防止配置文件被解析多次;
private boolean parsed;
// XML 文件DOM解析包装类,用于解析XML文件
private final XPathParser parser;
//字符串类型,暂时不知道有神码用处
private String environment;
// 反射工厂类,不知道是干什么的
private final ReflectorFactory localReflectorFactory = new DefaultReflectorFactory();
DefaultReflectorFactory
类DefaultReflectorFactory
实现了接口ReflectorFactory
,内部有两个属性:
private boolean classCacheEnabled = true;
private final ConcurrentMap<Class<?>, Reflector> reflectorMap = new ConcurrentHashMap<>();
最重要的方法有:
@Override
public Reflector findForClass(Class<?> type) {
if (classCacheEnabled) {
// synchronized (type) removed see issue #461
return reflectorMap.computeIfAbsent(type, Reflector::new);
} else {
return new Reflector(type);
}
}
可以看到DefaultReflectorFactory
是一个实现了缓存的保存type
和Reflector
见映射关系的线程安全的类。
Reflector
类Reflector
的构造方法为:
// 添加clazz中的所有Getter方法到创建的Map conflictingGetters中;
private void addGetMethods(Class<?> clazz) {
Map<String, List<Method>> conflictingGetters = new HashMap<>();
Method[] methods = getClassMethods(clazz);
// 将数组转换成流
Arrays.stream(methods)
// 筛选数组中没有参数且是Getter方法的method对象
.filter(m -> m.getParameterTypes().length == 0 && PropertyNamer.isGetter(m.getName()))
// 遍历每一个method对象,对于每一个getter,将其添加到map中;
.forEach(m -> addMethodConflict(conflictingGetters, PropertyNamer.methodToProperty(m.getName()), m));
// 处理map中的冲突。
resolveGetterConflicts(conflictingGetters);
}
// 方法将method对象添加到map conflictingMethods中;
private void addMethodConflict(Map<String, List<Method>> conflictingMethods, String name, Method method) {
if (isValidPropertyName(name)) {
List<Method> list = conflictingMethods.computeIfAbsent(name, k -> new ArrayList<>());
list.add(method);
}
}
// 处理getters中的冲突:
private void resolveGetterConflicts(Map<String, List<Method>> conflictingGetters) {
for (Entry<String, List<Method>> entry : conflictingGetters.entrySet()) {
// 最终选择的method对象;
Method winner = null;
String propName = entry.getKey();
boolean isAmbiguous = false;
for (Method candidate : entry.getValue()) {
if (winner == null) {
winner = candidate;
continue;
}
// 获取winner的返回类型
Class<?> winnerType = winner.getReturnType();
// 获取竞选者的返回值类型
Class<?> candidateType = candidate.getReturnType();
// 返回类型是否相同
if (candidateType.equals(winnerType)) {
if (!boolean.class.equals(candidateType)) {
isAmbiguous = true;
break;
} else if (candidate.getName().startsWith("is")) {
winner = candidate;
}
// 获胜者返回类型是否是竞选者返回类型的子类
} else if (candidateType.isAssignableFrom(winnerType)) {
// OK getter type is descendant
// 竞选者返回类型是否是获胜者返回类型的子类
} else if (winnerType.isAssignableFrom(candidateType)) {
winner = candidate;
} else {
// 出现了一个属性的两个getter,他们的返回类型不能互相转换;直接结束循环
isAmbiguous = true;
break;
}
}
// 添加getter method
addGetMethod(propName, winner, isAmbiguous);
}
}
// 功能:为传入的Method对象创建MethodInvoker对象,如果是冲突的,则创建AmbiguousMethodInvoker对象;否则创建MethodInvoker对象。
private void addGetMethod(String name, Method method, boolean isAmbiguous) {
MethodInvoker invoker = isAmbiguous
? new AmbiguousMethodInvoker(method, MessageFormat.format(
"Illegal overloaded getter method with ambiguous type for property ''{0}'' in class ''{1}''. This breaks the JavaBeans specification and can cause unpredictable results.",
name, method.getDeclaringClass().getName()))
: new MethodInvoker(method);
// 将创建的MethodInvoker添加到getMethods对象中;
getMethods.put(name, invoker);
Type returnType = TypeParameterResolver.resolveReturnType(method, type);
// 将Method对象的返回值添加到getTypes中;
getTypes.put(name, typeToClass(returnType));
}
private void addSetMethods(Class<?> clazz) {
Map<String, List<Method>> conflictingSetters = new HashMap<>();
Method[] methods = getClassMethods(clazz);
Arrays.stream(methods).filter(m -> m.getParameterTypes().length == 1 && PropertyNamer.isSetter(m.getName()))
.forEach(m -> addMethodConflict(conflictingSetters, PropertyNamer.methodToProperty(m.getName()), m));
resolveSetterConflicts(conflictingSetters);
}
private void resolveSetterConflicts(Map<String, List<Method>> conflictingSetters) {
for (Entry<String, List<Method>> entry : conflictingSetters.entrySet()) {
String propName = entry.getKey();
List<Method> setters = entry.getValue();
Class<?> getterType = getTypes.get(propName);
boolean isGetterAmbiguous = getMethods.get(propName) instanceof AmbiguousMethodInvoker;
boolean isSetterAmbiguous = false;
Method match = null;
for (Method setter : setters) {
if (!isGetterAmbiguous && setter.getParameterTypes()[0].equals(getterType)) {
// should be the best match
match = setter;
break;
}
if (!isSetterAmbiguous) {
match = pickBetterSetter(match, setter, propName);
isSetterAmbiguous = match == null;
}
}
if (match != null) {
addSetMethod(propName, match);
}
}
}