跳至主要內容

SpringBoot异步编程

wangdx大约 14 分钟

SpringBoot 异步处理简介

多线程并发处理

  • 在实际项目开发中,如果要想提升 WEB 应用的访问处理性能,那么最佳的做法就是启动若干个异步处理线程,而后分别由这些线程完成部分的业务处理,最终再将若干个线程的执行结果汇总在一起后进行响应。
  • 在 SpringBoot 中提供了非常方便的多线程实现机制,开发者可以直接在控制器中以多线程的方式进行用户的请求响应,同时为了便于异步线程的管理,还提供有 WebAsyncTask、DeferredResult 线程管理类

Callable 实现异步处理

异步线程处理

  • SpringBoot 应用程序运行在 Java 虚拟机之中,这样在进行用户请求处理时,就可以采用 Java 所提供的多线程机制,直接创建一个新的异步线程并进行处理。同时在 SpringBoot 中也可以直接进行该线程类对象的响应,这样就可以在线程处理完成后直接由异步线程进行用户请求响应

异步任务线程池

  • 从控制台可以看见,异步响应的线程使用的是名为:“task-1”的线程。再次访问时若采用默认设置,会使用默认的线程池进行处理就是“task-2”了。
1、
package com.yootk.action;

import com.yootk.common.action.abs.AbstractBaseAction;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.concurrent.Callable;

@RestController // 直接基于Rest架构进行处理,省略了@ResponseBody注解
@RequestMapping("/async/*") // 添加父路径
@Slf4j // 直接进行日志的启用
public class MessageAction extends AbstractBaseAction { // 控制层的实现类
    @RequestMapping("callable") // 子路径
    public Object echo(String msg) { // 进行请求参数的接收以及请求内容的回应
        log.info("外部线程:{}", Thread.currentThread().getName()); // 日志输出
        // 所有的子线程都是由主线程启动的,所以此时要观察主从两个线程的名称
        return new Callable<String>() { // 返回一个异步线程
            @Override
            public String call() throws Exception {
                log.info("内部线程:{}", Thread.currentThread().getName()); // 日志输出
                return "【ECHO】" + msg; // 数据响应
            }
        };
    }
}

2、
localhost/async/callable?msg=沐言科技:www.yootk.com

3、
package com.yootk.config;

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

import java.util.concurrent.ThreadPoolExecutor;

@Configuration
public class CustomeAsyncPoolConfig implements WebMvcConfigurer { // 自定义的线程池配置

    @Override
    public void configureAsyncSupport(AsyncSupportConfigurer configurer) { // 异步配置
        configurer.setDefaultTimeout(10000); // 配置超时时间
        configurer.registerCallableInterceptors(this.getTimeoutInterceptor()); // 设置Callable拦截器
        configurer.setTaskExecutor(this.getAysncThreadPoolTaskExecutor()); // 异步任务执行配置
    }
    @Bean(name = "asyncPoolTaskExecutor") // SpringBoot内部本身就有线程池提供
    public ThreadPoolTaskExecutor getAysncThreadPoolTaskExecutor() {
        ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
        taskExecutor.setCorePoolSize(20); // 内核线程的个数(物理线程个数 * 2)
        taskExecutor.setMaxPoolSize(200); // 工作线程池大小
        taskExecutor.setQueueCapacity(25); // 设置一个延迟队列大小
        taskExecutor.setKeepAliveSeconds(200); // 存活时间
        taskExecutor.setThreadNamePrefix("yootk - "); // 配置前缀,自定义的个性化配置
        taskExecutor.setRejectedExecutionHandler( // 配置拒绝策略
                new ThreadPoolExecutor.CallerRunsPolicy());
        taskExecutor.initialize(); // 线程池初始化配置
        return taskExecutor;
    }
    @Bean
    public TimeoutCallableProcessingInterceptor getTimeoutInterceptor() {   // 超时处理
        return new TimeoutCallableProcessingInterceptor();
    }
}

WebAsyncTask

WebAsyncTask

  • WebAsyncTask 是一个由 Spring 提供的异步任务管理类,开发者可以直接在此类中配置要执行的请求处理的异步线程,同时也可以配置一个个与之相关的超时管理及处理线程这样在程序出现了超时问题后,可以启动另外一个线程进行处理
1、
package com.yootk.action;

import com.yootk.common.action.abs.AbstractBaseAction;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.request.async.WebAsyncTask;

