• SpringBoot 2.x (11):定时任务与异步任务


    定时任务:有时候我们需要做定时的一些操作,比如统计信息,定时发送邮件等

    在SpringBoot中如何进行整合和使用呢?

    有哪些方式可以实现定时任务呢?

    Java自带的java.util.timer:

    优点:Java自带,无需导包

    缺点:配置复杂,时间延后等问题

    Quartz框架:

    优点:配置简单,使用方便

    缺点:需要导包

    @EnableSchedule:

    优点:SpringBoot自带,高兼容,无需导包

    使用:

     在启动类加入@EnableScheduling注解

    package org.dreamtech.demo;
    
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.scheduling.annotation.EnableScheduling;
    
    @SpringBootApplication
    @EnableScheduling
    public class DemoApplication {
    
        public static void main(String[] args) {
            SpringApplication.run(DemoApplication.class, args);
        }
    
    }

    定义Task:

    package org.dreamtech.demo.task;
    
    import java.util.Date;
    
    import org.springframework.scheduling.annotation.Scheduled;
    import org.springframework.stereotype.Component;
    
    @Component
    public class TestTask {
        @Scheduled(fixedRate = 2000)
        public void test() {
            System.out.println("[ " + "当前时间 : " + new Date() + " ]");
        }
    }

    效果:控制台每过两秒打印一次当前时间

    [ 当前时间 : Fri May 10 14:01:42 CST 2019 ]
    [ 当前时间 : Fri May 10 14:01:44 CST 2019 ]
    [ 当前时间 : Fri May 10 14:01:46 CST 2019 ]
    [ 当前时间 : Fri May 10 14:01:48 CST 2019 ]
    [ 当前时间 : Fri May 10 14:01:50 CST 2019 ]
    [ 当前时间 : Fri May 10 14:01:52 CST 2019 ]
    [ 当前时间 : Fri May 10 14:01:54 CST 2019 ]

    使用CRON表达式:

    每两秒执行一次的另一种表达方式:

        @Scheduled(cron = "*/2 * * * * *")

     关于CRON表达式的百度即可,一堆介绍,我就不多写了

    异步任务:适用于发送短信、邮件、处理Log等问题

    为什么需要异步任务?

    比如电商网站用户下单,要做的事情有这几样:库存查询、用户校验、余额校验等

    SpringBoot使用异步任务:@EnableAsync注解

    基本实现:

    启动类加入@EnableAsync注解

    package org.dreamtech.demo;
    
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.scheduling.annotation.EnableAsync;
    
    @SpringBootApplication
    @EnableAsync
    public class DemoApplication {
    
        public static void main(String[] args) {
            SpringApplication.run(DemoApplication.class, args);
        }
    
    }

    定义任务类:这里模拟耗时的操作

    package org.dreamtech.demo.task;
    
    import org.springframework.scheduling.annotation.Async;
    import org.springframework.stereotype.Component;
    
    @Component
    @Async
    public class AsyncTask {
        public void test1() throws InterruptedException {
            long begin = System.currentTimeMillis();
            Thread.sleep(1000);
            long end = System.currentTimeMillis();
            System.out.println("[ " + "任务1耗时 : " + (end - begin) + " ]");
        }
    
        public void test2() throws InterruptedException {
            long begin = System.currentTimeMillis();
            Thread.sleep(2000);
            long end = System.currentTimeMillis();
            System.out.println("[ " + "任务2耗时 : " + (end - begin) + " ]");
        }
    
        public void test3() throws InterruptedException {
            long begin = System.currentTimeMillis();
            Thread.sleep(3000);
            long end = System.currentTimeMillis();
            System.out.println("[ " + "任务2耗时 : " + (end - begin) + " ]");
        }
    }

    Controller层做测试:

    package org.dreamtech.demo.controller;
    
    import org.dreamtech.demo.task.AsyncTask;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    @RestController
    public class TestController {
        @Autowired
        private AsyncTask task;
    
        @GetMapping("/test")
        private Object test() {
            long begin = System.currentTimeMillis();
            try {
                task.test1();
                task.test2();
                task.test3();
            } catch (Exception e) {
                e.printStackTrace();
            }
            long end = System.currentTimeMillis();
            System.out.println("[ " + "Controller耗时 : " + (end - begin) + " ]");
            return "test";
        }
    }

    访问localhost:8080/test打印如下:

    [ Controller耗时 : 4 ]
    [ 任务1耗时 : 1000 ]
    [ 任务2耗时 : 2000 ]
    [ 任务2耗时 : 3000 ]

    结论:异步任务的执行类似多线程,可以用来做一些耗时操作

    有时候需要某个任务确定执行完毕才能继续其他操作

    如何获取任务的执行结果呢?

    package org.dreamtech.demo.task;
    
    import java.util.concurrent.Future;
    
    import org.springframework.scheduling.annotation.Async;
    import org.springframework.scheduling.annotation.AsyncResult;
    import org.springframework.stereotype.Component;
    
    @Component
    @Async
    public class AsyncTask {
    
        public Future<String> test1() throws InterruptedException {
            long begin = System.currentTimeMillis();
            Thread.sleep(1000);
            long end = System.currentTimeMillis();
            System.out.println("[ " + "任务1耗时 : " + (end - begin) + " ]");
            return new AsyncResult<String>("任务1");
        }
    
        public Future<String> test2() throws InterruptedException {
            long begin = System.currentTimeMillis();
            Thread.sleep(2000);
            long end = System.currentTimeMillis();
            System.out.println("[ " + "任务2耗时 : " + (end - begin) + " ]");
            return new AsyncResult<String>("任务2");
        }
    
        public Future<String> test3() throws InterruptedException {
            long begin = System.currentTimeMillis();
            Thread.sleep(3000);
            long end = System.currentTimeMillis();
            System.out.println("[ " + "任务3耗时 : " + (end - begin) + " ]");
            return new AsyncResult<String>("任务3");
        }
    }

    Controller:

    package org.dreamtech.demo.controller;
    
    import java.util.concurrent.Future;
    
    import org.dreamtech.demo.task.AsyncTask;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    @RestController
    public class TestController {
        @Autowired
        private AsyncTask task;
    
        @GetMapping("/test")
        private Object test() {
            long begin = System.currentTimeMillis();
            try {
                Future<String> task1Result = task.test1();
                Future<String> task2Result = task.test2();
                Future<String> task3Result = task.test3();
                while (true) {
                    if (task1Result.isDone() && task2Result.isDone() && task3Result.isDone()) {
                        break;
                    }
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
            long end = System.currentTimeMillis();
            System.out.println("[ " + "Controller耗时 : " + (end - begin) + " ]");
            return "test";
        }
    }

    访问localhost:8080/test

    等待3秒之后,打印如下:

    [ 任务1耗时 : 1001 ]
    [ 任务2耗时 : 2001 ]
    [ 任务3耗时 : 3001 ]
    [ Controller耗时 : 3007 ]

     只有三个任务都完成,Controller层代码才会执行完毕

    总结:异步任务大大地提高了开发的效率

    顺便来记录Logback的使用:

    Logback:一款优秀的日志框架

    SpringBoot的Starter默认日志框架:Logback,默认级别:INFO

    如果我们想以DEBUG方式启动:java -jar springboot.jar --debug

    配置文件:官方推荐名称logback-spring.xml

    <?xml version="1.0" encoding="UTF-8" ?>
    <configuration>
    
        <appender name="consoleApp"
            class="ch.qos.logback.core.ConsoleAppender">
            <layout class="ch.qos.logback.classic.PatternLayout">
                <pattern>
                    %date{yyyy-MM-dd HH:mm:ss.SSS} %-5level[%thread]%logger{56}.%method:%L -%msg%n
                </pattern>
            </layout>
        </appender>
    
        <appender name="fileInfoApp"
            class="ch.qos.logback.core.rolling.RollingFileAppender">
            <filter class="ch.qos.logback.classic.filter.LevelFilter">
                <level>ERROR</level>
                <onMatch>DENY</onMatch>
                <onMismatch>ACCEPT</onMismatch>
            </filter>
            <encoder>
                <pattern>
                    %date{yyyy-MM-dd HH:mm:ss.SSS} %-5level[%thread]%logger{56}.%method:%L -%msg%n
                </pattern>
            </encoder>
            <!-- 滚动策略 -->
            <rollingPolicy
                class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
                <!-- 路径 -->
                <fileNamePattern>app_log/log/app.info.%d.log</fileNamePattern>
            </rollingPolicy>
        </appender>
    
        <appender name="fileErrorApp"
            class="ch.qos.logback.core.rolling.RollingFileAppender">
            <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
                <level>ERROR</level>
            </filter>
            <encoder>
                <pattern>
                    %date{yyyy-MM-dd HH:mm:ss.SSS} %-5level[%thread]%logger{56}.%method:%L -%msg%n
                </pattern>
            </encoder>
            <!-- 设置滚动策略 -->
            <rollingPolicy
                class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
                <!-- 路径 -->
                <fileNamePattern>app_log/log/app.err.%d.log</fileNamePattern>
                <!-- 控制保留的归档文件的最大数量,超出数量就删除旧文件,假设设置每个月滚动, 且<maxHistory> 是1,则只保存最近1个月的文件,删除之前的旧文件 -->
                <MaxHistory>1</MaxHistory>
            </rollingPolicy>
        </appender>
        <root level="INFO">
            <appender-ref ref="consoleApp" />
            <appender-ref ref="fileInfoApp" />
            <appender-ref ref="fileErrorApp" />
        </root>
    </configuration>

    使用和测试:

    package org.dreamtech.demo.controller;
    
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    @RestController
    public class TestController {
    
        private Logger logger = LoggerFactory.getLogger(this.getClass());
    
        @GetMapping("/log")
        private Object log() {
            logger.debug("test");
            logger.info("test");
            logger.warn("test");
            logger.error("test");
            return "log";
        }
    }

    访问localhost:8080/log

    会在项目目录生成一个新目录app_log

    存在两个文件:一个用来存放ERROR级别信息,一个存放INFO级别和WARN级别

    2019-05-11 12:53:53.668 ERROR[http-nio-8080-exec-1]org.dreamtech.demo.controller.TestController.log:18 -test
    2019-05-11 12:53:36.264 INFO [main]org.dreamtech.demo.DemoApplication.logStarting:50 -Starting DemoApplication on DESKTOP-59O94TD with PID 14180 (D:PROJECTSTS_PROJECTdemo	argetclasses started by 20235 in D:PROJECTSTS_PROJECTdemo)
    2019-05-11 12:53:36.266 INFO [main]org.dreamtech.demo.DemoApplication.logStartupProfileInfo:675 -No active profile set, falling back to default profiles: default
    2019-05-11 12:53:37.019 INFO [main]o.s.boot.web.embedded.tomcat.TomcatWebServer.initialize:90 -Tomcat initialized with port(s): 8080 (http)
    2019-05-11 12:53:37.031 INFO [main]org.apache.coyote.http11.Http11NioProtocol.log:173 -Initializing ProtocolHandler ["http-nio-8080"]
    2019-05-11 12:53:37.039 INFO [main]org.apache.catalina.core.StandardService.log:173 -Starting service [Tomcat]
    2019-05-11 12:53:37.041 INFO [main]org.apache.catalina.core.StandardEngine.log:173 -Starting Servlet engine: [Apache Tomcat/9.0.17]
    2019-05-11 12:53:37.122 INFO [main]o.a.catalina.core.ContainerBase.[Tomcat].[localhost].[/].log:173 -Initializing Spring embedded WebApplicationContext
    2019-05-11 12:53:37.122 INFO [main]org.springframework.web.context.ContextLoader.prepareWebApplicationContext:296 -Root WebApplicationContext: initialization completed in 824 ms
    2019-05-11 12:53:37.289 INFO [main]o.s.scheduling.concurrent.ThreadPoolTaskExecutor.initialize:171 -Initializing ExecutorService 'applicationTaskExecutor'
    2019-05-11 12:53:37.413 INFO [main]org.apache.coyote.http11.Http11NioProtocol.log:173 -Starting ProtocolHandler ["http-nio-8080"]
    2019-05-11 12:53:37.447 INFO [main]o.s.boot.web.embedded.tomcat.TomcatWebServer.start:204 -Tomcat started on port(s): 8080 (http) with context path ''
    2019-05-11 12:53:37.450 INFO [main]org.dreamtech.demo.DemoApplication.logStarted:59 -Started DemoApplication in 1.528 seconds (JVM running for 2.39)
    2019-05-11 12:53:53.641 INFO [http-nio-8080-exec-1]o.a.catalina.core.ContainerBase.[Tomcat].[localhost].[/].log:173 -Initializing Spring DispatcherServlet 'dispatcherServlet'
    2019-05-11 12:53:53.642 INFO [http-nio-8080-exec-1]org.springframework.web.servlet.DispatcherServlet.initServletBean:524 -Initializing Servlet 'dispatcherServlet'
    2019-05-11 12:53:53.650 INFO [http-nio-8080-exec-1]org.springframework.web.servlet.DispatcherServlet.initServletBean:546 -Completed initialization in 8 ms
    2019-05-11 12:53:53.668 INFO [http-nio-8080-exec-1]org.dreamtech.demo.controller.TestController.log:16 -test
    2019-05-11 12:53:53.668 WARN [http-nio-8080-exec-1]org.dreamtech.demo.controller.TestController.log:17 -test
    2019-05-11 12:54:23.513 INFO [RMI TCP Connection(2)-127.0.0.1]o.s.b.a.SpringApplicationAdminMXBeanRegistrar$SpringApplicationAdmin.shutdown:163 -Application shutdown requested.
    2019-05-11 12:54:23.515 INFO [RMI TCP Connection(2)-127.0.0.1]o.s.scheduling.concurrent.ThreadPoolTaskExecutor.shutdown:208 -Shutting down ExecutorService 'applicationTaskExecutor'
  • 相关阅读:
    Linux下如何从mysql数据库里导出导入数据
    安装好Pycharm后如何配置Python解释器简易教程
    Windows离线安装Python第三方库的方法
    时间输入框的测试方法
    doc转html
    pdf转png图片
    html转pdf
    html转pdf
    复习 注解反射
    Mybatis实现插入数据的时候将主键赋值给对象的两种方法
  • 原文地址:https://www.cnblogs.com/xuyiqing/p/10843960.html
Copyright © 2020-2023  润新知