Mybatis source code analysis (three) – Mapper proxy class generation

As described in the first article in this series, in the Mybatis-Spring project, MapperFactoryBean’s getObject() method is used to get the Mapper proxy class and inject it into the Spring container. Before studying this chapter, we ask the following questions:

  • 1. How is Mapper interface loaded into Configuration?

  • 2. How are Mapper proxy classes generated?

  • 3. How do Mapper proxy classes implement interface methods?

The content of this chapter is around the above three problems for analysis, then with the problem to see the source code!

Load the Mapper interface

For Mybatis project, Mapper configuration is loaded from XmlConfigBuilder. MapperElement trigger () method. Let’s look at the source code:

  private void mapperElement(XNode parent) throws Exception {
    if(parent ! = null) {for (XNode child : parent.getChildren()) {
        if ("package".equals(child.getName())) {mapperPackage =.equals(child.getName())) child.getStringAttribute("name");
          configuration.addMappers(mapperPackage);
        } else {
          String resource = child.getStringAttribute("resource");
          String url = child.getStringAttribute("url");
          String mapperClass = child.getStringAttribute("class");
          if(resource ! = null && url == null && mapperClass == null) {// Load 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) {// Load errorContext.instance ().resource(url) of mapper.xml; InputStream inputStream = Resources.getUrlAsStream(url); XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments()); mapperParser.parse(); }else if(resource == null && url == null && mapperClass ! = null) {// Load 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.");
          }
        }
      }
    }
  }
        
Copy the code

The Mapper interface can be loaded in two ways: one is to find all the classes in the path according to the package set and load them through configuration.addmapper (). Alternatively, you can load the class directly through configuration.addmapper () according to the specified Mapper interface path. So the Mapper interface is loaded by configuration.addmapper ().

