mybatis源码篇(四)—— objectFactory、mappers标签解析

我们之前在mybatis中添加了mappers配置,但是没有添加过objectFactory配置:

    <mappers>
        <mapper resource="cn/zsm/mybatis/man/ManMapper.xml"/>
        <mapper resource="cn/zsm/mybatis/student/StudentMapper.xml"/>
    </mappers>

objectFactory:

  MyBatis 每次创建结果对象的新实例时,它都会使用一个对象工厂(ObjectFactory)实例来完成。默认的对象工厂需要做的仅仅是实例化目标类,要么通过默认构造方法,要么在参数映射存在的时候通过参数构造方法来实例化。默认情况下,我们不需要配置,mybatis会调用默认实现的objectFactory。 除非我们要自定义ObjectFactory的实现, 那么我们才需要去手动配置。 

  自定义ObjectFactory只需要去继承DefaultObjectFactory(是ObjectFactory接口的实现类),并重写其方法即可。配置自定义的ObjectFactory如下:

// ExampleObjectFactory.java
public class ExampleObjectFactory extends DefaultObjectFactory {
      public Object create(Class type) {
        return super.create(type);
      }
      public Object create(Class type, List<Class> constructorArgTypes, List<Object> constructorArgs) {
        return super.create(type, constructorArgTypes, constructorArgs);
      }
      public void setProperties(Properties properties) {
        super.setProperties(properties);
      }
      public <T> boolean isCollection(Class<T> type) {
        return Collection.class.isAssignableFrom(type);
      }
}


<!-- mybatis-config.xml -->
<objectFactory type="org.mybatis.example.ExampleObjectFactory">
  <property name="someProperty" value="100"/>
</objectFactory>

那么在我们没有定义objectFactory标签时,系统中设置的默认objectFactory是谁呢,我们debug模式来看一下:

1、我们首先进入build方法:

    public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
        SqlSessionFactory var5;
        try {
            //初始化XMLConfigBuilder,初始化一些默认的配置
            XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
            var5 = this.build(parser.parse());
        } catch (Exception var14) {
            throw ExceptionFactory.wrapException("Error building SqlSession.", var14);
        } finally {
            ErrorContext.instance().reset();
            try {
                inputStream.close();
            } catch (IOException var13) {
                ;
            }
        }
        return var5;
    }

在build方法中,会初始化XMLConfigBuilder实例,同时也会初始化一些Mybatis默认的配置。

2、new XMLConfigBuilder(......):

    public XMLConfigBuilder(InputStream inputStream, String environment, Properties props) {
        this(new XPathParser(inputStream, true, props, new XMLMapperEntityResolver()), environment, props);
    }


    private XMLConfigBuilder(XPathParser parser, String environment, Properties props) {
        super(new Configuration());
        this.localReflectorFactory = new DefaultReflectorFactory();
        ErrorContext.instance().resource("SQL Mapper Configuration");
        this.configuration.setVariables(props);
        this.parsed = false;
        this.environment = environment;
        this.parser = parser;
    }

初始化XMLConfigBuilder实例时,会设置一些默认属性值,但是这里并没有看到objectFactory属性。但是这里还有一个实例被初始化:new Configuration()

public Configuration() {
        this.safeResultHandlerEnabled = true;
        this.multipleResultSetsEnabled = true;
        this.useColumnLabel = true;
        this.cacheEnabled = true;
        this.useActualParamName = true;
        this.localCacheScope = LocalCacheScope.SESSION;
        this.jdbcTypeForNull = JdbcType.OTHER;
        this.lazyLoadTriggerMethods = new HashSet(Arrays.asList("equals", "clone", "hashCode", "toString"));
        this.defaultExecutorType = ExecutorType.SIMPLE;
        this.autoMappingBehavior = AutoMappingBehavior.PARTIAL;
        this.autoMappingUnknownColumnBehavior = AutoMappingUnknownColumnBehavior.NONE;
        this.variables = new Properties();
        this.reflectorFactory = new DefaultReflectorFactory();

        //默认的objectFactory实例
        this.objectFactory = new DefaultObjectFactory();
        this.objectWrapperFactory = new DefaultObjectWrapperFactory();
        this.lazyLoadingEnabled = false;
        this.proxyFactory = new JavassistProxyFactory();
        this.mapperRegistry = new MapperRegistry(this);
        this.interceptorChain = new InterceptorChain();
        this.typeHandlerRegistry = new TypeHandlerRegistry();
        this.typeAliasRegistry = new TypeAliasRegistry();
        this.languageRegistry = new LanguageDriverRegistry();
        this.mappedStatements = (new Configuration.StrictMap("Mapped Statements collection")).conflictMessageProducer((savedValue, targetValue) -> {
            return ". please check " + savedValue.getResource() + " and " + targetValue.getResource();
        });
        this.caches = new Configuration.StrictMap("Caches collection");
        this.resultMaps = new Configuration.StrictMap("Result Maps collection");
        this.parameterMaps = new Configuration.StrictMap("Parameter Maps collection");
        this.keyGenerators = new Configuration.StrictMap("Key Generators collection");
        this.loadedResources = new HashSet();
        this.sqlFragments = new Configuration.StrictMap("XML fragments parsed from previous mappers");
        this.incompleteStatements = new LinkedList();
        this.incompleteCacheRefs = new LinkedList();
        this.incompleteResultMaps = new LinkedList();
        this.incompleteMethods = new LinkedList();
        this.cacheRefMap = new HashMap();
        this.typeAliasRegistry.registerAlias("JDBC", JdbcTransactionFactory.class);
        this.typeAliasRegistry.registerAlias("SOFT", SoftCache.class);
        this.typeAliasRegistry.registerAlias("WEAK", WeakCache.class);
        this.typeAliasRegistry.registerAlias("DB_VENDOR", VendorDatabaseIdProvider.class);
        this.typeAliasRegistry.registerAlias("XML", XMLLanguageDriver.class);
        ......
        this.typeAliasRegistry.registerAlias("SLF4J", Slf4jImpl.class);
        this.languageRegistry.setDefaultDriverClass(XMLLanguageDriver.class);
    }

