SpringBoot异步回调

SpringBoot异步回调

实现后台异步处理请求,并将处理结果返回前端

Callable

使用Callable进行回调,直接返回Callable<目标类>即可。

需要进行WebMvcConfigurerAsyncSupportConfigurer,即MVC的异步支持配置

配置类

import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.web.servlet.config.annotation.AsyncSupportConfigurer;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;


@Configuration
public class webConfig implements WebMvcConfigurer {

    @Override
    public void configureAsyncSupport(AsyncSupportConfigurer configurer) {
        // 设定异步请求线程池callable等, spring默认线程不可重用
        configurer.setTaskExecutor(asyncExecutor());
    }

    public ThreadPoolTaskExecutor asyncExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        // 核心线程数
        executor.setCorePoolSize(10);
        // 最大线程数
        executor.setMaxPoolSize(100);
        // 队列容量,如果此数字大于0,使用队列LinkedBlockingQueue
        executor.setQueueCapacity(100);
        // 线程名称前缀
        executor.setThreadNamePrefix("WebmvcThread-");
        //初始化
        executor.initialize();

        return executor;
    }

}

Controller

import com.example.bean.Demo;
import com.example.service.TtlTool;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.Callable;

@RestController
public class IndexController {

    @Autowired
    private TtlTool ttlTool;
    
    @GetMapping("test1")
    public Callable<Demo> test1() {

        System.out.println("Controller方法被  "+Thread.currentThread().getName()+"  开始执行于 "+ new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));

        Callable<Demo> callable = new Callable<Demo>() {
            @Override
            public Demo call() throws Exception {

                System.out.println("Call方法被  "+Thread.currentThread().getName()+"  开始执行于 "+ new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));

                Demo d = ttlTool.ttl3();

                System.out.println("Call方法被  "+Thread.currentThread().getName()+"  结束执行于 "+ new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
                System.out.println();

                return d;
            }
        };

        System.out.println("Controller方法被  "+Thread.currentThread().getName()+"  结束执行于 "+ new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));

        return callable;
    }
    
}

Service

import org.springframework.stereotype.Component;
import com.example.bean.Demo;
import java.text.SimpleDateFormat;
import java.util.Date;


@Component
public class TtlTool {

    public Demo ttl3() throws InterruptedException {

        System.out.println("异步方法被  "+Thread.currentThread().getName()+"  开始执行于 "+ new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));


        Thread.sleep(5000);

        Demo d = new Demo();
        d.setOne("1");
        d.setTwo("2");
        d.setThree("3");
        d.setFour("4");

        System.out.println("异步方法被  "+Thread.currentThread().getName()+"  结束执行于"+ new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));


        return d;
    }

}

结果

同时发送两个http://localhost:8081/test1请求

Controller方法被  http-nio-8081-exec-1  开始执行于 2023-10-20 15:08:23
Controller方法被  http-nio-8081-exec-1  结束执行于 2023-10-20 15:08:23

Call方法被  WebmvcThread-1  开始执行于 2023-10-20 15:08:23
异步方法被  WebmvcThread-1  开始执行于 2023-10-20 15:08:23

Controller方法被  http-nio-8081-exec-2  开始执行于 2023-10-20 15:08:23
Controller方法被  http-nio-8081-exec-2  结束执行于 2023-10-20 15:08:23

Call方法被  WebmvcThread-2  开始执行于 2023-10-20 15:08:23
异步方法被  WebmvcThread-2  开始执行于 2023-10-20 15:08:23

异步方法被  WebmvcThread-1  结束执行于2023-10-20 15:08:28
Call方法被  WebmvcThread-1  结束执行于 2023-10-20 15:08:28

异步方法被  WebmvcThread-2  结束执行于2023-10-20 15:08:28
Call方法被  WebmvcThread-2  结束执行于 2023-10-20 15:08:28

并且浏览器有显示返回值

image-20231020153651796

DeferredResult

一是可以封装异步请求结果返回到前端

二是允许在处理请求的方法中延迟发送响应,直到**满足某个条件,或者达到某个时间限制**。

以下代码实现效果:

  • 如果Servicettl1方法的执行时间**长于5秒**,就不等它,转而将ttl2方法的超时处理结果进行返回给前端显示;
  • 如果Servicettl1方法的执行时间**短于5秒**,就将ttl1方法的正常业务结果返回给前端显示;
  • 如果Servicettl1方法的执行时间**等于5秒**,返回ttl1方法的正常业务结果

配置类

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

@Configuration
@EnableAsync
public class AsyncConfig {

    @Bean(name = "asyncExecutor")
    public ThreadPoolTaskExecutor asyncExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        // 核心线程数
        executor.setCorePoolSize(10);
        // 最大线程数
        executor.setMaxPoolSize(100);
        // 队列容量,如果此数字大于0,使用队列LinkedBlockingQueue
        executor.setQueueCapacity(100);
        // 线程名称前缀
        executor.setThreadNamePrefix("WebmvcThread-");
        //初始化
        executor.initialize();

        return executor;
    }

}

Controller

