【Mybatis+spring整合源码探秘】--- 创建Mapper动态代理类核心源码解读


1 从mybatis整合spring的配置类开始

上篇文章《【Mybatis+spring整合源码探秘】— 开篇 • 搭建一个最简单的Mybatis、Spring整合框架》已经搭建了一个比较简单的Mybatis、Spring整合框架。其配置类如下:

@EnableTransactionManagement//开启事务
@MapperScan(basePackages = "com.nrsc.mybatis.mapper") //指定Mapper接口的地址
@ComponentScan("com.nrsc.mybatis")//包扫描
public class SelfDBConfig {

    //创建数据源
    @Bean
    public DataSource dataSource() {
        DruidDataSource dataSource = new DruidDataSource();
        dataSource.setUsername("root");
        dataSource.setPassword("123456");
        dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
        dataSource.setUrl("jdbc:mysql://127.0.0.1:3306/mybatis-study?characterEncoding=utf-8&serverTimezone=GMT&useSSL=false");
        return dataSource;
    }

    //注册事务管理器
    @Bean("platformTransactionManager")
    public PlatformTransactionManager platformTransactionManager() {
        return new DataSourceTransactionManager(dataSource());
    }

    //配置SqlSession工厂
    @Bean
    public SqlSessionFactoryBean sqlSessionFactory() throws IOException {
        SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
        //指定数据源
        factoryBean.setDataSource(dataSource());
        //指定所有mapper.xml所在路径
        ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
        factoryBean.setMapperLocations(resolver.getResources("classpath:mysql/selfdbmapper/*.xml"));
        return factoryBean;
    }
}

其中DataSource 、PlatformTransactionManager 这两个bean的生成加上@EnableTransactionManagement和@ComponentScan这两个注解在我前面关于spring事务的博客里就已经讲到过了,比如说文章《【spring事务源码学习】— spring事务核心组件创建过程》、《【spring源码】— spring-aop和spring事务前置处理方法》等。且它们并不是Mybatis与spring整合的关键,真正关键的是SqlSessionFactoryBean对象的创建和@MapperScan这个注解及其背后的内容。


2 SqlSessionFactoryBean对象及其背后的秘密 — 构建SqlSessionFactory对象

这块代码我不打算细讲了,仅简单说一下其原理和具体做的事。


2.1 简单介绍

如1中代码所示,new出的SqlSessionFactoryBean对象,其实可以set好多属性,这些属性基本都是原来mybatis单独开发时主配置文件mybatis-config.xml中的标签对应的属性(可以参考一下前面那篇文章《【Mybatis源码探索】 — 开篇 • 搭建一个最简单的Mybatis框架》)。


由此即使猜应该也可以猜到,创建SqlSessionFactoryBean对像,其实就是在做《【Mybatis源码探索】 — Mybatis配置文件解析核心源码解读》那篇文章做的所有事情 — 即获取Configuration对象,并拿着Configuration对象创建SqlSessionFactory对象。


2.2 原理简介

2.2.1 必备的前置知识

(1)必须知道InitializingBean接口及其作用 — 可参考我的文章《【bean的生命周期】— 构造方法、@Autowired、BeanPostProcessor、InitializingBean等的执行顺序解析

(2)必须知道FactoryBean接口及其作用 — 可参考我的文章《【Spring之FactoryBean】


2.2.2 【具体原理简介】 — 实现InitializingBean接口 —> 构建SqlSessionFactory对象

首先SqlSessionFactoryBean实现了InitializingBean接口,实现了该接口,在实例化该对象时肯定会走afterPropertiesSet()方法,这里看一下SqlSessionFactoryBean中的afterPropertiesSet()源码:
所在类:SqlSessionFactoryBean

@Override
public void afterPropertiesSet() throws Exception {
  notNull(dataSource, "Property 'dataSource' is required");
  notNull(sqlSessionFactoryBuilder, "Property 'sqlSessionFactoryBuilder' is required");
  state((configuration == null && configLocation == null) || !(configuration != null && configLocation != null),
      "Property 'configuration' and 'configLocation' can not specified with together");
  //这句话的作用就是先获取到Configuration对象 ---> 然后拿着获取到的Configuration对象创建SqlSessionFactory对象
  //但是这里要注意,它把创建的SqlSessionFactory对象赋值给了SqlSessionFactoryBean的一个属性sqlSessionFactory
  //这是要干啥呢??? ---> 这就不得不提SqlSessionFactoryBean实现的另一个接口FactoryBean了
  this.sqlSessionFactory = buildSqlSessionFactory();
}

