Mybatis源码分析(5)---配置节点typeAliases源码解析

    xiaoxiao2023-10-27  145

    背景 在上篇Mybatis源码分析(4)—配置节点settings源码解析中,我们通过settings节点在于XMLConfigBuilder类中的settingsAsProperties(XNode context)、loadCustomVfs(Properties props)、loadCustomLogImpl(Properties props)以及settingsElement(Properties props)等方法源码分析,了解了mybatis的整个settings节点的源码解析过程。 今天接下来我们将对节点typeAliases进行源码分析,分析之前,依然先通过以下图来重温一下之前SqlSessionFactory对象创建的过程: 我们依然围绕着Demo工程mybatisCode对节点typeAliases进行深入的解析和测试节点typeAliases介绍 顾名思义:typeAliases直译:类型别名,为Java 类型设置的一个短的名字,它只和 XML 配置有关,存在的意义仅在于用来减少类完全限定名的冗余(本介绍来源于mybatis官网)。 类型别名(typeAliases)在mybatis-config.xml中的配置的方式可以有两种,我们拿Demo工程mybatisCode中的配置来讲解: 以上截图中,我可以看到,节点typeAliases可以不配置,但是如果配置的话,可以直接配置元素节点:package和typeAlias。package配置属性name为包路径,typeAlias配置的type为类完全限定名,alias为别名, 那么问题来了! 问题1:如果我们的mybatis-config.xml中不配置节点typeAliases,mybatis在解析配置文件的时候会做什么?问题2:如果配置了节点typeAliases信息,解析节点元素package和typeAlias顺序和方式是什么? 带着上面两个问题,我们接下来就开始对节点typeAliases源码进行深入的分析! 节点typeAliases源码分析 节点typeAliases源码解析的位置在XMLConfigBuilder类中typeAliasesElement(XNode parent)方法中 private void parseConfiguration(XNode root) { try { //issue #117 read properties first propertiesElement(root.evalNode("properties")); Properties settings = settingsAsProperties(root.evalNode("settings")); loadCustomVfs(settings); loadCustomLogImpl(settings); /**************解析节点typeAliases**************/ 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); } } private void typeAliasesElement(XNode parent) { if (parent != null) { //遍历节点typeAliases的所有子元素 for (XNode child : parent.getChildren()) { //如果配置的子元素为package if ("package".equals(child.getName())) { //获取package的属性name中的包路径名 String typeAliasPackage = child.getStringAttribute("name"); //注册到全局配置类Configuration中的TypeAliasRegistry类别名容器中 configuration.getTypeAliasRegistry().registerAliases(typeAliasPackage); } else { //如果配置的子元素是typeAlias,则获取alias别名和type类完全限定名 String alias = child.getStringAttribute("alias"); String type = child.getStringAttribute("type"); try { //反射获取type的Class对象 Class<?> clazz = Resources.classForName(type); //注册到全局配置类Configuration中的TypeAliasRegistry类别名容器中 if (alias == null) { //如果alias没有配置 typeAliasRegistry.registerAlias(clazz); } else { //如果alias配置了 typeAliasRegistry.registerAlias(alias, clazz); } } catch (ClassNotFoundException e) { throw new BuilderException("Error registering typeAlias for '" + alias + "'. Cause: " + e, e); } } } } }

    我们通过上面源码剖析总结: 1.解析节点:typeAliases的子元素解析顺序可以说是先package后typeAlias; 2.typeAlias中alias别名可以配置或者不配置,两者区别是什么,接下来我们将继续分析; 3.不管是package还是typeAlias元素节点配置,最终均注册到mybatis的全局配置类Configuration中的TypeAliasRegistry容器中;

    package元素源码解析过程 通过源码解析分析上面我们知道typeAliases节点中元素package,最终获取到的属性name中的包路径,然后调用mybatis的全局配置类Configuration中的TypeAliasRegistry容器类的 registerAliases(String packageName)方法进行注册。接下来我们顺着源码的调用看看package元素中的包路径是怎么注册到TypeAliasRegistry容器中的; public class TypeAliasRegistry { private final Map<String, Class<?>> typeAliases = new HashMap<>(); public void registerAliases(String packageName) { //实际调用的是registerAliases(String packageName, Class<?> superType) 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); } public void registerAlias(String alias, Class<?> value) { if (alias == null) { throw new TypeException("The parameter alias cannot be null"); } // 把解析完后的包路径下的bean的首字母进行小写 String key = alias.toLowerCase(Locale.ENGLISH); if (typeAliases.containsKey(key) && typeAliases.get(key) != null && !typeAliases.get(key).equals(value)) { throw new TypeException("The alias '" + alias + "' is already mapped to the value '" + typeAliases.get(key).getName() + "'."); } // 最终解析完之后包路径下的所有类的别名作为key,类的Class对象作为Value保存在TypeAliasRegistry容器中 //注意:typeAliases为一个HashMap集合 typeAliases.put(key, value); } }

    上面的源码调用链路比较长,这里就不具体的一一解释,我们重点落在两个地方: 1.解析完后的包路径下的bean的首字母进行小写; 2.包路径下的所有类的别名作为key,类的Class对象作为Value保存在typeAliases中 总结:package元素解析,就是扫描包下的所有bean,然后默认别名为类名的首字母小写,最终注册到全局配置类Configuration中的TypeAliasRegistry容器类中,容器中实际使用的是HashMap来存储:bean的首字母小写为作为key,bean的Class对象为Value。

    typeAlias元素源码解析过程 public class TypeAliasRegistry { 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"); } // 最终别名的命名规则都进行首字母小写 String key = alias.toLowerCase(Locale.ENGLISH); if (typeAliases.containsKey(key) && typeAliases.get(key) != null && !typeAliases.get(key).equals(value)) { throw new TypeException("The alias '" + alias + "' is already mapped to the value '" + typeAliases.get(key).getName() + "'."); } // 最终解析完之后typeAlias中配置的类别名作为key,类的Class对象作为Value保存在TypeAliasRegistry容器中 typeAliases.put(key, value); } }

    总结: 1.typeAlias中的属性type进行对bean反射获取它的Class对象 2.属性alias没配置,就使用bean本身的类的简写名称(不包含包路径),如果bean的类上面有注解@Alias(""),则以注解中的值作为类别名,别名都默认进行首字母小写。 3.最终注册到全局配置类Configuration中的TypeAliasRegistry容器类中,bean的别名首字母小写为作为key,bean的Class对象为Value; 4.源码分析到这里,我们解决了一开始提出的问题2:解析节点元素package和typeAlias顺序和方式是什么?那么问题1:如果不配置节点typeAliases的时候,mybatis自己会做些什么呢?接下来我们就开始寻找问题的答案;

    无节点typeAliases配置源码分析 首先我们来回顾XMLConfigBuilder实例化时父类BaseBuilder中的全局配置类Configuration初始化的地方 public class XMLConfigBuilder extends BaseBuilder { private XMLConfigBuilder(XPathParser parser, String environment, Properties props) { //初始化mybatis的全局配置类,调用的是无参构造方法 super(new Configuration()); ErrorContext.instance().resource("SQL Mapper Configuration"); this.configuration.setVariables(props); this.parsed = false; this.environment = environment; this.parser = parser; } } Configuration的无参构造方法 public class Configuration { protected final TypeAliasRegistry typeAliasRegistry = new TypeAliasRegistry(); 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); } }

    看到这里我们知道了,mybatis在初始化全局配置类Configuration的时候在其无参构造方法,自动注册了一堆bean别名和他们的Class对象到typeAliasRegistry容器类中,而节点typeAliases配置的别名最终也是注册到typeAliasRegistry容器类中,为了方便后面直接获取调用; 当然事情还没结束,我们接着来窥探typeAliasRegistry类别名注册容器的内部。

    public class TypeAliasRegistry { private final Map<String, Class<?>> typeAliases = new HashMap<>(); public 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); } }

    看到没有,TypeAliasRegistry 本身在初始化的时候,也会自动注册一些bean的别名和Class对象到自身最终存储HashMap中去,而这些大多是我们常常用到的基本类型的包装类和数组,字符串,集合等等,所以就算我们做相关的节点typeAliases配置,mybatis通过全局配置类Configuration,以其属性中的类别名注册容器TypeAliasRegistry为我们提前准备好了很多实用和常用的类别名,方便我们后期的调用。

    节点typeAliases配置的好处 上面对mybatis-config.xml配置文件节点typeAliases在源码中的解析分析了半天,我们接下来就来讲讲节点typeAliases配置的好处是什么? 好处就是,让我们mapper等映射文件中写sql的时候,不需要把类的完全路径全部写出来,直接写别名,mybatis通过类别名注册容器TypeAliasRegistry可以自动帮我们找到Class对象,通过反射进行一系列的操作,使用上更方便; 还有就是提前注册好了mybatis常常需要使用的一些类别名和他们的Class对象,不需要等用的时候在进行加载
    最新回复(0)