SpringBatch从入门到精通-5.2 数据源配置相关-原理【掘金日新计划】

439 阅读5分钟

\

持续创作,加速成长,6月更文活动来啦!| 掘金·日新计划

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第9天,点击查看活动详情

SpringBatch从入门到精通-1【掘金日新计划】

SpringBatch从入门到精通-2-StepScope作用域和用法【掘金日新计划】

SpringBatch从入门到精通-3-并行处理【掘金日新计划】

SpringBatch从入门到精通-3.2-并行处理-远程分区【掘金日新计划】

SpringBatch从入门到精通-3.3-并行处理-远程分区(消息聚合)【掘金日新计划】

SpringBatch从入门到精通-4 监控和指标【掘金日新计划】

SpringBatch从入门到精通-4.2 监控和指标-原理【掘金日新计划】

SpringBatch从入门到精通-5 数据源配置相关【掘金日新计划】

1. 配置BatchConfigurer生效原理

首先 使用springBatch必须在springBoot的启动类上添加@EnableBatchProcessing注解。

那么这个注解是如何生效,如何加载BatchConfigurer的呢?

看代码如下:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(BatchConfigurationSelector.class)
public @interface EnableBatchProcessing {
​
    /**
     * Indicate whether the configuration is going to be modularized into multiple application contexts. If true then
     * you should not create any @Bean Job definitions in this context, but rather supply them in separate (child)
     * contexts through an {@link ApplicationContextFactory}.
     *
     * @return boolean indicating whether the configuration is going to be
     * modularized into multiple application contexts.  Defaults to false.
     */
    boolean modular() default false;
​
}

看关键有一个@Import(BatchConfigurationSelector.class) 这个import

SpringBoot 的 @Import 用于将指定的类实例注入之Spring IOC Container中。

SpringBoot 提供了 三种使用 @Import 将 类实例注入至 Spring IOC Container中 的实例。

  • 直接注入
  • 实现 ImportBeanDefinitionRegistrar 接口 注入
  • 实现 ImportSelector 注入

这里我们看BatchConfigurationSelector 主要逻辑

public class BatchConfigurationSelector implements ImportSelector {
​
    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        Class<?> annotationType = EnableBatchProcessing.class;
        AnnotationAttributes attributes = AnnotationAttributes.fromMap(importingClassMetadata.getAnnotationAttributes(
                annotationType.getName(), false));
        Assert.notNull(attributes, String.format("@%s is not present on importing class '%s' as expected",
                annotationType.getSimpleName(), importingClassMetadata.getClassName()));
​
        String[] imports;
        //配置中未添加 modular 属性
        if (attributes.containsKey("modular") && attributes.getBoolean("modular")) {
            imports = new String[] { ModularBatchConfiguration.class.getName() };
        }
        else {
            // 那默认是走 SimpleBatchConfiguration 逻辑。
            imports = new String[] { SimpleBatchConfiguration.class.getName() };
        }
        return imports;
    }
}

可以看到主要是开启注解主要是把SimpleBatchConfiguration 这个配置个引进来了。

image-20220611230649431

这个SimpleBatchConfiguration 又继承了AbstractBatchConfiguration类

image-20220612000735957

在AbstractBatchConfiguration这里面又importScopConfiguration配置类。配置了stepScope和jobScope

还将我们在框架里使用的工具都生成bean注入到spring容器中了。

    @Bean
    public JobBuilderFactory jobBuilders() throws Exception {
        return new JobBuilderFactory(jobRepository());
    }
​
    @Bean
    public StepBuilderFactory stepBuilders() throws Exception {
        return new StepBuilderFactory(jobRepository(), transactionManager());
    }
​
    @Bean
    public abstract JobRepository jobRepository() throws Exception;
​
    @Bean
    public abstract JobLauncher jobLauncher() throws Exception;
​
    @Bean
    public abstract JobExplorer jobExplorer() throws Exception;

其中JobRepository 定义成一个抽象类,但同时也定义成bean了。具体实现在SimpleBatchConfiguration 里。(这里第一次见到这么使用,挺巧妙的,还是源码看的少啊)

这SimpleBatchConfiguration 里面有我们关注的jobRepository 还是个Bean。但里面是一个懒加载的代理bean