import com.example.bean.Demo;
import com.example.service.TtlTool;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RestController;
import java.text.SimpleDateFormat;
import java.util.Date;
import org.springframework.web.context.request.async.DeferredResult;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.function.BiConsumer;


@RestController
public class IndexController {

    @Autowired
    private TtlTool ttlTool;
    
    /**
     * 如果ttl1方法的执行时间短于5秒,Controller就会返回ttl1的结果
     * 如果ttl1方法的执行时间大于5秒,Controller就会返回ttl2的结果
     * */
    @RequestMapping("/user/{name}")
    public DeferredResult<Demo> demo(@PathVariable String name) throws InterruptedException, ExecutionException {

        System.out.println("Controller方法被  "+Thread.currentThread().getName()+"  开始执行于 "+ new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));

        //5秒之后,返回ttl2()方法结果给response
        DeferredResult<Demo> deferredResult = new DeferredResult<>(5000l,ttlTool.ttl2());

        CompletableFuture<Demo> d = ttlTool.ttl1();

        //当d中的任务完成后,whenComplete中的逻辑才会执行
        d.whenComplete(new BiConsumer<Demo, Throwable>() {
            @Override
            public void accept(Demo result, Throwable throwable) {
                if (throwable != null) {
                    deferredResult.setErrorResult(throwable.getMessage());
                } else {
                    //此方法一执行,上面的deferredResult的5秒限制就失效了
                    deferredResult.setResult(result);
                }
            }
        });

        System.out.println("Controller方法被  "+Thread.currentThread().getName()+"  结束执行于 "+ new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
        System.out.println();

        return deferredResult;
    }
    
}

Service

package com.example.service;

import com.example.bean.Demo;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.CompletableFuture;

@Component
public class TtlTool {

    @Async
    public CompletableFuture<Demo> ttl1() throws InterruptedException {

        System.out.println("异步方法被  "+Thread.currentThread().getName()+"  开始执行于 "+ new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
        
        //模拟耗时业务逻辑执行,把这里的数值改成大于5000和小于5000分别看结果
        Thread.sleep(3000);

        Demo d = new Demo();
        d.setOne("1");
        d.setTwo("2");
        d.setThree("3");
        d.setFour("4");

        System.out.println("异步方法被  "+Thread.currentThread().getName()+"  结束执行于"+ new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));

        CompletableFuture<Demo> result = CompletableFuture.completedFuture(d);

        return result;
    }


    public Demo ttl2(){

        System.out.println("超时处理方法被  "+Thread.currentThread().getName()+"  开始执行于 "+ new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));

        Demo d = new Demo();
        d.setOne("11");
        d.setTwo("12");
        d.setThree("13");
        d.setFour("14");

        System.out.println("超时处理方法被  "+Thread.currentThread().getName()+"  结束执行于"+ new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
        
        return d;
    }
    
}

结果

业务执行时间小于DeferredResult的超时时间:

ttl1方法执行时间小于5秒

Controller方法被  http-nio-8081-exec-1  开始执行于 2023-10-20 15:52:56

超时处理方法被  http-nio-8081-exec-1  开始执行于 2023-10-20 15:52:56
超时处理方法被  http-nio-8081-exec-1  结束执行于2023-10-20 15:52:56

异步方法被  WebmvcThread-1  开始执行于 2023-10-20 15:52:56

Controller方法被  http-nio-8081-exec-1  结束执行于 2023-10-20 15:52:56

异步方法被  WebmvcThread-1  结束执行于2023-10-20 15:52:59

返回的是ttl1方法的结果,即正常的业务结果

image-20231020155313220

业务执行时间大于DeferredResult的超时时间:

ttl1方法执行时间大于5秒,将上面Service代码的Thread.sleep()设置大于5秒,看结果:

Controller方法被  http-nio-8081-exec-1  开始执行于 2023-10-20 16:09:30

超时处理方法被  http-nio-8081-exec-1  开始执行于 2023-10-20 16:09:30
超时处理方法被  http-nio-8081-exec-1  结束执行于2023-10-20 16:09:30

异步方法被  WebmvcThread-1  开始执行于 2023-10-20 16:09:30

Controller方法被  http-nio-8081-exec-1  结束执行于 2023-10-20 16:09:30

异步方法被  WebmvcThread-1  结束执行于2023-10-20 16:09:36

返回的是ttl2方法的结果,即DeferredResult的超时结果

image-20231020160949530

业务执行时间等于DeferredResult的超时时间:

ttl1方法执行时间等于5秒,将上面Service代码的Thread.sleep()设置为5秒,看结果:

Controller方法被  http-nio-8081-exec-1  开始执行于 2023-10-20 16:11:16

超时处理方法被  http-nio-8081-exec-1  开始执行于 2023-10-20 16:11:16
超时处理方法被  http-nio-8081-exec-1  结束执行于2023-10-20 16:11:16

异步方法被  WebmvcThread-1  开始执行于 2023-10-20 16:11:16

Controller方法被  http-nio-8081-exec-1  结束执行于 2023-10-20 16:11:16

异步方法被  WebmvcThread-1  结束执行于2023-10-20 16:11:21

返回的是ttl1方法的结果,即正常的业务结果

