Spring Boot中如何捕获@SchedulerLock注解内抛出的异常?
捕获Shedlock @SchedulerLock注解内的数据库连接异常方案
问题背景
我们的Spring Boot API采用Quartz+Shedlock实现批处理任务,当数据库连接不可用时,@SchedulerLock注解的锁操作会抛出异常。由于该异常发生在任务启动前,无法在业务代码中捕获,需要找到捕获该异常的方案以便及时告警。
使用的注解:
@SchedulerLock(name = "AurCifBatchTask", lockAtLeastFor = "${quartz.cif.batch.lock-at-least}", lockAtMostFor = "${quartz.cif.batch.lock-at-most}")
核心异常信息:
org.springframework.transaction.CannotCreateTransactionException: Could not open JDBC Connection for transaction Caused by: java.sql.SQLTransientConnectionException: aur-service - Connection is not available, request timed out after 30002ms. Caused by: com.microsoft.sqlserver.jdbc.SQLServerException: The connection to the host YKE0-D16GP0108.devfg.rbc.com, named instance in08 failed. Error: "java.net.UnknownHostException: YKE0-D16GP0108.devfg.rbc.com"
可行解决方案
方案1:自定义Shedlock LockProvider包装类
实现自定义LockProvider,对原生LockProvider的lock方法做异常拦截,捕获数据库连接类异常并触发告警:
@Component public class ExceptionHandlingLockProvider implements LockProvider { private final LockProvider delegate; private final AlertService alertService; // 自定义告警服务 public ExceptionHandlingLockProvider(LockProvider delegate, AlertService alertService) { this.delegate = delegate; this.alertService = alertService; } @Override public Optional<Lock> lock(LockConfiguration lockConfiguration) { try { return delegate.lock(lockConfiguration); } catch (RuntimeException e) { if (isConnectionException(e)) { alertService.sendAlert("Shedlock锁操作失败:数据库连接异常", e); } throw e; // 抛出异常让Quartz处理任务失败逻辑 } } private boolean isConnectionException(Throwable e) { if (e instanceof CannotCreateTransactionException) { return true; } if (e instanceof SQLTransientConnectionException) { return true; } if (e instanceof SQLServerException && (e.getMessage().contains("Connection is not available") || e.getMessage().contains("UnknownHostException"))) { return true; } return e.getCause() != null && isConnectionException(e.getCause()); } }
在Shedlock配置类中替换默认LockProvider:
@Configuration public class ShedlockConfig { @Bean public LockProvider lockProvider(DataSource dataSource, AlertService alertService) { JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource); JdbcLockProvider jdbcLockProvider = JdbcLockProvider.builder() .withJdbcTemplate(jdbcTemplate) .usingDbTime() .build(); return new ExceptionHandlingLockProvider(jdbcLockProvider, alertService); } }
方案2:Spring AOP拦截@SchedulerLock方法
通过AOP环绕通知拦截所有标注@SchedulerLock的方法,捕获前置锁操作抛出的异常:
@Aspect @Component public class SchedulerLockExceptionAspect { private final AlertService alertService; public SchedulerLockExceptionAspect(AlertService alertService) { this.alertService = alertService; } @Around("@annotation(net.javacrumbs.shedlock.spring.annotation.SchedulerLock)") public Object handleSchedulerLockException(ProceedingJoinPoint joinPoint) throws Throwable { try { return joinPoint.proceed(); } catch (RuntimeException e) { if (isConnectionException(e)) { String taskName = joinPoint.getSignature().getName(); alertService.sendAlert("任务[" + taskName + "]Shedlock锁操作失败:数据库连接异常", e); } throw e; } } private boolean isConnectionException(Throwable e) { // 复用方案1的异常判断逻辑 if (e instanceof CannotCreateTransactionException) { return true; } if (e instanceof SQLTransientConnectionException) { return true; } if (e instanceof SQLServerException && (e.getMessage().contains("Connection is not available") || e.getMessage().contains("UnknownHostException"))) { return true; } return e.getCause() != null && isConnectionException(e.getCause()); } }
方案3:自定义Quartz JobFactory
如果使用Quartz触发任务,可自定义JobFactory在任务实例化阶段捕获异常:
@Component public class AlertingJobFactory extends SpringBeanJobFactory { private final AlertService alertService; private final AutowireCapableBeanFactory beanFactory; public AlertingJobFactory(AlertService alertService, AutowireCapableBeanFactory beanFactory) { this.alertService = alertService; this.beanFactory = beanFactory; } @Override protected Object createJobInstance(TriggerFiredBundle bundle) throws Exception { Object jobInstance = super.createJobInstance(bundle); beanFactory.autowireBean(jobInstance); return jobInstance; } @Override public Job newJob(TriggerFiredBundle bundle, Scheduler scheduler) throws SchedulerException { try { return super.newJob(bundle, scheduler); } catch (SchedulerException e) { if (isConnectionException(e.getCause())) { String jobName = bundle.getJobDetail().getKey().getName(); alertService.sendAlert("Quartz任务[" + jobName + "]Shedlock锁操作失败:数据库连接异常", e.getCause()); } throw e; } } private boolean isConnectionException(Throwable e) { // 复用异常判断逻辑 if (e == null) return false; if (e instanceof CannotCreateTransactionException) { return true; } if (e instanceof SQLTransientConnectionException) { return true; } if (e instanceof SQLServerException && (e.getMessage().contains("Connection is not available") || e.getMessage().contains("UnknownHostException"))) { return true; } return isConnectionException(e.getCause()); } }
配置Quartz使用自定义JobFactory:
@Configuration public class QuartzConfig { @Bean public SchedulerFactoryBean schedulerFactoryBean(DataSource dataSource, AlertingJobFactory jobFactory) { SchedulerFactoryBean schedulerFactory = new SchedulerFactoryBean(); schedulerFactory.setDataSource(dataSource); schedulerFactory.setJobFactory(jobFactory); // 其他Quartz配置项 return schedulerFactory; } }
内容的提问来源于stack exchange,提问作者Bruce Xie
相关产品推荐
相关产品推荐

