directory

  1. Mapper proxy generation process in Mybatis
  2. Mapper proxy generation process when integrated with Spring
  3. Mapper agent generation process when integrated with SpringBoot

Mapper proxy generation process in Mybatis

Build the proxy class factory

Step by step from the entry point, the build() method in the SqlSessionFactoryBuilder class loads the configuration file

  public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
    try {
      XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
      return build(parser.parse());
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error building SqlSession.", e);
    } finally {
      / /... omit}}Copy the code

Read the configuration file as an XMLConfigBuilder object and call the parse() method to parse the file into parse()

public Configuration parse() { // ... Omit parsed = true; parseConfiguration(parser.evalNode("/configuration")); return configuration; }Copy the code

You can see that parsing is done in the parseConfiguration method.

  private void parseConfiguration(XNode root) {
    try {
       / /... omit
      / / parsing mapper
      mapperElement(root.evalNode("mappers"));
    } catch (Exception e) {
      throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: "+ e, e); }}Copy the code

The last method to parse mapper, mapperElement(root.evalNode(“mappers”)),


  private void mapperElement(XNode parent) throws Exception {
    if(parent ! =null) {
      for (XNode child : parent.getChildren()) {
        if ("package".equals(child.getName())) {
          // Load all class files under package
          String mapperPackage = 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 through mapper.xml
            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 through mapper.xml
            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) {
            // Load through a single class fileClass<? > 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 entire mapperElement() method is the process of loading a Mapper. You can see that a Mapper can be loaded in two forms: through a class file and through an XML file. This is where the process of building a Mapper proxy starts, so step by step. Taking a look at the loading process through an XML file, Mybatis reads the mapper-related configuration as an XMLMapperBuilder object and parses it into this method using the parse() method


  public void parse(a) {
    if(! configuration.isResourceLoaded(resource)) {// Load the XML file
      configurationElement(parser.evalNode("/mapper"));
      configuration.addLoadedResource(resource);
      // Load the mapper class file
      bindMapperForNamespace();
    }
   / /... omit
  }
Copy the code

The parse() method does two main things, loading an XML file and loading a class file. Take a look at the loading of XML

  private void configurationElement(XNode context) {
    try {
      // Get the namespace of the XML file
      String namespace = context.getStringAttribute("namespace");
      if (namespace == null || namespace.equals("")) {
        throw new BuilderException("Mapper's namespace cannot be empty");
      }
      // Save the namespace that gets the XML file
      builderAssistant.setCurrentNamespace(namespace);
       / /... omit
    } catch (Exception e) {
      throw new BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: "+ e, e); }}Copy the code

This article analyzes the generation process of the Mapper agent, so the details of loading XML will not be analyzed in detail. Instead, we pay attention to the value of the namespace tag in the XML file. And set the value into the builderAssistant object. Now let’s go back to loading the class file. Go to the bindMapperForNamespace() method

  private void bindMapperForNamespace(a) {
    // Get the namespace value set in the XML file
    String namespace = builderAssistant.getCurrentNamespace();
    if(namespace ! =null) { Class<? > boundType =null;
      try {
        / / load the classes
        boundType = Resources.classForName(namespace);
      } catch (ClassNotFoundException e) {
        //ignore, bound type is not required
      }
      if(boundType ! =null) {
        if(! configuration.hasMapper(boundType)) {// Spring may not know the real resource name so we set a flag
          // to prevent loading again this resource from the mapper interface
          // look at MapperAnnotationBuilder#loadXmlResource
          configuration.addLoadedResource("namespace:" + namespace);
          // Add it to configurationconfiguration.addMapper(boundType); }}}}Copy the code

BindMapperForNamespace () loads the corresponding Mapper interface through the namespace value set in the XML file, and is added to the Configuration through configuration.addMapper().

Remember that there are two ways to load a Mapper: through a class file and through an XML file. The Mapper interface is loaded into the Configuration directly by calling configuration.addmapper () as a class file.

Configuration is the global Configuration class of MyBatis. All myBatis related information is saved in Configuration. Proceed to the addMapper method on Configuration

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

Configuration adds the corresponding Mapper interface to mapperRegistry and then to the mapperregistry.addmapper () method

  public <T> void addMapper(Class<T> type) {
    if (type.isInterface()) {
        / /... omit
      try {
        knownMappers.put(type, new MapperProxyFactory<T>(type));
         / /... omit
      } finally {
        if(! loadCompleted) { knownMappers.remove(type); }}}}Copy the code

This method first determines if it is an interface, and if it is, adds the Mapper interface to knownMappers. Take a look at the definition of knownMappers

  private finalMap<Class<? >, MapperProxyFactory<? >> knownMappers =newHashMap<Class<? >, MapperProxyFactory<? > > ();Copy the code

KnownMappers is a HashMap that holds all mapper interfaces and their corresponding Mapper agent factories.

At this point, mapper has been loaded, but no proxy objects for Mapper have been generated, just the corresponding proxy factories.

Generate and use proxy objects

Mybatis does not generate the proxy object when the Mapper interface is loaded, but when it is called. Start with the entrance

sqlSession.getMapper(XXX.class)
Copy the code

SqlSession Default is DefaultSqlSession. Go to the getMapper() method of DefaultSqlSession

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

Continue to the getMapper in Configuration

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

Continue to mapperregistry.getmapper ()

  public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
    / /... omit
    }
    try {
      return mapperProxyFactory.newInstance(sqlSession);
    } catch (Exception e) {
      throw new BindingException("Error getting mapper instance. Cause: "+ e, e); }}Copy the code

The proxy factory class MapperProxyFactory corresponding to the Mapper interface is retrieved from knownMappers, and then the real proxy object is retrieved via MapperProxyFactory. Enter newInstance() to the MapperProxyFactory

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

   protected T newInstance(MapperProxy<T> mapperProxy) {
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
  }
Copy the code

The MapperProxy class is generated first, and then the real Proxy class is generated through Proxy. Take a look at the MapperProxy class

public class MapperProxy<T> implements InvocationHandler.Serializable {
  / /... omit
}
Copy the code

MapperProxy implements the InvocationHandler interface. The specific processing logic of the Mapper interface is also processed in this class.

At this point, the proxy object is actually generated.

Mapper proxy generation process when integrated with Spring

The JAR of Mybatis – Spring is needed when mybatis is integrated with Spring.

Spring registers the Mapper proxy class

Since it is integrated with Spring, it is necessary to configure myBatis to be managed by Spring. Spring XML file configuration

    <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName" value="driverClassName"/>
        <property name="url" value="url"/>
        <property name="username" value="username"/>
        <property name="password" value="password"/>
    </bean>

    <! --sqlSessionFactory-->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="dataSource"/>
        <! -- Bind mybatis profile -->
        <property name="configLocation" value="classpath:mybatis-config.xml"/>
        <! Mapper.xm -->
        <property name="mapperLocations" value="classpath:cn/ycl/mapper/*.xml"/>
    </bean>

    <! -- Register all mapper-->
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <! The basePackage property is the package path to the mapper interface file. -->
        <! -- You can set more than one package path using semicolons or commas -->
        <property name="basePackage" value="cn/ycl/mapper"/>
        <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
    </bean>
Copy the code

Mybatis dataSource dataSource dataSource dataSource dataSource dataSource dataSource dataSource dataSource dataSource dataSource dataSource dataSource dataSource dataSource dataSource dataSource dataSource dataSource dataSource dataSource dataSource dataSource dataSource dataSource dataSource dataSource dataSource dataSource dataSource dataSource dataSource dataSource dataSource dataSource dataSource dataSource dataSource dataSource dataSource dataSource dataSource dataSource dataSource dataSource dataSource dataSource dataSource dataSource dataSource dataSource dataSource dataSource dataSource dataSource dataSource dataSource dataSource dataSource dataSource dataSource dataSource dataSource dataSource dataSource dataSource dataSource dataSource dataSource dataSource dataSource dataSource dataSource dataSource dataSource dataSource dataSource dataSource dataSource dataSource dataSource dataSource dataSource dataSource dataSource MapperScannerConfigurer The process of generating the Mapper agent is mainly implemented in MapperScannerConfigurer

public class MapperScannerConfigurer
    implements BeanDefinitionRegistryPostProcessor.InitializingBean.ApplicationContextAware.BeanNameAware {
    / /... omit
}
Copy the code

Key points in MapperScannerConfigurer implements BeanDefinitionRegistryPostProcessor BeanDefinitionRegistryPostProcessor are Spring to keep the extension points, You can register custom beans into Spring.

In MapperScannerConfigurer realized BeanDefinitionRegistryPostProcessor postProcessBeanDefinitionRegistry () method, Mapper registration is registered in this method

  public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
    / /... omit

    // Instantiate ClassPathMapperScanner and configure the scanner-related properties
    ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
    scanner.setAddToConfig(this.addToConfig);
    scanner.setAnnotationClass(this.annotationClass);
    scanner.setMarkerInterface(this.markerInterface);
    scanner.setSqlSessionFactory(this.sqlSessionFactory);
    scanner.setSqlSessionTemplate(this.sqlSessionTemplate);
    scanner.setSqlSessionFactoryBeanName(this.sqlSessionFactoryBeanName);
    scanner.setSqlSessionTemplateBeanName(this.sqlSessionTemplateBeanName);
    scanner.setResourceLoader(this.applicationContext);
    scanner.setBeanNameGenerator(this.nameGenerator);
    scanner.setMapperFactoryBeanClass(this.mapperFactoryBeanClass);
    if (StringUtils.hasText(lazyInitialization)) {
      scanner.setLazyInitialization(Boolean.valueOf(lazyInitialization));
    }
    if (StringUtils.hasText(defaultScope)) {
      scanner.setDefaultScope(defaultScope);
    }
    // Register scan rules
    scanner.registerFilters();
    // Scan and register all mapper
    scanner.scan(
        StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
  }
Copy the code

PostProcessBeanDefinitionRegistry () the main logic is to define a ClassPathMapperScanner object, then calls the registerFilters scan () registration rules, finally call the scan () method.

Defined in XML MapperScannerConfigurerbean can set a annotationClass attribute, the value is an annotation class, call registerFilters (), RegisterFilters () will add a class that scans only classes with annotationClass annotations set, which are not set here, scanning all interfaces. This field will be used when SpringBoot integrates mybatis

Take a look at the definition of the ClassPathMapperScanner class

public class ClassPathMapperScanner extends ClassPathBeanDefinitionScanner {
    / /... omit
}
Copy the code

ClassPathMapperScanner inherited ClassPathBeanDefinitionScanner ClassPathBeanDefinitionScanner is defined in the Spring, Is a Spring utility that scans all bean definitions from a specified package.

Take a look at the Scan () method of ClassPathMapperScanner

  public Set<BeanDefinitionHolder> doScan(String... basePackages) {
    Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);

    if (beanDefinitions.isEmpty()) {
        / /... omit
    } else {
      processBeanDefinitions(beanDefinitions);
    }

    return beanDefinitions;
  }
Copy the code

All mappers have been scanned by super.doscan (basePackages), continue with the processBeanDefinitions() method

  private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
    AbstractBeanDefinition definition;
    BeanDefinitionRegistry registry = getRegistry();
    // Iterate over all the scanned beans
    for (BeanDefinitionHolder holder : beanDefinitions) {
      definition = (AbstractBeanDefinition) holder.getBeanDefinition();
      boolean scopedProxy = false;
      if (ScopedProxyFactoryBean.class.getName().equals(definition.getBeanClassName())) {
        definition = (AbstractBeanDefinition) Optional
            .ofNullable(((RootBeanDefinition) definition).getDecoratedDefinition())
            .map(BeanDefinitionHolder::getBeanDefinition).orElseThrow(() -> new IllegalStateException(
                "The target bean definition of scoped proxy bean not found. Root bean definition[" + holder + "]"));
        scopedProxy = true;
      }
      String beanClassName = definition.getBeanClassName();
      LOGGER.debug(() -> "Creating MapperFactoryBean with name '" + holder.getBeanName() + "' and '" + beanClassName
          + "' mapperInterface");


      // Add a constructor that takes the interface type as an input
      definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName);

      // Convert the bean type to mapperFactoryBean
      definition.setBeanClass(this.mapperFactoryBeanClass);

      // Add the addToConfig attribute
      definition.getPropertyValues().add("addToConfig".this.addToConfig);


      definition.setAttribute(FACTORY_BEAN_OBJECT_TYPE, beanClassName);

      boolean explicitFactoryUsed = false;
      // Add sqlSessionFactory
      if (StringUtils.hasText(this.sqlSessionFactoryBeanName)) {
        definition.getPropertyValues().add("sqlSessionFactory".new RuntimeBeanReference(this.sqlSessionFactoryBeanName));
        explicitFactoryUsed = true;
      } else if (this.sqlSessionFactory ! =null) {
        definition.getPropertyValues().add("sqlSessionFactory".this.sqlSessionFactory);
        explicitFactoryUsed = true;
      }

      // Add the sqlSessionTemplate attribute
      if (StringUtils.hasText(this.sqlSessionTemplateBeanName)) {
        if (explicitFactoryUsed) {
          LOGGER.warn(
              () -> "Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.");
        }
        definition.getPropertyValues().add("sqlSessionTemplate".new RuntimeBeanReference(this.sqlSessionTemplateBeanName));
        explicitFactoryUsed = true;
      } else if (this.sqlSessionTemplate ! =null) {
        if (explicitFactoryUsed) {
          LOGGER.warn(
              () -> "Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.");
        }
        definition.getPropertyValues().add("sqlSessionTemplate".this.sqlSessionTemplate);
        explicitFactoryUsed = true;
      }

      if(! explicitFactoryUsed) { LOGGER.debug(() ->"Enabling autowire by type for MapperFactoryBean with name '" + holder.getBeanName() + "'.");
        definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
      }

      definition.setLazyInit(lazyInitialization);

      if (scopedProxy) {
        continue;
      }

      if(ConfigurableBeanFactory.SCOPE_SINGLETON.equals(definition.getScope()) && defaultScope ! =null) {
        definition.setScope(defaultScope);
      }

      if(! definition.isSingleton()) { BeanDefinitionHolder proxyHolder = ScopedProxyUtils.createScopedProxy(holder, registry,true);
        if(registry.containsBeanDefinition(proxyHolder.getBeanName())) { registry.removeBeanDefinition(proxyHolder.getBeanName());  } registry.registerBeanDefinition(proxyHolder.getBeanName(), proxyHolder.getBeanDefinition()); }}}Copy the code

This method is long, but not complicated. The main logic is to change the scanned bean type to MapperFactoryBean type and add a constructor that takes the interface type as an input parameter. In other words, Spring obtains mapper from FactoryBean. Finally by calling egistry. RegisterBeanDefinition () method to register in the Spring.

Take a look at the MapperFactoryBean definition provided by Mybatis

public class MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T> {}Copy the code

MapperFactoryBean implements FactoryBean, which is a FactoryBean provided by Spring that produces objects

MapperFactoryBean also inherits SqlSessionDaoSupport, which inherits DaoSupport and implements InitializingBean. The purpose of the InitializingBean is to call the afterPropertiesSet() method of the InitializingBean first when Spring initializes the bean object.

The checkDaoConfig() method is called in afterPropertiesSet() of DaoSupport.

    public final void afterPropertiesSet(a) throws IllegalArgumentException, BeanInitializationException {
        this.checkDaoConfig();

        try {
            this.initDao();
        } catch (Exception var2) {
            throw new BeanInitializationException("Initialization of DAO failed", var2); }}Copy the code

The implementation logic for the specific checkDaoConfig() method is in the MapperFactoryBean

 protected void checkDaoConfig(a) {
    super.checkDaoConfig();

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

    Configuration configuration = getSqlSession().getConfiguration();
    if (this.addToConfig && ! configuration.hasMapper(this.mapperInterface)) {
      try {
        configuration.addMapper(this.mapperInterface);
      } catch (Exception e) {
        / /.. omit
      } finally{ ErrorContext.instance().reset(); }}}Copy the code

OK, this is back to Mybatis. As mentioned earlier, the configuration.addmapper () method simply generates the corresponding agent factory.

The whole process above is to register mapper as the bean of Spring, and set mapper to the configuration in Mybatis. Therefore, Spring can use automatic injection of that set when using. Mybatis can also use sqlSession to obtain the proxy object of Mapper

Spring generates proxy objects

All beans corresponding to mapper in Spring are mapperFactoryBeans corresponding to MapperFactoryBean, so the mapper bean is generated by the getObject() method of MapperFactoryBean

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

The MapperFactoryBean gets the proxy object from getMapper() after getting sqlSession. This is where myBatis returns to the process of generating proxy objects.

Mapper agent generation process when integrated with SpringBoot

The jar of Mybatis -spring-boot-starter is used when mybatis is integrated with Spring. Mybatis -spring-boot-starter relies on the jar mybatis-spring-boot-autoconfigure, Mybatis = mybatis = mybatis = mybatis = mybatis = mybatis = mybatis = mybatis = mybatis = mybatis = mybatis = mybatis = mybatis

Check the meta-INF /spring.factories file under mybatis- spring-boot-AutoConfigurejar according to the SpringBoot automatic loading mechanism

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.mybatis.spring.boot.autoconfigure.MybatisLanguageDriverAutoConfiguration,\
org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration
Copy the code

SpringBoot automatically loads the MybatisAutoConfiguration class, which defines the beans required by MyBtis.


    / / generated SqlSessionFactory
    @Bean
    @ConditionalOnMissingBean
    public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
        / /... omit
    }

    / / generated SqlSessionTemplate
    @Bean
    @ConditionalOnMissingBean
    public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
     / /... omit
    }

    / / scanning mapper
     @Configuration
    @Import({MybatisAutoConfiguration.AutoConfiguredMapperScannerRegistrar.class})
    @ConditionalOnMissingBean({MapperFactoryBean.class, MapperScannerConfigurer.class})
    public static class MapperScannerRegistrarNotFoundConfiguration implements InitializingBean {
        public MapperScannerRegistrarNotFoundConfiguration(a) {}public void afterPropertiesSet(a) {
            MybatisAutoConfiguration.logger.debug("Not found configuration for registering mapper bean using @MapperScan, MapperFactoryBean and MapperScannerConfigurer."); }}/ / scanning mapper
     public static class AutoConfiguredMapperScannerRegistrar implements BeanFactoryAware.ImportBeanDefinitionRegistrar {
        private BeanFactory beanFactory;

        public AutoConfiguredMapperScannerRegistrar(a) {}public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
            if(! AutoConfigurationPackages.has(this.beanFactory)) {
                MybatisAutoConfiguration.logger.debug("Could not determine auto-configuration package, automatic mapper scanning disabled.");
            } else {
                MybatisAutoConfiguration.logger.debug("Searching for mappers annotated with @Mapper");
                List<String> packages = AutoConfigurationPackages.get(this.beanFactory);
                if (MybatisAutoConfiguration.logger.isDebugEnabled()) {
                    packages.forEach((pkg) -> {
                        MybatisAutoConfiguration.logger.debug("Using auto-configuration base package '{}'", pkg);
                    });
                }
                / / generated MapperScannerConfigurer
                BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class);
                builder.addPropertyValue("processPropertyPlaceHolders".true);
                // Register scan rules
                builder.addPropertyValue("annotationClass", Mapper.class);
                builder.addPropertyValue("basePackage", StringUtils.collectionToCommaDelimitedString(packages));
                BeanWrapper beanWrapper = new BeanWrapperImpl(MapperScannerConfigurer.class);
                Stream.of(beanWrapper.getPropertyDescriptors()).filter((x) -> {
                    return x.getName().equals("lazyInitialization");
                }).findAny().ifPresent((x) -> {
                    builder.addPropertyValue("lazyInitialization"."${mybatis.lazy-initialization:false}"); }); registry.registerBeanDefinition(MapperScannerConfigurer.class.getName(), builder.getBeanDefinition()); }}public void setBeanFactory(BeanFactory beanFactory) {
            this.beanFactory = beanFactory; }}Copy the code