文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

Mybatis基于MapperScan注解的动态代理加载机制详解

2023-01-28 12:03

关注

1.如下图在代码开发中使用mybatis时,通过一个接口UserDao对应的方法selectUserNameById执行xml里配置的selectUserNameById查询sql语句。接口dao没有具体的实现方法,那真正执行时mybatis是通过哪个类的哪个方法去找到对应的sql语句并执行的?

package com.changshin.dao;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.changshin.entity.po.User;

public interface UserDao extends BaseMapper<User> {
    public User selectUserNameById(Integer id);
}
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.changshin.dao.UserDao">
    <select id="selectUserNameById" parameterType="Integer" resultType="com.changshin.entity.po.User">
        select username from t_user where id = #{id}
    </select>
</mapper>

application.yml中关于mybatis-plus的配置

mybatis-plus:
  mapper-locations: classpath*:mapper @Configuration @EnableTransactionManagement(order = 2) @MapperScan(value = "com.changshin.dao") public class MybatisPlusConfig { @Bean public PaginationInterceptor paginationInterceptor() { return new PaginationInterceptor(); } }

进入MapperScan类,在类注解上发现@Import(MapperScannerRegistrar.class)这行代码,@Import注解是用来导入配置类或者一些需要前置加载的类,类似原来Spring XML 里面 的 一样。

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(MapperScannerRegistrar.class)  //关注该行代码
@Repeatable(MapperScans.class)
public @interface MapperScan {

在启动springboot项目时,根据导入的配置类的类型有以下四种处理方式:

1. 如果Abc类实现了ImportSelector接口,spring容器就会实例化Abc类,并且调用其selectImports方法;

2. DeferredImportSelector是ImportSelector的子类,如果Abc类实现了DeferredImportSelector接口,spring容器就会实例化Abc类,并且调用其selectImports方法,和ImportSelector的实例不同的是,DeferredImportSelector的实例的selectImports方法调用时机晚于ImportSelector的实例,要等到@Configuration注解中相关的业务全部都处理完了才会调用(具体逻辑在ConfigurationClassParser.processDeferredImportSelectors方法中),想了解更多DeferredImportSelector和ImportSelector的区别,请参考《ImportSelector与DeferredImportSelector的区别(spring4) 》;

3. 如果Abc类实现了ImportBeanDefinitionRegistrar接口,spring容器就会实例化Abc类,并且调用其registerBeanDefinitions方法;

4. 如果Abc没有实现ImportSelector、DeferredImportSelector、ImportBeanDefinitionRegistrar等其中的任何一个,spring容器就会实例化Abc类。

在MapperScannerRegistrar类中实现了ImportBeanDefinitionRegistrar接口。

//实现了ImportBeanDefinitionRegistrar接口
public class MapperScannerRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware {

那么spring在初始化上下文时会调用registerBeanDefinitions方法。

  @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));
    }
  }

加载MapperScannerConfigurer类的bean定义。

void registerBeanDefinitions(AnnotationMetadata annoMeta, AnnotationAttributes annoAttrs,
      BeanDefinitionRegistry registry, String beanName) {
    //获取MapperScannerConfigurer的bean定义,注意该行方法
    BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class);
    builder.addPropertyValue("processPropertyPlaceHolders", true);
 
    Class<? extends Annotation> annotationClass = annoAttrs.getClass("annotationClass");
    if (!Annotation.class.equals(annotationClass)) {
      builder.addPropertyValue("annotationClass", annotationClass);
    }
    //省略.....
}

MapperScannerConfigurer类实现了BeanDefinitionRegistryPostProcessor接口。这个接口支持自定义beanDefinition的注册,在标准的注册完成后(解析xml或者注解),在与实例化对象之前,实现这个接口

public class MapperScannerConfigurer
    implements BeanDefinitionRegistryPostProcessor, InitializingBean, ApplicationContextAware, BeanNameAware {
    //省略....
}

实现接口对应的postProcessBeanDefinitionRegistry方法,可以修改增加BeanDefinition。

此特性可以用来动态生成bean,比如读取某个配置项,然后根据配置项动态生成bean。在代码中

初始化ClassPathMapperScanner类来扫描包里的接口信息

@Override
  public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
    if (this.processPropertyPlaceHolders) {
      //该方法可以加载包信息
      //PropertyValues values = mapperScannerBean.getPropertyValues();
      //this.basePackage = updatePropertyValue("basePackage", values);
      processPropertyPlaceHolders();
    }
    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));
    }
    scanner.registerFilters();
    //扫描mapperscan里配置的com.changshin.dao包
    scanner.scan(
        StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
  }

ClassPathMapperScanner继承了ClassPathBeanDefinitionScanner,因ClassPathMapperScanner没有scan方法,调用scan方法时调用了ClassPathBeanDefinitionScanner里的scan方法,

