\
持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第9天,点击查看活动详情。
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 这个配置个引进来了。
这个SimpleBatchConfiguration 又继承了AbstractBatchConfiguration类
在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。即真正第一次调用的时候才会去加载真正的对象。
还有一个关键方法
在这个方法里首先判单是否初始化过了,
如果未初始化。那么去判断应用有没有自己实现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 干了啥事
该类是batchConfigurer接口的默认实现
在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包下面的。
配置主要是BatchProperties.java文件中
主要配置是schema
默认的是 classpath:org/springframework/batch/core/schema-@@platform@@.sql
可以看到在源码core包下,支持了db2,derby,h2,mysql,hsqldb,prostgresql,sqlite,sqlf,sqlserver,sybase,oracle10g我们可以看到是支持了很多的数据库模式
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/…