Hi,大家好,我是编程小6,很荣幸遇见你,我把这些年在开发过程中遇到的问题或想法写出来,今天说一说
java sort方法_java多线程面试题及答案,希望能够帮助你!!!。
时间是宝贵的,委派时间和资源来执行琐碎的任务往往会浪费资源和金钱。因此,组织努力在其系统中实现全面自动化,因为它更具可扩展性且成本更低。
随着业务流程复杂性的增加,系统自动化的好处也在增加。
自动化是作业或调度程序概念流行的地方。作业调度通常被称为在给定时间执行的任何类型的批处理(作业)。由于这些作业中的大多数不需要立即执行,因此可以安排在不久的将来或以重复的时间间隔处理它们。
通常,通过手动方法自动化任何类型的流程会导致:
用于大型 Java 应用程序的最强大和最灵活的调度框架之一被称为Quartz。
在本指南中,我们将在 Spring Boot 应用程序中实现 Quartz 及其组件,为自定义Jobs和Triggers构建我们自己的Quartz 管理控制台。
注意:我们将要构建的Quartz 管理控制台的可运行的、成熟的副本在我们的 GitHub 上可用。
Quartz是一个用 Java 编写的开源、功能强大的作业调度框架,旨在与任何类型的 J2EE 或 J2SE 框架集成。它在不牺牲复杂性或可扩展性的情况下提供了巨大的灵活性。
据推测,这个名字来源于极其精确的钟表中使用的石英晶体,它在电振荡的作用下,使表针在固定的时间范围内移动。
如果应用程序需要按计划的时间间隔或由于某些事件执行任务,Quartz非常适合:
Quartz 的一些显着特点是:
为了提高可扩展性,Quartz在多线程环境中运行。这有助于框架同时运行作业。
整个框架的核心是Scheduler接口。Scheduler为他们跟踪所有的sJobDetail和Triggers。它们代表需要运行什么Job(哪个)和何时(Trigger那个工作是什么)。
因此,它们构成了框架的主要组成部分。所有其他组件的其余部分确保它通过尽职调查和有效地发生。
让我们从鹰的角度来看待我们将使用的关键组件:
我们将构建自己的Quartz 管理控制台,以了解和欣赏 Quartz 调度程序中的生命周期。
为此,我们将构建一个简单的 UI 驱动的管理控制台,它可以执行两种任务:
实施后看起来像这样:
让我们创建一个 Spring Boot 项目,并一个一个地实现每个 Quartz 组件。从骨架项目开始的最简单方法是通过Spring Initializr:
我们添加了用于 MVC 功能的Spring Web 、将数据存储到数据存储中的Spring Data JPA 、作为内存数据库的H2 、 Lombok(可选的样板减少库)和Thymeleaf(用于 Spring/MVC 应用程序的模板引擎)。我们还包含了spring-boot-starter-quartz将 Quartz 纳入我们的项目的包。
Quartz 引入了自己的内置JobStores。在 Spring Boot 中,我们可以选择:
注意:JobStore无论您的数据库如何,您都可以选择这些类型。
我们将使用 H2 作为我们的数据存储,并设置 Quartz 来持久化数据。
Quartz 要求您为 JDBC JobStores 初始化数据库表,因为它们不是自动创建的。为此,我们将在初始化数据库时使用 SQL 脚本来运行。您可以在我们的 GitHub 上
找到初始化脚本。
让我们通过定义 H2 的数据库连接参数来开始我们的管理应用程序的开发。在您的application.properties文件中,让我们定义init.schema(初始设置脚本)以及datasource参数:
server.port=8080 spring.sql.init.schema-locations=classpath:db/quartz_tables_h2.sql spring.h2.console.enabled=true spring.datasource.url=jdbc:h2:mem:testdb spring.datasource.username=sa spring.datasource.password= logging.level.org.hibernate.SQL=debug
该quartz_tables_h2.sql脚本由一组冗长的 SQL 命令组成,用于最初设置它:
-- Note, Quartz depends on row-level locking which means you must use the MVC=TRUE -- setting on your H2 database, or you will experience dead-locks -- -- In your Quartz properties file, you'll need to set -- org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.StdJDBCDelegate CREATE TABLE QRTZ_CALENDARS ( SCHED_NAME VARCHAR(120) NOT NULL, CALENDAR_NAME VARCHAR (200) NOT NULL , CALENDAR IMAGE NOT NULL ); ... -- Download the entire script from our GitHub repository ... COMMIT;
一旦可以建立数据库连接并且我们已经准备好初始化脚本 - 我们将要设置Quartz 调度程序及其组件。
大多数方面和组件在一定程度上是可定制的,例如JobStore应该使用哪些驱动程序,其中有多少线程ThreadPool以及它们具有哪些优先级等。
All of these are defined in a quartz.properties file, which should be located under /src/resources/. This is the directory in which the QuartzProperties class looks for the required properties by default.
Note: If you want to define it in another property file, you'll have to point the org.quartz.properties system property to point to that file.
Let's define some of the properties now:
#============================================================================ # Configure Main Scheduler Properties #============================================================================ org.quartz.scheduler.instanceName=spring-boot-quartz org.quartz.scheduler.instanceId=AUTO #============================================================================ # Configure ThreadPool #============================================================================ org.quartz.threadPool.class = org.quartz.simpl.SimpleThreadPool org.quartz.threadPool.threadCount = 25 org.quartz.threadPool.threadPriority = 5 #============================================================================ # Configure JobStore #============================================================================ org.quartz.jobStore.misfireThreshold=1000 org.quartz.jobStore.class=org.quartz.impl.jdbcjobstore.JobStoreTX org.quartz.jobStore.driverDelegateClass=org.quartz.impl.jdbcjobstore.StdJDBCDelegate org.quartz.jobStore.useProperties=true org.quartz.jobStore.tablePrefix=QRTZ_ #============================================================================ # Configure Cluster properties #============================================================================ org.quartz.jobStore.isClustered=true org.quartz.jobStore.clusterCheckinInterval=1000
All of these properties don't mean much if we don't use them in a @Configuration class to customize how Quartz works. Let's inject the properties from quartz.properties into a SchedulerConfig class, where we'll initialize the SchedulerJobFactoryBean class, passing the properties in.
We'll be implementing our own SchedulerJobFactoryBean as a SpringBeanJobFactory from the Quartz project:
@Configuration public class SchedulerConfig { @Autowired private DataSource dataSource; @Autowired private ApplicationContext applicationContext; @Autowired private QuartzProperties quartzProperties; @Bean public SchedulerFactoryBean schedulerFactoryBean() { SchedulerJobFactory jobFactory = new SchedulerJobFactory(); jobFactory.setApplicationContext(applicationContext); Properties properties = new Properties(); properties.putAll(quartzProperties.getProperties()); SchedulerFactoryBean factory = new SchedulerFactoryBean(); factory.setOverwriteExistingJobs(true); factory.setDataSource(dataSource); factory.setQuartzProperties(properties); factory.setJobFactory(jobFactory); return factory; } }
该类QuartzProperties包含文件中定义的属性quartz.properties。我们可以通过检索它们getProperties()并将它们添加到、和SchedulerFactoryBean旁边。DataSourceSchedulerJobFactory
这是Quartz 为我们提供SchedulerJobFactory的自定义实现。SpringBeanJobFactory让我们扩展它:
public class SchedulerJobFactory extends SpringBeanJobFactory implements ApplicationContextAware { private AutowireCapableBeanFactory beanFactory; @Override public void setApplicationContext(final ApplicationContext context) { beanFactory = context.getAutowireCapableBeanFactory(); } @Override protected Object createJobInstance(final TriggerFiredBundle bundle) throws Exception { final Object job = super.createJobInstance(bundle); beanFactory.autowireBean(job); return job; } }
现在,我们可以通过我们的工厂创造就业机会,并在需要时自动装配它。在这个阶段——我们可以启动一个正在运行的 Quartz 调度程序实例。如果我们运行我们的应用程序,我们会收到以下内容:
Quartz 中有两种类型的触发器 -CronTrigger和SimpleTrigger. 它们分别对应于CronScheduler和SimpleScheduler,我们可以使用它们各自的工厂来创建触发器。
CronTrigger基于cron 表达式的触发器,而SimpleTrigger基于间隔的触发器。
为了创建作业触发器,让我们定义几个方便的方法,通过它们各自的工厂实例化并返回它们。这些方法将位于我们将用于创建作业和触发器的 -a 中JobSchedulerCreator:@Component
@Component public class JobScheduleCreator { // Creation methods }
让我们从CronTrigger创建者方法开始:
public CronTrigger createCronTrigger(String triggerName, Date startTime, String cronExpression, int misFireInstruction) { CronTriggerFactoryBean factoryBean = new CronTriggerFactoryBean(); factoryBean.setName(triggerName); factoryBean.setStartTime(startTime); factoryBean.setCronExpression(cronExpression); factoryBean.setMisfireInstruction(misFireInstruction); try { factoryBean.afterPropertiesSet(); } catch (ParseException e) { log.error(e.getMessage(), e); } return factoryBean.getObject(); }
使用CronTriggerFactoryBean,我们传入有关 a 的所需信息Trigger,例如它的名称、开始时间以及 cron 表达式和 misfire 指令。一旦生成 - 返回对象。
几乎相同的过程适用于创建SimpleTrigger对象:
public SimpleTrigger createSimpleTrigger(String triggerName, Date startTime, Long repeatTime, int misFireInstruction) { SimpleTriggerFactoryBean factoryBean = new SimpleTriggerFactoryBean(); factoryBean.setName(triggerName); factoryBean.setStartTime(startTime); factoryBean.setRepeatInterval(repeatTime); factoryBean.setRepeatCount(SimpleTrigger.REPEAT_INDEFINITELY); factoryBean.setMisfireInstruction(misFireInstruction); factoryBean.afterPropertiesSet(); return factoryBean.getObject(); }
通过构建触发器的简单方法,我们还可以构建一个方便的方法来构建作业 - 依赖于JobDetailFactoryBean:
public JobDetail createJob(Class<? extends QuartzJobBean> jobClass, boolean isDurable, ApplicationContext context, String jobName, String jobGroup) { JobDetailFactoryBean factoryBean = new JobDetailFactoryBean(); factoryBean.setJobClass(jobClass); factoryBean.setDurability(isDurable); factoryBean.setApplicationContext(context); factoryBean.setName(jobName); factoryBean.setGroup(jobGroup); // Set job data map JobDataMap jobDataMap = new JobDataMap(); jobDataMap.put(jobName + jobGroup, jobClass.getName()); factoryBean.setJobDataMap(jobDataMap); factoryBean.afterPropertiesSet(); return factoryBean.getObject(); }
要跟踪工作详细信息和信息 - 我们可以使用JobDetails该类。这就是它的意义所在。但是,我们可以从为我们自己的类定义代理中受益。
不建议自己直接写入 Quartz 表,因此作业及其详细信息虽然保留,但已修复。我们可以定义一个新实体来在单独的表中跟踪这些作业,并按照我们的意愿处理它们 - 同时还可以将这些对象用作数据传输对象 (DTO)。
这使我们能够对传入数据执行验证,并允许我们对作业在数据库中的持久化方式进行更精细的控制。
虽然是可选的,但建议使用这样的代理 - 我们将其命名为SchedulerJobInfo:
// Lombok annotations for getters, setters and constructor public class SchedulerJobInfo { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private String jobId; private String jobName; private String jobGroup; private String jobStatus; private String jobClass; private String cronExpression; private String desc; private String interfaceName; private Long repeatTime; private Boolean cronJob; }
对于简单的 CRUD 功能 - 我们将为JpaRepository这个实体创建一个简单的:
@Repository public interface SchedulerRepository extends JpaRepository<SchedulerJobInfo, Long> { SchedulerJobInfo findByJobName(String jobName); }
每个作业都必须扩展QuartzJobBean类或实现Job接口。
QuartzJobBean实现Job,唯一的区别是QuartzJobBean应用传递JobDataMap的和SchedulerContextas bean 属性值,而Job没有。
此外,Job需要您实现该execute()方法,同时QuartzJobBean需要您实现该executeInternal()方法。
让我们创建一个SimpleJob,extends QuartzJobBean并打印从0到5的整数:
public class SimpleJob extends QuartzJobBean { @Override protected void executeInternal(JobExecutionContext context) throws JobExecutionException { log.info("SimpleJob Start................"); IntStream.range(0, 5).forEach(i -> { log.info("Counting - {}", i); try { Thread.sleep(1000); } catch (InterruptedException e) { log.error(e.getMessage(), e); } }); log.info("SimpleJob End................"); } }
同样,我们可以创建一个SimpleCronJob在特定 cron 表达式上触发的函数:
@DisallowConcurrentExecution public class SimpleCronJob extends QuartzJobBean { @Override protected void executeInternal(JobExecutionContext context) throws JobExecutionException { log.info("SimpleCronJob Start................"); IntStream.range(0, 10).forEach(i -> { log.info("Counting - {}", i); try { Thread.sleep(1000); } catch (InterruptedException e) { log.error(e.getMessage(), e); } }); log.info("SimpleCronJob End................"); } }
注意:我们正在申请@DisallowConcurrentExecution,以便在集群设置中多个调度程序不会同时执行此作业。
在Quartz 管理控制台中,我们将有几个作业选项:
这些允许我们创建、编辑、暂停、恢复、删除和运行一次作业。为了使这一点更加有趣 - 我们将为每个任务编写方法,使我们能够从一个非常简单直观的 UI 控制作业。
为了将这一切联系在一起,我们将创建一个新类 -SchedulerJobService来执行这些操作:
@Transactional @Service public class SchedulerJobService { @Autowired private Scheduler scheduler; @Autowired private SchedulerFactoryBean schedulerFactoryBean; @Autowired private SchedulerRepository schedulerRepository; @Autowired private ApplicationContext context; @Autowired private JobScheduleCreator scheduleCreator; // Create, edit, pause jobs, etc...
To create jobs, a proprietary saveOrUpdate() method determines whether the instance created via a specific SchedulerJobInfo DTO is to be saved into an existing entity or if a new job should be created. Based on the paramaters in the payload, we'll either create a SimpleCronJob or SimpleJob:
public void saveOrUpdate(SchedulerJobInfo scheduleJob) throws Exception { if (scheduleJob.getCronExpression().length() > 0) { scheduleJob.setJobClass(SimpleCronJob.class.getName()); scheduleJob.setCronJob(true); } else { scheduleJob.setJobClass(SimpleJob.class.getName()); scheduleJob.setCronJob(false); scheduleJob.setRepeatTime((long) 1); } if (StringUtils.isEmpty(scheduleJob.getJobId())) { log.info("Job Info: {}", scheduleJob); scheduleNewJob(scheduleJob); } else { updateScheduleJob(scheduleJob); } scheduleJob.setDesc("i am job number " + scheduleJob.getJobId()); scheduleJob.setInterfaceName("interface_" + scheduleJob.getJobId()); log.info(">>>>> jobName = [" + scheduleJob.getJobName() + "]" + " created."); }
如果作业不存在 - 我们scheduleNewJob()使用之前的自动装配JobScheduleCreator组件调用 which 安排新作业:
private void scheduleNewJob(SchedulerJobInfo jobInfo) { try { Scheduler scheduler = schedulerFactoryBean.getScheduler(); JobDetail jobDetail = JobBuilder .newJob((Class<? extends QuartzJobBean>) Class.forName(jobInfo.getJobClass())) .withIdentity(jobInfo.getJobName(), jobInfo.getJobGroup()).build(); if (!scheduler.checkExists(jobDetail.getKey())) { jobDetail = scheduleCreator.createJob( (Class<? extends QuartzJobBean>) Class.forName(jobInfo.getJobClass()), false, context, jobInfo.getJobName(), jobInfo.getJobGroup()); Trigger trigger; if (jobInfo.getCronJob()) { trigger = scheduleCreator.createCronTrigger( jobInfo.getJobName(), new Date(), jobInfo.getCronExpression(), SimpleTrigger.MISFIRE_INSTRUCTION_FIRE_NOW); } else { trigger = scheduleCreator.createSimpleTrigger( jobInfo.getJobName(), new Date(), jobInfo.getRepeatTime(), SimpleTrigger.MISFIRE_INSTRUCTION_FIRE_NOW); } scheduler.scheduleJob(jobDetail, trigger); jobInfo.setJobStatus("SCHEDULED"); schedulerRepository.save(jobInfo); log.info(">>>>> jobName = [" + jobInfo.getJobName() + "]" + " scheduled."); } else { log.error("scheduleNewJobRequest.jobAlreadyExist"); } } catch (ClassNotFoundException e) { log.error("Class Not Found - {}", jobInfo.getJobClass(), e); } catch (SchedulerException e) { log.error(e.getMessage(), e); } }
创建触发器时,我们传入一个MISFIRE_INSTRUCTION. 有时,Quartz 可能会错过某项工作。如果工作线程很忙,如果调度程序已关闭或过去计划触发作业,以及类似问题,则可能会发生这种情况。
我们已将触发器设置为MISFIRE_INSTRUCTION_FIRE_NOW- 如果发生失火,则会再次触发。如果没有MISFIRE_INSTRUCTION定义,Quartz 采用Smart Policy - MISFIRE_INSTRUCTION__SMART_POLICY。
要编辑作业,我们可以使用与创建它们大致相同的方法 - 但是,我们必须通知调度程序在其指令更新后重新安排作业:
private void updateScheduleJob(SchedulerJobInfo jobInfo) { Trigger newTrigger; if (jobInfo.getCronJob()) { newTrigger = scheduleCreator.createCronTrigger( jobInfo.getJobName(), new Date(), jobInfo.getCronExpression(), simpleTrigger.MISFIRE_INSTRUCTION_FIRE_NOW); } else { newTrigger = scheduleCreator.createSimpleTrigger( jobInfo.getJobName(), new Date(), jobInfo.getRepeatTime(), SimpleTrigger.MISFIRE_INSTRUCTION_FIRE_NOW); } try { schedulerFactoryBean.getScheduler().rescheduleJob(TriggerKey.triggerKey(jobInfo.getJobName()), newTrigger); jobInfo.setJobStatus("EDITED & SCHEDULED"); schedulerRepository.save(jobInfo); log.info(">>>>> jobName = [" + jobInfo.getJobName() + "]" + " updated and scheduled."); } catch (SchedulerException e) { log.error(e.getMessage(), e); } }
有时您想在临时情况下触发触发器。您可能还想在提交时间表之前解雇一份工作以测试它是否工作正常。
我们可以使用triggerJob()方法立即触发它,而无需等待预定的 cron 或时间。这允许我们创建一个测试热键:
public boolean startJobNow(SchedulerJobInfo jobInfo) { try { SchedulerJobInfo getJobInfo = schedulerRepository.findByJobName(jobInfo.getJobName()); getJobInfo.setJobStatus("SCHEDULED & STARTED"); schedulerRepository.save(getJobInfo); schedulerFactoryBean.getScheduler().triggerJob(new JobKey(jobInfo.getJobName(), jobInfo.getJobGroup())); log.info(">>>>> jobName = [" + jobInfo.getJobName() + "]" + " scheduled and started now."); return true; } catch (SchedulerException e) { log.error("Failed to start new job - {}", jobInfo.getJobName(), e); return false; } }
如果您想暂停正在运行的Cron Job 或 Simple Job,我们可以使用该pauseJob()方法,该方法将暂停一个作业,直到您恢复它:
public boolean pauseJob(SchedulerJobInfo jobInfo) { try { SchedulerJobInfo getJobInfo = schedulerRepository.findByJobName(jobInfo.getJobName()); getJobInfo.setJobStatus("PAUSED"); schedulerRepository.save(getJobInfo); schedulerFactoryBean.getScheduler().pauseJob(new JobKey(jobInfo.getJobName(), jobInfo.getJobGroup())); log.info(">>>>> jobName = [" + jobInfo.getJobName() + "]" + " paused."); return true; } catch (SchedulerException e) { log.error("Failed to pause job - {}", jobInfo.getJobName(), e); return false; } }
resumeJob()自然,我们可以通过简单地使用以下方法来恢复暂停的作业:
public boolean resumeJob(SchedulerJobInfo jobInfo) { try { SchedulerJobInfo getJobInfo = schedulerRepository.findByJobName(jobInfo.getJobName()); getJobInfo.setJobStatus("RESUMED"); schedulerRepository.save(getJobInfo); schedulerFactoryBean.getScheduler().resumeJob(new JobKey(jobInfo.getJobName(), jobInfo.getJobGroup())); log.info(">>>>> jobName = [" + jobInfo.getJobName() + "]" + " resumed."); return true; } catch (SchedulerException e) { log.error("Failed to resume job - {}", jobInfo.getJobName(), e); return false; } }
最后,我们可以通过调用该deleteJob()方法删除一个作业:
public boolean deleteJob(SchedulerJobInfo jobInfo) { try { SchedulerJobInfo getJobInfo = schedulerRepository.findByJobName(jobInfo.getJobName()); schedulerRepository.delete(getJobInfo); log.info(">>>>> jobName = [" + jobInfo.getJobName() + "]" + " deleted."); return schedulerFactoryBean.getScheduler().deleteJob(new JobKey(jobInfo.getJobName(), jobInfo.getJobGroup())); } catch (SchedulerException e) { log.error("Failed to delete job - {}", jobInfo.getJobName(), e); return false; } }
现在,我们已经拥有了将Quartz 管理控制台与我们可以测试功能的 Web 应用程序用户界面结合在一起所需的所有功能。
注意:此应用程序的 UI 用于演示调度程序的生命周期管理,并且 UI 比后端多变。因此,我们不会过多关注其实施。但是,您可以在我们的GitHub 存储库中访问前端的完整代码。
任何触发我们之前定义的方法的 REST API 都可以正常工作。我们的实现使用 Thymeleaf 作为渲染引擎。
如果您想了解有关 Thymeleaf 的更多信息,请阅读Java 和 Spring 中的 Thymeleaf 入门。
如果您想了解有关构建 REST API 的更多信息,请阅读我们的使用 Spring Boot 构建 REST API 指南。
运行应用程序后,让我们导航到http://localhost:8080/index,也可以查看仪表板。
首先,让我们选择创建按钮来创建一个新作业。它将打开一个弹出窗口并提示我们填写作业详细信息。
让我们创建一个 Cron Job 和一个 Simple Job:
我们可以在 JobStore 中看到所有添加的作业:
您还可以查看作业日志 - 当作业被触发时,根据其触发条件一一触发。
在本指南中,我们介绍了 Quartz - 一个强大的调度程序并将其实现到 Spring Boot 应用程序中。
我们通过简单 UI 中的演示研究了 Quartz 调度程序的整体生命周期管理。我们使用了一些简约的作业,但您可以尝试定义复杂的作业,例如触发电子邮件警报或异步消息处理等,并使用 Quartz 进行调度。
与往常一样,您可以在GitHub 上找到完整的源代码。
github: https://github.com/StackAbuse/spring-boot-quartz
https://stackabuse.com/guide-to-quartz-with-spring-boot-job-scheduling-and-automation/
今天的分享到此就结束了,感谢您的阅读,如果确实帮到您,您可以动动手指转发给其他人。
上一篇
已是最后文章
下一篇
已是最新文章