源码配置解读
配置文件加载入口解释:
public void setUpBeforeClass() throws Exception {
//全局配置文件名称
String resource="SqlMapConfig.xml";
//写入流中
InputStream inputStream=Resources.getResourceAsStream(resource);
//通过建造者模式来获得SqlSession 我们的源码学习就是通过该入口进入
sqlSessionFactory =new SqlSessionFactoryBuilder().build(inputStream);
}
首先:
- 创建SqlSessionFactoryBuilder对象
通过源码可以知道我们如果想创建SqlSessionFactory对象 必须通过SqlSessionFactoryBuilder这个对象来创建
通过字面意思可以看出来这是一个用于创建SqlSessionFactory工厂对象的方法类。
我们通过解读下面的代码片段即可知道如何获得SqlSessionFactory对象的
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
try {
//1.全局配置文件解析器
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.
}
}
}
我们发现一个类 名字叫 XMLConfigBuilder 他继承与BaseBuilder那么 BaseBuilder这个父类中一共有多少子类呢。我们看下他们的关系图
通过这张图我们可以清楚的发现 BaseBuilder中一共有四个子类
1.XMLConfigBuilder :是用来解析全局配置文件
2.XMLMapperBuilder: 用来解析映射文件
3.XMLStatementBuilder :用来解析MappedStatement 语句
4.MapperBuilderAssistant:是一个辅助方法类 用来辅助解析映射文件并胜场MappedStatement对象、
我们打开他们的父类 即抽象类BaseBuilder:
public abstract class BaseBuilder {
//1.Configuration 是用于存储全局配置文件
protected final Configuration configuration;
//2.别名注册器
protected final TypeAliasRegistry typeAliasRegistry;
//3.类型注册器
protected final TypeHandlerRegistry typeHandlerRegistry;
我们回到刚才的地方
public XMLConfigBuilder(InputStream inputStream, String environment, Properties props) {
//通过Xpath来解析节点然后调用内部方法XMLConfigBuilder
this(new XPathParser(inputStream, true, props, new XMLMapperEntityResolver()), environment, props);
}
//内部方法
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;
}
我们会发现在构建XmlConfigBuilder对象时候会初始化Configuation对象
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);
}
会把一些基础配置加载到别名注册器中。
build(parser.parse());//用来构建SqlSessionFactory的主要方法
public Configuration parse() {
//如果已经存在XmlConfigBuilder对象就会抛出异常 只能唯一
if (parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
parsed = true;
//解析获得所有配置文件中的节点
parseConfiguration(parser.evalNode("/configuration"));
//返回Configuration对象
return configuration;
}
我们主要看看如何依次解析文件中的节点的:
private void parseConfiguration(XNode root) {
try {
//issue #117 read properties first
//解析properties配置
propertiesElement(root.evalNode("properties"));
//解析settings
Properties settings = settingsAsProperties(root.evalNode("settings"));
//加载Vfs
loadCustomVfs(settings);
//解析typeAliases
typeAliasesElement(root.evalNode("typeAliases"));
//解析plugins配置
pluginElement(root.evalNode("plugins"));
objectFactoryElement(root.evalNode("objectFactory"));
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
reflectorFactoryElement(root.evalNode("reflectorFactory"));
// settings 中的信息设置到 Configuration 对象中
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);
}
}
上面的代码比较核心我们现在一次分析
2.1 properties 节点
propertiesElement(root.evalNode("properties"));
private void propertiesElement(XNode context) throws Exception {
//如果节点不为空
if (context != null) {
//加载里面所有的子节点。
Properties defaults = context.getChildrenAsProperties();
//获得节点属性 Resource & url
String resource = context.getStringAttribute("resource");
String url = context.getStringAttribute("url");
if (resource != null && url != null) {
//可以看出 我们只能选择俩种方式 要么用resource 要么用Url
throw new BuilderException("The properties element cannot specify both a URL and a resource based property file reference. Please specify one or the other.");
}
//依次加载把配置文件中的内容加载到Properties中
if (resource != null) {
defaults.putAll(Resources.getResourceAsProperties(resource));
} else if (url != null) {
defaults.putAll(Resources.getUrlAsProperties(url));
}
//我们既然可以通过xml也可以通过java方式配置 我们在这里可以看出 会覆盖掉前面相同的配置。那么说明 优先级为:
//java>外部配置文件>Xml子节点方式配置
Properties vars = configuration.getVariables();
if (vars != null) {
defaults.putAll(vars);
}
//把信息重新加载到Xml解析器中
parser.setVariables(defaults);
//把信息加载到Configuration中
configuration.setVariables(defaults);
}
}
我们可以大致的认为:properties这个节点可以分成3个部分。
- 获得子节点 解析到Properties中
- 从外部文件 以及java 读取配置文件到Propeties ,配置相同可以覆盖
- 把信息加载到Configuration中
2.2settings 解析过程
Properties settings = settingsAsProperties(root.evalNode("settings"));
private Properties settingsAsProperties(XNode context) {
if (context == null) {
return new Properties();
}
Properties props = context.getChildrenAsProperties();
// Check that all settings are known to the configuration class
//创建 Configuration 类的“元信息”对象
MetaClass metaConfig = MetaClass.forClass(Configuration.class, localReflectorFactory);
for (Object key : props.keySet()) {
// 检测 Configuration 中是否存在相关属性,不存在则抛出异常
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;
}
目前看这段代码很简单。但是我们发现一个MetaClass的类,从字面意思以及在这的作用可以发现。我们是根据反射获得所有的configuration对象中的属性。
然后通过hasSetter()方法判断Configuration中是否存在设置全局XML中setting中的属性。
MetaClass类
public class MetaClass {
private ReflectorFactory reflectorFactory;
private Reflector reflector;
//私有构造方法。所以必须设置个静态方法供外面访问
private MetaClass(Class<?> type, ReflectorFactory reflectorFactory) {
this.reflectorFactory = reflectorFactory;
//根据类型创建reflector
this.reflector = reflectorFactory.findForClass(type);
}
public static MetaClass forClass(Class<?> type, ReflectorFactory reflectorFactory) {
return new MetaClass(type, reflectorFactory);
}
findForClass方法:
public Reflector findForClass(Class<?> type) {
//是否缓存了信息
if (classCacheEnabled) {
// synchronized (type) removed see issue #461
//从缓存中获得Reflector对象
Reflector cached = reflectorMap.get(type);
//如果Map中没有对应的Key
if (cached == null) {
//new一个Reflector
cached = new Reflector(type);
//放到缓存中,方便下次使用
reflectorMap.put(type, cached);
}
//返回对应的Reflector
return cached;
} else {
return new Reflector(type);
}
}
Reflector 源码分析
本小节,我们来看一下 Reflector 的源码。Reflector 这个类的用途主要是是通过反射获取目标类的 getter 方法及其返回值类型,setter 方法及其参数值类型等元信息。并将获取到的元信息缓存到相应的集合中,供后续使用。Reflector 本身代码比较多,这里不能一一分析。本小节,我将会分析三部分逻辑,分别如下
- Reflector 构造方法及成员变量分析
- getter 方法解析过程
- setter 方法解析过程
- Reflector 构造方法及成员变量分析
public class Reflector {
private static final String[] EMPTY_STRING_ARRAY = new String[0];
private Class<?> type;
private String[] readablePropertyNames = EMPTY_STRING_ARRAY;
private String[] writeablePropertyNames = EMPTY_STRING_ARRAY;
private Map<String, Invoker> setMethods = new HashMap<String, Invoker>();
private Map<String, Invoker> getMethods = new HashMap<String, Invoker>();
private Map<String, Class<?>> setTypes = new HashMap<String, Class<?>>();
private Map<String, Class<?>> getTypes = new HashMap<String, Class<?>>();
private Constructor<?> defaultConstructor;
private Map<String, String> caseInsensitivePropertyMap = new HashMap<String, String>();
public Reflector(Class<?> clazz) {
type = clazz;
//解析目标类的默认构造方法 并且赋值给
addDefaultConstructor(clazz);
//获得get方法 放到getMethods中
addGetMethods(clazz);
//获得set方法 放到setMethods中
addSetMethods(clazz);
//解析属性名 放到对应的get setMethods中
addFields(clazz);
//从getMethods中获得可读的属性名 放到数组中
readablePropertyNames = getMethods.keySet().toArray(new String[getMethods.keySet().size()]);
//从setMethods中获得可写的属性名 放到数组中
writeablePropertyNames = setMethods.keySet().toArray(new String[setMethods.keySet().size()]);
//将所有属性值易键大写的形式放到 caseInsensitivePropertyMap中
for (String propName : readablePropertyNames) {
caseInsensitivePropertyMap.put(propName.toUpperCase(Locale.ENGLISH), propName);
}
for (String propName : writeablePropertyNames) {
caseInsensitivePropertyMap.put(propName.toUpperCase(Locale.ENGLISH), propName);
}
}
变量名 | 类型 | 作用 |
readablePropertyNames | String[] | 存放可以读取的属性名称,是getter方法的属性名称 |
writeablePropertyNames | String[] | 存放可以写入的属性名称,是setter方法的属性名称 |
setMethods | Map<String, Invoker> | 保存属性名称到Invoker的映射,setter会被封装到MethodInvoker对象中。 |
getMethods | Map<String, Invoker> | 同上 |
setTypes | Map<String, Class<?>> | 用于保存setter对应的属性名与返回值类型的映射 |
getTypes | Map<String, Class<?>> | 同上 |
caseInsensitivePropertyMap | Map<String, String> | 存放大写的属性名称为Key与属性名之间的映射。 |
现在分析addGetMethods方法
private void addGetMethods(Class<?> cls) {
Map<String, List<Method>> conflictingGetters = new HashMap<String, List<Method>>();
Method[] methods = getClassMethods(cls);
for (Method method : methods) {
if (method.getParameterTypes().length > 0) {
continue;
}
String name = method.getName();
if ((name.startsWith("get") && name.length() > 3)
|| (name.startsWith("is") && name.length() > 2)) {
name = PropertyNamer.methodToProperty(name);
addMethodConflict(conflictingGetters, name, method);
}
}
resolveGetterConflicts(conflictingGetters);
}
上面很简单
- 创建一个属性为Key List为值的空Map
- 获得所有的属性值对应的方法
- 遍历获得get set方法
- 只要get方法。因为set方法参数长度一定不为空
- 加入冲突属性名称
- 解决冲突
addMethodConflict方法为加入冲突
private void addMethodConflict(Map<String, List<Method>> conflictingMethods, String name, Method method) {
List<Method> list = conflictingMethods.get(name);
if (list == null) {
list = new ArrayList<Method>();
conflictingMethods.put(name, list);
}
list.add(method);
}
上面也很简单很好理解。就是找到对应的Map中的Key 然后获得List集合。判断如果为空 就新建立List放进去 在加入Map中 如果不位空 就加载进List 在放到对应的Key中在下面进行解决
resolveGetterConflicts 方法为解决冲突
private void resolveGetterConflicts(Map<String, List<Method>> conflictingGetters) {
//便利Map
for (Entry<String, List<Method>> entry : conflictingGetters.entrySet()) {
//看名字都能看懂 胜利者。即解决冲突中的获胜者
Method winner = null;
//获得属性名称
String propName = entry.getKey();
//便利属性名称中所有的Method方法
for (Method candidate : entry.getValue()) {
//如果没有获胜者。即第一次进来没有冲突属性值的话 就把当前这个方法作为获胜者返回。
if (winner == null) {
winner = candidate;
continue;
}
//取得获胜者的返回值类型
Class<?> winnerType = winner.getReturnType();
//竞争者的返回值类型
Class<?> candidateType = candidate.getReturnType();
//如果俩个人的返回值都一样。如果竞争者不是Boolean 就会抛出异常
if (candidateType.equals(winnerType)) {
if (!boolean.class.equals(candidateType)) {
throw new ReflectionException(
"Illegal overloaded getter method with ambiguous type for property "
+ propName + " in class " + winner.getDeclaringClass()
+ ". This breaks the JavaBeans specification and can cause unpredictable results.");
} else if (candidate.getName().startsWith("is")) {
//如果竞争者是Boolean 则把胜利者淘汰
winner = candidate;
}
//如果竞争者是胜利者的父类。也就是胜利者是子类。那么我们优先子类 而不选取父类属性的get
} else if (candidateType.isAssignableFrom(winnerType)) {
// OK getter type is descendant
} else if (winnerType.isAssignableFrom(candidateType)) {
//相反就要用竞争者为子类就要用竞争者
winner = candidate;
} else {
//都不满足就抛出异常
throw new ReflectionException(
"Illegal overloaded getter method with ambiguous type for property "
+ propName + " in class " + winner.getDeclaringClass()
+ ". This breaks the JavaBeans specification and can cause unpredictable results.");
}
}
addGetMethod(propName, winner);
}
}
addGetMethod 方法
private void addGetMethod(String name, Method method) {
//一些验证 即 name不以$开头 并且 不等于 serialVersionUID 并且 不等于class
if (isValidPropertyName(name)) {
//放到Map中
getMethods.put(name, new MethodInvoker(method));
//解析返回值类型
Type returnType = TypeParameterResolver.resolveReturnType(method, type);
//把对应的返回类型 转换成Class
getTypes.put(name, typeToClass(returnType));
}
}
下面都是addSetMethods方法的讲述
private void addSetMethods(Class<?> cls) {
Map<String, List<Method>> conflictingSetters = new HashMap<String, List<Method>>();
Method[] methods = getClassMethods(cls);
for (Method method : methods) {
String name = method.getName();
if (name.startsWith("set") && name.length() > 3) {
if (method.getParameterTypes().length == 1) {
name = PropertyNamer.methodToProperty(name);
addMethodConflict(conflictingSetters, name, method);
}
}
}
resolveSetterConflicts(conflictingSetters);
}
基本步骤都是一样的。但是有一点解决冲突的方法变了。具体如下代码片段
private void resolveSetterConflicts(Map<String, List<Method>> conflictingSetters) {
for (String propName : conflictingSetters.keySet()) {
List<Method> setters = conflictingSetters.get(propName);
Class<?> getterType = getTypes.get(propName);
Method match = null;
ReflectionException exception = null;
for (Method setter : setters) {
Class<?> paramType = setter.getParameterTypes()[0];
//用getter的返回值类型与setter的参数类型 比较 如果相等就推出
if (paramType.equals(getterType)) {
// should be the best match
match = setter;
break;
}
if (exception == null) {
try {
//进行比较
match = pickBetterSetter(match, setter, propName);
} catch (ReflectionException e) {
// there could still be the 'best match'
match = null;
exception = e;
}
}
}
if (match == null) {
throw exception;
} else {
addSetMethod(propName, match);
}
}
}
pickBetterSetter方法的如下分析
private Method pickBetterSetter(Method setter1, Method setter2, String property) {
//如果一为空 则直接返回竞争者 即二
if (setter1 == null) {
return setter2;
}
Class<?> paramType1 = setter1.getParameterTypes()[0];
Class<?> paramType2 = setter2.getParameterTypes()[0];
//如果setter1是setter2的父类 就选择参数二
if (paramType1.isAssignableFrom(paramType2)) {
return setter2;
//否则选择参数一
} else if (paramType2.isAssignableFrom(paramType1)) {
return setter1;
}
throw new ReflectionException("Ambiguous setters defined for property '" + property + "' in class '"
+ setter2.getDeclaringClass() + "' with types '" + paramType1.getName() + "' and '"
+ paramType2.getName() + "'.");
}
然后
private Properties settingsAsProperties(XNode context) {
if (context == null) {
return new Properties();
}
Properties props = context.getChildrenAsProperties();
// Check that all settings are known to the configuration class
MetaClass metaConfig = MetaClass.forClass(Configuration.class, localReflectorFactory);
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;
}
最后调用metaClass中的hasSetter方法
public boolean hasSetter(String name) {
PropertyTokenizer prop = new PropertyTokenizer(name);
if (prop.hasNext()) {
if (reflector.hasSetter(prop.getName())) {
MetaClass metaProp = metaClassForProperty(prop.getName());
return metaProp.hasSetter(prop.getChildren());
} else {
return false;
}
} else {
return reflector.hasSetter(prop.getName());
}
}
PropertyTokenizer 处理更为复杂的属性 解析例如数组,或者字符串以.分割的进行 循环判断 然后不能重复 然后 把Properties返回
至此setting的 解析已经分析完毕了。
2.3解析TypeAliases配置项
typeAliases标签的作用及为Mybatis中, 我们可以使用类名称用自己定义的的别名,无需把全限定类名称写出来。Mybatis提供俩种配置选择。一种是批量 <package>
标签 、一种是<typeAlias>
标签。俩者区别在一个扫描整个包下。另一个单个申明
<typeAliases>
<!-- 批量 -->
<!-- <package name="com.ly.entity"/> -->
<!-- 单个 -->
<typeAlias type="com.ly.entity.User" alias="user"/>
</typeAliases>
我们通过分析对应的源码来看看Mybatis是如何加载别名的
private void typeAliasesElement(XNode parent) {
if (parent != null) {
//遍历<typeAliases>下面所有的节点为子节点
for (XNode child : parent.getChildren()) {
if ("package".equals(child.getName())) {
//说明是<package>下面 找到name属性 然后获得路径
String typeAliasPackage = child.getStringAttribute("name");、
//通过注册把路径下的所有类都加载进去
configuration.getTypeAliasRegistry().registerAliases(typeAliasPackage);
} else {
//获得别名
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);
}
}
}
}
}
registerAliases方法
public void registerAliases(String packageName){
registerAliases(packageName, Object.class);
}
public void registerAliases(String packageName, Class<?> superType){
ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<Class<?>>();
//这个是找到这个包路径下 父类为object的所有的类。
//通过找到这些类然后加载到缓存内部集合中
//通过 IsA中的matches方法进行判断
// matches.add((Class<T>) type);
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
Alias aliasAnnotation = type.getAnnotation(Alias.class);
//如果注解为空
if (aliasAnnotation != null) {
//就用类名称
alias = aliasAnnotation.value();
}
registerAlias(alias, type);
}
public void registerAlias(String alias, Class<?> value) {
//如果名称为空就抛出异常
if (alias == null) {
throw new TypeException("The parameter alias cannot be null");
}
// issue #748
//把别名专换成大写然后为Key
String key = alias.toLowerCase(Locale.ENGLISH);
//如果别名存在 (包含这个key 并且key的Value不为空。这个key与value不一致)抛出异常
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() + "'.");
}//加入到别名Map中
TYPE_ALIASES.put(key, value);
}
其实殊途同归到 跟到最后的源码都引用的一样的方法
TypeAliasRegistry里面会有基本的别名加载
registerAlias("string", String.class);
registerAlias("byte", Byte.class);
registerAlias("long", Long.class);
registerAlias("short", Short.class);
registerAlias("int", Integer.class);
registerAlias("integer", Integer.class);
registerAlias("double", Double.class);
registerAlias("float", Float.class);
registerAlias("boolean", Boolean.class);
registerAlias("byte[]", Byte[].class);
registerAlias("long[]", Long[].class);
registerAlias("short[]", Short[].class);
registerAlias("int[]", Integer[].class);
registerAlias("integer[]", Integer[].class);
registerAlias("double[]", Double[].class);
registerAlias("float[]", Float[].class);
registerAlias("boolean[]", Boolean[].class);
registerAlias("_byte", byte.class);
registerAlias("_long", long.class);
registerAlias("_short", short.class);
registerAlias("_int", int.class);
registerAlias("_integer", int.class);
registerAlias("_double", double.class);
registerAlias("_float", float.class);
registerAlias("_boolean", boolean.class);
registerAlias("_byte[]", byte[].class);
registerAlias("_long[]", long[].class);
registerAlias("_short[]", short[].class);
registerAlias("_int[]", int[].class);
registerAlias("_integer[]", int[].class);
registerAlias("_double[]", double[].class);
registerAlias("_float[]", float[].class);
registerAlias("_boolean[]", boolean[].class);
registerAlias("date", Date.class);
registerAlias("decimal", BigDecimal.class);
registerAlias("bigdecimal", BigDecimal.class);
registerAlias("biginteger", BigInteger.class);
registerAlias("object", Object.class);
registerAlias("date[]", Date[].class);
registerAlias("decimal[]", BigDecimal[].class);
registerAlias("bigdecimal[]", BigDecimal[].class);
registerAlias("biginteger[]", BigInteger[].class);
registerAlias("object[]", Object[].class);
registerAlias("map", Map.class);
registerAlias("hashmap", HashMap.class);
registerAlias("list", List.class);
registerAlias("arraylist", ArrayList.class);
registerAlias("collection", Collection.class);
registerAlias("iterator", Iterator.class);
registerAlias("ResultSet", ResultSet.class);
加上 上面我们曾经写道过的Configuration中的
其实Mybatis在加载的时候就会在别名注册中心注册一些默认的别名进去
2.4 pluginElement方法来加载plugin标签
此处引用
作者:田小波
链接:https://www.imooc.com/article/48573?block_id=tuijian_wz 来源:慕课网
插件是 MyBatis 提供的一个拓展机制,通过插件机制我们可在 SQL 执行过程中的某些点上做一些自定义操作。实现一个插件需要比简单,首先需要让插件类实现Interceptor接口。然后在插件类上添加@Intercepts和@Signature注解,用于指定想要拦截的目标方法。MyBatis 允许拦截下面接口中的一些方法:
- Executor: update 方法,query 方法,flushStatements 方法,commit 方法,rollback 方法, getTransaction 方法,close 方法,isClosed 方法
- ParameterHandler: getParameterObject 方法,setParameters 方法
- ResultSetHandler: handleResultSets 方法,handleOutputParameters 方法
- StatementHandler: prepare 方法,parameterize 方法,batch 方法,update 方法,query 方法
比较常见的插件有分页插件、分表插件等,有兴趣的朋友可以去了解下。本节我们来分析一下插件的配置的解析过程,先来了解插件的配置。如下:
<plugins>
<!-- com.github.pagehelper为PageHelper类所在包名 -->
<plugin interceptor="com.github.pagehelper.PageInterceptor">
<!-- 使用下面的方式配置参数,后面会有所有的参数介绍 -->
<property name="param1" value="value1"/>
</plugin>
</plugins>
private void pluginElement(XNode parent) throws Exception {
if (parent != null) {
//还是遍历节点
for (XNode child : parent.getChildren()) {
String interceptor = child.getStringAttribute("interceptor");
//找到interceptor 属性
Properties properties = child.getChildrenAsProperties();
//解析拦截器属性并创建拦截器 里面代码过于简单不在此处说明了。。一看newInstance 就是创建了。。
Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor).newInstance();
//设置属性
interceptorInstance.setProperties(properties);
//添加拦截器进入configuration中
configuration.addInterceptor(interceptorInstance);
}
}
}
public Properties getChildrenAsProperties() {
Properties properties = new Properties();
for (XNode child : getChildren()) {
String name = child.getStringAttribute("name");
String value = child.getStringAttribute("value");
if (name != null && value != null) {
properties.setProperty(name, value);
}
}
return properties;
}
值得看的也就这块了。其他真的没什么可说的。
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 (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);
}
}
2.5objectFactoryElement
说句心里话本身抱着学习源码的精神就学习一下。不过没什么蛋用。。你用Mybatis实际生产中难道还需要重写他工厂方法么。。真的蛋疼。。
2.6objectWrapperFactoryElement|reflectorFactoryElement
其实上面在写setting中的时候已经涉及了这方面的只是了。就是反射。反射。框架都是反射用的多。我觉得这三个可以不用深入了解。除非公司需要。一般大多数日常生产中 并不需要关注
2.7environmentsElement
在 MyBatis 中,事务管理器和数据源是配置在 environments 中的。它们的配置大致如下:
<environments default="development">
<environment id="development">
<transactionManager type="JDBC" />
<dataSource type="POOLED">
<property name="driver" value="${db.driver}" />
<property name="url" value="${db.url}" />
<property name="username" value="${db.username}" />
<property name="password" value="${db.password}" />
</dataSource>
</environment>
</environments>
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");
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());
}
}
}
}
上面的代码一眼就看到头。。本着分析源码的态度。
- default=“development” 默认使用
- 判断id是否有值 判断environment是否有 没有都返回false 然后判断这俩个是不是一样
- 然后就是加载事务工厂。数据源工厂。然后通过构造者模式 。加载者俩种进Environment
- 添加进Configuration
2.8settingsElement
这个本身应该衔接setting那快进行分析。但是忘记了。。现在补一下。其实很简单就是把对应的配置文件加载到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")));
}
resolveClass我们上面在alias中也讲述过了。。就是别名注册那块
2.9 databaseIdProviderElement
多数据源配置
<databaseIdProvider type="DB_VENDOR">
<property name="MySQL" value="mysql" />
<property name="Oracle" value="oracle" />
</databaseIdProvider>
private void databaseIdProviderElement(XNode context) throws Exception {
DatabaseIdProvider databaseIdProvider = null;
if (context != null) {
String type = context.getStringAttribute("type");
// awful patch to keep backward compatibility
if ("VENDOR".equals(type)) {
type = "DB_VENDOR";
}
//获得属性
Properties properties = context.getChildrenAsProperties();
//创建实例
databaseIdProvider = (DatabaseIdProvider) resolveClass(type).newInstance();
//属性赋值
databaseIdProvider.setProperties(properties);
}
//获得environments对象
Environment environment = configuration.getEnvironment();
if (environment != null && databaseIdProvider != null) {
//这个里面有逻辑大家自行点击。我总结一下。就是他根据DataSource 数据源 然后获得是什么数据库而已。然后加载进configuration
String databaseId = databaseIdProvider.getDatabaseId(environment.getDataSource());
configuration.setDatabaseId(databaseId);
}
}
篇幅已经很大了。下一篇继续分析typeHandlerElement|mapperElement