SpringBoot 项目引入 Quartz 包报错源码分析

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 视图:
      file
    • 打开 Quartz 的 jar 包 quartz-2.3.0.jar!/org/quartz/impl/jdbcjobstore/ 可以看到 Quartz 提供了一下数据库平台的 schema
      file
  • 执行填充器 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
file

读取 script 文件中的内容, 并执行 Sql
file

  • 抛错
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 {
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值