[Mybatis source code] How does Mybatis generate proxy objects for the mapper interface--JDK dynamic proxy

introduction

  • mybatis version: 3.5.1
  • mybatis-spring:2.0.1

Developers who have used the Mybatis framework should know that when writing the dao layer, they only need to provide the mapper interface and the corresponding xxxMapper.xml, and the mapper interface object can be handed over to the Spring container for management without implementing classes. Questions:

  1. How does Mybatis generate proxy objects for the mapper interface?
  2. How does Mybatis hand over the mapper object to Spring for management?

When we integrate mybatis and spring, we will use MyBatis-Spring to seamlessly integrate MyBatis code into Spring.

Motivation for the emergence of MyBatis-Spring:

Spring 2.0 only supports iBatis 2.0. So, we want to add support for MyBatis3 to Spring 3.0 (see the question in Spring Jira   ). Unfortunately, the development of Spring 3.0 ended before the official release of MyBatis 3.0. Since the Spring development team does not want to release an integrated support based on the unreleased version of MyBatis, if you want to get official Spring support, you can only wait for the next release. Based on the interest in supporting MyBatis in Spring, the MyBatis community believes that contributors who are interested in participating should start to convene the integration of Spring as a community sub-project of MyBatis.

So, in mybatis-spring, what extensions has mybatis made to integrate spring?

@MapperScan annotation

First, you need to scan the interfaces under the specified mapper package, generate proxy objects for these interfaces, and then pass these proxy objects to Spring for management through FactoryBean .

Note: If you are not familiar with FactoryBean, you can check: https://blog.csdn.net/a1036645146/article/details/111661211

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(MapperScannerRegistrar.class)
@Repeatable(MapperScans.class)
public @interface MapperScan {
    // 省略部分。。。
    Class<? extends MapperFactoryBean> factoryBean() default MapperFactoryBean.class;
}

The main function of this annotation is to put MapperScannerConfigurer into BeanDefinitionMap .

So how does it work? In spring, there is a built-in class ConfigurationClassPostProcessor. The function of this class is to scan classes with spring annotations and add them to bdmap, and to parse the @Import tag on the configuration class. If the class in this tag implements the ImportBeanDefinitionRegistrar interface, this This class is instantiated and placed in the importBeanDefinitionRegistrars cache . After scanning, the registerBeanDefinitions() method of the objects in this collection will be executed .

The function of the MapperScannerRegistrar#registerBeanDefinitions() method in mybatis-spring is to register the MapperScannerConfigurer to the BeanDefinitionMap.

//MapperScannerRegistrar
@Override
  public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
    AnnotationAttributes mapperScanAttrs = AnnotationAttributes
        .fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName()));
    if (mapperScanAttrs != null) {
      registerBeanDefinitions(importingClassMetadata, mapperScanAttrs, registry,
          generateBaseBeanName(importingClassMetadata, 0));
    }
  }
 void registerBeanDefinitions(AnnotationMetadata annoMeta, AnnotationAttributes annoAttrs,
    BeanDefinitionRegistry registry, String beanName) {

  BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class);
  // 省略部分。。。

  registry.registerBeanDefinition(beanName, builder.getBeanDefinition());

}

FoldersScannerConfigure

mybatis-spring frame MapperScannerConfigurer  achieved based on the expansion of the spring BeanDefinitionRegistryPostProcessor interface. After Spring's built-in scanner scans, the postProcessBeanDefinitionRegistry method of this class will be triggered to build a mybatis scanner ( ClassPathMapperScanner ) to execute the scan mapper interface, and the mapper interface object is handed over to Spring for management.

public class MapperScannerConfigurer implements BeanDefinitionRegistryPostProcessor, InitializingBean, ApplicationContextAware, BeanNameAware {


  @Override
  public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
    if (this.processPropertyPlaceHolders) {
      processPropertyPlaceHolders();
    }
    // 创建一个mybatis 的扫描器,扫描mapper接口包,可将mapper.class到BeanDefinition的转换
    ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
    // 下面的一系列set方法是对这个扫描器进行初始化
    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);
    scanner.registerFilters();//注册一些扫描用的过滤器
    scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
  }

。。。
}

Among them, ClassPathMapperScanner is a scanner implemented by mybatis, inheriting Spring's built-in scanner ClassPathBeanDefinitionScanner , but it does not override the scan method of the parent class, so the scan method of the parent class is called here, as follows:

public class ClassPathBeanDefinitionScanner extends ClassPathScanningCandidateComponentProvider {
	
    public int scan(String... basePackages) {
		int beanCountAtScanStart = this.registry.getBeanDefinitionCount();

        // 可由子类实现
		doScan(basePackages);

		// Register annotation config processors, if necessary.
		if (this.includeAnnotationConfig) {
			AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
		}

		return (this.registry.getBeanDefinitionCount() - beanCountAtScanStart);
	}
。。。
}

The focus is on the implementation of the doScan method, which is rewritten by ClassPathMapperScanner , mainly to convert mapper.class to BeanDefinition .

public class ClassPathMapperScanner extends ClassPathBeanDefinitionScanner {

  // 扫描mapper接口包,将mapper.class转为对应的beanDefinition
  @Override
  public Set<BeanDefinitionHolder> doScan(String... basePackages) {
    Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);//扫描mapper包下的接口

    if (beanDefinitions.isEmpty()) {
      LOGGER.warn(() -> "No MyBatis mapper was found in '" + Arrays.toString(basePackages) + "' package. Please check your configuration.");
    } else {
      processBeanDefinitions(beanDefinitions);// //将这些mapper接口变成FactoryBean
    }