public class ClassPathMapperScanner extends ClassPathBeanDefinitionScanner {
    //省略。。。。。
}

scan方法中调用了doScan方法,在继承时,子类调用了父类的方法,父类的方法中调用的方法被子类重写时会调用子类的重写方法而不是父类自己的方法,ClassPathBeanDefinitionScanner里的doScan方法,调用的并非本类的doScan方法而是ClassPathMapperScanner重写的doScan方法。

public int scan(String... basePackages) {
		int beanCountAtScanStart = this.registry.getBeanDefinitionCount();
        //注意该方法调用的并非是ClassPathBeanDefinitionScanner里的doScan方法,而是ClassPathMapperScanner重写的doScan方法
		doScan(basePackages);
		// Register annotation config processors, if necessary.
		if (this.includeAnnotationConfig) {
			AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
		}
		return (this.registry.getBeanDefinitionCount() - beanCountAtScanStart);
	}

在ClassPathMapperScanner的doScan方法中调用父类ClassPathBeanDefinitionScanner的doScan加载bean定义

@Override
  public Set<BeanDefinitionHolder> doScan(String... basePackages) {
    //调用父类ClassPathBeanDefinitionScanner的doScan加载bean定义
    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 {
      //注意该方法
      processBeanDefinitions(beanDefinitions);
    }
    return beanDefinitions;
  }
protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
		Assert.notEmpty(basePackages, "At least one base package must be specified");
		Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<>();
		for (String basePackage : basePackages) {
            //获取符合要求的bean定义
			Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
			for (BeanDefinition candidate : candidates) {
				ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);
				candidate.setScope(scopeMetadata.getScopeName());
				String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);
				if (candidate instanceof AbstractBeanDefinition) {
					postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);
				}
				if (candidate instanceof AnnotatedBeanDefinition) {
					AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);
				}
				if (checkCandidate(beanName, candidate)) {
					BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
					definitionHolder =
							AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
					beanDefinitions.add(definitionHolder);
                    //将bean定义注册到registry中
					registerBeanDefinition(definitionHolder, this.registry);
				}
			}
		}
		return beanDefinitions;
	}

在加载bean定义后执行ClassPathMapperScanner的doScan中的processBeanDefinitions方法。在方法中将bean对应的class替换成MapperFactoryBean,这样调用dao接口时,指向的类被替换成

MapperFactoryBean。

private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
    GenericBeanDefinition definition;
    for (BeanDefinitionHolder holder : beanDefinitions) {
      definition = (GenericBeanDefinition) holder.getBeanDefinition();
      String beanClassName = definition.getBeanClassName();
      //省略。。。。。
      //将bean对应的class替换成MapperFactoryBean
      //private Class<? extends MapperFactoryBean> mapperFactoryBeanClass = MapperFactoryBean.class;  
      definition.setBeanClass(this.mapperFactoryBeanClass);
      //省略。。。。。
      if (!explicitFactoryUsed) {
        LOGGER.debug(() -> "Enabling autowire by type for MapperFactoryBean with name '" + holder.getBeanName() + "'.");
        //通过AUTOWIRE_BY_TYPE注入bean,注意此处在后面会用到,bean在初始化时会调用beanClass里的set方法
        definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
      }
      definition.setLazyInit(lazyInitialization);
    }
  }

替换前:

替换后:

继续研究MapperFactoryBean,该类继承了SqlSessionDaoSupport类,实现了FactoryBean接口。

public class MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T> {
    //省略。。。。。。。。。。。
}

在继承的SqlSessionDaoSupport类中有两个set方法,分别是setSqlSessionFactory和setSqlSessionTemplate方法,在上面说到MapperFactoryBean在设置bean定义时,会将AutowireMode设置为AUTOWIRE_BY_TYPE,bean在初始化时会调用beanClass里的set方法。

上述调用set方法可以参考

AbstractAutowireCapableBeanFactory#populateBean方法中调用的autowireByType方法的逻辑。

在这两个set方法中setSqlSessionFactory和setSqlSessionTemplate都对sqlSessionTemplate进行了赋值,但是setSqlSessionTemplate后赋值,对setSqlSessionFactory赋的值进行了覆盖。继续观察setSqlSessionTemplate方法,该方法直接将上下文中的sqlSessionTemplate bean对象直接赋值到SqlSessionDaoSupport的sqlSessionTemplate属性中。

public abstract class SqlSessionDaoSupport extends DaoSupport {
  private SqlSessionTemplate sqlSessionTemplate;
  
  public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {
    if (this.sqlSessionTemplate == null || sqlSessionFactory != this.sqlSessionTemplate.getSqlSessionFactory()) {
      this.sqlSessionTemplate = createSqlSessionTemplate(sqlSessionFactory);
    }
  }
  