image-20231020161226811

Future及其派生

Future以及它的子类CompletableFuture都可以进行回调返回

CompletableFuture更加灵活,优于Future

配置类

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

@Configuration
@EnableAsync
public class AsyncConfig {

    @Bean(name = "asyncExecutor")
    public ThreadPoolTaskExecutor asyncExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        // 核心线程数
        executor.setCorePoolSize(10);
        // 最大线程数
        executor.setMaxPoolSize(100);
        // 队列容量,如果此数字大于0,使用队列LinkedBlockingQueue
        executor.setQueueCapacity(100);
        // 线程名称前缀
        executor.setThreadNamePrefix("WebmvcThread-");
        //初始化
        executor.initialize();

        return executor;
    }

}

Controller

import com.example.bean.Demo;
import com.example.service.TtlTool;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RestController;
import java.text.SimpleDateFormat;
import java.util.Date;
import org.springframework.web.context.request.async.DeferredResult;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.function.BiConsumer;


@RestController
public class IndexController {

    @Autowired
    private TtlTool ttlTool;

    @RequestMapping("/user/{name}")
    public Future<Demo> demo(@PathVariable String name) throws InterruptedException, ExecutionException {

        System.out.println("Controller方法被  "+Thread.currentThread().getName()+"  开始执行于 "+ new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
		
        //返回CompletableFuture也可以,把方法上面的Future<Demo>也改成CompletableFuture
        //CompletableFuture<Demo> d = ttlTool.ttl1();

        Future<Demo> d = ttlTool.ttl1();

        System.out.println("Controller方法被  "+Thread.currentThread().getName()+"  结束执行于 "+ new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));

        return d;
    }

}

Service

import com.example.bean.Demo;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.CompletableFuture;

@Component
public class TtlTool {

    @Async
    public CompletableFuture<Demo> ttl1() throws InterruptedException {

        System.out.println("异步方法被  "+Thread.currentThread().getName()+"  开始执行于 "+ new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));

        Thread.sleep(3000);

        Demo d = new Demo();
        d.setOne("1");
        d.setTwo("2");
        d.setThree("3");
        d.setFour("4");

        System.out.println("异步方法被  "+Thread.currentThread().getName()+"  结束执行于"+ new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));

        CompletableFuture<Demo> result = CompletableFuture.completedFuture(d);

        return result;
    }
    
}

结果

Controller方法被  http-nio-8081-exec-1  开始执行于 2023-10-20 16:30:25
Controller方法被  http-nio-8081-exec-1  结束执行于 2023-10-20 16:30:25
异步方法被  WebmvcThread-1  开始执行于 2023-10-20 16:30:25
异步方法被  WebmvcThread-1  结束执行于2023-10-20 16:30:28

前端有返回

image-20231020163056009

三者区别

Callable

比较简单,直接在call方法中写任务即可,结合WebMvcConfigurer配置实现请求异步处理

DeferredResult

真正的作用是延迟返回。

在异步任务处理完成后,可以使用DeferredResultsetResult方法封装异步结果,这样Controller就会在setResult方法执行后才进行结果返回。除此之外,还可以通过DeferredResult的构造方法设置超时时间,实现异步处理的超时兜底。

值得注意的是,在使用DeferredResult进行结果返回时,会开启一个新的tomcat线程进行返回,前面执行Controller请求的线程会被释放。

一般使用自己配置的异步线程池执行任务,并使用setResult方法设置结果,这样用于返回结果的tomcat线程就不会涉及到耗时处理。从而节省资源。

Future及其派生

其实主要是它的子类CompletableFuture

  • CompletableFuture可以对多种有参无参以及有无返回值的情况处理,还可以组合多个异步任务执行,非常灵活。而Callable无法做到这一点
  • CompletableFuture可以创建异步任务,Future只能接收异步结果

CallableFuture区别

在Java中,CallableFuture是用于支持异步计算和获取结果的关键接口。它们之间的区别如下:

  1. 功能不同:
    • Callable接口代表一个可以返回结果的异步计算任务,它的call()方法可以在计算完成后返回一个结果。Callable通常被用于ExecutorService中提交任务。
    • Future接口代表一个异步计算的结果,它提供了一些方法来检查任务是否完成、获取任务的计算结果或取消任务的执行。
  2. 返回值类型不同:
    • Callablecall()方法定义了一个泛型返回值类型,可以通过Future<T>来获取计算结果,其中T表示计算结果的类型。
    • Futureget()方法返回一个泛型类型的结果,可以是Callable的计算结果或者抛出的异常。
  3. 异步处理方式不同:
    • Callable是一个主动执行的接口,任务执行完成后会返回结果。
    • Future是一个被动获取结果的接口,可以通过调用get()方法来等待并获取异步任务的结果。
  4. 取消任务的能力不同:
    • Callable接口没有提供取消任务的功能。
    • Future接口提供了cancel()方法来取消异步任务的执行。

综上所述,CallableFuture是Java中用于支持异步计算的接口,Callable用于定义异步计算任务,而Future用于获取异步计算的结果,并提供了一些其他的方法来管理和取消任务的执行。

  • 11
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值