初始化Configuration时,我们会设置很多默认的属性值,包括我们这里说的objectFactory,而它默认的实例化对象时DefaultObjectFactory类的实例。关于DefaultObjectFactory,我们后续会再做详细讲解,它主要是在实例化查询结果对象时,创建对象并给属性赋值。

3、如果我们在配置文件中配置了objectFactory:

    private void parseConfiguration(XNode root) {
        try {
            this.propertiesElement(root.evalNode("properties"));
            Properties settings = this.settingsAsProperties(root.evalNode("settings"));
            this.loadCustomVfs(settings);
            this.loadCustomLogImpl(settings);
            this.typeAliasesElement(root.evalNode("typeAliases"));
            this.pluginElement(root.evalNode("plugins"));
            //解析objectFactory标签
            this.objectFactoryElement(root.evalNode("objectFactory"));
            this.objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
            this.reflectorFactoryElement(root.evalNode("reflectorFactory"));
            this.settingsElement(settings);
            this.environmentsElement(root.evalNode("environments"));
            this.databaseIdProviderElement(root.evalNode("databaseIdProvider"));
            this.typeHandlerElement(root.evalNode("typeHandlers"));
            this.mapperElement(root.evalNode("mappers"));
        } catch (Exception var3) {
            throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + var3, var3);
        }
    }

objectFactoryElement:

    private void objectFactoryElement(XNode context) throws Exception {
        if (context != null) { //如果标签内容不为空
            String type = context.getStringAttribute("type");
            Properties properties = context.getChildrenAsProperties();
            ObjectFactory factory = (ObjectFactory)this.resolveClass(type).newInstance(); //重新初始化factory属性
            factory.setProperties(properties);
            this.configuration.setObjectFactory(factory);
        }
    }

mappers:

mappers 节点是myabtis必须配置的节点,它里面配置了我们的mapper映射文件, 所谓的mapper映射文件,就是让mybatis 用来建立数据表和javabean映射的一个桥梁。在我们实际开发中,通常一个mapper文件对应一个dao接口, 这个mapper可以看做是dao的实现。所以,mappers必须配置。

某大厂有一个非常经典的面试题,这里大家看看能都答的出来:

  • Myabtis加载mapper有几种方式??

关于这个问题我在前面的博文也提到过,不过看完mybatis解析mapper的源码,我们也能了解:

this.mapperElement(root.evalNode("mappers")):

    private void mapperElement(XNode parent) throws Exception {
        if (parent != null) {
            //获取并遍历mappers下的所有子节点
            Iterator var2 = parent.getChildren().iterator();

            while(true) {
                while(var2.hasNext()) {
                    XNode child = (XNode)var2.next();
                    String resource;
                    //如果mappers节点的子节点是package, 那么就扫描package下的文件, 注入进configuration
                    if ("package".equals(child.getName())) {
                        resource = child.getStringAttribute("name");
                        this.configuration.addMappers(resource);
                    } else {//如果不是package,则扫描resource、url、class这三个子节点,总会有一个存在
                        resource = child.getStringAttribute("resource");
                        String url = child.getStringAttribute("url");
                        String mapperClass = child.getStringAttribute("class");
                        XMLMapperBuilder mapperParser;
                        InputStream inputStream;
                        if (resource != null && url == null && mapperClass == null) {
                            ErrorContext.instance().resource(resource);
                            inputStream = Resources.getResourceAsStream(resource);
                            //mapper映射文件都是通过XMLMapperBuilder解析
                            mapperParser = new XMLMapperBuilder(inputStream, this.configuration, resource, this.configuration.getSqlFragments());
                            mapperParser.parse();
                        } else if (resource == null && url != null && mapperClass == null) {
                            ErrorContext.instance().resource(url);
                            inputStream = Resources.getUrlAsStream(url);
                            mapperParser = new XMLMapperBuilder(inputStream, this.configuration, url, this.configuration.getSqlFragments());
                            mapperParser.parse();
                        } else {
                            if (resource != null || url != null || mapperClass == null) {
                                throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
                            }

                            Class<?> mapperInterface = Resources.classForName(mapperClass);
                            this.configuration.addMapper(mapperInterface);
                        }
                    }
                }

                return;
            }
        }
    }

从上面的源码可以看出,解析mappers 节点时,会先检查和解析package子节点,然后再解析resource、url、class这三个子节点,也就是说我们在mappers中最多可以设置四种子节点:package、resource、url、class。

回到上面提到的面试题,mybatis加载mapper的方式也就有四种:package、resource、url、class

发布了74 篇原创文章 · 获赞 19 · 访问量 5万+

猜你喜欢

转载自blog.csdn.net/zhoushimiao1990/article/details/100538364