2.2.3 【具体原理简介】— 实现FactoryBean接口 —> 将sqlSessionFactory对象注入到ioc容器

实现了FactoryBean接口,就需要重写一个getObject()方法,spring会把该方法的返回值放到IOC容器里。这里再看一下SqlSessionFactoryBean中的getObject()方法对应的源码:
所在类:SqlSessionFactoryBean

@Override
public SqlSessionFactory getObject() throws Exception {
  if (this.sqlSessionFactory == null) {
    afterPropertiesSet();
  }

  return this.sqlSessionFactory;
}

这就很明显了,SqlSessionFactoryBean正是通过getObject()方法将afterPropertiesSet()方法获得的SqlSessionFactory对象放到了IOC容器里。


2.2.4 特别注意

这里要特别注意 获取的SqlSessionFactory和Configuration对象在IOC容器里都是单例的。


3 @MapperScan注解及其背后的秘密 — Mapper动态代理对象生成的底层原理

3.1 @MapperScan注解源码

首先来看一下MapperScan注解的源码:
所在类:MapperScan

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(MapperScannerRegistrar.class)
@Repeatable(MapperScans.class)
public @interface MapperScan {
  String[] value() default {};
  String[] basePackages() default {};
  Class<?>[] basePackageClasses() default {};
  Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class;
  Class<? extends Annotation> annotationClass() default Annotation.class;
  Class<?> markerInterface() default Class.class;
  String sqlSessionTemplateRef() default "";
  String sqlSessionFactoryRef() default "";
  Class<? extends MapperFactoryBean> factoryBean() default MapperFactoryBean.class;
  String lazyInitialization() default "";
}

可以看到其可配的属性还是挺多的,本篇文章主要探索Mapper动态代理对象的生成原理,所以这些属性也不细究了,我只配置了basePackages,即Mapper接口的扫描包。

从源码中还可以看到MapperScan上面还有一个注解 — @Import(MapperScannerRegistrar.class) — 该注解的知识可以看我的文章《【Spring注解】@Import》,当然spring事务的底层源码、spring-aop的底层源码都用到了这个注解,感兴趣的可以翻翻我前面的文章,都有讲解。


3.2 MapperScannerRegistrar — 注册MapperScannerConfigurer的bean定义信息

MapperScannerRegistrar对象实现了ImportBeanDefinitionRegistrar接口,那它会调用registerBeanDefinitions(...)方法向spring里注册一个bean定义信息,之后spring会根据该bean定义信息创建一个对象并放到IOC容器里。 — 不明白为什么的可以先参考一下我的另一篇文章《【Spring注解】@Import》。

接着来看一下MapperScannerRegistrar中registerBeanDefinitions(…)方法的源码, 看看到底它向spring中注册了哪个bean定义信息:
所在类:MapperScannerRegistrar

@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
  //获取MapperScan注解上配置的属性
  AnnotationAttributes mapperScanAttrs = AnnotationAttributes
      .fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName()));
  if (mapperScanAttrs != null) {
  	//真正进行bean定义对象的注册
    registerBeanDefinitions(mapperScanAttrs, registry, generateBaseBeanName(importingClassMetadata, 0));
  }
}
//可以看到这块代码的主要功能就是构建MapperScannerConfigurer的bean定义对象
void registerBeanDefinitions(AnnotationAttributes annoAttrs, BeanDefinitionRegistry registry, String beanName) {
  //用到了建造者模式 --- 抽空会整理一下☺☺☺
  BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class);
  //此处省略n行代码 --- 主要做的事情为解析MapperScan注解中的内容
	
  //注册MapperScannerConfigurer的bean定义信息
  registry.registerBeanDefinition(beanName, builder.getBeanDefinition());
}

由此可知@Import(MapperScannerRegistrar.class)注解,往spring中注册的bean定义信息为MapperScannerConfigurer对象的bean定义信息。


3.3 MapperScannerConfigurer对象探秘 ★★★

3.3.1 必备前置知识

MapperScannerConfigurer对象实现了四个接口,这四个接口的必备前置知识分别如下:

(1)BeanDefinitionRegistryPostProcessor — 该接口实又实现了BeanFactoryPostProcessor接口 — 可参看我的文章《【bean的生命周期】— BeanDefinition和BeanFactoryPostProcessor简介