import java.util.concurrent.Callable;
import java.util.concurrent.TimeUnit;

@RestController // 直接基于Rest架构进行处理,省略了@ResponseBody注解
@RequestMapping("/async/*") // 添加父路径
@Slf4j // 直接进行日志的启用
public class MessageAction extends AbstractBaseAction { // 控制层的实现类
    @RequestMapping("callable") // 子路径
    public Object echo(String msg) { // 进行请求参数的接收以及请求内容的回应
        log.info("外部线程:{}", Thread.currentThread().getName()); // 日志输出
        // 所有的子线程都是由主线程启动的,所以此时要观察主从两个线程的名称
        Callable<String> callable = new Callable<String>() { // 返回一个异步线程
            @Override
            public String call() throws Exception {
                log.info("内部线程:{}", Thread.currentThread().getName()); // 日志输出
                TimeUnit.SECONDS.sleep(1); // 模拟操作延迟
                return "【ECHO】" + msg; // 数据响应
            }
        };
        WebAsyncTask task = new WebAsyncTask(200, callable); // 200毫秒解决异步处理
        task.onTimeout(new Callable<String>() { // 超时线程
            @Override
            public String call() throws Exception {
                log.info("超时线程:{}", Thread.currentThread().getName());
                return "【ERROR】" + msg;
            }
        });
        return task;
    }
}

2、
localhost/async/webAsyncTask?msg=沐言科技:www.yootk.com

DeferredResult

DeferredResult

  • DeferredResult 也是一个实现异步线程处理结构,开发者可以直接针将异步处理线程的执行结果保存在 DeferredResult 对象实例中进行保存,同时也可以使用 DeferredResult 中提供的状态监听方法对处理超时以及处理完成的状态设置不同的处理线程
1、
package com.yootk.action;

import com.yootk.common.action.abs.AbstractBaseAction;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.context.request.async.DeferredResult;
import org.springframework.web.context.request.async.WebAsyncTask;

import javax.servlet.http.HttpServletRequest;
import java.util.concurrent.Callable;
import java.util.concurrent.TimeUnit;

@RestController // 直接基于Rest架构进行处理,省略了@ResponseBody注解
@RequestMapping("/async/*") // 添加父路径
@Slf4j // 直接进行日志的启用
public class MessageAction extends AbstractBaseAction { // 控制层的实现类
    @Autowired // 实例注入
    private ThreadPoolTaskExecutor threadPoolTaskExecutor; // 获取线程池
    @RequestMapping("runnable") // 子路径
    public Object echo(String msg) { // 进行请求参数的接收以及请求内容的回应
        log.info("外部线程:{}", Thread.currentThread().getName()); // 日志输出
        HttpServletRequest request = ((ServletRequestAttributes)RequestContextHolder.getRequestAttributes()).getRequest();
        DeferredResult<String> result = new DeferredResult<>(6000L); // 设置异步响应
        result.onTimeout(new Runnable() { // 超时处理
            @Override
            public void run() {
                log.info("超时线程:{}", Thread.currentThread().getName()); // 日志输出
                result.setResult("【请求超时】" + request.getRequestURL()); // 超时路径
            }
        });
        result.onCompletion(new Runnable() { // 完成处理线程
            @Override
            public void run() {
                log.info("完成线程:{}", Thread.currentThread().getName()); // 日志输出
            }
        });
        this.threadPoolTaskExecutor.execute(new Runnable() { //线程核心任务
            @Override
            public void run() {
                log.info("内部线程:{}", Thread.currentThread().getName()); // 日志输出
                try {
                    TimeUnit.SECONDS.sleep(2); // 模拟延迟
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                result.setResult("【ECHO】" + msg); // 执行最终的响应
            }
        });
        return result;
    }
}

2、
localhost/async/deferredResult?msg=沐言科技:www.yootk.com

SpringBoot 异步任务

异步任务

  • 在异步编程中,除了可以使用异步线程进行用户请求响应之外,也可以开启一个新的异步任务执行某些耗时操作,同时此任务与用户的响应无关,并且在用户已经响应完成后还有可能继续执行
1、
package com.yootk.config;

import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler;
import org.springframework.aop.interceptor.SimpleAsyncUncaughtExceptionHandler;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.AsyncConfigurer;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

import java.util.concurrent.Executor;

@Configuration
@EnableAsync // 启用异步处理
public class DefaultThreadPoolConfig implements AsyncConfigurer { // 异步配置

