Spring核心源码深度解析(七)Spring-Mybatis核心思想

时间:2021-7-3 作者:qvyue

Spring-Mybatis核心思想

经过漫长的学习,我们总算对Spring IOC 和DI的整体流程有了一定的认识, 可能读者读完之后并不觉得Spring的设计有多牛逼,甚至觉得Spring的设计过于复杂,那么本章的内容将会让大家大开眼界,震撼大家如此的想法,但前提是对笔者的前面所提及的内容、Spring的主干一定要熟悉。

Spring如何实现对外拓展

在Spring源码的第五章,笔者提到了Import注解,不知大家是否还有印象,如果没有印象请读者自行回顾IOC的知识,因为笔者这里将重点介绍Spring提供拓展的接口ImportSelector、ImportBeanDefinitionRegistrar、BeanDefinitionRegistryPostProcessor,当然读者可能会提到BeanFactoryPostProcessor、BeanPostProcessor这两个接口,确实Spring在内部也大量使用了这两个接口,但笔者这里为什么没有提出这两个接口,我们看下面的实例

​public class UserBeanPostProcessor implements BeanFactoryPostProcessor,BeanPostProcessor {
​
  @Override
  public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
    GenericBeanDefinition bd= (GenericBeanDefinition) beanFactory.getBeanDefinition("user1");
    bd.getConstructorArgumentValues().addGenericArgumentValue("1","dsadasdsa");
​
  }
​
  @Override
  public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
    return null;
  }
​
  @Override
  public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
    return null;
  }
}

可以看到我们只能获取BeanFactory或者BeanName,也就是说我们只能拿到我们所需要的对象进行修改,而我所提出的ImportSelector、ImportBeanDefinitionRegistrar、BeanDefinitionRegistryPostProcessor这三个接口,他们都可以直接对工厂添加BeanDefination,我们对Spring IOC 源码熟悉的读者肯定知道,创建了BeanDefination就等于创建了Bean,我们看实例:

public class
MyImportBeanDefinitionRegistrarimplements ImportBeanDefinitionRegistrar,BeanDefinitionRegistryPostProcessor { 
  @Override  
  public void  registerBeanDefinitions(AnnotationMetadata importingClassMetadata,BeanDefinitionRegistry registry) {   
  } 
  @Override   public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throwsBeansException { 
    }  
  @Override   public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throwsBeansException {   
  }
 }

可以看到我们通过实现这些接口就可以获取registry,这个registry能干什么呢,那就是注册BeanDefination,我们看下图:

Spring核心源码深度解析(七)Spring-Mybatis核心思想
image

,好了现在我们对Spring拓展Bean的一些重要接口做了简要的分析
,现在我们回到我们的主题。

Spring-Mybatis

首先我们思考一下Mybatis到底干了什么事情,无非就是通过我们创建的接口进行代理,创建代理对象,然后通过代理对象绑定表字段和对象属性、方法名和SQL语句等,然后执行sql语句返回对象。了解Mybatis的核心思想后,Spring-Mybatis就很简单了,无非就是将我们的代理对象放进Spring进行管理,这又回到上文我所提出的Spring如何实现对外拓展的知识了,那么现在我们已经知道先用代理的方式代理通过接口创建对象,然后在用Spring扩展的技术将代理对象放进Spring里,是不是就完成了呢?好像是这么回事,那么我们开始实操吧:

public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar{
​
  @Override
  public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
    // 执行包扫描,获取所需要的Mapper 这一步省略掉
  UserMapper userMapper = (UserMapper) Proxy.newProxyInstance(UserMapper.class.getClassLoader(), new Class[]{UserMapper.class},new InvocationHandler() {
      @Override
      public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        return null;
      }
    });
​
    BeanDefinitionBuilder beanDefinitionBuilder=BeanDefinitionBuilder.genericBeanDefinition(userMapper.getClass());
    GenericBeanDefinition bd= (GenericBeanDefinition) beanDefinitionBuilder.getBeanDefinition();
    registry.registerBeanDefinition("userMapper",bd);
  }
}
Spring核心源码深度解析(七)Spring-Mybatis核心思想
image

抛出异常,可以看到代理类似乎不能直接放入Spring,道理很简答,我们再看下面的输出

Spring核心源码深度解析(七)Spring-Mybatis核心思想
image

我在代码里打印了两种获取class的方式,可以看到是不一样的,因此我们创建BD的时候传入的参数是class com.sun.proxy.$Proxy12,如果我们改成接口形式的class

