大家好,我是冰河~~最近有朋友出去面试回来告诉我:冰河,我去XXX公司面试,面试官居然问了我一个关于Scheduledand的问题Spring中的异步调度。没有答案,你能写一篇关于这个问题的文章吗?我:对,安排!因此这篇文章。好了,开始写吧~~Spring调度的两种方式Spring提供了后台任务的两种方式,分别是:调度任务、@Schedule异步任务、@Async当然,使用这两个是有条件的,需要在spring应用的上下文中声明的当然,如果我们是基于java配置的,我们需要在配置类中添加@EnableScheduling和@EnableAsync注解,例如下面的代码片段。@EnableScheduling@EnableAsyncpublicclassWebAppConfig{....}另外还有第三方库可以调用,比如Quartz。在文章的最后,我们会简单地提到Quartz。@Schedule调度先看@Schedule是怎么调用的。publicfinalstaticlongONE_DAY=24*60*60*1000;publicfinalstaticlongONE_HOUR=60*60*1000;@Scheduled(fixedRate=ONE_DAY)publicvoidscheduledTask(){System.out.println("我是一个定时任务,隔天执行一次");}@Scheduled(fixedDelay=ONE_HOURS)publicvoidscheduleTask2(){System.out.println("我是一个任务,执行后每隔一小时执行一次");}@Scheduled(initialDelay=1000,fixedRate=5000)publicvoiddoSomething(){//somethingthatshouldperiodicallyexecute}@Scheduled(cron="00/1***?")publicvoidScheduledTask3(){System.out.println("我是一个每分钟都会执行的任务");}需要注意的是:关于@Scheduled注解,里面用到了Cron表达式,我们看到两个不同的面孔fixedDelay&fixedRate,前者fixedDelay的意思是在指定的时间间隔运行程序,比如这个程序是今晚九点运行程序,运行这个方法一小时后,会再次执行,后面的fixedDelay表示这个函数每调用一次在一段时间内(我们这里设置为一天),无论什么时候再次调度,this方法是否正在运行或者已经完成。前者要求在函数运行后才开始计时,这是两者的区别。这个还有一个参数initialDelay,就是第一次调用前的等待时间。这里是指被调用后,一秒后执行,适用于一些特殊情况。我们在serviceImpl类中写这些调度任务的时候,还需要在ServiceInterface的接口端口写多个接口,否则会抛出beanJDKproxy找不到任何接口。接口或异常。@Async调度有时候我们会调用一些特殊的任务,任务会比较耗时,重要的是我们不关心他返回的后果。这时候我们就需要用到这种异步任务,调用之后让它运行,不阻塞主线程,我们继续做其他的事情。代码如下所示:{Thread.sleep(3000);}catch(InterruptedExceptione){e.printStackTrace();}returnull;}publicvoidprintLog(){System.out.println("iprintalog,time="+System.currentTimeMillis());}}我们写一个简单的测试类来测试@RunWith(SpringJUnit4ClassRunner.class)@WebAppConfiguration@ContextConfiguration(classes=AsyncTaskConfig.class)//声明@EnableASyncpublicclassAsyncTaskTest{@AutowiredAsyncTaskasyncTask;@TestpublicvoidAsyncTaskTest()throwsInterruptedException{if(async)Task!=asyncTask.doSomeHeavyBackgroundTask(4000);asyncTask.printLog();Thread.sleep(5000);}}}这个感觉比手动开线程方便多了。如果你不想异步,你可以去掉@Async。另外,如果要返回结果,需要使用Future<>接口。如果要修改SpringBoot默认的线程池配置,可以实现AsyncConfigurer。需要注意的是:相对于@scheduled,这个可以有参数,可以返回一个结果,因为这是我们调用的,定时任务是spring调用的。异步方法不能在内部调用,只能像上面那样在外部调用,否则会变成阻塞主线程的同步任务!在这里,我就给大家看个活坑!例如,下面的代码案例。publicvoidAsyncTask(){publicvoidfakeAsyncTaskTest(){doSomeHeavyBackgroundTask(4000);printLog();//你会发现,你在内部这样调用的时候,是同步执行的,不是异步的!!}@AsyncpublicvoiddoSomeHeavyBackgroundTask(intsleepTime){try{Thread.sleep(sleepTime);}catch(InterruptedExceptione){e.printStackTrace();}}publicvoidprintLog(){System.out.println("iprintalog");}}另一点就是不要重复扫描,这样也会造成异步失效。具体可以参考stackoveflow的spring-async-not-workingIssue。关于异常处理,在这个异步执行过程中难免会出现异常。对于这个问题,Spring提供的解决方案如下,实现了AsyncUncaughtExceptionHandler接口。publicclassMyAsyncUncaughtExceptionHandlerimplementsAsyncUncaughtExceptionHandler{@OverridepublicvoidhandleUncaughtException(Throwableex,Methodmethod,Object...params){//handleexception}}写完我们的异常处理后,我们需要配置它并告诉Spring当我们运行一个异步任务Exceptionfinalizer时抛出这个异常处理错误。@Configuration@EnableAsyncpublicclassAsyncConfigimplementsAsyncConfigurer{@BeanpublicAsyncTaskasyncBean(){returnnewAsyncTask();}@OverridepublicExecutorgetAsyncExecutor(){ThreadPoolTask??Executorexecutor=newThreadPoolTask??Executor();executor.setCorePoolSize(7);executor.setMaxPoolSize(42);executorThreadacutor.set1Queue;("MyExecutor-");executor.initialize();returnexecutor;}@OverridepublicAsyncUncaughtExceptionHandlergetAsyncUncaughtExceptionHandler(){returnnewMyAsyncUncaughtExceptionHandler();}}简单说一下Quartz的登场除了Spring中的@Scheduled和@Async注解整合,还有还有一个Spring的第三方库,叫做Quartz。看了官网上的介绍,还挺搞笑的。现在习惯用Maven、Gradle等来关联这些依赖。他还要求人们下载它。我不知道为什么。详情点击->http://quartz-scheduler.org/documentation/quartz-2.2.x/quick-start估计是因为不再维护了。看了下,最新的2.2版本其实是9月,2013年更新的。。。Quartz其实已经停止更新了,但是Quartz作为企业级应用的任务调度框架,还是一个可能的候选项目,作为一个其他解决方案的自下而上的解决方案。这里就不多说了,有兴趣的朋友可以去官网看看。总体感觉不如Spring自带的后台任务方便,但也可以接受,简单配置即可使用。本文转载自微信公众号“冰河科技”,可通过以下二维码关注。转载本文请联系冰川科技公众号。