  @SuppressWarnings("WeakerAccess")
  protected SqlSessionTemplate createSqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
    return new SqlSessionTemplate(sqlSessionFactory);
  }
  
  public final SqlSessionFactory getSqlSessionFactory() {
    return (this.sqlSessionTemplate != null ? this.sqlSessionTemplate.getSqlSessionFactory() : null);
  }
  
  public void setSqlSessionTemplate(SqlSessionTemplate sqlSessionTemplate) {
    this.sqlSessionTemplate = sqlSessionTemplate;
  }
  
  public SqlSession getSqlSession() {
    return this.sqlSessionTemplate;
  }
  
  public SqlSessionTemplate getSqlSessionTemplate() {
    return this.sqlSessionTemplate;
  }
  
  @Override
  protected void checkDaoConfig() {
    notNull(this.sqlSessionTemplate, "Property 'sqlSessionFactory' or 'sqlSessionTemplate' are required");
  }
}

 sqlSessionTemplate bean对象的来源是通过MybatisPlusAutoConfiguration类(被@Configuration注解)中sqlSessionTemplate放到上下文管理的。在代码中@Configuration下的@Bean注解会将方法返回的对象放入上下文管理并且beanName默认为方法名。

    @Bean
    @ConditionalOnMissingBean
    public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
        ExecutorType executorType = this.properties.getExecutorType();
        if (executorType != null) {
            return new SqlSessionTemplate(sqlSessionFactory, executorType);
        } else {
            return new SqlSessionTemplate(sqlSessionFactory);
        }
    }

在生成SqlSessionTemplate bean对象时,SqlSessionFactory作为属性传入,SqlSessionFactory对象的生成是在MybatisPlusAutoConfiguration类的sqlSessionFactory方法获取的。在该方法中的

factory.getObject()调用了MybatisSqlSessionFactoryBean中的getObject方法。

    @Bean
    @ConditionalOnMissingBean
    public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
        // TODO 使用 MybatisSqlSessionFactoryBean 而不是 SqlSessionFactoryBean
        MybatisSqlSessionFactoryBean factory = new MybatisSqlSessionFactoryBean();
        factory.setDataSource(dataSource);
        factory.setVfs(SpringBootVFS.class);
        if (StringUtils.hasText(this.properties.getConfigLocation())) {
            factory.setConfigLocation(this.resourceLoader.getResource(this.properties.getConfigLocation()));
        }
        applyConfiguration(factory);
        if (this.properties.getConfigurationProperties() != null) {
            factory.setConfigurationProperties(this.properties.getConfigurationProperties());
        }
        if (!ObjectUtils.isEmpty(this.interceptors)) {
            factory.setPlugins(this.interceptors);
        }
        if (this.databaseIdProvider != null) {
            factory.setDatabaseIdProvider(this.databaseIdProvider);
        }
        if (StringUtils.hasLength(this.properties.getTypeAliasesPackage())) {
            factory.setTypeAliasesPackage(this.properties.getTypeAliasesPackage());
        }
        if (this.properties.getTypeAliasesSuperType() != null) {
            factory.setTypeAliasesSuperType(this.properties.getTypeAliasesSuperType());
        }
        if (StringUtils.hasLength(this.properties.getTypeHandlersPackage())) {
            factory.setTypeHandlersPackage(this.properties.getTypeHandlersPackage());
        }
        if (!ObjectUtils.isEmpty(this.typeHandlers)) {
            factory.setTypeHandlers(this.typeHandlers);
        }
        // 注意该行代码获取xml配置路径
        Resource[] mapperLocations = this.properties.resolveMapperLocations();
        if (!ObjectUtils.isEmpty(mapperLocations)) {
            factory.setMapperLocations(mapperLocations);
        }
        // TODO 修改源码支持定义 TransactionFactory
        this.getBeanThen(TransactionFactory.class, factory::setTransactionFactory);
        // TODO 对源码做了一定的修改(因为源码适配了老旧的mybatis版本,但我们不需要适配)
        Class<? extends LanguageDriver> defaultLanguageDriver = this.properties.getDefaultScriptingLanguageDriver();
        if (!ObjectUtils.isEmpty(this.languageDrivers)) {
            factory.setScriptingLanguageDrivers(this.languageDrivers);
        }
        Optional.ofNullable(defaultLanguageDriver).ifPresent(factory::setDefaultScriptingLanguageDriver);
        // TODO 自定义枚举包
        if (StringUtils.hasLength(this.properties.getTypeEnumsPackage())) {
            factory.setTypeEnumsPackage(this.properties.getTypeEnumsPackage());
        }
        // TODO 此处必为非 NULL
        GlobalConfig globalConfig = this.properties.getGlobalConfig();
        // TODO 注入填充器
        this.getBeanThen(MetaObjectHandler.class, globalConfig::setMetaObjectHandler);
        // TODO 注入主键生成器
        this.getBeanThen(IKeyGenerator.class, i -> globalConfig.getDbConfig().setKeyGenerator(i));
        // TODO 注入sql注入器
        this.getBeanThen(ISqlInjector.class, globalConfig::setSqlInjector);
        // TODO 注入ID生成器
        this.getBeanThen(IdentifierGenerator.class, globalConfig::setIdentifierGenerator);
        // TODO 设置 GlobalConfig 到 MybatisSqlSessionFactoryBean
        factory.setGlobalConfig(globalConfig);
        //注意该行方法
        return factory.getObject();
    }

MybatisSqlSessionFactoryBean#getObject方法中的afterPropertiesSet方法中的buildSqlSessionFactory方法有一段这样的代码

final SqlSessionFactory sqlSessionFactory = new MybatisSqlSessionFactoryBuilder().build(targetConfiguration);

在代码中通过builder方法可以找到MybatisSqlSessionFactoryBuilder类里的builder方法

SqlSessionFactory sqlSessionFactory = super.build(configuration);

通过该方法里的super.build发现调用的是父类SqlSessionFactoryBuilder里的builder方法

 public SqlSessionFactory build(Configuration config) {
    return new DefaultSqlSessionFactory(config);
  }

该方法返回的是一个DefaultSqlSessionFactory对象。作为sqlSessionFactory Bean对应的对象。也就是说sqlSessionTemplate bean实例里的sqlSessionFactory属性是一个DefaultSqlSessionFactory类型的实例。

分析完SqlSessionDaoSupport,继续分析MapperFactoryBean实现的FactoryBean,FactoryBean是一个工厂Bean,可以生成某一个类型Bean实例,它最大的一个作用是:可以让我们自定义Bean的创建过程。在FactoryBean中有一个方法getObject可以返回该bean定义的实例。在MapperFactoryBean中查看getObject方法的实现方法。该方法中getSqlSession返回sqlSessionTemplate对象。

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

getMapper方法执行的是sqlSessionTemplate的getMapper方法。其中getConfiguration方法执行的是SqlSessionFactory的getConfiguration方法,SqlSessionFactory是一个接口,实现类是DefaultSqlSessionFactory。getConfiguration方法获取的是getConfiguration方法获取的是DefaultSqlSessionFactory中的configuration属性,该属性是MybatisConfiguration类型的。

  @Override
  public <T> T getMapper(Class<T> type) {
    return getConfiguration().getMapper(type, this);
  }

MybatisConfiguration中的getMapper方法是从MybatisMapperRegistry类的getMapper方法。

   public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
        return mybatisMapperRegistry.getMapper(type, sqlSession);
    }

MybatisMapperRegistry#getMapper方法通过type(mapperInterface即接口全路径)从knownMappers(HashMap中存储的MybatisMapperProxyFactory)中获取MybatisMapperProxyFactory

public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
        // TODO 这里换成 MybatisMapperProxyFactory 而不是 MapperProxyFactory
        final MybatisMapperProxyFactory<T> mapperProxyFactory = (MybatisMapperProxyFactory<T>) knownMappers.get(type);
        if (mapperProxyFactory == null) {
            throw new BindingException("Type " + type + " is not known to the MybatisPlusMapperRegistry.");
        }
        try {
            //注意该行代码
            return mapperProxyFactory.newInstance(sqlSession);
        } catch (Exception e) {
            throw new BindingException("Error getting mapper instance. Cause: " + e, e);
        }
    }

通过newInstance方法返回一个MybatisMapperProxy的实例。

 @SuppressWarnings("unchecked")
    protected T newInstance(MybatisMapperProxy<T> mapperProxy) {
        return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[]{mapperInterface}, mapperProxy);
    }
    public T newInstance(SqlSession sqlSession) {
        //该sqlSession是sqlSessionTemplate类型的对象
        final MybatisMapperProxy<T> mapperProxy = new MybatisMapperProxy<>(sqlSession, mapperInterface, methodCache);
        return newInstance(mapperProxy);
    }

这样,这样客户只用调用dao接口实际上调用的是MybatisMapperProxy类里的invoke方法。

总结:该文章讲述了springboot在启动时是如何通过MapperScan注解的来实现Mybatis动态代理的,dao接口世界上的代理对象其实就是MybatisMapperProxy类型的实例。下一篇文章我们会讲述dao方法是如何同调用的sql语句对应起来的。

到此这篇关于Mybatis基于MapperScan注解的动态代理加载机制详解的文章就介绍到这了,更多相关Mybatis动态代理加载内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

阅读原文内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     807人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     351人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     314人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     433人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     221人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