天天看點

Spring整合Mybatis源碼分析(mybatis-spring-2.0.6)

示例
  • 首先引入Mybatis的使用示例,其中兩個關鍵變量:SqlSessionFactory ,SqlSession
InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession sqlSession = sqlSessionFactory.openSession();
Mapper1 mapper = sqlSession.getMapper(Mapper1.class);
String result = mapper.select1();           
  • 問題引入,以下代碼中(系統中存在Mapper1,Mapper2,Mapper3接口),如果直接運作main函數,背景會報錯,mapper注入錯誤,此時可以引入FactoryBean機制,生成mapper的代理對象:
@Service
public class MybatisAndSpringService {

    @Autowired
    private Mapper1 mapper1;
    
    // ......多個Mapper接口(省略)

    @Autowired
    private Mappern mappern;

    public void test() {
        String result1 = mapper1.select1();
        System.out.println("result1 = " + result1);
        String result2 = mapper2.select2();
        System.out.println("result2 = " + result2);
        //……
    }

}

// 主函數
public static void main(String[] args) {
    AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext();
    // 注冊主配置類
    ac.register(MyConfig.class);

    // 重新整理容器
    ac.refresh();
    MybatisAndSpringService service = ac.getBean("mybatisAndSpringService", MybatisAndSpringService.class);
    service.test();
}           
  • 引入FactoryBean接口,此時如果直接運作main函數依然會報錯,因為自定義FactoryBean不在Spring容器中,是以需要采用聲明式程式設計的方式,修改main函數:
public class ArtistFactoryBean implements FactoryBean {

    @Override
    public Object getObject() throws Exception {
        // JDK動态代理
        Object obj = Proxy.newProxyInstance(this.getClass().getClassLoader(), new Class<?>[]{Mapper1.class}, new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                System.out.println("目前調用的方法名:" + method.getName());
                return null;
            }
        });
        return obj;

        // Mybatis的代理邏輯
        // return sqlSession.getMapper(mapperClazz);
    }

    @Override
    public Class<?> getObjectType() {
        return Mapper1.class;
    }
}

// main函數
public static void main(String[] args) {
    AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext();
    // 注冊主配置類
    ac.register(MyConfig.class);
    
    AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.genericBeanDefinition().getBeanDefinition();
    beanDefinition.setBeanClass(ArtistFactoryBean.class);
    ac.registerBeanDefinition("facotrybean", beanDefinition);

    // 重新整理容器
    ac.refresh();
    MybatisAndSpringService service = ac.getBean("mybatisAndSpringService", MybatisAndSpringService.class);
    service.test();
}           
  • 到了這一步,直接運作是沒有問題的,繼續思考:FactoryBean的getObject方法,生成代理對象的接口是在代碼中寫死的,是以還需要通過構造函數傳參的方式修改
// 修改FactoryBean
public class ArtistFactoryBean implements FactoryBean {

    private Class<?> mapperClazz;
    
    public ArtistFactoryBean(Class<?> mapperClazz) {
        this.mapperClazz = mapperClazz;
    }

    @Override
    public Object getObject() throws Exception {
        // JDK動态代理
        Object obj = Proxy.newProxyInstance(this.getClass().getClassLoader(), new Class<?>[]{mapperClazz}, new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                System.out.println("目前調用的方法名:" + method.getName());
                return null;
            }
        });
        return obj;

        // Mybatis的代理邏輯
        // return sqlSession.getMapper(mapperClazz);
    }

    @Override
    public Class<?> getObjectType() {
        return mapperClazz;
    }
}

// 同時在main函數中也要修改
public static void main(String[] args) {
    AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext();
    // 注冊主配置類
    ac.register(MyConfig.class);
    
    AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.genericBeanDefinition().getBeanDefinition();
    beanDefinition.setBeanClass(ArtistFactoryBean.class);
    // 新增邏輯
    beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(Mapper1.class);
    ac.registerBeanDefinition("facotrybean", beanDefinition);

    // 重新整理容器
    ac.refresh();
    MybatisAndSpringService service = ac.getBean("mybatisAndSpringService", MybatisAndSpringService.class);
    service.test();
}           
  • 經過上述修改之後,FactoryBean中mapper接口的傳入方式得到了解決,但是,如果此時要添加其他的mapper,比如:Mapper2,Mapper3……,就會出現下面的編碼:
public static void main(String[] args) {
    AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext();
    // 注冊主配置類
    ac.register(MyConfig.class);
    
    // Mapper1
    AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.genericBeanDefinition().getBeanDefinition();
    beanDefinition.setBeanClass(ArtistFactoryBean.class);
    // 新增邏輯
    beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(Mapper1.class);
    ac.registerBeanDefinition("facotrybean", beanDefinition);
    
    // ……很多Mapper(省略)
    
    // Mappern
    AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.genericBeanDefinition().getBeanDefinition();
    beanDefinition.setBeanClass(ArtistFactoryBean.class);
    // 新增邏輯
    beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(Mappern.class);
    ac.registerBeanDefinition("facotrybean", beanDefinition);

    // 重新整理容器
    ac.refresh();
    MybatisAndSpringService service = ac.getBean("mybatisAndSpringService", MybatisAndSpringService.class);
    service.test();
}           
  • 顯然這種編碼方式非常不優雅,那麼接下來可以考慮包掃描的方式,把Mapper接口掃描出來儲存到一個集合中,然後周遊集合,就可以替換掉這麼一大片備援的代碼,可以利用Spring提供的ImportBeanDefinitionRegistrar接口,實作它的registerBeanDefinitions方法
  • 同時結合Spring的類掃描器ClassPathBeanDefinitionScanner,但是Spring隻掃描類,而這裡我們隻掃描接口,是以需要自定義一個類掃描器,需要修改isCandidateComponent的邏輯,隻傳回接口,否則Mapper接口無法掃描到
  • 重寫doScan邏輯,因為我們最終需要的是Mapper接口的代理對象,是以需要把之前定義的FactoryBean設定進去
  • 需要注意,在配置類上需要加入注解:@Import(ArtistImportBeanDefinitionRegistrar.class),使Bean定義注冊器成為Spring容器中的Bean
//自定義類掃描器
public class ArtistScanner extends ClassPathBeanDefinitionScanner {

    public ArtistScanner(BeanDefinitionRegistry registry) {
        super(registry);
    }

    // 需要掃描到接口
    @Override
    protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
        return beanDefinition.getMetadata().isInterface();
    }

    @Override
    protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
        Set<BeanDefinitionHolder> holders = super.doScan(basePackages);

        for (BeanDefinitionHolder holder : holders) {
            GenericBeanDefinition beanDefinition = (GenericBeanDefinition) holder.getBeanDefinition();
            beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(holder.getBeanDefinition().getBeanClassName());
            // 為了擷取被掃描到的Mapper接口的代理對象
            beanDefinition.setBeanClass(ArtistFactoryBean.class);
        }
        return holders;
    }
}


// 批量注冊Bean定義
public class ArtistImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {

    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        String scanPath = "com.xxxx.mapper";
        // 此時要自定義包掃描器,因為Spring的包掃描器隻掃描類,不掃描接口;而這裡要掃描接口
        ArtistScanner scanner = new ArtistScanner(registry);
        // match方法傳回true,保證接口能被掃描到
        scanner.addIncludeFilter(new TypeFilter() {
            @Override
            public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
                return true;
            }
        });
        scanner.scan(scanPath);
    }
}
           
  • 經過上述的修改,就可以把main方法中備援的代碼替換掉,到此還有一個最大的問題,getObject中生成代理對象是手工操作的,而代理對象應該由Mybatis架構生成,對應到文章開頭提到的SqlSessionFactory
// 修改後的FactoryBean
public class ArtistFactoryBean implements FactoryBean {

    private Class<?> mapperClazz;
    public ArtistFactoryBean(Class<?> mapperClazz) {
        this.mapperClazz = mapperClazz;
    }
    
    @Autowired
    public void setSqlSession(SqlSessionFactory sqlSessionFactory) {
        this.sqlSession = sqlSessionFactory.openSession();
    }

    @Override
    public Object getObject() throws Exception {
        // Mybatis的代理邏輯
        return sqlSession.getMapper(mapperClazz);
    }

    @Override
    public Class<?> getObjectType() {
        return mapperClazz;
    }
}           
  • 可以看到,通過setter注入SqlSessionFactory對象,擷取到SqlSession
  • 在配置類中也要添加SqlSessionFactory的Bean對象
@Configuration
@ComponentScan("com.mybatisandspring")
public class MyConfig {

    @Bean
    public SqlSessionFactory sqlSessionFactory() throws IOException {
        InputStream inputStream = Resources.getResourceAsStream("mybatis-spring-config.xml");
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        return sqlSessionFactory;
    }
}           
  • 繼續看,我們的掃描路徑是在代碼中寫死的,可以進一步作修改
  • 自定義一個注解,把mapper接口所在的包路徑作為value,傳入注解,然後在registerBeanDefinitions方法中,通過參數AnnotationMetadata擷取該注解的value值,這樣就可以靈活地配置了。
  • 首先自定義注解(這裡把配置類中的@Import移到此處,為了代碼的可讀性,這裡不影響邏輯的描述)
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(ArtistImportBeanDefinitionRegistrar.class)
public @interface ArtistMapperScan {
    String value();
}           
  • 接着在配置類上加上自定義注解,最終的配置類如下:
@Configuration
@ComponentScan("com.mybatisandspring")
@ArtistMapperScan("com.mybatisandspring.mapper")
public class MyConfig {