    return beanDefinitions;
  }

。。。
}

Among them, the method processBeanDefinitions will set the parameters of the constructor to this interface, and then modify the beanClass to MapperFactoryBean . After instantiation, the getObject() method of this FactoryBean will be called to construct a proxy Mapper object . This is the key to how to turn the map interface into an object for Spring management

  private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
    GenericBeanDefinition definition;
    for (BeanDefinitionHolder holder : beanDefinitions) {
      definition = (GenericBeanDefinition) holder.getBeanDefinition();
      String beanClassName = definition.getBeanClassName();
      LOGGER.debug(() -> "Creating MapperFactoryBean with name '" + holder.getBeanName()
          + "' and '" + beanClassName + "' mapperInterface");

      // the mapper interface is the original class of the bean
      // but, the actual class of the bean is MapperFactoryBean
      // 添加构造函数需要的参数
      definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName); // issue #59
      //设置类型为mapperFactoryBeanClass。之后的实例化会调用MapperFactoryBean中的getObject()来构建出一个基于接口的代理对象交给spring管理
      definition.setBeanClass(this.mapperFactoryBeanClass);
      。。。

    }
  }

MapperProxyFactory

In the Mybatis framework, MapperProxyFactory is a mapper proxy factory, which can construct a mapper proxy object based on the interface and MapperProxy , and realize the conversion of the interface into an object:

MapperProxy implements the invoke method of the InvocationHandler interface, so we understand the first problem, Mybatis is based on the JDK dynamic proxy to generate the object of the mapper interface.

Note: If you are not familiar with jdk dynamic agent, you can check: https://blog.csdn.net/a1036645146/article/details/111881599

Among them, MapperMethod # execute () is as follows:

public class MapperMethod {

  private final SqlCommand command;
  private final MethodSignature method;

  public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
    this.command = new SqlCommand(config, mapperInterface, method);
    this.method = new MethodSignature(config, mapperInterface, method);
  }

  public Object execute(SqlSession sqlSession, Object[] args) {
    Object result;
    // 根据sql语句的类型,选择不同的分支流程执行
    switch (command.getType()) {
      case INSERT: {// 新增语句
        Object param = method.convertArgsToSqlCommandParam(args);
        result = rowCountResult(sqlSession.insert(command.getName(), param));
        break;
      }
      case UPDATE: {// 更新语句
        Object param = method.convertArgsToSqlCommandParam(args);
        result = rowCountResult(sqlSession.update(command.getName(), param));
        break;
      }
      case DELETE: {// 删除语句
        Object param = method.convertArgsToSqlCommandParam(args);
        result = rowCountResult(sqlSession.delete(command.getName(), param));
        break;
      }
      case SELECT:// 查询
        if (method.returnsVoid() && method.hasResultHandler()) {
          executeWithResultHandler(sqlSession, args);
          result = null;
        } else if (method.returnsMany()) {
          result = executeForMany(sqlSession, args);
        } else if (method.returnsMap()) {
          result = executeForMap(sqlSession, args);
        } else if (method.returnsCursor()) {
          result = executeForCursor(sqlSession, args);
        } else {
          Object param = method.convertArgsToSqlCommandParam(args);
          result = sqlSession.selectOne(command.getName(), param);
          if (method.returnsOptional()
              && (result == null || !method.getReturnType().equals(result.getClass()))) {
            result = Optional.ofNullable(result);
          }
        }
        break;
      case FLUSH:
        result = sqlSession.flushStatements();
        break;
      default:
        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;
  }
。。。
}

MapperFactoryBean

MapperFactoryBean implements Spring's FactoryBean extension interface. FactoryBean is a factory Bean, which can generate a certain type of Bean instance. One of its biggest functions is to allow us to customize the Bean creation process. For details, please refer to "[Spring Source Code: FactoryBean One] Finally understand how FactoryBean customizes the bean creation process" .

It can call the getObject() method to return different interface proxy objects according to different interfaces. This object is also generated by the MapperProxyFactory above .

public class MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T> {

  private Class<T> mapperInterface;
  private boolean addToConfig = true;

  public MapperFactoryBean() {
    //intentionally empty 
  }
  
  public MapperFactoryBean(Class<T> mapperInterface) {
    this.mapperInterface = mapperInterface;
  }

  @Override
  public T getObject() throws Exception {
    return getSqlSession().getMapper(this.mapperInterface);
  }

。。。

}

to sum up

1. Mybatis first scans the mapper interface under the specified package, creates a proxy factory MapperProxyFactory for each mapper interface, and puts it into an attribute collection cache of MapperRegistry:

Map<Class<?>, MapperProxyFactory<?>> knownMappers

2. Use these proxy factories to create the proxy object corresponding to the mapper interface, MapperProxy by implementing JDK dynamic proxy;
3. Call the actual business logic through the proxy object.

Reference: https://blog.csdn.net/gongsenlin341/article/details/108600008

●The strongest Tomcat8 performance optimization in history

Why can Alibaba resist 10 billion in 90 seconds? --The evolution of server-side high-concurrency distributed architecture

B2B e-commerce platform--ChinaPay UnionPay electronic payment function

Learn Zookeeper distributed lock, let interviewers look at you with admiration

SpringCloud e-commerce spike microservice-Redisson distributed lock solution

Check out more good articles, enter the official account--please me--excellent in the past

A deep and soulful public account 0.0

Guess you like

Origin blog.csdn.net/a1036645146/article/details/112359624