(2)InitializingBean — 可参看我的文章《【bean的生命周期】— 构造方法、@Autowired、BeanPostProcessor、InitializingBean等的执行顺序解析

(3)ApplicationContextAware和BeanNameAware — 可参看我的文章《真实工作中经常用到的Aware使用简介

卧槽,我从没想过自己之前写的文章几乎在mybatis-spring整合原理中几乎都看到了具体的应用!!!

分析 :
(1)首先实现了InitializingBean接口,会在实例化当前对象时调用afterPropertiesSet()方法 ,通过源码发现它只是做了一个断言,即要求MapperScan注解中的basePackage属性不能为空,具体代码不细看了。
(2)实现ApplicationContextAware和BeanNameAware接口,则分别可以将FactoryBean(可以等同于IOC容器)对象和当前bean在IOC容器中的名称赋值给当前bean的两个属性 — 在MapperScannerConfigurer对象里这两个属性分别为applicationContext (类型ApplicationContext)和beanName(类型String)。
(3)实现了BeanDefinitionRegistryPostProcessor接口,而该接口又实现了BeanFactoryPostProcessor接口,则可以调用当前对象的后置处理方法对业务bean的bean定义信息进行修改,从而影响bean对象的创建 。


3.3.2 MapperScannerConfigurer的后置处理方法探秘 —》 创建Mapper动态代理对象的底层秘密

3.3.2.1 扫描所有的Mapper接口,将它们变为BeanDefinition对像的Set集合

这里要注意一下:

  • MapperScannerConfigurer类里真正干活的后置处理方法其实是postProcessBeanDefinitionRegistry(...)方法 — 该方法是BeanDefinitionRegistryPostProcessor接口的抽象方法。

  • BeanDefinitionRegistryPostProcessor接口实现了BeanFactoryPostProcessor接口,但是在MapperScannerConfigurer类里BeanFactoryPostProcessor接口对应的抽象方法postProcessBeanFactory()方法,啥事也没做。

(1)接下来看一下MapperScannerConfigurer后置处理方法的具体源码:
所在类:MapperScannerConfigurer

@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
  if (this.processPropertyPlaceHolders) {
    processPropertyPlaceHolders(); //看方法名可以知道,这里是处理占位符的,不细究了
  }
  //--这里就是在创建并实例化ClassPathMapperScanner对象(翻译过来就是:类路径下的Mapper扫描对象,不细究具体代码了------
  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); //将ioc容器设置到scanner里
  scanner.setBeanNameGenerator(this.nameGenerator);
  scanner.setMapperFactoryBeanClass(this.mapperFactoryBeanClass);
  if (StringUtils.hasText(lazyInitialization)) {
    scanner.setLazyInitialization(Boolean.valueOf(lazyInitialization));
  }
  scanner.registerFilters();
  //--这里就是在创建并实例化ClassPathMapperScanner对象(翻译过来就是:类路径下的Mapper扫描对象,不细究具体代码了------

  //拿着构建好的ClassPathMapperScanner对象去this.basePackage路径下扫描Mapper接口
  scanner.scan(
      StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
}

(2)接着跟一下scan()方法的具体源码:
所在类:ClassPathBeanDefinitionScanner

public int scan(String... basePackages) {
	int beanCountAtScanStart = this.registry.getBeanDefinitionCount();
	
	//真正的对Mapper进行扫描的方法
	doScan(basePackages); 

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

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

(3)接着跟一下doScan(basePackages)方法的源码:
所在类:ClassPathBeanDefinitionScanner ,其父类为 ClassPathBeanDefinitionScanner(真正扫描Mapper接口的方法在这个类里)

@Override
public Set<BeanDefinitionHolder> doScan(String... basePackages) {
  //调用父类的doScan方法进行真正的扫描basePackages目录下的Mapper接口
  //这里的具体逻辑不细讲了,最终结果就是为basePackages目录下的所有接口都建立了一个BeanDefinition对象
  //并将这些BeanDefinition对象放到了一个Set集合里
  //到了这里相信大家就都可以猜到它到底想干什么了,肯定是想拿着这些BeanDefinition对象创建真正的bean对象,
  //但是这些BeanDefinition对象都是Mapper接口对应的对象,不可能直接实例化为具体的对象,
  //那到底该怎么办呢? ---> 请看 processBeanDefinitions(beanDefinitions)方法
  Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);

  if (beanDefinitions.isEmpty()) {
    LOGGER.warn(() -> "No MyBatis mapper was found in '" + Arrays.toString(basePackages)
        + "' package. Please check your configuration.");
  } else {
  	//处理获取的BeanDefinition对象集合
    processBeanDefinitions(beanDefinitions);
  }

  return beanDefinitions;
}