Spring核心源码深度解析(七)Spring-Mybatis核心思想
image

然后再次执行,可以发现还是无法创建

Spring核心源码深度解析(七)Spring-Mybatis核心思想
image

因为我们交给Spring的是接口,并没有对该接口进行实现,因此Spring并不知道如何创建对象,所以总的来说就是我们不能按照这种思路去将我们的代理对象交给Spring管理,那Mybatis是如何解决这种问题的呢?不知道读者时候了解过FactoryBean接口,我们点开此接口

public interface FactoryBean {
  @Nullable
  T getObject() throws Exception;
 
  @Nullable
  Class> getObjectType();
  
  default boolean isSingleton() {
    return true;
  }
​
}

我们可以看到FactoryBean有三个方法,返回对象、返回类型、是否是单例对象,我们重点关注前面两个方法FactoryBean提供了返回对象和类型的方式,笔者结合代理的方式简单梳理一下,希望读者能够逐渐明白我上文提出的问题,以及解决方案

public class MappperFactoryBean implements FactoryBean, InvocationHandler {
​
  private Class clazz;
​
  public MapperFactoryBean(Class clazz){
    this.clazz=clazz;
  }
​
  @Override
  public Object getObject() throws Exception {
    Class[] clazzs=new Class[]{clazz};
    return Proxy.newProxyInstance(this.getClass().getClassLoader(),clazzs,this);
  }
​
  @Override
  public Class> getObjectType() {
    return clazz;
  }
​
  @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    Annotation annotation=method.getAnnotation(Select.class);
    System.out.println(((Select) annotation).value());
    return clazz;
  }
}

这里我们创建了一个实现了FactoryBean, InvocationHandler的类,然后将 getObject() 改变成返回一个代理对象,getObjectType()返回我们的接口类型,然后我们在对MyImportBeanDefinitionRegistrar进行改造:

public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar{
​
  @Override
  public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) throws IllegalAccessException, InstantiationException {
    // 执行包扫描,获取所需要的Mapper 这一步省略掉
    BeanDefinitionBuilder beanDefinitionBuilder=BeanDefinitionBuilder.genericBeanDefinition(MapperFactoryBean.class);
    GenericBeanDefinition bd= (GenericBeanDefinition) beanDefinitionBuilder.getBeanDefinition();
    bd.getConstructorArgumentValues().addGenericArgumentValue(UserMapper.class);
    // 改变自动装配的模式
    /*bd.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);*/
    registry.registerBeanDefinition("userMapper",bd);
  }
}

在改造测试类

  public static void main(String[] args) {
    AnnotationConfigApplicationContext annotationConfigApplicationContext=
        new AnnotationConfigApplicationContext(AppConfig.class);
​
    UserMapper userMapper = (UserMapper) annotationConfigApplicationContext.getBean("userMapper");
    userMapper.selectUserById();
  }

查看执行结果

Spring核心源码深度解析(七)Spring-Mybatis核心思想
image

这里我们已经执行了代理方法,并获取了我们注解的内容,这里可能读者有一些疑问,为什么我们实现了FactoryBean就能够成功,笔者简单描述下吧,如果我们的一个类实现了FactoryBean,那么Spring调用我们的getBean()也就是我们所说的依赖查找就不会走单例对象的普通路线,而是回调我们实现了FactoryBean的getObject() ,getObjectType()方法,这样设置什么好处呢?个人认为这样设置其实是为了对象的灵活性,我们的对象不一定类型和Object是同一种东西,就好比我们的代理对象,好了多余的就不在描述了,我们简单看看Spring-Mybatis的源码部分吧

Spring-Mybatis源码

在对笔者上文有所了解后,其实Spring-Mybatis源码基本就大差不差了,但有一点或许会有所区别,那就是属性设置的问题,在Spring-Mybatis源码中不同的版本可能采用不同的方式实现,笔者上文采用的是构造器方式进行属性注入,而Spring-Mybatis源码也会采用set方式注入,我们回到IDEA

然后我们直接进入@MapperScan注解:

Spring核心源码深度解析(七)Spring-Mybatis核心思想
image

可以看到在Mybatis它使用到了我们在案例中写到的@Import注解,我们跟踪MapperScannerRegistrar这个类发现它实现了ImportBeanDefinitionRegistrar,我们找到registerBeanDefinitions方法

