SpringBoot 项目引入 Quartz 包报错源码分析
1.1 分析
- Quartz 的自动配置:
QuartzAutoConfiguration#quartzDataSourceInitializer
当引入 Quartz 的依赖之后 Quartz 的自动配置生效
@Configuration
// 如果这些 class 存在(即引入了 Quartz 的依赖)再自动装配 Quartz
@ConditionalOnClass({ Scheduler.class, SchedulerFactoryBean.class, PlatformTransactionManager.class })
@EnableConfigurationProperties(QuartzProperties.class)
// 指定该自动配置类 在 DataSourceAutoConfiguration 自动配置之后(因为需要 DataSource)(如果有 jpa 也在 HibernateJpaAutoConfiguration 之后)
@AutoConfigureAfter({ DataSourceAutoConfiguration.class, HibernateJpaAutoConfiguration.class })
public class QuartzAutoConfiguration {
private DataSource getDataSource(DataSource dataSource, ObjectProvider<DataSource> quartzDataSource) {
DataSource dataSourceIfAvailable = quartzDataSource.getIfAvailable(); // 没有配置 Quartz 的 jobStoreType 为 JobStoreType.JDBC 默认为 null
return (dataSourceIfAvailable != null) ? dataSourceIfAvailable : dataSource;
}
@Bean
@ConditionalOnMissingBean
public QuartzDataSourceInitializer quartzDataSourceInitializer(DataSource dataSource,
@QuartzDataSource ObjectProvider<DataSource> quartzDataSource, ResourceLoader resourceLoader,
QuartzProperties properties) {
// 获取 dataSource. dataSource 为我们项目的 DataSource,
// quartzDataSource 为 Quartz 配置 jobStoreType 为JobStoreType.JDBC时创建的 DataSource, 只引入 Quartz 的 jar 包默认 为 null.
DataSource dataSourceToUse = getDataSource(dataSource, quartzDataSource);
// QuartzDataSourceInitializer 传入 QuartzDataSourceInitializer 的 DataSource 为项目的 DataSource
return new QuartzDataSourceInitializer(dataSourceToUse, resourceLoader, properties);
}
QuartzDataSourceInitializer
继承了AbstractDataSourceInitializer
public class QuartzDataSourceInitializer extends AbstractDataSourceInitializer {
private final QuartzProperties properties;
public QuartzDataSourceInitializer(DataSource dataSource, ResourceLoader resourceLoader,
QuartzProperties properties) {
super(dataSource, resourceLoader);
Assert.notNull(properties, "QuartzProperties must not be null");
this.properties = properties;
}
AbstractDataSourceInitializer
配置了@PostConstruct
@PostConstruct
protected void initialize() {
if (!isEnabled()) {
return;
}
ResourceDatabasePopulator populator = new ResourceDatabasePopulator();
// 获取 schema 的存储路径, 该路径 通过 @@platform@@ 占位了数据库平台
String schemaLocation = getSchemaLocation();
// 获取到的 schemaLocation 为: classpath:org/quartz/impl/jdbcjobstore/tables_@@platform@@.sql
// 如果存在数据库平台占位符(默认存在), 通过具体数据库平台替换占位符
if (schemaLocation.contains(PLATFORM_PLACEHOLDER)) {
String platform = getDatabaseName(); // 我使用 h2 数据库, 这里就是 h2
schemaLocation = schemaLocation.replace(PLATFORM_PLACEHOLDER, platform);
// 替换后为: classpath:org/quartz/impl/jdbcjobstore/tables_h2.sql
}
populator.addScript(this.resourceLoader.getResource(schemaLocation));
populator.setContinueOnError(true);
customize(populator);
// 执行填充器
DatabasePopulatorUtils.execute(populator, this.dataSource);
}
-
- Debug 视图:
- Debug 视图:
-
- 打开 Quartz 的 jar 包
quartz-2.3.0.jar!/org/quartz/impl/jdbcjobstore/
可以看到 Quartz 提供了一下数据库平台的 schema
- 打开 Quartz 的 jar 包
-
执行填充器
DatabasePopulatorUtils#execute
public static void execute(DatabasePopulator populator, DataSource dataSource) throws DataAccessException {
Assert.notNull(populator, "DatabasePopulator must not be null");
Assert.notNull(dataSource, "DataSource must not be null");
try {
Connection connection = DataSourceUtils.getConnection(dataSource);
try {
// 在这里执行
populator.populate(connection);
}
finally {
DataSourceUtils.releaseConnection(connection, dataSource);
}
}
ResourceDatabasePopulator#populate
@Override
public void populate(Connection connection) throws ScriptException {
Assert.notNull(connection, "Connection must not be null");
for (Resource script : this.scripts) { // script 就是我们上边看到的 sql 文件
EncodedResource encodedScript = new EncodedResource(script, this.sqlScriptEncoding);
ScriptUtils.executeSqlScript(connection, encodedScript, this.continueOnError, this.ignoreFailedDrops,
this.commentPrefix, this.separator, this.blockCommentStartDelimiter, this.blockCommentEndDelimiter);
}
}
-
- debug 视图
load script
读取 script 文件中的内容, 并执行 Sql
- 抛错
org.h2.jdbc.JdbcSQLSyntaxErrorException: Table "QRTZ_CALENDARS" already exists; SQL statement:
CREATE TABLE QRTZ_CALENDARS ( SCHED_NAME VARCHAR(120) NOT NULL, CALENDAR_NAME VARCHAR (200) NOT NULL , CALENDAR IMAGE NOT NULL ) [42101-199]
at org.h2.message.DbException.getJdbcSQLException(DbException.java:451) ~[h2-1.4.199.jar:1.4.199]
at org.h2.message.DbException.getJdbcSQLException(DbException.java:427) ~[h2-1.4.199.jar:1.4.199]
at org.h2.message.DbException.get(DbException.java:205) ~[h2-1.4.199.jar:1.4.199]
at org.h2.message.DbException.get(DbException.java:181) ~[h2-1.4.199.jar:1.4.199]
at org.h2.command.ddl.CreateTable.update(CreateTable.java:85) ~[h2-1.4.199.jar:1.4.199]
at org.h2.command.CommandContainer.update(CommandContainer.java:133) ~[h2-1.4.199.jar:1.4.199]
at org.h2.command.Command.executeUpdate(Command.java:267) ~[h2-1.4.199.jar:1.4.199]
at org.h2.jdbc.JdbcStatement.executeInternal(JdbcStatement.java:233) ~[h2-1.4.199.jar:1.4.199]
at org.h2.jdbc.JdbcStatement.execute(JdbcStatement.java:205) ~[h2-1.4.199.jar:1.4.199]
at com.alibaba.druid.filter.FilterChainImpl.statement_execute(FilterChainImpl.java:2958) [druid-1.1.18.jar:1.1.18]
at com.alibaba.druid.filter.FilterAdapter.statement_execute(FilterAdapter.java:2474) [druid-1.1.18.jar:1.1.18]
at com.alibaba.druid.filter.FilterEventAdapter.statement_execute(FilterEventAdapter.java:188) [druid-1.1.18.jar:1.1.18]
at com.alibaba.druid.filter.FilterChainImpl.statement_execute(FilterChainImpl.java:2956) [druid-1.1.18.jar:1.1.18]
at com.alibaba.druid.wall.WallFilter.statement_execute(WallFilter.java:422) [druid-1.1.18.jar:1.1.18]
at com.alibaba.druid.filter.FilterChainImpl.statement_execute(FilterChainImpl.java:2956) [druid-1.1.18.jar:1.1.18]
at com.alibaba.druid.filter.FilterAdapter.statement_execute(FilterAdapter.java:2474) [druid-1.1.18.jar:1.1.18]
at com.alibaba.druid.filter.FilterEventAdapter.statement_execute(FilterEventAdapter.java:188) [druid-1.1.18.jar:1.1.18]
at com.alibaba.druid.filter.FilterChainImpl.statement_execute(FilterChainImpl.java:2956) [druid-1.1.18.jar:1.1.18]
at com.alibaba.druid.proxy.jdbc.StatementProxyImpl.execute(StatementProxyImpl.java:147) [druid-1.1.18.jar:1.1.18]
at com.alibaba.druid.pool.DruidPooledStatement.execute(DruidPooledStatement.java:632) [druid-1.1.18.jar:1.1.18]
at org.springframework.jdbc.datasource.init.ScriptUtils.executeSqlScript(ScriptUtils.java:488) [spring-jdbc-5.1.8.RELEASE.jar:5.1.8.RELEASE]
at org.springframework.jdbc.datasource.init.ResourceDatabasePopulator.populate(ResourceDatabasePopulator.java:238) [spring-jdbc-5.1.8.RELEASE.jar:5.1.8.RELEASE]
at org.springframework.jdbc.datasource.init.DatabasePopulatorUtils.execute(DatabasePopulatorUtils.java:48) [spring-jdbc-5.1.8.RELEASE.jar:5.1.8.RELEASE]
at org.springframework.boot.jdbc.AbstractDataSourceInitializer.initialize(AbstractDataSourceInitializer.java:65) [spring-boot-2.1.6.RELEASE.jar:2.1.6.RELEASE]
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_161]
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_161]
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_161]
at java.lang.reflect.Method.invoke(Method.java:498) ~[na:1.8.0_161]
at org.springframework.beans.factory.annotation.InitDestroyAnnotationBeanPostProcessor$LifecycleElement.invoke(InitDestroyAnnotationBeanPostProcessor.java:363) [spring-beans-5.1.8.RELEASE.jar:5.1.8.RELEASE]
at org.springframework.beans.factory.annotation.InitDestroyAnnotationBeanPostProcessor$LifecycleMetadata.invokeInitMethods(InitDestroyAnnotationBeanPostProcessor.java:307) [spring-beans-5.1.8.RELEASE.jar:5.1.8.RELEASE]
at org.springframework.beans.factory.annotation.InitDestroyAnnotationBeanPostProcessor.postProcessBeforeInitialization(InitDestroyAnnotationBeanPostProcessor.java:136) [spring-beans-5.1.8.RELEASE.jar:5.1.8.RELEASE]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.applyBeanPostProcessorsBeforeInitialization(AbstractAutowireCapableBeanFactory.java:414) [spring-beans-5.1.8.RELEASE.jar:5.1.8.RELEASE]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1770) [spring-beans-5.1.8.RELEASE.jar:5.1.8.RELEASE]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:593) [spring-beans-5.1.8.RELEASE.jar:5.1.8.RELEASE]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:515) [spring-beans-5.1.8.RELEASE.jar:5.1.8.RELEASE]
at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:320) [spring-beans-5.1.8.RELEASE.jar:5.1.8.RELEASE]
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:222) ~[spring-beans-5.1.8.RELEASE.jar:5.1.8.RELEASE]
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:318) [spring-beans-5.1.8.RELEASE.jar:5.1.8.RELEASE]
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:199) [spring-beans-5.1.8.RELEASE.jar:5.1.8.RELEASE]
at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:845) ~[spring-beans-5.1.8.RELEASE.jar:5.1.8.RELEASE]
at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:877) ~[spring-context-5.1.8.RELEASE.jar:5.1.8.RELEASE]
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:549) ~[spring-context-5.1.8.RELEASE.jar:5.1.8.RELEASE]
at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:140) ~[spring-boot-2.1.6.RELEASE.jar:2.1.6.RELEASE]
at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:742) ~[spring-boot-2.1.6.RELEASE.jar:2.1.6.RELEASE]
at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:389) ~[spring-boot-2.1.6.RELEASE.jar:2.1.6.RELEASE]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:311) ~[spring-boot-2.1.6.RELEASE.jar:2.1.6.RELEASE]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1213) ~[spring-boot-2.1.6.RELEASE.jar:2.1.6.RELEASE]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1202) ~[spring-boot-2.1.6.RELEASE.jar:2.1.6.RELEASE]
at org.angboot.AngbootApplication.main(AngbootApplication.java:26) ~[main/:na]
郁闷, 从错误堆栈信息中你竟然完全找不到 Quartz 的字样, 这错也是坑死人…
1.2 解决方案
- jobStoreType 使用 JDBC
这样就不会通过我们项目的 DataSource 去创建 Quartz 的表
但是像帅帅这么死犟死犟的人就是不想使用 JDBC 的 type 啊, 我甚至就只是想引下包, 让一些 class 的引用可以找到不报错, 那可咋整? 继续看下面
- 排除 Quartz 自动配置, 自己接管 Quartz 的配置
这样你想咋样就咋样, 帅帅这下开心了吧?
在项目启动类的
@SpringBootApplication
注解上排除QuartzAutoConfiguration
@EnableCaching
@EnableSwagger2
@SpringBootApplication(exclude = {QuartzAutoConfiguration.class})
@MapperScan(basePackages = "org.angboot.authority.dao")
public class AngbootApplication {