    @Bean
    public SqlSessionFactory sqlSessionFactory() throws IOException {
        InputStream inputStream = Resources.getResourceAsStream("mybatis-spring-config.xml");
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        return sqlSessionFactory;
    }
}           
  • 到此為止,基本上mybatis-spring架構的核心原理基本都展現出來了,概括地說:就是把Mybatis生成的代理對象注入到Spring容器中,mybatis-spring.jar包起到了整合的橋梁的作用
  • 下面對比自己寫的類和mybatis-spring.jar中的核心類
mybatis-spring包的整合源碼分析
  • 對比Mapper掃描注解:
// @MapperScan注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(MapperScannerRegistrar.class)
@Repeatable(MapperScans.class)
public @interface MapperScan {
    
}

// 自定義掃描注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(ArtistImportBeanDefinitionRegistrar.class)
public @interface ArtistMapperScan {
    String value();
}
           
  • MapperScannerRegistrar的registerBeanDefinitions方法會先解析MapperScan注解,然後再注冊Bean定義
void registerBeanDefinitions(AnnotationMetadata annoMeta, AnnotationAttributes annoAttrs,
      BeanDefinitionRegistry registry, String beanName) {

    // MapperScannerConfigurer執行掃描邏輯
    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);
    }

    Class<?> markerInterface = annoAttrs.getClass("markerInterface");
    if (!Class.class.equals(markerInterface)) {
      builder.addPropertyValue("markerInterface", markerInterface);
    }

    Class<? extends BeanNameGenerator> generatorClass = annoAttrs.getClass("nameGenerator");
    if (!BeanNameGenerator.class.equals(generatorClass)) {
      builder.addPropertyValue("nameGenerator", BeanUtils.instantiateClass(generatorClass));
    }

    Class<? extends MapperFactoryBean> mapperFactoryBeanClass = annoAttrs.getClass("factoryBean");
    if (!MapperFactoryBean.class.equals(mapperFactoryBeanClass)) {
      builder.addPropertyValue("mapperFactoryBeanClass", mapperFactoryBeanClass);
    }

    String sqlSessionTemplateRef = annoAttrs.getString("sqlSessionTemplateRef");
    if (StringUtils.hasText(sqlSessionTemplateRef)) {
      builder.addPropertyValue("sqlSessionTemplateBeanName", annoAttrs.getString("sqlSessionTemplateRef"));
    }

    String sqlSessionFactoryRef = annoAttrs.getString("sqlSessionFactoryRef");
    if (StringUtils.hasText(sqlSessionFactoryRef)) {
      builder.addPropertyValue("sqlSessionFactoryBeanName", annoAttrs.getString("sqlSessionFactoryRef"));
    }

    List<String> basePackages = new ArrayList<>();
    basePackages.addAll(
        Arrays.stream(annoAttrs.getStringArray("value")).filter(StringUtils::hasText).collect(Collectors.toList()));

    basePackages.addAll(Arrays.stream(annoAttrs.getStringArray("basePackages")).filter(StringUtils::hasText)
        .collect(Collectors.toList()));

    basePackages.addAll(Arrays.stream(annoAttrs.getClassArray("basePackageClasses")).map(ClassUtils::getPackageName)
        .collect(Collectors.toList()));

    if (basePackages.isEmpty()) {
      basePackages.add(getDefaultBasePackage(annoMeta));
    }

    String lazyInitialization = annoAttrs.getString("lazyInitialization");
    if (StringUtils.hasText(lazyInitialization)) {
      builder.addPropertyValue("lazyInitialization", lazyInitialization);
    }

    String defaultScope = annoAttrs.getString("defaultScope");
    if (!AbstractBeanDefinition.SCOPE_DEFAULT.equals(defaultScope)) {
      builder.addPropertyValue("defaultScope", defaultScope);
    }

    builder.addPropertyValue("basePackage", StringUtils.collectionToCommaDelimitedString(basePackages));

    // 注冊Bean定義
    registry.registerBeanDefinition(beanName, builder.getBeanDefinition());

}           
  • 在MapperScannerConfigurer中有一個屬性mapperFactoryBeanClass,類型是MapperFactoryBean,它的getObject方法就是傳回Mapper接口的代理對象,mapperInterface也是作為被掃描的屬性傳入
@Override
public T getObject() throws Exception {
    return getSqlSession().getMapper(this.mapperInterface);
}           
  • 總結:
    • MapperFactoryBean完成的功能是生成Mapper接口的代理對象
    • MapperScannerConfigurer掃描Mapper接口
    • MapperScannerRegistrar完成的功能是把掃描到的Mapper接口的代理對象轉為Bean定義,存入Spring容器
    • SqlSessionFactory負責解析配置的mybatis配置檔案,包括資料源、類型别名、映射檔案的掃描路徑等等資訊
上一篇: ECS使用體驗
下一篇: ECS使用體驗