For Mybatis-Spring, the basePackage parameter of MapperScannerConfigurer is obtained. The ClassPathMapperScanner scans all classes in the basePackage path and returns the BeanDefinition, which was covered in the first article. MapperFactoryBean = MapperFactoryBean = MapperFactoryBean = MapperFactoryBean = MapperFactoryBean = MapperFactoryBean


  @Override
  protected void checkDaoConfig() {
    super.checkDaoConfig();

    notNull(this.mapperInterface, "Property 'mapperInterface' is required");

    Configuration configuration = getSqlSession().getConfiguration();
    if(this.addToConfig && ! Configuration.hasmapper (this.mapperInterface)) {try {// The final load is through the addMapper() method. configuration.addMapper(this.mapperInterface); } catch (Throwable t) { logger.error("Error while adding the mapper '" + this.mapperInterface + "' to configuration.", t); throw new IllegalArgumentException(t); } finally { ErrorContext.instance().reset(); }}}Copy the code

From the source code we can see that the interior is actually loaded via configuration.addmapper (). Some of you may be wondering when checkDaoConfig() was called. This can be traced back to the inheritance diagram of MapperFactoryBean, which implements the InitializingBean interface. CheckDaoConfig () is called via afterPropertiesSet(). So checkDaoConfig() is called when the MapperFactoryBean is initially created, which loads the Mapper interface.

AddMapper () is the core method to load the Mapper interface, so we will take a good look at this method internal implementation source code:


  public <T> void addMapper(Class<T> type) {
    mapperRegistry.addMapper(type);
  }
  
Copy the code

It is loaded internally by delegating mapperRegistry, so read on:


  public <T> void addMapper(Class<T> type{// 1. Check whether it is an interfaceif(type.isInterface()) {// 2if (hasMapper(type)) {
        throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
      }
      boolean loadCompleted = false; Try {// 3, take Mapper's class as an argument to MapperProxyFactory, the factory class that generates Mapper proxy objects, and save it into knownMappers. knownMappers.put(type, new MapperProxyFactory<T>(type)); // 4, parse all mybatis framework annotations contained in the Class object and generate Cache, ResultMap, MappedStatement. MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config,type);
        parser.parse();
        loadCompleted = true;
      } finally {
        if(! loadCompleted) { knownMappers.remove(type); }}}}Copy the code

According to the source code, the whole loading process can be divided into four steps:

  • 1. Check whether it is an interface

  • 2. Check whether the file is loaded

  • 3. Take Mapper’s class as an argument to MapperProxyFactory, the factory class that generates Mapper proxy objects, and save it into knownMappers.

  • 4. Parse all myBatis framework annotations contained in the Class object, and generate Cache, ResultMap, MappedStatement.

The third step is to load the core of Mapper, that is, to create a factory object that generates Mapper proxy objects, and put it into the map, and so on, the need to create Mapper proxy objects is to obtain the factory object in the map. The fourth step, which has been popular in recent years, is to write parsing in THE form of SQl through annotations. We know that Mybatis supports Sql writing in XML and annotation form. Therefore, the MapperAnnotationBuilder parses annotation forms. Just as the root parses XML, ResultMap and MappedStatement objects are eventually generated and encapsulated in the Configuration. For how it is parsed, interested students can see ah may be the source, here is not described.

Mapper dynamic proxy object (MapperProxy) creation

From the previous article, we learned that MapperFactoryBean implements FactoryBean, That is to say, in the Spring load according to BeanDefinition Bean will call MapperFactoryBean. GetObject () to obtain the real Bean and injected into the container. GetObject () is a proxy implementation of the MapperProxy interface.


  public T getObject() throws Exception {
    return getSqlSession().getMapper(this.mapperInterface);
  }
  
Copy the code

GetObject () gets MapperProxy from getSqlSession().getmapper (). The SqlSession template is the SqlSessionTemplate.


  public <T> T getMapper(Class<T> type) {
    return getConfiguration().getMapper(type, this);
  }
  
Copy the code

Finally, the MapperProxy is obtained through configuration().getmapper (). Keep checking:


  public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    return mapperRegistry.getMapper(type, sqlSession);
  }
  
Copy the code

No surprise, of course, to delegate mapperregistry.getmapper () to get it. Go on:


  public <T> T getMapper(Class<T> type. SqlSession SqlSession) {// 1, MapperProxyFactory<T> MapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
    if (mapperProxyFactory == null)
      throw new BindingException("Type " + type + " is not known to the MapperRegistry."); Try {/ / 2, through the mapperProxyFactory. NewInstance () to create MapperProxyreturn mapperProxyFactory.newInstance(sqlSession);
    } catch (Exception e) {
      throw new BindingException("Error getting mapper instance. Cause: "+ e, e); }}Copy the code

Also no surprise get from knownMappers MapperProxyFactory, again through the MapperProxyFactory. NewInstance () to create MapperProxy. Continue to see mapperProxyFactory. NewInstance () internal implementation:


  @SuppressWarnings("unchecked")
  protected T newInstance(MapperProxy<T> mapperProxy) {
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
  }

  public T newInstance(SqlSession sqlSession) {
    final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
    return newInstance(mapperProxy);
  }

Copy the code

Proxy.newproxyinstance () specifies the class to be proxied as mapperInterface, and its Proxy class as mapperProxy. So you end up creating a dynamic proxy class for mapperInterface MapperProxy@xxxx

3. Implementation of MapperProxy interface method

The MapperProxy proxy is dynamically generated by the JDK, but how is the interface method implemented? MapperProxy source code:

public class MapperProxy<T> implements InvocationHandler, Serializable { private static final long serialVersionUID = -6424540398559729838L; Private final SqlSession SqlSession; private final Class<T> mapperInterface; private final Map<Method, MapperMethod> methodCache; public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethod> methodCache) { this.sqlSession = sqlSession; this.mapperInterface = mapperInterface; this.methodCache = methodCache; } public Object invoke(Object proxy, Method Method, Object[] args) throws Throwable {// If the Method is an Object defined Method, execute it directlyif (Object.class.equals(method.getDeclaringClass())) {
      try {
        returnmethod.invoke(this, args); } catch (Throwable t) { throw ExceptionUtil.unwrapThrowable(t); }} final MapperMethod MapperMethod = cachedMapperMethod(method);returnmapperMethod.execute(sqlSession, args); } // Each method of the Mapper interface generates a MapperMethod object, Private MapperMethod cachedMapperMethod(Method Method) {MapperMethod MapperMethod = methodCache.get(method);ifMapperMethod = new mapperMethod (mapperInterface, method, sqlSession.getConfiguration()); methodCache.put(method, mapperMethod); }returnmapperMethod; }}Copy the code