@Override
    @Bean
    public JobRepository jobRepository() throws Exception {
        return createLazyProxy(jobRepository, JobRepository.class);
    }

那什么是懒加载的代理呢?

private <T> T createLazyProxy(AtomicReference<T> reference, Class<T> type) {
   ProxyFactory factory = new ProxyFactory();
   factory.setTargetSource(new ReferenceTargetSource<>(reference)); //指定代理的对象
   factory.addAdvice(new PassthruAdvice()); //代理对象之后干的事情。调用方法
   factory.setInterfaces(new Class<?>[] { type });// 代理对象的接口
   @SuppressWarnings("unchecked")
   T proxy = (T) factory.getProxy();  //代理生成器。是jdk还是cglib实现
   return proxy;
}

上边最关键的是ProxyFactory,全称:org.springframework.aop.framework.ProxyFactory,spring帝国spring aop军工厂boss,职责就是生产proxy,即,代理工厂。

这个返回的是指定的T泛型即传入的JobRepository的代理对象。

这里定义的是一个懒加载的bean。即真正第一次调用的时候才会去加载真正的对象。

还有一个关键方法

image-20220611234550953

在这个方法里首先判单是否初始化过了,

如果未初始化。那么去判断应用有没有自己实现batchConfigurer

通过getConfigurer()获取一个batchConfigurer然后将batchConfigure里面的jobrepository,jobLauncher设置上。

protected BatchConfigurer getConfigurer(Collection<BatchConfigurer> configurers) throws Exception {
        if (this.configurer != null) {
            return this.configurer; //如果设置过了。那么返回设置的
        }
        if (configurers == null || configurers.isEmpty()) {//如果应用未设置
            if (dataSource == null) {//如果未设置数据源
                DefaultBatchConfigurer configurer = new DefaultBatchConfigurer();
                configurer.initialize();
                this.configurer = configurer;
                return configurer;
            } else {//如果设置了数据源,那么将数据源设置到batchConfigurer里。
                DefaultBatchConfigurer configurer = new DefaultBatchConfigurer(dataSource);
                configurer.initialize();
                this.configurer = configurer;
                return configurer;
            }
        }
        if (configurers.size() > 1) {
            throw new IllegalStateException(
                    "To use a custom BatchConfigurer the context must contain precisely one, found "
                            + configurers.size());
        }
        this.configurer = configurers.iterator().next();
        return this.configurer;
    }

从这里我们得知,系统如有含有数据源的话,且系统未设置batchConfigurer那么框架会未系统设置一个DefaultBatchConfigurer(datasource)。

系统里也不允许配置多个batchconfigurer。

那在继续看看DefaultBatchConfigurer 干了啥事

image-20220611235200061

该类是batchConfigurer接口的默认实现

image-20220611235236822

在DefaultBatchConfigurer 中创建了JobRepository 这里才是默认的实现。

protected JobRepository createJobRepository() throws Exception {
   JobRepositoryFactoryBean factory = new JobRepositoryFactoryBean();
   factory.setDataSource(dataSource);
   factory.setTransactionManager(getTransactionManager());
   factory.afterPropertiesSet();
   return factory.getObject();
}
​
public JobRepository getObject() throws Exception {
        if (proxyFactory == null) {
            afterPropertiesSet();// 创建代理工厂
        }
        //从代理工厂中获取对象
        return (JobRepository) proxyFactory.getProxy(getClass().getClassLoader());
    }
    
@PostConstruct
    public void initialize() {
        try {
            // 这里兼容了 datasource 为空的形式。
            if(dataSource == null) {
                logger.warn("No datasource was provided...using a Map based JobRepository");
​
                if(getTransactionManager() == null) {
                    logger.warn("No transaction manager was provided, using a ResourcelessTransactionManager");
                    this.transactionManager = new ResourcelessTransactionManager();
                }
​
                MapJobRepositoryFactoryBean jobRepositoryFactory = new MapJobRepositoryFactoryBean(getTransactionManager());
                jobRepositoryFactory.afterPropertiesSet();
                this.jobRepository = jobRepositoryFactory.getObject();
​
                MapJobExplorerFactoryBean jobExplorerFactory = new MapJobExplorerFactoryBean(jobRepositoryFactory);
                jobExplorerFactory.afterPropertiesSet();
                this.jobExplorer = jobExplorerFactory.getObject();
            } else { //datasource 非空的情况
                this.jobRepository = createJobRepository();
                this.jobExplorer = createJobExplorer();
            }
​
            this.jobLauncher = createJobLauncher();
        } catch (Exception e) {
            throw new BatchConfigurationException(e);
        }
    }

