For code debugging, we can use any test code in the previous chapter as the Debug carrier. In this chapter, we actually study these two codes:
InputStream xml = Resources.getResourceAsStream("mybatis-config.xml"); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(xml);
That is, how to load the MyBatis global configuration file and how to build it from the global configuration file SqlSessionFactory .
1. Loading of global configuration file
First, let's look at the loading of the configuration file Resources.getResourceAsStream The method can only be guessed from the method name. It should be with the help of class loader. Let's take a quick look at the source code:
public static InputStream getResourceAsStream(String resource) throws IOException { return getResourceAsStream(null, resource); } public static InputStream getResourceAsStream(ClassLoader loader, String resource) throws IOException { InputStream in = classLoaderWrapper.getResourceAsStream(resource, loader); if (in == null) { throw new IOException("Could not find resource " + resource); } return in; }
This place doesn't seem to come in ClassLoader , Actually take ClassLoader In another location:
ClassLoader[] getClassLoaders(ClassLoader classLoader) { return new ClassLoader[]{ classLoader, defaultClassLoader, Thread.currentThread().getContextClassLoader(), getClass().getClassLoader(), systemClassLoader }; }
Take so much at once ClassLoader , What's the picture? Obviously, it wants to go one by one ClassLoader Try it all. As long as you can get the resources, it's OK . The following is the actual use ClassLoader Load the underlying source code of the global configuration file:
InputStream getResourceAsStream(String resource, ClassLoader[] classLoader) { for (ClassLoader cl : classLoader) { if (null != cl) { // try to find the resource as passed InputStream returnValue = cl.getResourceAsStream(resource); // now, some class loaders want this leading "/", so we'll add it and try again if we didn't find the resource if (null == returnValue) { returnValue = cl.getResourceAsStream("/" + resource); } if (null != returnValue) { return returnValue; } } } return null; }
The logic is very clear, so using this method, you can easily obtain the binary stream of the global configuration file.
2. Parse configuration file
The following is the parsing process. Our test code directly creates a new one SqlSessionFactoryBuilder , Subsequent adjustment build Method to construct SqlSessionFactory :
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(xml);
and build The method finally comes to an overloaded method with three parameters:
public SqlSessionFactory build(InputStream inputStream) { return build(inputStream, null, null); } public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) { try { XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties); return build(parser.parse()); } // catch finally ...... }
It can be seen that the first underlying core component used here is XMLConfigBuilder , xml based configuration Builder (embodiment of builder pattern). And this XMLConfigBuilder , First inherited a family called BaseBuilder What you need:
public class XMLConfigBuilder extends BaseBuilder { // ...... }
Let's first study the construction of these two classes.
2.1 BaseBuilder
BaseBuilder As its name implies, it is a basic constructor. Its initialization needs to pass in the global configuration object of MyBatis Configuration :
public abstract class BaseBuilder { protected final Configuration configuration; protected final TypeAliasRegistry typeAliasRegistry; protected final TypeHandlerRegistry typeHandlerRegistry; public BaseBuilder(Configuration configuration) { this.configuration = configuration; this.typeAliasRegistry = this.configuration.getTypeAliasRegistry(); this.typeHandlerRegistry = this.configuration.getTypeHandlerRegistry(); }
this Configuration As we all know, after MyBatis is initialized, all configuration items, Mapper and statement will be stored here. It's OK if you can remember.
Take a general look at the defined methods. Most of them are methods such as parsing and obtaining, which looks more like providing basic tool class method support: (the following is a reference) BaseBuilder Two methods defined in)
protected Boolean booleanValueOf(String value, Boolean defaultValue) { return value == null ? defaultValue : Boolean.valueOf(value); } protected JdbcType resolveJdbcType(String alias) { if (alias == null) { return null; } try { return JdbcType.valueOf(alias); } catch (IllegalArgumentException e) { throw new BuilderException("Error resolving JdbcType. Cause: " + e, e); } }
In this way, the core processing logic is not BaseBuilder In, we return to the implementation class XMLConfigBuilder Yes.
2.2 XMLConfigBuilder
As usual, let's take a look at the internal members and the definition of the construction method.
2.2.1 definition of construction method
public class XMLConfigBuilder extends BaseBuilder { private boolean parsed; private final XPathParser parser; private String environment; private final ReflectorFactory localReflectorFactory = new DefaultReflectorFactory(); public XMLConfigBuilder(InputStream inputStream, String environment, Properties props) { // Notice that an XPath parser is added here 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; }
There are many overloaded construction methods in the source code, and the above is SqlSessionFactoryBuilder Invoked in build Method, and finally the three parameter constructor in the above code is called. This constructor calls the overloaded constructor below. Note that in the source code, its overloaded construction method is no longer required InputStream , But constructed a XPathParser , Although we haven't seen this guy, we can probably guess that it is a parser for parsing xml global configuration files. We don't need to study this thing first. Just look by when we use it below.
In addition, in the lowest construction method, it can be found that Configuration The object is generated by new here, and it is very simple. It is generated by using the null parameter construction method new, so you can first understand one thing: if you really want us to operate by ourselves, initialize MyBatis Configuration , It's not that we can't. It doesn't matter if we operate by ourselves.
The rest, in the lowest construction method, are ordinary assignment operations, and there is nothing to say.
2.2.2 core parse method
The next code is return build(parser.parse()); Well, this line of code is actually two methods. First, it calls XMLConfigBuilder of parse Methods, generating Configuration , After that SqlSessionFactoryBuilder of build method. Let's look at it first XMLConfigBuilder How the configuration file is parsed.
public Configuration parse() { if (parsed) { throw new BuilderException("Each XMLConfigBuilder can only be used once."); } parsed = true; parseConfiguration(parser.evalNode("/configuration")); return configuration; }
The core method is still in the middle parseConfiguration Method, but before that, let's focus on parser.evalNode("/configuration") This action.
2.2.2.1 XPathParser#evalNode
XPathParser The function of is to convert the xml configuration file to Document Object and provide the corresponding xml tag node. its evalNode Method is used to obtain the tag specified in xml:
public XNode evalNode(String expression) { return evalNode(document, expression); } public XNode evalNode(Object root, String expression) { Node node = (Node) evaluate(expression, root, XPathConstants.NODE); if (node == null) { return null; } return new XNode(this, node, variables); } private Object evaluate(String expression, Object root, QName returnType) { try { // Parsing xml using javax's XPath return xpath.evaluate(expression, root, returnType); } catch (Exception e) { throw new BuilderException("Error evaluating XPath. Cause: " + e, e); } }
as for XPath How to do it internally is the mechanism of xml parsing. We don't care. The only thing we should care about is the middle evalNode Method analysis Node After that, the return is encapsulated into a XNode . What is it?
2.2.2.2 intent of node encapsulation as XNode
Watch XNode The construction method of it is passed into a variables Object, and this variables In fact, they are those defined in the global configuration file < properties> Labels, and introduced . properties File. But why are these configuration attribute values involved? Let's recall that we wrote such code in the global configuration file before:
<environments default="development"> <environment id="development"> <transactionManager type="JDBC"/> <dataSource type="POOLED"> <property name="driver" value="${jdbc.driverClassName}"/> <property name="url" value="${jdbc.url}"/> <property name="username" value="${jdbc.username}"/> <property name="password" value="${jdbc.password}"/> </dataSource> </environment> </environments>
If you really want to take the driver attribute, you must not $ {jdbc.driverClassName} Take it out. You have to dynamically replace the value of the configuration attribute. But javax is native Node I can't do this, so MyBatis is based on javax Node Encapsulates a XNode , Combined XPathParser , You can achieve the effect of dynamically parsing the configuration attribute value.
If it is explained in this way, many small partners will look at a loss. For example, the booklet.
Take the above xml as an example, when we get < dataSource> After the tag is, the driver , url And other attributes, and the values of these attributes are placeholders. When parsing, MyBatis will first parse them as usual < property> Tag, and then get the value attribute (for example, parsing) driver Property, so the returned value is $ {jdbc.driverClassName} ), Then! It will use a placeholder parser to parse the placeholder and properties> Replace the configuration attribute defined / loaded in the tag with the corresponding attribute value. Through this step, ${JDBC. Driverclassname} Is replaced by com.mysql.jdbc.Driver , In this way, the resolution of dynamic configuration attribute value is realized.
Is it easier to understand after this explanation? Let's look at the source code:
// XNode public String evalString(String expression) { // If you don't parse it yourself, delegate XPathParser to parse it return xpathParser.evalString(node, expression); } // XPathParser public String evalString(Object root, String expression) { // First take the plaintext attribute value from the label String result = (String) evaluate(expression, root, XPathConstants.STRING); // The placeholder is handled by the placeholder service parser and replaced with the real configuration value result = PropertyParser.parse(result, variables); return result; }
The processing idea is clear at a glance, so we can understand why MyBatis chose to seal an additional layer XNode , Not the one that uses javax directly Node Let's go.
2.2.3 parseConfiguration
Go back to the top parse Method, parse The essence of method is analysis < configuration> The content of the tag, so let's enter parseConfiguration Method:
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); 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); } }
Ah, ah ah ah ah ah ah ah ah ah ah ah ah ah ah ah ah ah ah ah ah ah ah ah ah ah ah ah ah ah ah ah ah ah ah ah ah ah ah ah ah ah ah ah ah ah ah ah ah ah ah ah ah ah ah ah ah ah ah ah ah ah ah ah ah ah ah ah ah ah ah ah ah ah ah ah ah ah ah ah ah ah ah ah ah? This is too simple! OK, now that we have excavated this place, we can take a look one by one.
2.2.3.1 propertiesElement - parsing properties
The first thing to analyze is < properties> Tag, which will parse the internal definition < property> , And configured resource , url Attributes: (key comments have been marked in the source code)
private void propertiesElement(XNode context) throws Exception { if (context != null) { // Load the internally defined < property > first Properties defaults = context.getChildrenAsProperties(); String resource = context.getStringAttribute("resource"); String url = context.getStringAttribute("url"); if (resource != null && url != null) { // You can't have both // throw ex ...... } if (resource != null) { defaults.putAll(Resources.getResourceAsProperties(resource)); } else if (url != null) { defaults.putAll(Resources.getUrlAsProperties(url)); } // Configuration property values loaded by the programmer Properties vars = configuration.getVariables(); if (vars != null) { defaults.putAll(vars); } // Put the configuration attribute value into the parser and global configuration parser.setVariables(defaults); configuration.setVariables(defaults); } }
After reading the whole source code, you can find the whole loading process, which is the process we explained in the previous chapter. Moreover, through the daily reading of the source code, we can understand why the priority of configuration is the highest in programming, followed by the properties file, and the lowest defined in the configuration file.
This method is very simple. Notes are also marked in the source code, so there is no more explanation in the booklet.
2.2.3.2 settingsAsProperties - add in configuration item
Here is the analysis < settings> Tag. The parsing of this tag involves 3 lines of code:
Properties settings = settingsAsProperties(root.evalNode("settings")); loadCustomVfs(settings); loadCustomLogImpl(settings);
It is not difficult to understand that this operation is obviously < settings> The label is configured line by line and encapsulated as one Properties , Then deal with the VFS and Log components in addition. The contents of VFS will not be mentioned for the time being. Let's take a look at how the underlying layer handles the configuration of Log components:
private void loadCustomLogImpl(Properties props) { Class<? extends Log> logImpl = resolveClass(props.getProperty("logImpl")); configuration.setLogImpl(logImpl); }
Oh, such a simple logic? It is taken directly from the configuration logImpl And then set to Configuration It's over in, and this resolveClass Method is actually resolved by alias:
// BaseBuilder protected <T> Class<? extends T> resolveClass(String alias) { if (alias == null) { return null; } try { return resolveAlias(alias); } // catch ...... } protected <T> Class<? extends T> resolveAlias(String alias) { return typeAliasRegistry.resolveAlias(alias); }
And the registration of these aliases, as early as Configuration When it is created, it is all initialized:
public Configuration() { typeAliasRegistry.registerAlias("JDBC", JdbcTransactionFactory.class); typeAliasRegistry.registerAlias("MANAGED", ManagedTransactionFactory.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); // ...... }
so when we configure those setting configuration items, the content that can be filled in actually refers to the alias predetermined by MyBatis here (which can also explain why we configure them in settings log4j It doesn't work well. It must be configured LOG4J Can).
2.2.3.3 typeAliasesElement - register type alias
OK, the next one to parse is typeAliases Label, we know it can be used < package> Direct scanning can also be used < typeAlias> Directly declare an alias for the specified type. The bottom layer deals with these two situations respectively:
private void typeAliasesElement(XNode parent) { if (parent != null) { for (XNode child : parent.getChildren()) { // Specify alias for package scan processing package if ("package".equals(child.getName())) { String typeAliasPackage = child.getStringAttribute("name"); // Note that registerAliases is called to register a group configuration.getTypeAliasRegistry().registerAliases(typeAliasPackage); } else { // Handles the typeAlias label definition on a case by case basis 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); } } } } }
Note that in the source code, if it is processing < package> The method called here is also different for the package scanning declared by the tag! Enter into registerAliases In the method, we will find that one will be used here ResolverUtil Tool class to scan all classes (the parent class is Object ), After the scanning is completed, in the following for loop, judge whether these classes are ordinary classes (non interface, non anonymous inner class, non inner class). If so, register the alias.
public void registerAliases(String packageName) { registerAliases(packageName, Object.class); } public void registerAliases(String packageName, Class<?> superType) { ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<>(); // The above passed in is Object // Note that this scanning action is a full-level scanning, which will scan the sub packages 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); } } }
Note! There is a full-level package scanning action! Therefore, the configured package actually scans all classes under the specified package and its sub packages, and registers them with alias aliases.
2.2.3.4 pluginElement - registered plug-in
The next thing to register is plugins Plug in. The code logic is simple:
private void pluginElement(XNode parent) throws Exception { if (parent != null) { for (XNode child : parent.getChildren()) { String interceptor = child.getStringAttribute("interceptor"); Properties properties = child.getChildrenAsProperties(); // Create interceptor object directly Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor).getDeclaredConstructor().newInstance(); // Attribute assignment of interceptor interceptorInstance.setProperties(properties); configuration.addInterceptor(interceptorInstance); } } }
On the whole, it simply creates interceptors and registers them in the global Configuration. Of course, one thing we should be aware of: interceptors are created by MyBatis. If we want to integrate MyBatis with Spring and want Spring to manage MyBatis interceptors, it seems unrealistic.
2.2.3.5 register a bunch of factories
The following three lines of code register some factories:
objectFactoryElement(root.evalNode("objectFactory")); objectWrapperFactoryElement(root.evalNode("objectWrapperFactory")); reflectorFactoryElement(root.evalNode("reflectorFactory"));
The registration as like as two peas of the 3 Factory is almost the same at the bottom. ObjectFactory For example:
private void objectFactoryElement(XNode context) throws Exception { if (context != null) { String type = context.getStringAttribute("type"); Properties properties = context.getChildrenAsProperties(); ObjectFactory factory = (ObjectFactory) resolveClass(type).getDeclaredConstructor().newInstance(); factory.setProperties(properties); configuration.setObjectFactory(factory); } }
As like as two peas, the logic of interceptor is almost the same as that above, so we know all these logic OK.
2.2.3.6 settingsElement - application configuration item
The next action is to apply the configuration items initialized before. This method is long and wide (each line of code is so long! I really don't want to paste them all), so we only intercept a few lines of code for a symbolic look:
private void settingsElement(Properties props) { // ...... configuration.setCacheEnabled(booleanValueOf(props.getProperty("cacheEnabled"), true)); configuration.setProxyFactory((ProxyFactory) createInstance(props.getProperty("proxyFactory"))); configuration.setLazyLoadingEnabled(booleanValueOf(props.getProperty("lazyLoadingEnabled"), false)); // ...... }
emmmm, come on, isn't that what's in the global configuration file < settings> All applied to the global Configuration Among the objects, there's no mystery. Just scan it quickly.
2.2.3.7 environmentsElement - data source environment configuration
The following is the part of parsing the database environment configuration, because there are nested tags < transactionManager> And < dataSource> , So the source code here will be a little more complicated (just a little):
private void environmentsElement(XNode context) throws Exception { if (context != null) { if (environment == null) { // Get the default database environment configuration ID from default environment = context.getStringAttribute("default"); } for (XNode child : context.getChildren()) { String id = child.getStringAttribute("id"); // Only the default database environment configuration will be constructed if (isSpecifiedEnvironment(id)) { // Resolve the transactionManager tag to generate TransactionFactory TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager")); // Parse datasource tag to generate datasource DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource")); DataSource dataSource = dsFactory.getDataSource(); // Simple builder to construct Environment object Environment.Builder environmentBuilder = new Environment.Builder(id) .transactionFactory(txFactory) .dataSource(dataSource); configuration.setEnvironment(environmentBuilder.build()); } } } }
Reading the whole source code, the idea is still very clear. What it does is to load the transaction manager and the configuration of the data source and construct it Environment Object, and resolve when it's done transactionManager Labels, and dataSource The logic of the label is almost identical to the pile of factories analyzed above, so the source code of the booklet will not be pasted repeatedly. Just follow the IDE to turn over the source code.
In addition, Environment The structure of is just a combination of the above TransactionFactory And DataSource :
public final class Environment { private final String id; private final TransactionFactory transactionFactory; private final DataSource dataSource; // ...... }
2.2.3.8 databaseIdProviderElement - database vendor identification resolution
Next is < databaseIdProvider> For tag parsing, we say that if you want to use it, you can declare "DB_VENDOR", and then define the alias according to different database manufacturers. It is not difficult to understand this logic reflected in the source code:
private void databaseIdProviderElement(XNode context) throws Exception { DatabaseIdProvider databaseIdProvider = null; if (context != null) { String type = context.getStringAttribute("type"); // awful patch to keep backward compatibility // Write VENDOR and write DB_ Like VENDOR if ("VENDOR".equals(type)) { type = "DB_VENDOR"; } Properties properties = context.getChildrenAsProperties(); databaseIdProvider = (DatabaseIdProvider) resolveClass(type).getDeclaredConstructor().newInstance(); databaseIdProvider.setProperties(properties); } Environment environment = configuration.getEnvironment(); if (environment != null && databaseIdProvider != null) { String databaseId = databaseIdProvider.getDatabaseId(environment.getDataSource()); configuration.setDatabaseId(databaseId); } }
The first half is normal initialization DatabaseIdProvider The key is that there is another one below configuration.setDatabaseId(databaseId); What does it do?
Think about it carefully. We mentioned in the previous chapter, databaseidprovider It's cooperation mapper.xml It is used to define the statement in, and when the source code comes to this position, it is not yet its turn mapper.xml Analysis, if, as we did in the previous chapter, in a mapper.xml Two identical statements are defined in, and then it's your turn mapper.xml MyBatis as like as two peas, two statement are exactly the same as id. Isn't that equivalent to a crash? No, I have to hang him up! Therefore, an exception with the same id as the statement will be thrown. But! This logic is wrong. Although the IDs of the two statements are the same, the databaseId is different. One SqlSessionFactory Only one data source can be connected, and the database manufacturer of this data source is determined. Therefore, only one of the two statements can be used, so it is necessary to parse mapper.xml , Compare the databaseId when reading the statement! Where does this databaseId come from? Obviously, it can be seen from the overall situation Configuration Get it. This one up there setDatabaseId You can understand my action! This action is to determine the database manufacturer corresponding to the data source in advance and prepare for later parsing mapper.xml.
2.2.3.9 typeHandlerElement - register type processor
The following one resolves TypeHandler Yes, the logic is also relatively simple. You can either scan the package or register one by one, but in the end, you are registered to typeHandlerRegistry Medium:
private void typeHandlerElement(XNode parent) { if (parent != null) { for (XNode child : parent.getChildren()) { // Packet scanning if ("package".equals(child.getName())) { String typeHandlerPackage = child.getStringAttribute("name"); typeHandlerRegistry.register(typeHandlerPackage); } else { // Register typehandlers one by one 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); } } } } }
The logic is very simple. Just turn over the source code and you'll be OK. The booklet is no longer wordy.
2.2.3.10 mapperElement - parse mapper.xml
The resolution of the last node is mapper. At first glance, it doesn't seem very complicated:
private void mapperElement(XNode parent) throws Exception { if (parent != null) { for (XNode child : parent.getChildren()) { // Package scan Mapper interface if ("package".equals(child.getName())) { String mapperPackage = child.getStringAttribute("name"); configuration.addMappers(mapperPackage); } else { String resource = child.getStringAttribute("resource"); String url = child.getStringAttribute("url"); String mapperClass = child.getStringAttribute("class"); // Process mapper.xml loaded by resource if (resource != null && url == null && mapperClass == null) { ErrorContext.instance().resource(resource); InputStream inputStream = Resources.getResourceAsStream(resource); XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments()); mapperParser.parse(); } else if (resource == null && url != null && mapperClass == null) { // Process mapper.xml loaded by url ErrorContext.instance().resource(url); InputStream inputStream = Resources.getUrlAsStream(url); XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments()); mapperParser.parse(); } else if (resource == null && url == null && mapperClass != null) { // Register a single Mapper interface Class<?> mapperInterface = Resources.classForName(mapperClass); configuration.addMapper(mapperInterface); } else { throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one."); } } } } }
But please don't forget! After this method is completed, the initialization of the whole MyBatis is completed! But at this time mapper.xml Not loaded yet! So this link is also very important! In the source code, it can be found that except for the package scanning Mapper interface and a single registered Mapper interface, the other two are parsing mapper.xml File. As for analysis mapper.xml How to deal with the bottom layer of the mapping file. We will explain it after the explanation of the mapping file. Here we can know one thing first: mapper.xml The resolution of is using XMLMapperBuilder Completed.
So far, the loading of the entire MyBatis global configuration file has been completed, and the processing flow is relatively uncomplicated