使用过 mybatis 的人应该都知道,mybatis 有个特性就是对于 Mapper 类,只须要声明接口就能够了,而不须要写具体的实现类,上层在使用 Mapper 接口时只须要直接注入 Mapper 接口就能够正常工做,下面咱们就来具体剖析下 mybatis 是怎样经过这个 Mapper 接口来自动生成 Mapper 实现类,而且注册到 spring 容器中。java
applicationContext.xmlspring
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="configLocation" value="classpath:config/mybatis-config.xml" /> <property name="dataSource" ref="dataSource" /> <property name="mapperLocations" value="classpath:mapping/*Mapper.xml" /> </bean> <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <!-- 扫描全部 RepositoryMapper 注解的类 --> <property name="annotationClass" value="com.jaf.framework.core.mapper.RepositoryMapper" /> <!-- 扫描全部 BaseMapper 接口实现类 --> <property name="markerInterface" value="com.jaf.framework.core.mapper.BaseMapper" /> <property name="basePackage" value="com.hbvtc.exam.mapper.*" /> <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory" /> </bean>
TeacherMapper.java & BaseMapper.javasql
@RepositoryMapper public interface TeacherMapper extends BaseMapper<Teacher> { } public interface BaseMapper<E extends BaseEntity<?>> { void insertEntity(E var1); void updateById(E var1); <T> void deleteById(T var1); <T> void deleteByIds(T[] var1); Page<E> pageQuery(Map<String, Object> var1); } @Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface RepositoryMapper { }
这个 mapper 只有接口,没有具体的实现类,而且这个 Mapper 接口是被 @RepositoryMapper
注解标识的,因此能够被 mybatis 扫描到。apache
注意,这里的 @RepositoryMapper
注解只是个普通的注解,并无被 spring @Component
注解标识,那么 mybatis 是怎样扫描到这些 mapper 接口,而且生成对应的实现类对象,而后注册到 spring 容器中的呢?缓存
TeacherDaoImpl.javamybatis
@Repository("teacherDao") public class TeacherDaoImpl extends BaseDaoImpl<Teacher> implements TeacherDao { // 像使用普通的 spring bean 同样注入 mapper 类 @Autowired private TeacherMapper teacherMapper; @Override protected BaseMapper<Teacher> getMapper() { return teacherMapper; } }
上层 dao 实现类中,对 mapper 的注入像普通的 bean 同样。app
MapperScannerConfigurer
从上面的配置文件中能够看到,mybatis 配置的类为 org.mybatis.spring.mapper.MapperScannerConfigurer
,也正是这个类实现了 mybatis mapper 接口的扫描,而且注册具体的实例到 spring 容器中。ide
先看下这个类的类继承关系图post
能够看到这个类是实现了 BeanDefinitionRegistryPostProcessor
接口的,以前已经介绍过 BeanDefinitionRegistryPostProcessor
这个接口能够在 spring 容器启动过程当中对 Beandefinition 注册作一些扩展。this
下面咱们直接来看 BeanDefinitionRegistryPostProcessor#postProcessBeanDefinitionRegistry
方法
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) { if (this.processPropertyPlaceHolders) { processPropertyPlaceHolders(); } // ClassPathBeanDefinitionScanner 从 ClassPathBeanDefinitionScanner 继承,这个类主要负责 mybatis mapper 文件的扫描 // 扫描的基本规则就是根据以前配置文件中配置的 // basePackage: 在哪一个包下扫描 // markerInterface: 扫描哪一个接口,markerInterface 接口的全部实现类会被扫描到 // annotationClass: 或者是扫描被 annotationClass 注解的全部类 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.registerFilters(); scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS)); }
org.springframework.context.annotation.ClassPathBeanDefinitionScanner#scan
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); }
#scan
方法是在父类(ClassPathBeanDefinitionScanner
)中定义的,其中最主要的是 #doScan
方法。
org.mybatis.spring.mapper.ClassPathMapperScanner#doScan
public Set<BeanDefinitionHolder> doScan(String... basePackages) { // 主要仍是经过 super.doScan 方法来扫描,返回全部符合条件的 BeanDefinitionHolder 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 { // mybatis 对返回的 Beandefinition 进行了进一步的处理 processBeanDefinitions(beanDefinitions); } return beanDefinitions; }
mybatis 的 ClassPathMapperScanner
类对父类中的 #doScan
方法作了重写,但对于 Beandefinition 的扫描和注册到 spring 容器中,主要仍是经过 super.doScan
方法来实现。
org.springframework.context.annotation.ClassPathBeanDefinitionScanner#doScan
方法
protected Set<BeanDefinitionHolder> doScan(String... basePackages) { Assert.notEmpty(basePackages, "At least one base package must be specified"); Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<BeanDefinitionHolder>(); for (String basePackage : basePackages) { // 这个方法的具体逻辑就不展开了,感兴趣的能够本身跟进下源码 // 主要过滤条件是经过 org.mybatis.spring.mapper.ClassPathMapperScanner#registerFilters 方法来完成注册的 // 过滤条件包含配置文件中配置的 annotationClass 和 markerInterface // 另外这里须要注意的一点是,这里返回的 BeanDefinition 具体的实现类是 ScannedGenericBeanDefinition,而且 BeanDefinition 属性 beanClass 对应的是接口类(正常状况下应该是一个具体的实现类,由于接口是没法实例化的) 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) { // 处理其余的一些注解(@Lazy, @Primary, @DependsOn...) AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate); } if (checkCandidate(beanName, candidate)) { BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName); definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry); beanDefinitions.add(definitionHolder); // 向 spring 容器注册 BeanDefinition registerBeanDefinition(definitionHolder, this.registry); } } } return beanDefinitions; }
这个方法负责扫描 MapperScannerConfigurer
配置的 basePackage 下的全部符合 annotationClass 注解的接口/类,或者实现了 markerInterface 接口的接口/类,而且生成对应的 BeanDefinition,而后注册到 spring 容器中。
到目前为止咱们应该解决了 mybatis 是如何扫描到全部的 mapper 接口,而且注册到 spring 容器中的问题,可是须要注意的是这里返回的 BeanDefinition 里面的 beanClass 属性对应的可能接口类,一个接口是没办法被实例化的,也就是目前为止扫描到的这些 BeanDefinition 在后续 spring 容器启动过程当中没办法生成对应的 bean 实例。
咱们继续回到上面的 org.mybatis.spring.mapper.ClassPathMapperScanner#doScan
这个方法,这个方法里面还有调用了一个很重要的方法就是 org.mybatis.spring.mapper.ClassPathMapperScanner#processBeanDefinitions
,具体源代码以下(删除了部分日志打印相关代码):
private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) { GenericBeanDefinition definition; for (BeanDefinitionHolder holder : beanDefinitions) { definition = (GenericBeanDefinition) holder.getBeanDefinition(); // 这两句代码时关键,这里将 beanClass 进行了从新设置,设置为了 MapperFactoryBean,而且将原来接口 class 设置为构造方法参数 definition.getConstructorArgumentValues().addGenericArgumentValue(definition.getBeanClassName()); // issue #59 definition.setBeanClass(this.mapperFactoryBean.getClass()); definition.getPropertyValues().add("addToConfig", this.addToConfig); // 设置 sqlSessionFactory,若是有配置 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; } // 设置 sqlSessionTemplate,若是有配置 if (StringUtils.hasText(this.sqlSessionTemplateBeanName)) { definition.getPropertyValues().add("sqlSessionTemplate", new RuntimeBeanReference(this.sqlSessionTemplateBeanName)); explicitFactoryUsed = true; } else if (this.sqlSessionTemplate != null) { definition.getPropertyValues().add("sqlSessionTemplate", this.sqlSessionTemplate); explicitFactoryUsed = true; } // 若是 sqlSessionFactory & sqlSessionTemplate 都没有指定,那么启用类型自动注入 if (!explicitFactoryUsed) { definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE); } } }
通过这一步的处理,如今 mybatis 扫描到的全部符合条件的 BeanDefinition 都是能够在后续 spring 容器启动过程当中,转换成具体 bean 的了,也就是如今的 BeanDefinition 已是能够正常使用的了。 下面咱们再深刻一步地看下这个 MapperFactoryBean
是如何生成具体的实现类的,由于咱们的代码中并无对 Mapper 接口生成具体的实现类,查看 org.apache.ibatis.binding.MapperProxyFactory#newInstance
源码以下:
public T getObject() throws Exception { return getSqlSession().getMapper(this.mapperInterface); }
跟着调用链一直追踪,最后会调用到 org.apache.ibatis.binding.MapperRegistry#getMapper
public T newInstance(SqlSession sqlSession) { final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache); return newInstance(mapperProxy); } protected T newInstance(MapperProxy<T> mapperProxy) { // mybatis 经过 jdk 的动态代理方式来为全部的 mapper 接口生成一个代理实现类 // 使用 MapperProxy 做为动态代理的 InvocationHandler return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy); }
到这里就已经很是明确了,mybatis 经过 jdk 的动态代理方式来为全部的 mapper 接口生成一个代理实现类。
顺便咱们来看下 org.apache.ibatis.binding.MapperProxy#invoke
方法
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { try { // 若是 mapper 是一个具体的实现类,那么直接调用实现类的方法 if (Object.class.equals(method.getDeclaringClass())) { return method.invoke(this, args); } else if (isDefaultMethod(method)) { // default method 是指那些例如:toString, equals 方法等 return invokeDefaultMethod(proxy, method, args); } } catch (Throwable t) { throw ExceptionUtil.unwrapThrowable(t); } // 当 mapper 接口中的一个方法被调用的时候,会进入到这里,mybatis 会对这个方法生成一个 MapperMethod 对象,而且缓存这个对象 final MapperMethod mapperMethod = cachedMapperMethod(method); return mapperMethod.execute(sqlSession, args); }
org.apache.ibatis.binding.MapperMethod#execute
// mapper 接口中的 select / update 等方法最终被执行的地方 public Object execute(SqlSession sqlSession, Object[] args) { Object result; switch (command.getType()) { case INSERT: { // 调用了 sqlSession 的 insert / update / delete ... 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); } 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; }
至此 mybatis 对于 mapper 接口的扫描、如何注册到 spring 容器中、如何对 mapper 接口生成动态代理实现类,以及动态代理类方法执行已经所有解析完了。
本文全部源代码基于 mybatis:3.4.5 & mybatis-spring:1.3.1