mybatis——Spring中的接口映射器
一、接口映射器的配置
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd"> <!-- 自动扫描package下bean --> <context:component-scan base-package="org.study.*"/> <!-- aop代理 --> <aop:aspectj-autoproxy/> <!-- 属性文件 --> <context:property-placeholder location="classpath*:db.properties"/> <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"> <property name="driverClassName" value="${mysql.driver}"/> <property name="url" value="${mysql.url}"/> <property name="username" value="${mysql.username}"/> <property name="password" value="${mysql.password}"/> <property name="initialSize" value="${mysql.initialSize}"/> <property name="minIdle" value="${mysql.minIdle}"/> <property name="maxActive" value="${mysql.maxActive}"/> <property name="maxWait" value="${mysql.maxWait}"/> </bean> <!-- mybatis的SQLSessionFactory --> <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" ref="dataSource"/> <!--扫描配置sql的xml文件--> <property name="mapperLocations" value="classpath:mapper/*.xml"/> </bean> <!-- mybatis的mapper接口映射器 --> <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/> <!--扫描package下的接口--> <property name="basePackage" value="org.study.mapper"/> </bean> <!-- 事务管理 --> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"/> </bean> <!-- 启用声明式事务管理 支持注解@Transaction--> <tx:annotation-driven/> </beans>
前文说明了:sql的xml解析和注解解析,所以省去sqlSessionFactory根据mapperLocations="classpath:mapper/*.xml "匹配对应sql.xml,然后解析。直接看接口映射器实现
二、MapperScannerConfigurer
MapperScannerConfigurer是mybatis-spring.jar中的类,不是mybatis.jar中的类,
主要目的:将mapper接口放入到SpringIOC容器中,并与sql文件映射。跟踪源码前需要要先了解SpringIOC——scan,疑问:
- 有@Component注解的class才会生成的BeanDefinition,然后初始化到SpringIOC容器中,因此接口需要生成BeanDefinition必须重写判断方法,在哪里重写的?
- 生成了BeanDefinition后,由于mapper.class是接口,IOC容器是怎样实例化的?
1、扫描mapper接口生成BeanDefinition实例
MapperScannerConfigurer只有两个方法
- postProcessBeanDefinitionRegistry:扫描mapper接口生成BeanDefinition的主要实现方法。
- processPropertyPlaceHolders:根据properties文件获取MapperScannerConfigurer的属性参数。
重点跟踪postProcessBeanDefinitionRegistry方法
/* org.mybatis.spring.mapper.MapperScannerConfigurer#postProcessBeanDefinitionRegistry * postProcessBeanDefinitionRegistry这个方法的执行时机: * IOC初始化-refresh()中的⑥ invokeBeanFactoryPostProcessors(beanfactory)时执行的 * 即方法执行时。容器(registry)已经创建并且已经执行过一次scan,这里是另外附加一次scan */ public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) { if (this.processPropertyPlaceHolders) { //propertis中获取basePackage参数 //应该是适配springboot无xml方式实现 processPropertyPlaceHolders(); } //mybatis-spring.jar自实现的scanner ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry); scanner.setAddToConfig(this.addToConfig); //includeFilters.add(annotationClass) scanner.setAnnotationClass(this.annotationClass); //includerFilters.add(markerInterface) 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); //mapper接口的实际类型MapperFactoryBean.class scanner.setMapperFactoryBeanClass(this.mapperFactoryBeanClass); if (StringUtils.hasText(lazyInitialization)) { scanner.setLazyInitialization(Boolean.valueOf(lazyInitialization)); } //最重要的实现之一:解决为什么接口没有@Component也会被扫描到 //回忆一下spring中scan阶段includeFilters的作用:默认@Component注解的class才能被IOC扫描到并生成BeanDefinition实例(注意只是生成实例,还没有加入到容器中) //registerFIlters:修改了includeFilters范围 //若配置了annotationClass:includeFilter.add(annotationClass):例如mapper接口使用@Mapper注解。 //若配置了markerInterface:includeFilter.add(markerInterface) //都没有配置默认所有的class都可以生成BeanDefinition scanner.registerFilters(); //解析basePackage下的class生成BeanDefinition(根据上面的includeFilters) scanner.scan( StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS)); }
scanner.registerFilters(): includeFilters修改
/* org.mybatis.spring.mapper.ClassPathMapperScanner#registerFilters */ public void registerFilters() { boolean acceptAllInterfaces = true; // 配置注解,includeFilters添加注解类,basePackage下使用注解的类会被扫描到 if (this.annotationClass != null) { addIncludeFilter(new AnnotationTypeFilter(this.annotationClass)); acceptAllInterfaces = false; } // 配置接口,includeFilters添加接口,basePackage下实现接口的class会被扫描到 if (this.markerInterface != null) { addIncludeFilter(new AssignableTypeFilter(this.markerInterface) { @Override protected boolean matchClassName(String className) { return false; } }); acceptAllInterfaces = false; } //若没有配置注解和接口,默认扫描所有的class if (acceptAllInterfaces) { addIncludeFilter((metadataReader, metadataReaderFactory) -> true); } // 不扫描 *package-info.java addExcludeFilter((metadataReader, metadataReaderFactory) -> { String className = metadataReader.getClassMetadata().getClassName(); return className.endsWith("package-info"); }); }
scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS)):扫描basePackage下的class
/* org.mybatis.spring.mapper.ClassPathMapperScanner#doScan */ public Set<BeanDefinitionHolder> doScan(String... basePackages) { //org.springframework.context.annotation.ClassPathBeanDefinitionScanner#doScan //前面springioc-scan()中的doScan()方法 //ClassPathMapperScanner重写了isCandidateComponent() 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; }
调用super.doScan(basePackages)时: 由于重写isCandidateComponent():接口生成的BeanDefinition也会加入到容器中。
原方法:includeFilters扫描到class文件并生成BeanDefinition实例,isCandidateComponent()会判断不是接口、抽象类,才会将BeanDefinition放入到IOC容器中
重写后:接口的BeanDefinition也会放入到IOC容器中(仅限basePackage下)
/* org.mybatis.spring.mapper.ClassPathMapperScanner#isCandidateComponent */ protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) { //是接口返回true,原判断是metadata.isConcrete():!(isInterface() || isAbstract()) return beanDefinition.getMetadata().isInterface() && beanDefinition.getMetadata().isIndependent(); }
processBeanDefinitions():初始化BeanDefinition的属性:
/* org.mybatis.spring.mapper.ClassPathMapperScanner#processBeanDefinitions */ 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"); //解决:接口为什么能实例化成一个Bean //原beanClassName设置为构造方法的参数 //beanClassName = MapperFactoryBean.class //经过上面配置,SpringIOC容器生成接口的实例实际是MapperFactoryBean类型的 //最后beanName(userMapper) - singletonObject(new MapperFactoryBean(UserMapper.class)) definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName); // issue #59 definition.setBeanClass(this.mapperFactoryBeanClass); definition.getPropertyValues().add("addToConfig", this.addToConfig); boolean explicitFactoryUsed = false; if (StringUtils.hasText(this.sqlSessionFactoryBeanName)) { //新增属性:definition.sqlSessionFactory根据BeanName注入 definition.getPropertyValues().add("sqlSessionFactory", new RuntimeBeanReference(this.sqlSessionFactoryBeanName)); explicitFactoryUsed = true; } else if (this.sqlSessionFactory != null) { //新增属性:definition.sqlSessionFactory直接ref注入 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.sqlSessionTemplate根据BeanName注入 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.sqlSessionTemplate直接ref注入 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); } //延迟实例化 默认false definition.setLazyInit(lazyInitialization); } }
到这里mapper接口的BeanDefinition实例已经全部初始化完成了,接下来就是Bean实例化了。Bean实例前先猜想一下这个实例是什么样的。
// 例如一个UserMapper.class, MapperFactoryBean factoryBean = new MapperFactoryBean(UserMapper.class); factoryBean.setSqlSessionFactory(sqlSessionFactory); factoryBean.setSqlSessionTemplate(sqlSessionTemplate);
2、MapperFactoryBean初始化
SpringIOC——refresh初始化中漏掉的实现FactoryBean接口的Bean的特殊处理,除beanName= "&" + beanName,其他初始化逻辑与Bean一模一样。
这里结合MapperFactoryBean实现,发现了FactoryBean作用:提供接口实例化的机会,接口绑定技术。接口是肯定不能实例化的,但是可以实例化一个FactoryBean实例代替接口。
例如:UserMapper接口的BeanDefinition实例化出来就是一个MapperFactoryBean实例,
断点发现,确实是一个MapperFactoryBean实例,其中记录了UserMapper.class信息。
接口映射器实例化已经完成了,但是实际代码一般是下面这样的
@Autowired private UserMapper userMapper;//MapperFactoryBean没有实现UserMaper,所以依赖注入时,注入的肯定不是MapperFactoryBean实例。 ... userMapper.seletUserById(1L);
断点发现依赖注入时,userMapper确实不是MapperFactoryBean实例,而是一个代理实例
根据FactoryBean接口,猜想可能是在依赖注入时,依赖注入的是FactoryBean.getObject()实例
public interface FactoryBean<T> { String OBJECT_TYPE_ATTRIBUTE = "factoryBeanObjectType"; T getObject() throws Exception; @Nullable Class<?> getObjectType(); default boolean isSingleton() { return true; } }
最终发现Bean初始化时有一个了解不够全面的地方:org.springframework.beans.factory.support.AbstractBeanFactory#getObjectForBeanInstance,
也是实现FactoryBean接口的Bean与其他类型Bean的区别所在。
① !(Bean instanceof FactoryBean) 直接返回Bean实例
②(Bean instanceof FactoryBean) 初始化时返回FactoryBean实例,其他情况下(例如DI)时返回的是FactoryBean.getObject()实例
protected Object getObjectForBeanInstance( Object beanInstance, String name, String beanName, @Nullable RootBeanDefinition mbd) { //UserMapper初始化时由于MapperFactoryBean instanceof FactoryBean,传入的name = "&userMapper" //依赖注入时传入的是name = "userMapper" if (BeanFactoryUtils.isFactoryDereference(name)) { //Bean instanceof FactoryBean的Bean初始化时,name = "&userMapper"直接返回FactoryBean实例 if (beanInstance instanceof NullBean) { return beanInstance; } if (!(beanInstance instanceof FactoryBean)) { throw new BeanIsNotAFactoryException(beanName, beanInstance.getClass()); } if (mbd != null) { mbd.isFactoryBean = true; } return beanInstance; } // 没有实现FactoryBean的Bean直接返回Bean实例 if (!(beanInstance instanceof FactoryBean)) { return beanInstance; } //依赖注入时name = "userMapper" //返回的是FactoryBean.getObject()实例, Object object = null; if (mbd != null) { mbd.isFactoryBean = true; } else { object = getCachedObjectForFactoryBean(beanName); } if (object == null) { // Return bean instance from factory. FactoryBean<?> factory = (FactoryBean<?>) beanInstance; // Caches object obtained from FactoryBean if it is a singleton. if (mbd == null && containsBeanDefinition(beanName)) { mbd = getMergedLocalBeanDefinition(beanName); } boolean synthetic = (mbd != null && mbd.isSynthetic()); object = getObjectFromFactoryBean(factory, beanName, !synthetic); } return object; }
到这里初始化就剩下一个MapperFactoryBean.getObject():先小结一下:
① SpringIOC容器中userMapper是MapperFactoryBean类型的
② SpringIOC容器中userService中userMapper是MapperFactoryBean.getObject()类型的(UserMapper.class)
3、MapperFactoryBean.getObject()
/* org.mybatis.spring.mapper.MapperFactoryBean#getObject */ public T getObject() throws Exception { //这里sqlSession是SqlSessionTemplate类型的 return getSqlSession().getMapper(this.mapperInterface); } /* org.mybatis.spring.SqlSessionTemplate#getMapper */ public <T> T getMapper(Class<T> type) { return getConfiguration().getMapper(type, this); } /* * 下面就是mybatis.jar中的逻辑了,也就回到了前文的逻辑了 */ /* org.apache.ibatis.session.Configuration#getMapper */ public <T> T getMapper(Class<T> type, SqlSession sqlSession) { return mapperRegistry.getMapper(type, sqlSession); } /* org.apache.ibatis.binding.MapperRegistry#getMapper */ public <T> T getMapper(Class<T> type, SqlSession sqlSession) { final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type); if (mapperProxyFactory == null) { throw new BindingException("Type " + type + " is not known to the MapperRegistry."); } try { return mapperProxyFactory.newInstance(sqlSession); } catch (Exception e) { throw new BindingException("Error getting mapper instance. Cause: " + e, e); } } /* org.apache.ibatis.binding.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); }