MyBatis 传统模式

  1. public static void main(String[] args) throws IOException {
  2. InputStream inputStream = Resources.getResourceAsStream("mybatis.xml");
  3. SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
  4. SqlSession sqlSession = sqlSessionFactory.openSession();
  5. // 通过 JDK 动态代理,生成代理对象
  6. AdminMapper mapper = sqlSession.getMapper(AdminMapper.class);
  7. List<Admin> admins = mapper.selectAll();
  8. for (Admin admin : admins) {
  9. System.out.println(admin.getId() + ", " + admin.getName());
  10. }
  11. }

其中 AdminMapper mapper = sqlSession.getMapper(AdminMapper.class); 就是通过 JDK 动态代理生成的代理对象,我们可以往下跟源码发现

  1. protected T newInstance(MapperProxy<T> mapperProxy) {
  2. return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(),
  3. new Class[] { mapperInterface }, mapperProxy);
  4. }

MyBatis 如何和 Spring 整合的(MyBatis-Spring 1.3.X 的实现)

一、演化一,FactoryBean

FactoryBean 的模式

  1. @Component
  2. public class MyFactoryBean implements FactoryBean<Object> {
  3. @Override
  4. public UserMapper getObject() throws Exception {
  5. Object proxyInstance = Proxy.newProxyInstance(MyFactoryBean.class.getClassLoader(), new Class<?>[]{UserMapper.class}, new InvocationHandler() {
  6. @Override
  7. public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  8. String select = method.getAnnotation(Select.class).value();
  9. System.out.println(method.getName() + " => " + select);
  10. return null;
  11. }
  12. });
  13. return (UserMapper) proxyInstance;
  14. }
  15. @Override
  16. public Class<?> getObjectType() {
  17. return UserMapper.class;
  18. }
  19. }

我们可以通过 FactoryBean 的模式来给 UserMapper 生成一个代理对象,这样虽然可以成功,但是,如果在还有其他的 Mapper 怎么处理呢?难道在写一个 FactoryBean 么?肯定不是

二、演化二,ImportBeanDefinitionRegistrar

通过 ImportBeanDefinitionRegistrar 接口,我们可以想容器中注册 BeanDefinition,这也是 MyBatis 与 Spring 整合的方式

  1. public class MyBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
  2. @Override
  3. public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
  4. // 这里可以用 扫描 来实现
  5. List<Class<?>> mapperClasses = new ArrayList<>();
  6. mapperClasses.add(UserMapper.class);
  7. mapperClasses.add(OrderMapper.class);
  8. for (Class<?> mapperClass : mapperClasses) {
  9. AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.genericBeanDefinition().getBeanDefinition();
  10. beanDefinition.setBeanClass(MyFactoryBean.class);
  11. beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(mapperClass);
  12. // 注册到容器
  13. registry.registerBeanDefinition(mapperClass.getSimpleName(), beanDefinition);
  14. }
  15. }
  16. }
  17. public class MyFactoryBean implements FactoryBean<Object> {
  18. private Class<?> mapperInterface;
  19. public MyFactoryBean(Class<?> mapperInterface) {
  20. this.mapperInterface = mapperInterface;
  21. }
  22. @Override
  23. public Object getObject() throws Exception {
  24. return Proxy.newProxyInstance(MyFactoryBean.class.getClassLoader(), new Class<?>[]{mapperInterface}, new InvocationHandler() {
  25. @Override
  26. public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  27. String select = method.getAnnotation(Select.class).value();
  28. System.out.println(method.getName() + " => " + select);
  29. return null;
  30. }
  31. });
  32. }
  33. @Override
  34. public Class<?> getObjectType() {
  35. return mapperInterface;
  36. }
  37. }
  38. @Configuration
  39. @ComponentScan("com.sourceflag.spring.mybatis")
  40. @Import(MyBeanDefinitionRegistrar.class)
  41. public static class Config {
  42. }

MyBatis 整合 Spring 源码

MapperScan 注解

我们可以看到 MapperScan 上面也有一个 @Import(MapperScannerRegistrar.class) 注解

  1. @Retention(RetentionPolicy.RUNTIME)
  2. @Target(ElementType.TYPE)
  3. @Documented
  4. @Import(MapperScannerRegistrar.class)
  5. public @interface MapperScan {
  6. }

MapperScannerRegistrar 的实现

  1. public class MapperScannerRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware {
  2. private ResourceLoader resourceLoader;
  3. /**
  4. * {@inheritDoc}
  5. */
  6. @Override
  7. public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
  8. // 获取 MapperScan 注解
  9. AnnotationAttributes annoAttrs = AnnotationAttributes.fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName()));
  10. ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
  11. // this check is needed in Spring 3.1
  12. if (resourceLoader != null) {
  13. scanner.setResourceLoader(resourceLoader);
  14. }
  15. Class<? extends Annotation> annotationClass = annoAttrs.getClass("annotationClass");
  16. if (!Annotation.class.equals(annotationClass)) {
  17. scanner.setAnnotationClass(annotationClass);
  18. }
  19. Class<?> markerInterface = annoAttrs.getClass("markerInterface");
  20. if (!Class.class.equals(markerInterface)) {
  21. scanner.setMarkerInterface(markerInterface);
  22. }
  23. Class<? extends BeanNameGenerator> generatorClass = annoAttrs.getClass("nameGenerator");
  24. if (!BeanNameGenerator.class.equals(generatorClass)) {
  25. scanner.setBeanNameGenerator(BeanUtils.instantiateClass(generatorClass));
  26. }
  27. Class<? extends MapperFactoryBean> mapperFactoryBeanClass = annoAttrs.getClass("factoryBean");
  28. if (!MapperFactoryBean.class.equals(mapperFactoryBeanClass)) {
  29. scanner.setMapperFactoryBean(BeanUtils.instantiateClass(mapperFactoryBeanClass));
  30. }
  31. scanner.setSqlSessionTemplateBeanName(annoAttrs.getString("sqlSessionTemplateRef"));
  32. scanner.setSqlSessionFactoryBeanName(annoAttrs.getString("sqlSessionFactoryRef"));
  33. List<String> basePackages = new ArrayList<String>();
  34. for (String pkg : annoAttrs.getStringArray("value")) {
  35. if (StringUtils.hasText(pkg)) {
  36. basePackages.add(pkg);
  37. }
  38. }
  39. for (String pkg : annoAttrs.getStringArray("basePackages")) {
  40. if (StringUtils.hasText(pkg)) {
  41. basePackages.add(pkg);
  42. }
  43. }
  44. for (Class<?> clazz : annoAttrs.getClassArray("basePackageClasses")) {
  45. basePackages.add(ClassUtils.getPackageName(clazz));
  46. }
  47. // 注册 filters
  48. scanner.registerFilters();
  49. // 开始进行扫描
  50. scanner.doScan(StringUtils.toStringArray(basePackages));
  51. }
  52. /**
  53. * {@inheritDoc}
  54. */
  55. @Override
  56. public void setResourceLoader(ResourceLoader resourceLoader) {
  57. this.resourceLoader = resourceLoader;
  58. }
  59. }

看下 MyBatis 是如果定义扫描器的
  1. public class ClassPathMapperScanner extends ClassPathBeanDefinitionScanner {
  2. public ClassPathMapperScanner(BeanDefinitionRegistry registry) {
  3. // 我们可以看到传递的 useDefaultFilters 属性为 false
  4. super(registry, false);
  5. }
  6. @Override
  7. protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
  8. // 必须是接口
  9. return beanDefinition.getMetadata().isInterface() && beanDefinition.getMetadata().isIndependent();
  10. }
  11. }

我们可以看到传递的 useDefaultFilters 属性为 false,表示不使用 Spring 中的默认过滤器,因为 Spring 默认不会使用接口来注册 BeanDefinition

我们继续看 MyBatis 注册的过滤器

从这里我们可以看出 MyBatis 会把所有的类(包括接口)都扫描出来

  1. addIncludeFilter(new TypeFilter() {
  2. @Override
  3. public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
  4. return true;
  5. }
  6. });

doScan 的实现

经过 MyBatis 的过滤器和 isCandidateComponent 的判断,现在可以进行扫描了

  1. public class ClassPathMapperScanner extends ClassPathBeanDefinitionScanner {
  2. @Override
  3. public Set<BeanDefinitionHolder> doScan(String... basePackages) {
  4. // 经过 MyBatis 的过滤器和 isCandidateComponent 的判断,这里扫描出来的都是接口了
  5. Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);
  6. if (beanDefinitions.isEmpty()) {
  7. logger.warn("No MyBatis mapper was found in '" + Arrays.toString(basePackages) + "' package. Please check your configuration.");
  8. } else {
  9. processBeanDefinitions(beanDefinitions);
  10. }
  11. return beanDefinitions;
  12. }
  13. private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
  14. GenericBeanDefinition definition;
  15. for (BeanDefinitionHolder holder : beanDefinitions) {
  16. definition = (GenericBeanDefinition) holder.getBeanDefinition();
  17. if (logger.isDebugEnabled()) {
  18. logger.debug("Creating MapperFactoryBean with name '" + holder.getBeanName()
  19. + "' and '" + definition.getBeanClassName() + "' mapperInterface");
  20. }
  21. // the mapper interface is the original class of the bean
  22. // but, the actual class of the bean is MapperFactoryBean
  23. // 将 beanClass 作为 MapperFactoryBean 的构造方法参数
  24. definition.getConstructorArgumentValues().addGenericArgumentValue(definition.getBeanClassName()); // issue #59
  25. // 同时修改 beanClass 为 MapperFactoryBean
  26. definition.setBeanClass(this.mapperFactoryBean.getClass());
  27. definition.getPropertyValues().add("addToConfig", this.addToConfig);
  28. boolean explicitFactoryUsed = false;
  29. if (StringUtils.hasText(this.sqlSessionFactoryBeanName)) {
  30. definition.getPropertyValues().add("sqlSessionFactory", new RuntimeBeanReference(this.sqlSessionFactoryBeanName));
  31. explicitFactoryUsed = true;
  32. } else if (this.sqlSessionFactory != null) {
  33. definition.getPropertyValues().add("sqlSessionFactory", this.sqlSessionFactory);
  34. explicitFactoryUsed = true;
  35. }
  36. if (StringUtils.hasText(this.sqlSessionTemplateBeanName)) {
  37. if (explicitFactoryUsed) {
  38. logger.warn("Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.");
  39. }
  40. definition.getPropertyValues().add("sqlSessionTemplate", new RuntimeBeanReference(this.sqlSessionTemplateBeanName));
  41. explicitFactoryUsed = true;
  42. } else if (this.sqlSessionTemplate != null) {
  43. if (explicitFactoryUsed) {
  44. logger.warn("Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.");
  45. }
  46. definition.getPropertyValues().add("sqlSessionTemplate", this.sqlSessionTemplate);
  47. explicitFactoryUsed = true;
  48. }
  49. if (!explicitFactoryUsed) {
  50. if (logger.isDebugEnabled()) {
  51. logger.debug("Enabling autowire by type for MapperFactoryBean with name '" + holder.getBeanName() + "'.");
  52. }
  53. definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
  54. }
  55. }
  56. }
  57. }

MyBatis-Spring 2.0.X 的实现

在 MyBatis-Spring-2.0.X 版本中并没有直接通过 registerBeanDefinitions 进行扫描,而是直接向容器中注册了一个 MapperScannerConfigurer 类,我们先看下这个类

  1. public class MapperScannerConfigurer
  2. implements BeanDefinitionRegistryPostProcessor, InitializingBean, ApplicationContextAware, BeanNameAware {
  3. }

我们可以看到 MapperScannerConfigurer 这类实现了 BeanDefinitionRegistryPostProcessor 接口,那么 Spring 在启动后,会找到 MapperScannerConfigurer 类,然后执行 postProcessBeanDefinitionRegistry 方法来进行扫描和注册,其他并没有变动,只是多做了一层

  • 老版本:MapperScannerRegistrar => registerBeanDefinitions => scanner => 注册 BeanDefinition
  • 新版本:MapperScannerRegistrar => registerBeanDefinitions => 注册 MapperScannerConfigurer => Spring 调用 BeanDefinitionRegistryPostProcessor 的后置处理器 => scanner => 注册 BeanDefinition

乍一看新版本反而还多做了一步,反而麻烦了,为什么要多注册一个 MapperScannerConfigurer,然后通过后置处理器的方式来进行扫描呢?

其实这里 MyBatis 提供了一种扩展机制,也就是说,我们可以不需要写 @MapperScan 注解来进行扫描,而通过 @Bean 的方式注册 MapperScannerConfigurer 来替代 @MapperScan 注解

  1. @Bean
  2. public MapperScannerConfigurer mapperScannerConfigurer(){
  3. MapperScannerConfigurer configurer = new MapperScannerConfigurer();
  4. configurer.setBasePackage("com.sourceflag.spring.mybatis");
  5. return configurer;
  6. }