public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
​
    AnnotationAttributes annoAttrs = AnnotationAttributes.fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName()));
    ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
​
    if (resourceLoader != null) { // this check is needed in Spring 3.1
      scanner.setResourceLoader(resourceLoader);
    }
​
    Class extends Annotation> annotationClass = annoAttrs.getClass("annotationClass");
    if (!Annotation.class.equals(annotationClass)) {
      scanner.setAnnotationClass(annotationClass);
    }
​
    Class> markerInterface = annoAttrs.getClass("markerInterface");
    if (!Class.class.equals(markerInterface)) {
      scanner.setMarkerInterface(markerInterface);
    }
​
    Class extends BeanNameGenerator> generatorClass = annoAttrs.getClass("nameGenerator");
    if (!BeanNameGenerator.class.equals(generatorClass)) {
      scanner.setBeanNameGenerator(BeanUtils.instantiateClass(generatorClass));
    }
​
    scanner.setSqlSessionTemplateBeanName(annoAttrs.getString("sqlSessionTemplateRef"));
    scanner.setSqlSessionFactoryBeanName(annoAttrs.getString("sqlSessionFactoryRef"));
​
    List basePackages = new ArrayList();
    for (String pkg : annoAttrs.getStringArray("value")) {
      if (StringUtils.hasText(pkg)) {
        basePackages.add(pkg);
      }
    }
    for (String pkg : annoAttrs.getStringArray("basePackages")) {
      if (StringUtils.hasText(pkg)) {
        basePackages.add(pkg);
      }
    }
    for (Class> clazz : annoAttrs.getClassArray("basePackageClasses")) {
      basePackages.add(ClassUtils.getPackageName(clazz));
    }
    scanner.registerFilters();
    scanner.doScan(StringUtils.toStringArray(basePackages));
  }

前面都是一些准备工作,我们直接看到最后一行 scanner.doScan(StringUtils.toStringArray(basePackages));点进去:

@Override
  public Set doScan(String... basePackages) {
    Set beanDefinitions = super.doScan(basePackages);
​
    if (beanDefinitions.isEmpty()) {
      logger.warn("No MyBatis mapper was found in '" + Arrays.toString(basePackages) + "' package. Please check your configuration.");
    } else {
      for (BeanDefinitionHolder holder : beanDefinitions) {
        GenericBeanDefinition definition = (GenericBeanDefinition) holder.getBeanDefinition();
​
        if (logger.isDebugEnabled()) {
          logger.debug("Creating MapperFactoryBean with name '" + holder.getBeanName() 
              + "' and '" + definition.getBeanClassName() + "' mapperInterface");
        }
​
        // the mapper interface is the original class of the bean
        // but, the actual class of the bean is MapperFactoryBean
        definition.getPropertyValues().add("mapperInterface", definition.getBeanClassName());
        definition.setBeanClass(MapperFactoryBean.class);
​
        definition.getPropertyValues().add("addToConfig", this.addToConfig);
​
        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;
        }
​
        if (StringUtils.hasText(this.sqlSessionTemplateBeanName)) {
          if (explicitFactoryUsed) {
            logger.warn("Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.");
          }
          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.getPropertyValues().add("sqlSessionTemplate", this.sqlSessionTemplate);
          explicitFactoryUsed = true;
        }
​
        if (!explicitFactoryUsed) {
          if (logger.isDebugEnabled()) {
            logger.debug("Enabling autowire by type for MapperFactoryBean with name '" + holder.getBeanName() + "'.");
          }
          definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
        }
      }
    }
​
    return beanDefinitions;
  }

简单解释下他的代码思路,获取扫描出来的beanDefinitions,然后对beanDefinition属性设置,注意这个beanDefinition,其实是一个MapperFactoryBean,在上面的代码中有这么一句代码definition.setBeanClass(MapperFactoryBean.class);意思就是将beanDefinition的class设置为MapperFactoryBean,所以Mybatis对将所有的创建的代理对象都转换成MapperFactoryBean,而这个MapperFactoryBean是什么呢?我们点开这个类

public class MapperFactoryBean extends SqlSessionDaoSupport implements FactoryBean

它继承了SqlSessionDaoSupport 还实现了FactoryBean,SqlSessionDaoSupport 就简单说两句,我们一直跟踪SqlSessionDaoSupport,

Spring核心源码深度解析(七)Spring-Mybatis核心思想
image