MapperMethod is used to implement interface methods, and each method of the Mapper interface generates a MapperMethod object, and maintains the relationship between them through methodCache. MethodCache is passed through the MapperProxyFactory.

MapperMethod

The MapperMethod implementation interface method entry is the execute() method.

Public Object execute(SqlSession SqlSession, Object[] args) {Object result; // Determine the type of method to request executionif(SqlCommandType. INSERT = = command. GetType ()) {/ / parameter conversion Object param = method. The convertArgsToSqlCommandParam (args); Insert result = rowCountResult(sqlsession.insert (command.getName(), param)); }else if(SqlCommandType.UPDATE == command.getType()) { Object param = method.convertArgsToSqlCommandParam(args); Update result = rowCountResult(sqlsession.update (command.getName(), param)); }else if(SqlCommandType.DELETE == command.getType()) { Object param = method.convertArgsToSqlCommandParam(args); // Execute delete result = rowCountResult(sqlsession.delete (command.getName(), param)); }else if(sqlCommandType.select == command.getType()) {// Perform SELECTif(method.returnsvoid () &&method.hasResulthAndler ()) {return executeWithResultHandler(sqlSession, args); result = null; }else if(method.returnsmany ()) {// Return List result = executeForMany(sqlSession, args); }else if(method.returnsmap ()) {// Return Map result = executeForMap(sqlSession, args); }else{/ / returns an Object Object param = method. The convertArgsToSqlCommandParam (args); result = sqlSession.selectOne(command.getName(), param); }}else {
      throw new BindingException("Unknown execution method for: " + command.getName());
    }
    if(result == null && method.getReturnType().isPrimitive() && ! method.returnsVoid()) { throw new BindingException("Mapper method '" + command.getName() 
          + " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
    }
    return result;
  }


Copy the code

According to the source code we can find that in fact, internal are entrusted with SqlSession method implementation, but it is how to distinguish when to call which SqlSession method? SqlCommand: MapperMethod SqlCommand: MapperMethod SqlCommand

public SqlCommand(Configuration configuration, Class<? > mapperInterface, Method Method) throws BindingException {// The full name path of the completion Method is com.xxx.selectByName String statementName = mapperInterface.getName() +"."+ method.getName(); MappedStatement ms = null; // The MappedStatement object is obtained from configurationif (configuration.hasStatement(statementName)) {
        ms = configuration.getMappedStatement(statementName);
      } else if(! mapperInterface.equals(method.getDeclaringClass().getName())) { // issue# 35
        String parentStatementName = method.getDeclaringClass().getName() + "." + method.getName();
        if(configuration.hasStatement(parentStatementName)) { ms = configuration.getMappedStatement(parentStatementName); }}if (ms == null) {
        throw new BindingException("Invalid bound statement (not found): "+ statementName); } // Get the method name from MappedStatement (note that the id attribute in the node contains the namespace) name = ms.getid (); / / get from MappedStatement method of node labels, namely select | insert | update | deletetype = ms.getSqlCommandType();
      if (type == SqlCommandType.UNKNOWN) {
        throw new BindingException("Unknown execution method for: "+ name); }}Copy the code

As we know, by analyzing the SqlCommand is get from the MappedStatement method name, and to execute the SQl command type (select | insert | update | delete). We can clearly see that the MappedStatement obtained from the Configuration file is obtained through the full path method, such as com.xxx.selectByName, The SqlSession method gets the ID from the MappedStatement (note that the id attribute in the node contains the namespace), but is essentially com.xxx.selectByName. Mybatis Mapper interface methods can not be overloaded.

Iv. Personal summary

  • 1. Configuration maintains a MapperRegistry object, which loads the Mapper interface and obtains MapperProxy.

  • 2. MapperRegistry maintains a map whose key is mapper interface class object and value is MapperProxyFactory

  • 3. MapperProxy is created by MapperProxyFactory.

  • 4. MapperProxy implements Mapper interface methods by entrusting MapperMethod.

  • 5, MapperMethod to execute the interface method is to determine the specific SQL node to execute through SqlCommand, and finally entrust SqlSession execution.

  • SqlCommand internal information is obtained from MappedStatement.

If you are interested in these, welcome to star, follow, favorites, forward to give support!

This article is published by OpenWrite!