    @Override
    public Executor getAsyncExecutor() { // 异步的执行者
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); // 创建一个线程池
        executor.setCorePoolSize(10); // 核心线程数
        executor.setMaxPoolSize(20); // 最大数量
        executor.setQueueCapacity(100); // 延迟队列长度
        executor.setThreadNamePrefix("muyan - "); // 设置线程名称后缀
        executor.initialize();
        return executor;
    }

    @Override
    public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() { // 异常处理
        return new SimpleAsyncUncaughtExceptionHandler();
    }
}


2、
package com.yootk.task;

import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;

import java.util.concurrent.TimeUnit;

@Component
@Slf4j
public class YootkThreadTask {
    @Async // 异步处理
    public void startTaskHandle() { // 这是一个普通的方法
        log.info("【异步线程】开启,执行线程:{}", Thread.currentThread().getName());
        try {   // 往往都是耗时任务进行异步处理,所以此时模拟一个延迟
            TimeUnit.SECONDS.sleep(5);
        } catch (InterruptedException e) {
        }
        log.info("【异步线程】结束,执行线程:{}", Thread.currentThread().getName());
    }
}


3、
package com.yootk.action;

import com.yootk.common.action.abs.AbstractBaseAction;
import com.yootk.task.YootkThreadTask;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController // 直接基于Rest架构进行处理,省略了@ResponseBody注解
@RequestMapping("/async/*") // 添加父路径
@Slf4j // 直接进行日志的启用
public class MessageAction extends AbstractBaseAction { // 控制层的实现类
    @Autowired
    private YootkThreadTask task; // 异步任务
    @RequestMapping("task") // 子路径
    public Object echo(String msg) { // 进行请求参数的接收以及请求内容的回应
        log.info("外部线程:{}", Thread.currentThread().getName()); // 日志输出
        this.task.startTaskHandle(); // 异步任务
        return "【ECHO】" + msg;
    }
}

4、
localhost/async/task?msg=沐言科技:www.yootk.com

响应式编程简介

传统请求处理

  • 在 Servlet 3.0 标准以前,每一个 Servet 都是采用“Thread-Per-Request"(每个请求对应一个处理线程)的方式进行请求处理的,即:每一次的 HTTP 请求都是由某一个容器的工作线程从头负责到尾的,而如果此时该请求线程执行了某些有高延时操作的代码(例如:数据库访问、第三方接口调用等)

异步请求处理

  • 在 Servlet 3.0 标准之后为了解决此类问题,所以提供了异步响应的支持,在异步响应处理结构中,可以将耗时的操作部分交由一个专属的异步线程进行响应处理,同时请求线程资源将被释放,并将该线程返回到线程池中,以供其他用户使用这样的操作机制将极大的提升程序的并发处理性能

WebFlux 与 SpringMVC

  • 而要想在 Spring 中实现响应式编程,那么则需要使用到 Spring WebFlux,该组件是-个重新构建的且基于 Reactive Streams 标准实现的异步非阻塞 WEB 开发框架,以 Reactor 开发框架为基础,可以更加容易的实现高并发访问下的请求处理模型,在 spingBoot?x 版本中提供了“spng wabn-依赖模 Й√ 多横块有!y-webflux”依赖模块,该模块有两种编程模型实现:
1、
java.util.stream

2、
https://projectreactor.io/

WebFlux 终端响应

WebFlux 处理终端

  • Spring WebFlux 模块基于 Reactor 开发框架实现,在进行具体请求处理前,需要首先个请求终端,而后依据路由匹配的地址找到指定终端类中提供的处理方法进行操配置一作
1、
// https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-webflux
implementation group: 'org.springframework.boot', name: 'spring-boot-starter-webflux', version: '2.4.5'


2、
project('microboot-webflux') { // 子模块
    dependencies { // 配置子模块依赖
        implementation('org.springframework.boot:spring-boot-starter-webflux')
    }
}

3、
package com.yootk.webflux.handler;