3.3.2.2 修改Mapper接口的BeanDefinition对像,使其可以实例化 — 本文最关键的内容★★★★★

上面创建的BeanDefinition对象都是Mapper接口的BeanDefinition对象,而接口是不可以被实例化的,那该怎么办呢??? —> 修改获取的BeanDefinition对象,让其可以实例化。

(1)必备的前置知识 :《【bean的生命周期】— BeanDefinition和BeanFactoryPostProcessor简介

(2) 接着看一下processBeanDefinitions(beanDefinitions)方法的具体源码:
所在类:ClassPathMapperScanner

private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
  GenericBeanDefinition definition;
  //循环遍历每一个Mapper接口对应的BeanDefinition对象
  for (BeanDefinitionHolder holder : beanDefinitions) {
    definition = (GenericBeanDefinition) holder.getBeanDefinition(); //拿到当前BeanDefinition对象
    String beanClassName = definition.getBeanClassName();//获取到当前BeanDefinition对象的名字
    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.setBeanClass(...)这句话的作用
    //即当前BeanDefinition对象原本的class是属性是mapper接口的class类型,但现在通过修改将其改为MapperFactoryBean的class类型
	//这是想干啥呢???
	//【必备前置知识】 首先你应该知道BeanDefinition对象里的beanClass决定了当前BeanDefinition对象究竟可以实例化为什么对象。
	//而这里将当前definition的beanClass属性改为了this.mapperFactoryBeanClass 即MapperFactoryBean.class,
	//则说明所有的Mapper对象最终实例化后的对象将都是MapperFactoryBean对象!!!!
	
	//这句话也是非常重要的一句话
	//【必备前置知识】可以通过BeanDefinition对象中constructorArgumentValues的
	//addGenericArgumentValue方法来为当前BeanDefinition对应的具体对象的属性赋值。
	//这里是给当前Mapper对应的MapperFactoryBean对象里的mapperInterface属性赋值
    definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName); // issue #59  //★★★★★
    //将当前BeanDefinition对象的beanClass属性改为this.mapperFactoryBeanClass 即MapperFactoryBean.class
    definition.setBeanClass(this.mapperFactoryBeanClass);//★★★★★
	
	//--------不细究了 -----------------------------------------------------------------
    definition.getPropertyValues().add("addToConfig", this.addToConfig);
	
    boolean explicitFactoryUsed = false;
    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;
    }

    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() + "'.");
      //这句话也非常重要
      //【必备前置知识】BeanDefinition对象的autowireMode属性会控制Bean属性的注入方式。
      //这里是将Bean的注入方式改为了AUTOWIRE_BY_TYPE,即通过类型注入 ---- 一会揭晓为什么要这样!!!
      definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);//★★★★★
    }
    definition.setLazyInit(lazyInitialization);
  }
}

上面这块代码是利用Mapper接口的BeanDefinition对象创建出具体的代理对象的关键性代码,而最最关键的三句代码,应该是我标了★★★★★的那三句。


3.3.2.3 MapperFactoryBean对象 — 创建Mapper动态代理对象的底层秘密

通过上面的内容已经可以知道:

(1)Spring会扫描所有的Mapper接口,并为其创建BeanDefinition对象,而这些BeanDefinition对象实质上都是接口的定义对象,无法被实例化为具体的对象。
(2)为了解决(1)中的问题,spring将所有Mapper对应的BeanDefinition对象都进行了修改:

  • 首先将beanClass属性改为了MapperFactoryBean.class — 表明该这些BeanDefinition对象最终都会被实例化为MapperFactoryBean对象
  • 其次调用addGenericArgumentValue方法为要创建的每一个MapperFactoryBean对象的mapperInterface属性进行了赋值 —赋值内容虽然是当前Mapper接口的全额限定名,但是在MapperFactoryBean对象里会转为当前Mapper的class类型
  • 最后修改了autowireMode属性的值 — MapperFactoryBean中的属性注入会通过类型注入,而不是@Autowired注入 — 接下来会细讲,到底是要注入什么属性。

接下来要做的就是看一下这一个个被修改了的BeanDefinition对象是如何被调用并创建+实例化为一个个具体的MapperFactoryBean对象的 —> 其实MapperFactoryBean对象的创建和实例化也是用到了FactoryBean接口的机制 — 可参考我的文章《【Spring之FactoryBean】》。知道了这些以后,我们就来分析一下MapperFactoryBean对象:

(1)MapperFactoryBean对象有一个属性private Class<T> mapperInterface; 它的值正是在修改Mapper的BeanDefinition对象时赋上的;


(2)MapperFactoryBean对象实现了FactoryBean接口,所以它要重写一个getObject方法,并且会将该方法的返回对象放到IOC容器里。这里看一下MapperFactoryBean中的getObject方法源码:
所在类:MapperFactoryBean

@Override
public T getObject() throws Exception {
  //注意这里的T其实就是mapperInterface的泛型,而mapperInterface是通过修改BeanDefinition对象时传的当前Mapper接口
  //那这句话的意思就很明显了---> 就是要创建当前Mapper接口的具体实现类--->也就是Mapper接口的动态代理类
  // ---> 并将创建的Mapper接口的动态代理对象放入到IOC容器里
  return getSqlSession().getMapper(this.mapperInterface);
}

(3)接着我们看一下 getSqlSession()方法的底层源码: ★★★★★
所在类:SqlSessionDaoSupport

public SqlSession getSqlSession() {
  return this.sqlSessionTemplate;
}

可以看到源码很简单,但是这里的sqlSessionTemplate是啥?它是怎么来的呢???—这里就不得不提为什么前面会将BeanDefinition的注入类型改为AUTOWIRE_BY_TYPE的具体原因了。
先来看一下SqlSessionDaoSupport与MapperFactoryBean对象的关系:
在这里插入图片描述
由此可知SqlSessionDaoSupport原来是MapperFactoryBean对象的父类,而sqlSessionTemplate正是SqlSessionDaoSupport中的一个属性,而在修改BeanDefinition对象时,将其注入模型改为了AUTOWIRE_BY_TYPE —> 即不会用@Autowired方式进行属性注入,则该属性的注入方式必定为通过set方法进行注入,接着我们来看一下sqlSessionTemplate的具体注入代码:
所在类:SqlSessionDaoSupport

//通过set方法注入sqlSessionTemplate属性,而此方法需要的参数是SqlSessionFactory对象,
//而该对象正是SqlSessionFactoryBean对象创建时获得的  ----  本文第2部分
public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {
  if (this.sqlSessionTemplate == null || sqlSessionFactory != this.sqlSessionTemplate.getSqlSessionFactory()) {
    this.sqlSessionTemplate = createSqlSessionTemplate(sqlSessionFactory);
  }
}

知道了sqlSessionTemplate属性是如何注入到MapperFactoryBean对象中的,接下来我们开看一下这个属性到底是个啥。
该属性的类继承关系如下:
在这里插入图片描述
由此可知,原来这个sqlSessionTemplate其实就是SqlSession对象的具体实现类。


(4)回到(2)中的源码,我们接着看getSqlSession().getMapper(this.mapperInterface)这句话到底做了什么:
所在类:SqlSessionTemplate

//调用Configuration对象中的getMapper生成当前Mapper的动态代理对象
@Override
public <T> T getMapper(Class<T> type) {
 //获取Configuration,并调用Configuration对象的getMapper方法
 //而Configuration对象正是SqlSessionFactoryBean对象创建时获得的  ----  本文第2部分
  return getConfiguration().getMapper(type, this);
}

之后是如何利用动态代理模式创建当前Mapper的动态代理对象的源码解析,请参考我的另外一篇文章《【Mybatis源码探索】 — Mybatis查询过程核心源码解读 — mapper调用方式》中的第2部门内容。


4 简单总结

(1)SqlSessionFactoryBean对象创建时 —> 相当于完成了单独用mybatis的情况下,项目启动时所做的所有工作 — > 最主要的是创建了Configuration对象,并拿着Configuration对象创建了SqlSessionFactory对象;

(2)@MapperScan注解的作用 —> 扫描到所有的Mapper接口,并通过一些列操作,生成对应Mapper接口的动态代理类,然后将这些Mapper的动态代理类放到IOC容器里 ---- 这就是在mybatis和spring框架整合后,虽然我们没写Mapper接口的具体实现类,但是却可以直接通过@Autowired注解就可以获取到Mapper接口的实现类的原因!!!

发布了191 篇原创文章 · 获赞 188 · 访问量 40万+

猜你喜欢

转载自blog.csdn.net/nrsc272420199/article/details/103940051