会找到InitializingBean,InitializingBean是干什么的可能大家比较陌生,@PostConstruct大家都不陌生吧,其实他们两个都差不多的,那Mybatis为什么实现了它呢?其实目的就是在将Mapper交给Spring后自己还有其他的操作吧,比如绑定xml的sql到方法上,比如数据库和对象之前的关系映射等等就不多说了FactoryBean大家应该有印象吧,我已在上文对它做了一些铺垫,至于作用就不多说了,我们继续回到代码可以看到这有点区别 definition.getPropertyValues().add(“mapperInterface”, definition.getBeanClassName());这里是并非采用的构造器方式属性注入,我们可以看到整个MapperFactoryBean都没有构造器去传入我们的被代理接口,那他是怎么实现属性注入的呢?我看可以找到一个setXX的方法

Spring核心源码深度解析(七)Spring-Mybatis核心思想
image

说明我们的Mybatis是采用的set方式去实现属性注入的,有人可能有疑问了,为什么不用@Autowired的方式呢?大家可以思考一下,我们的需要注入的属性是不是class类型,如果是class类型,我们对class注入有意义吗?就好比每一个类都是Object类型,我们却对Object注入类型一样,毫无意义,而且Spring在属性注入已经排除了这种类型,所以你就算想@Autowired也没有办法@Autowired,好了说了那么多,回到正轨,既然我们确认需要setXXX的方式,那么我们怎么通过代码的方式改变它的注入类型呢?不知大家是否还记得AUTOWIRE_MODE,自动装配模式,记得我在IOC的文章提到过Spring对属性注入的模式是NO,为什么时候,Spring认为如果你对属性没有加@Autowired,那么就不会对属性进行赋值,为什么这么做,其实也很简单,因为Spring并不知道你的属性需不需赋值,好了,我看到AbstractBeanDefinition这个类

public abstract class AbstractBeanDefinition extends BeanMetadataAttributeAccessor
    implements BeanDefinition, Cloneable {
​
  /**
   * Constant that indicates no external autowiring at all.
   * @see #setAutowireMode
   */
  public static final int AUTOWIRE_NO = AutowireCapableBeanFactory.AUTOWIRE_NO;
​
  /**
   * Constant that indicates autowiring bean properties by name.
   * @see #setAutowireMode
   */
  public static final int AUTOWIRE_BY_NAME = AutowireCapableBeanFactory.AUTOWIRE_BY_NAME;
​
  /**
   * Constant that indicates autowiring bean properties by type.
   * @see #setAutowireMode
   */
  public static final int AUTOWIRE_BY_TYPE = AutowireCapableBeanFactory.AUTOWIRE_BY_TYPE;
​
  /**
   * Constant that indicates autowiring a constructor.
   * @see #setAutowireMode
   */
  public static final int AUTOWIRE_CONSTRUCTOR = AutowireCapableBeanFactory.AUTOWIRE_CONSTRUCTOR;
​
  
  @Deprecated
  public static final int AUTOWIRE_AUTODETECT = AutowireCapableBeanFactory.AUTOWIRE_AUTODETECT;
​
  private int autowireMode = AUTOWIRE_NO;

可以发现Spring确实默认是 AUTOWIRE_NO;那么他还有其他几种方式,其中有AUTOWIRE_BY_TYPE,AUTOWIRE_BY_NAME 这么两个模式,这两种模式都可以用来改变Spring注入的方式,改成SetXXX的方式,我们写个实例:首先改造一下之前的MyImportBeanDefinitionRegistrar这个类

Spring核心源码深度解析(七)Spring-Mybatis核心思想
image

其次我们再改变之前的MapperFactoryBean

Spring核心源码深度解析(七)Spring-Mybatis核心思想
image

然后debug一下将代码停在setClazz上面

Spring核心源码深度解析(七)Spring-Mybatis核心思想
image

可以看到笔者所讲的已经得到验证,是不是觉得Spring-Mybatis设计的十分优雅啊,好了,对Spring-Mybatis的核心思想,笔者已经对其描述完了,相信真正理解到的读者一定大有收获,那么本章内容就结束了,咱们下个世纪见。

声明:本文内容由互联网用户自发贡献自行上传,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任。如果您发现有涉嫌版权的内容,欢迎发送邮件至:qvyue@qq.com 进行举报,并提供相关证据,工作人员会在5个工作日内联系你,一经查实,本站将立刻删除涉嫌侵权内容。