import org.springframework.stereotype.Component;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.reactive.function.server.ServerResponse;
import reactor.core.publisher.Mono;
@Component // 进行组件注册
public class MessageHandler { // 这是一个WebFlux处理模块了
    // 在WebFlux编程里面如果要进行响应的话会区分单个响应(单一对象)以及多个响应(集合)
    public Mono<ServerResponse> echoHandler(ServerRequest request) {    // 请求接收以及响应
        return ServerResponse.ok()
                .header("Content-Type", "text/html;charset=UTF-8")  // 响应头信息
                .body(BodyInserters.fromValue("沐言科技:www.yootk.com"));
    }
}


4、
package com.yootk.webflux.router;

import com.yootk.webflux.handler.MessageHandler;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.MediaType;
import org.springframework.web.reactive.function.server.*;

@Configuration
public class MessageRouter { // 消息路由处理
    @Bean // 进行路由的功能注册
    public RouterFunction<ServerResponse> routeEcho(MessageHandler messageHandler) {
        // 此时绑定的访问模式为GET请求模式,而后设置了具体的访问地址“/echo”
        return RouterFunctions.route(RequestPredicates.GET("/echo") // 匹配最终的处理路由地址
                .and(RequestPredicates.accept(MediaType.TEXT_PLAIN)) // 设置了匹配的请求类型
                , messageHandler::echoHandler); // 匹配最终的处理路由地址
    }
}


5、
package com.yootk;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class StartWebFluxApplication {
    public static void main(String[] args) {
        SpringApplication.run(StartWebFluxApplication.class, args);
    }
}


6、
localhost:8080/echo

7、
package com.yootk.action;

import com.yootk.vo.Message;
import com.yootk.webflux.handler.MessageHandler;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.InitBinder;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.beans.PropertyEditorSupport;
import java.time.Instant;
import java.time.LocalDate;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;

@RestController
@RequestMapping("/message/*")
@Slf4j
public class MessageAction { // 正常访问的路由配置
    @Autowired
    private MessageHandler messageHandler; // WebFlux处理程序
    // 在现在的开发之中如果要将字符串转为日期时间,考虑到多线程环境下的并发问题,所以一定要使用LocalDate
    private static final DateTimeFormatter LOCAL_DATE_FORMAT = DateTimeFormatter.ofPattern("yyyy-MM-dd");
    @InitBinder
    public void initBinder(WebDataBinder binder) {
        binder.registerCustomEditor(java.util.Date.class, new PropertyEditorSupport() {
            @Override
            public void setAsText(String text) throws IllegalArgumentException {
                LocalDate localDate = LocalDate.parse(text, LOCAL_DATE_FORMAT);
                Instant instant = localDate.atStartOfDay().atZone(ZoneId.systemDefault()).toInstant();
                super.setValue(java.util.Date.from(instant));
            }
        });
    }
    @RequestMapping("echo")
    public Object echo(Message message) {
        log.info("接收用户范维根信息,用户发送的参数为:message = {}", message);
        // 思考:按照常规的做法此时肯定也要返回业务层的处理结果
        return this.messageHandler.echoHandler(message);
    }
}


8、
localhost:8080/message/echo?title=沐言科技&content=www.yootk.com&pubdate=2025-10-01

Springboot 整合 webflus

1、
package com.yootk.vo;

import lombok.Data;

import java.util.Date;

@Data
public class Message {
    private String title;
    private Date pubdate;
    private String content;
}


2、
package com.yootk.webflux.handler;

import com.yootk.vo.Message;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Mono;

@Component // 进行组件注册
@Slf4j // 日志输出
public class MessageHandler { // 这是一个WebFlux处理模块了
    // 此时不再关注传统的ServerResponse以及ServletRequest类型了
    public Mono<Message> echoHandler(Message message) { // 直接以最终的数据类型进行操作
        log.info("【{}】业务层接收处理数据:{}", Thread.currentThread().getName(), message);
        message.setTitle("【" + Thread.currentThread().getName() + "】" + message.getTitle());
        message.setContent("【" + Thread.currentThread().getName() + "】" + message.getContent());
        return Mono.create(monoSink -> monoSink.success(message)); // 实现数据响应
    }
}

Flux 返回集合数据

1、
package com.yootk.webflux.handler;