在从代理工厂里获取对象的时候,如果代理工厂为空,那么需要在创建个代理工厂。

private void initializeProxy() throws Exception {
   if (proxyFactory == null) {
      proxyFactory = new ProxyFactory();
      // 在创建代理工厂的时候,对create* 和getLastJobExecution* 方法限定了传播属性和隔离级别
      TransactionInterceptor advice = new TransactionInterceptor(transactionManager,
            PropertiesConverter.stringToProperties("create*=PROPAGATION_REQUIRES_NEW,"
                  + isolationLevelForCreate + "\ngetLastJobExecution*=PROPAGATION_REQUIRES_NEW,"
                  + isolationLevelForCreate + "\n*=PROPAGATION_REQUIRED"));
      if (validateTransactionState) {
         DefaultPointcutAdvisor advisor = new DefaultPointcutAdvisor(new MethodInterceptor() {
            @Override
            public Object invoke(MethodInvocation invocation) throws Throwable {
               if (TransactionSynchronizationManager.isActualTransactionActive()) {
                  throw new IllegalStateException(
                        "Existing transaction detected in JobRepository. "
                              + "Please fix this and try again (e.g. remove @Transactional annotations from client).");
               }
               return invocation.proceed();
            }
         });
         NameMatchMethodPointcut pointcut = new NameMatchMethodPointcut();
         pointcut.addMethodName("create*");
         advisor.setPointcut(pointcut);
         proxyFactory.addAdvisor(advisor);
      }
      proxyFactory.addAdvice(advice);
      proxyFactory.setProxyTargetClass(false);
      proxyFactory.addInterface(JobRepository.class);
      proxyFactory.setTarget(getTarget());
   }
}

这里和上节里面的设置隔离级别就对应上了。isolationLevelForCreate 就是在batchconfigurer里设置的。在这里用到了。

2.配置数据表生效原理

我们在application.properties中配置了

platform=mysql
spring.batch.schema=classpath:org/springframework/batch/core/schema-@@platform@@.sql
spring.batch.initialize-schema=ALWAYS
spring.batch.table-prefix=batch_

看是如何生效的。这个我们在springBatch包下面确实没看到相关的。配置是在SpringBootautoconfigure包下面的。

image-20220612002210565

配置主要是BatchProperties.java文件中

image-20220612002256407

主要配置是schema

默认的是 classpath:org/springframework/batch/core/schema-@@platform@@.sql

可以看到在源码core包下,支持了db2,derby,h2,mysql,hsqldb,prostgresql,sqlite,sqlf,sqlserver,sybase,oracle10g我们可以看到是支持了很多的数据库模式

image-20220612002546656

initialize-schema 主要支持

ALWAYS,  //总是
EMBEDDED, //内置
NEVER; //从不

那现在配置都已经配置好了。但这些配置又是被谁引用的呢?

这个后边在写个自动BatchAutoConfiguration的。

先直接看这些配置被谁使用的 。主要是被同包下的BatchDataSourceInitializer.java

主要内容在AbstractDataSourceInitializer#initialize() 里看内容

@PostConstruct
protected void initialize() {
    if (this.isEnabled()) {
        ResourceDatabasePopulator populator = new ResourceDatabasePopulator();
        String schemaLocation = this.getSchemaLocation();
        if (schemaLocation.contains("@@platform@@")) {
            String platform = this.getDatabaseName();
            //替换查找的数据库类型对应的sql脚本
            schemaLocation = schemaLocation.replace("@@platform@@", platform);
        }

        populator.addScript(this.resourceLoader.getResource(schemaLocation));
        populator.setContinueOnError(true);
        this.customize(populator);
        // 这里是执行脚本 核心关键点
        DatabasePopulatorUtils.execute(populator, this.dataSource);
    }
}

代码位置: github.com/jackssybin/…