Mybatis基于MapperScan注解的动态代理加载机制详解
xl649138628 人气:01.如下图在代码开发中使用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; /** * 后台管理用户表 dao 注意:只是一个接口没有对应的实现类 * */ 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/*.xml
typeAliasesPackage: >
com.changshin.entity.po
global-config:
id-type: 0 # 0:数据库ID自增 1:用户输入id 2:全局唯一id(IdWorker) 3:全局唯一ID(uuid)
db-column-underline: false
refresh-mapper: true
configuration:
map-underscore-to-camel-case: true
cache-enabled: true #配置的缓存的全局开关
lazyLoadingEnabled: true #延时加载的开关
multipleResultSetsEnabled: true #开启的话,延时加载一个属性时会加载该对象全部属性,否则按需加载属性
在springboot开发过程中项目集成了mybatis-plus,在集成时做了如下配置,通过@MapperScan来扫描com.changshin.dao包下的dao接口,dao是接口不能直接生成实例,在调用时只能时通过代理的方式执行代理对象的方法。
package com.changshin.config; import com.baomidou.mybatisplus.extension.plugins.PaginationInterceptor; import org.mybatis.spring.annotation.MapperScan; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.transaction.annotation.EnableTransactionManagement; /** * MybatisPlus配置 */ @Configuration @EnableTransactionManagement(order = 2) @MapperScan(value = "com.changshin.dao") public class MybatisPlusConfig { /** * mybatis-plus分页插件 */ @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; /** * Set MyBatis SqlSessionFactory to be used by this DAO. Will automatically create SqlSessionTemplate for the given * SqlSessionFactory. * * @param sqlSessionFactory * a factory of SqlSession */ public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) { if (this.sqlSessionTemplate == null || sqlSessionFactory != this.sqlSessionTemplate.getSqlSessionFactory()) { this.sqlSessionTemplate = createSqlSessionTemplate(sqlSessionFactory); } } /** * Create a SqlSessionTemplate for the given SqlSessionFactory. Only invoked if populating the DAO with a * SqlSessionFactory reference! * <p> * Can be overridden in subclasses to provide a SqlSessionTemplate instance with different configuration, or a custom * SqlSessionTemplate subclass. * * @param sqlSessionFactory * the MyBatis SqlSessionFactory to create a SqlSessionTemplate for * @return the new SqlSessionTemplate instance * @see #setSqlSessionFactory */ @SuppressWarnings("WeakerAccess") protected SqlSessionTemplate createSqlSessionTemplate(SqlSessionFactory sqlSessionFactory) { return new SqlSessionTemplate(sqlSessionFactory); } /** * Return the MyBatis SqlSessionFactory used by this DAO. * * @return a factory of SqlSession */ public final SqlSessionFactory getSqlSessionFactory() { return (this.sqlSessionTemplate != null ? this.sqlSessionTemplate.getSqlSessionFactory() : null); } /** * Set the SqlSessionTemplate for this DAO explicitly, as an alternative to specifying a SqlSessionFactory. * * @param sqlSessionTemplate * a template of SqlSession * @see #setSqlSessionFactory */ public void setSqlSessionTemplate(SqlSessionTemplate sqlSessionTemplate) { this.sqlSessionTemplate = sqlSessionTemplate; } /** * Users should use this method to get a SqlSession to call its statement methods This is SqlSession is managed by * spring. Users should not commit/rollback/close it because it will be automatically done. * * @return Spring managed thread safe SqlSession */ public SqlSession getSqlSession() { return this.sqlSessionTemplate; } /** * Return the SqlSessionTemplate for this DAO, pre-initialized with the SessionFactory or set explicitly. * <p> * <b>Note: The returned SqlSessionTemplate is a shared instance.</b> You may introspect its configuration, but not * modify the configuration (other than from within an {@link #initDao} implementation). Consider creating a custom * SqlSessionTemplate instance via {@code new SqlSessionTemplate(getSqlSessionFactory())}, in which case you're * allowed to customize the settings on the resulting instance. * * @return a template of SqlSession */ public SqlSessionTemplate getSqlSessionTemplate() { return this.sqlSessionTemplate; } /** * {@inheritDoc} */ @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语句对应起来的。
加载全部内容