import com.yootk.vo.Message;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@Component // 进行组件注册
@Slf4j // 日志输出
public class MessageHandler { // 这是一个WebFlux处理模块了
    public Flux<Message> list(Message message) {    // 返回集合数据
        List<Message> messageList = new ArrayList<>(); // 创建一个List集合
        for (int x = 0; x < 10; x++) {  // 10个长度的集合
            Message msg = new Message(); // 创建新的Message实例
            msg.setTitle("【" + x + "】" + message.getTitle());
            msg.setContent("【" + x + "】" + message.getContent());
            msg.setPubdate(message.getPubdate()); // 直接使用已经配置的日期时间
            messageList.add(msg); // 实现集合的存储
        }
        return Flux.fromIterable(messageList); // 实现了集合响应
    }
    // 每一次进行响应结果配置的时候,都只能够配置具体的类型
    public Flux<Map.Entry<String, Message>> map(Message message) {
        Map<String, Message> map = new HashMap<>();
        for (int x = 0; x < 10; x++) { // 通过迭代配置数据
            Message msg = new Message(); // 创建新的Message实例
            msg.setTitle("【" + x + "】" + message.getTitle());
            msg.setContent("【" + x + "】" + message.getContent());
            msg.setPubdate(message.getPubdate()); // 直接使用已经配置的日期时间
            map.put("yootk - " + x, msg); // 保存Map集合项
        }
        return Flux.fromIterable(map.entrySet());
    }
    public Mono<Message> echoHandler(Message message) { // 直接以最终的数据类型进行操作
        log.info("【{}】业务层接收处理数据:{}", Thread.currentThread().getName(), message);
        message.setTitle("【" + Thread.currentThread().getName() + "】" + message.getTitle());
        message.setContent("【" + Thread.currentThread().getName() + "】" + message.getContent());
        return Mono.create(monoSink -> monoSink.success(message)); // 实现数据响应
    }
}


2、
package com.yootk.action;

import com.yootk.vo.Message;
import com.yootk.webflux.handler.MessageHandler;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.InitBinder;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.beans.PropertyEditorSupport;
import java.time.Instant;
import java.time.LocalDate;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;

@RestController
@RequestMapping("/message/*")
@Slf4j
public class MessageAction { // 正常访问的路由配置
    @Autowired
    private MessageHandler messageHandler; // WebFlux处理程序
    // 在现在的开发之中如果要将字符串转为日期时间,考虑到多线程环境下的并发问题,所以一定要使用LocalDate
    private static final DateTimeFormatter LOCAL_DATE_FORMAT = DateTimeFormatter.ofPattern("yyyy-MM-dd");
    @InitBinder
    public void initBinder(WebDataBinder binder) {
        binder.registerCustomEditor(java.util.Date.class, new PropertyEditorSupport() {
            @Override
            public void setAsText(String text) throws IllegalArgumentException {
                LocalDate localDate = LocalDate.parse(text, LOCAL_DATE_FORMAT);
                Instant instant = localDate.atStartOfDay().atZone(ZoneId.systemDefault()).toInstant();
                super.setValue(java.util.Date.from(instant));
            }
        });
    }
    @RequestMapping("echo")
    public Object echo(Message message) {
        log.info("接收用户范维根信息,用户发送的参数为:message = {}", message);
        // 思考:按照常规的做法此时肯定也要返回业务层的处理结果
        return this.messageHandler.echoHandler(message);
    }
    @RequestMapping("list")
    public Object list(Message message) {
        log.info("接收用户范维根信息,用户发送的参数为:message = {}", message);
        return this.messageHandler.list(message);
    }
    @RequestMapping("map")
    public Object map(Message message) {
        log.info("接收用户范维根信息,用户发送的参数为:message = {}", message);
        return this.messageHandler.map(message);
    }
}


3、
localhost:8080/message/list?title=沐言科技&content=www.yootk.com&pubdate=2025-10-01

4、
localhost:8080/message/map?title=沐言科技&content=www.yootk.com&pubdate=2025-10-01

WebSocket 处理支持

1、
package com.yootk.webflux.websocket;

import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.web.reactive.socket.WebSocketHandler;
import org.springframework.web.reactive.socket.WebSocketSession;
import reactor.core.publisher.Mono;
@Component
@Slf4j
public class EchoHandler implements WebSocketHandler { // 直接实现WebSocket支持
    @Override
    public Mono<Void> handle(WebSocketSession session) {
        log.info("WebSocket客户端握手信息:{}", session.getHandshakeInfo().getUri());
        return session.send(session.receive()
                .map(msg -> session.textMessage("【ECHO】" + msg.getPayloadAsText())));
    }
}



2、
package com.yootk.config;

import com.yootk.webflux.websocket.EchoHandler;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.web.reactive.HandlerMapping;
import org.springframework.web.reactive.handler.SimpleUrlHandlerMapping;
import org.springframework.web.reactive.socket.WebSocketHandler;
import org.springframework.web.reactive.socket.server.support.WebSocketHandlerAdapter;

import java.util.HashMap;
import java.util.Map;

@Configuration
public class WebSocketConfig { // 配置WebSocket
    @Bean
    public HandlerMapping websocketMapping(
            @Autowired EchoHandler echoHandler) {
        Map<String, WebSocketHandler> map = new HashMap<>(); // 定义所有的映射集合
        map.put("/websocket/{token}", echoHandler); // 配置映射模型
        SimpleUrlHandlerMapping mapping = new SimpleUrlHandlerMapping(); // 映射处理
        mapping.setOrder(Ordered.HIGHEST_PRECEDENCE); // 优先配置
        mapping.setUrlMap(map); // 映射路径
        return mapping; // 返回最终的映射
    }
    @Bean
    public WebSocketHandlerAdapter handlerAdapter() {
        return new WebSocketHandlerAdapter(); // 处理配置器
    }
}


3、
server:
  port: 80

4、
<!DOCTYPE HTML>
<head>
    <title>WebSocket交互</title>
    <meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/> <!-- 页面编码 -->
    <link rel="icon" type="image/x-icon" href="images/favicon.ico"/> <!-- 页面ICON -->
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <script type="text/javascript" src="js/jquery.min.js"></script>
    <script type="text/javascript" src="bootstrap/js/bootstrap.min.js"></script>
    <link rel="stylesheet" type="text/css" href="bootstrap/css/bootstrap.min.css"/>
    <script type="text/javascript"> // WebSocket是需要通过JS编写的
    	url = "ws://localhost/websocket/yootk-token"; // 此时采用的是一个默认的token标记
    	window.onload = function() {	// 本次不使用开发框架,直接采用原生的JS进行开发
    		webSocket = new WebSocket(url); // 获取WebSocket类的实例化对象
    		webSocket.onopen = function(dev) {	// 进行连接的开启
    			document.getElementById("messageDiv").innerHTML +=
    				"<p>服务器连接成功,开始进行消息的交互处理。</p>"
    		}
    		webSocket.onclose = function() {
    			document.getElementById("messageDiv").innerHTML +=
    				"<p>消息交互完毕,关闭连接通道。</p>"
    		}
    		document.getElementById("send").addEventListener("click", function() {
    			inputMessage = document.getElementById("msg").value; // 获取文本输入内容
    			webSocket.send(inputMessage); // 进行消息的发送
    			webSocket.onmessage = function(obj) {	// 消息的回应处理
    				document.getElementById("messageDiv").innerHTML +=
    					"<p>" + obj.data + "</p>"; // 接收内容响应
    				document.getElementById("msg").value = ""; // 清空文本输入框
    			}
    		});
    		document.getElementById("close").addEventListener("click", function() {
    			webSocket.close(); // 关闭
    		})
    	}
    </script>
</head>
<body>
<div>&nbsp;</div>
<div class="row" style="margin: 10px;">
    <div class="panel panel-success">
        <div class="panel-heading">
            <strong><i class="glyphicon glyphicon-th-list"></i>&nbsp;WebSocket数据交互</strong>
        </div>
        <div class="panel-body">
			<div id="inputDiv">
				<form class="form-horizontal" id="messageform">
					<div class="form-group" id="midDiv">
						<label class="col-md-2 control-label">输入信息:</label>
						<div class="col-md-8">
							<input type="text" id="msg" name="msg" class="form-control" placeholder="请输入交互信息...">
						</div>
						<div class="col-md-2">
							<button type="button" class="btn btn-primary btn-sm" id="send">发送</button>
							<button type="button" class="btn btn-danger btn-sm" id="close">关闭</button>
						</div>
					</div>
				</form>
			</div>
			<div id="messageDiv"></div>
        </div>
        <div class="panel-footer">
            <div style="text-align:right;">
                <img src="images/logo.png" style="height: 30px;">
                <strong>沐言科技(www.yootk.com) —— 新时代软件教育领导品牌</strong>
            </div>
        </div>
    </div>
</div>
</body>
</html>

demo


上次编辑于: