SpringBoot的DeferredResult案例:DeferredResult的超时处理方式

DeferredResult的超时处理,采用委托机制,也就是在实例DeferredResult时给予一个超时时长(毫秒),同时在onTimeout中委托(传入)一个新的处理线程(我们可以认为是超时线程);当超时时间到来,DeferredResult启动超时线程,超时线程处理业务,封装返回数据,给DeferredResult赋值(正确返回的或错误返回的)。

这个实例可以对上一个实例的代码稍作改动即可。

一、增加超时处理任务TimeOutWork

package com.example;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.context.request.async.DeferredResult;

public class TimeOutWork implements Runnable{
 private final Logger logger = LoggerFactory.getLogger(this.getClass());
 private DeferredResult<ResponseMsg<String>> deferredResult;

 public TimeOutWork(DeferredResult<ResponseMsg<String>> deferredResult) {
 this.deferredResult = deferredResult;
 }

 @Override
 public void run() {
 logger.debug("我超时啦!");
 ResponseMsg<String> msg = new ResponseMsg<String>();
 msg.fail("我超时啦!");
 //deferredResult.setResult(msg);
 deferredResult.setErrorResult(msg);
 }
}

二、DeferredResult请求中注册超时任务处理

修改第一个请求,修改了两处,请自己比较

package com.example;
import java.util.HashMap;
import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.request.async.DeferredResult;

@RestController
@RequestMapping("/api")
public class DeferredRestController {

 private final Logger logger = LoggerFactory.getLogger(this.getClass());
 private final Map<Integer, DeferredResult<ResponseMsg<String>>> responseBodyMap = new HashMap<Integer, DeferredResult<ResponseMsg<String>>>();
 private final Map<Integer, RequestMsg> requestBodyMap = new HashMap<Integer, RequestMsg>();

 /**
 * 第一个请求
 *
 * @param req
 * @return
 */
 @RequestMapping("/request1")
 @ResponseBody
 public DeferredResult<ResponseMsg<String>> request1(RequestMsg req) {
 logger.debug("request1:请求参数{}", req.getParam());
 DeferredResult<ResponseMsg<String>> result =new DeferredResult<ResponseMsg<String>>(10000l);//10秒
 result.onTimeout(new TimeOutWork(result));//超时任务
 requestBodyMap.put(1, req);// 把请求放到第一个请求map中
 responseBodyMap.put(1, result);// 把请求响应的DeferredResult实体放到第一个响应map中
 return result;
 }

 /**
 * 第二个请求
 *
 * @param req
 * @return
 */
 @RequestMapping("/request2")
 @ResponseBody
 public DeferredResult<ResponseMsg<String>> request2(RequestMsg req) {
 logger.debug("request2:请求参数{}", req.getParam());
 DeferredResult<ResponseMsg<String>> result = new DeferredResult<ResponseMsg<String>>();
 requestBodyMap.put(2, req);// 把请求放到第二个请求map中
 responseBodyMap.put(2, result);// 把请求响应的DeferredResult实体放到第二个响应map中
 return result;
 }

 /**
 * 第三个请求
 *
 * @param req
 * @return
 */
 @RequestMapping("/request3")
 @ResponseBody
 public DeferredResult<ResponseMsg<String>> request3(RequestMsg req) {
 logger.debug("request3:请求参数{}", req.getParam());
 DeferredResult<ResponseMsg<String>> result = new DeferredResult<ResponseMsg<String>>();
 requestBodyMap.put(3, req);// 把请求放到第三个请求map中
 responseBodyMap.put(3, result);// 把请求响应的DeferredResult实体放到第三个响应map中
 return result;
 }

 /**
 * 控制第x个请求执行返回操作,同时自己也返回同样的值
 *
 * @param x
 * @return
 */
 @RequestMapping(value = "/requestXReturn", method = RequestMethod.POST)
 @ResponseBody
 public ResponseMsg<String> request1Return(Integer x) {
 ResponseMsg<String> msg = new ResponseMsg<String>();
 logger.debug("requestXReturn--1:请求参数{}", x);
 DeferredResult<ResponseMsg<String>> result = responseBodyMap.get(x);
 if (result == null) {
 msg.fail("錯誤!请求已经释放");
 return msg;
 }
 String resultStr = "result" + x.toString() + ". Received:" + requestBodyMap.get(x).getParam();
 msg.success("成功", resultStr);
 result.setResult(msg);// 设置DeferredResult的结果值,设置之后,它对应的请求进行返回处理
 responseBodyMap.remove(x);// 返回map删除
 logger.debug("requestXReturn--2:请求参数{}", x);
 logger.debug("requestXReturn--3:返回参数{}", msg);
 return msg;
 }
}

三、修改页面index.html

<script th:src="@{jquery-1.12.4.min.js}" type="text/javascript"></script>
 <script th:inline="javascript">
 function button1RequestClick(){
 var param=$("#request1RequestId").val();
 $.ajax({
  type:'post',
 url:'/api/request1',
 dataType : 'json',
 data : {
 'param' : param
 },
 success : function(data) {
 console.log(data);
 if (data.status==0){
  $("#request1ResultId").val(data.data);
 } else {
  $("#request1ResultId").val(data.msg);
 }
 },
 error : function(data) {
 console.log("button1RequestClick---error");
 console.log(data);
 //alert("错误消息:" + data);
 }
 });
 };

前后的代码都省略了,其实仅仅修改了

if (data.status==0){
 $("#request1ResultId").val(data.data);
} else {
 $("#request1ResultId").val(data.msg);
}

四、小结

DeferredResult的超时处理比较简单,定义时长及注册一个处理Runnable实例即可。对于setResult、setErrorResult还需要继续研究。

1、setResult

2、setErrorResult

3、isSetOrExpired

补充:解决了DeferredResult请求长时间占用数据库连接的问题

最近看了看开源项目appllo配置中心的源码,发现一个很有意思的东东:

(1)原理:由于使用了DeferredResult,根据Spring DispatcherServlet的默认逻辑,数据库连接只有在异步请求真正返回给客户端的时候才会释放回连接池

(2)应用场景:长连接时间很长,对于大部分请求可能都要数小时以上才会返回。在这么长的一段时间内一直占用着数据库连接是不合理的

长连接场景解决:

@Component
public class EntityManagerUtil extends EntityManagerFactoryAccessor {
 private static final Logger logger = LoggerFactory.getLogger(EntityManagerUtil.class);
 /**
 * close the entity manager.
 * Use it with caution! This is only intended for use with async request, which
 * Spring won't close the entity manager until the async request is finished.
 */
 public void closeEntityManager() {
 EntityManagerHolder emHolder = (EntityManagerHolder)
 TransactionSynchronizationManager.getResource(getEntityManagerFactory());
 if (emHolder == null) {
 return;
 }
 logger.debug("Closing JPA EntityManager in EntityManagerUtil");
 EntityManagerFactoryUtils.closeEntityManager(emHolder.getEntityManager());
 }
}

以上为个人经验,希望能给大家一个参考,也希望大家多多支持我们。如有错误或未考虑完全的地方,望不吝赐教。

(0)

相关推荐

  • 基于springboot 长轮询的实现操作

    springboot 长轮询实现 基于 @EnableAsync , @Sync @SpringBootApplication @EnableAsync public class DemoApplication { public static void main(String[] args) { SpringApplication.run(DemoApplication.class, args); } } @RequestMapping("/async") @RestControlle

  • springboot2.x整合tkmapper的示例代码

    springboot整合tkmapper 1.导入pom依赖 1.1 导入springboot的parent依赖 <parent> <artifactId>spring-boot-starter-parent</artifactId> <groupId>org.springframework.boot</groupId> <version>2.1.9.RELEASE</version> </parent> 1.

  • SpringBoot整合JDBC的实现

    简介 JDBC是最原基本的连接数据源的方式,在springboot中所有和数据源有关系的都在Spring Data家族中,所以我们看看springboot中如何使用JDBC来实现对数据库的增删改查操作. 简单使用 引入依赖 这里我们只引入基本的依赖就好,创建一个springboot项目(这里版本是2.1.6),然后添加以下依赖: <dependencies> <!--jdbc--> <dependency> <groupId>org.springframew

  • SpringBoot集成SpringMVC的方法示例

    Spring MVC是一款优秀的.基于MVC思想的应用框架,它是Spring的一个子框架.是当前最优秀的MVC框架. Spring Boot整合Spring MVC只需在pom.xml中引入 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <version>2.3.7.RE

  • 从零开始搭建springboot+springcloud+mybatis本地项目全过程(图解)

    记录一下从零开始搭建一个springboot+springcloud+mybatis本地项目的demo的过程.纯代码小白一枚,若有不足或错误之处,欢迎广大朋友指出! 开发环境准备: IDE:IntelliJ Idea 2019.3 数据库:mysql 8.0.2.2 SpringBoot版本:2.2.0.RELEASE SpringCloud版本:Hoxton.RELEASE 一.创建一个新的Idea项目 打开Idea,单击New->File->Project,选择Maven,直接下一步 输入

  • SpringBoot的DeferredResult案例:DeferredResult的超时处理方式

    DeferredResult的超时处理,采用委托机制,也就是在实例DeferredResult时给予一个超时时长(毫秒),同时在onTimeout中委托(传入)一个新的处理线程(我们可以认为是超时线程):当超时时间到来,DeferredResult启动超时线程,超时线程处理业务,封装返回数据,给DeferredResult赋值(正确返回的或错误返回的). 这个实例可以对上一个实例的代码稍作改动即可. 一.增加超时处理任务TimeOutWork package com.example; impor

  • 关于SpringBoot整合redis使用Lettuce客户端超时问题

    参考的博客 问题起因 做毕设的时候,使用到Lettuce连接redis,一段时间后不操作,再去操作redis,会报连接超时错误,在其重连后又可使用. 原因是:Lettuce 自适应拓扑刷新(Adaptive updates)与定时拓扑刷新(Periodic updates) 是默认关闭的导致问题的出现 解决的方案 1.重写连接工厂实例,更改其LettuceClientConfiguration 为开启拓扑更新 @Configuration public class RedisConfig { @

  • Java Springboot websocket使用案例详解

    什么是WebSocket WebSocket是一种在单个TCP连接上进行全双工通信的协议 - 为什么要实现握手监控管理 如果说,连接随意创建,不管的话,会存在错误,broken pipe 表面看单纯报错,并没什么功能缺陷等,但实际,请求数增加,容易导致系统奔溃.这边画重点. 出现原因有很多种,目前我这边出现的原因,是因为客户端已关闭连接,服务端还持续推送导致. 如何使用 下面将使用springboot集成的webSocket 导入Maven 首先SpringBoot版本 <parent> &l

  • Java SpringBoot Validation用法案例详解

    目录 constraints分类 对象集成constraints示例 SpringBoot集成自动验证 集成maven依赖 验证RequestBody.Form对象参数 验证简单参数 验证指定分组 全局controller验证异常处理 自定义constraints @DateFormat @PhoneNo 使用自定义constraint注解 问题 提到输入参数的基本验证(非空.长度.大小.格式-),在以前我们还是通过手写代码,各种if.else.StringUtils.isEmpty.Colle

  • 基于Redis结合SpringBoot的秒杀案例详解

    目录 1.构建SpringBoot项目 2.启动类 3.在Controller层里定义秒杀接口 4.在Service层里通过lua脚本实现秒杀效果 5.配置redis连接参数 6.演示秒杀效果 6.1 准备redis环境 6.2 启动项目 6.3 多线程形式发起秒杀请求 1.构建SpringBoot项目 搭建名为quickbuy的springboot项目,相关的依赖包如下所示: <?xml version="1.0" encoding="UTF-8"?>

  • SpringBoot整合Mybatis-plus案例及用法实例

    目录 一.mybatis-plus简介: 二.springboot整合mybatis-plus案例 mybatis plus强大的条件构造器queryWrapper.updateWrapper 总结 一.mybatis-plus简介: Mybatis-Plus(简称MP)是一个 Mybatis 的增强工具,在 Mybatis 的基础上只做增强不做改变,为简化开发.提高效率而生.这是官方给的定义,关于mybatis-plus的更多介绍及特性,可以参考mybatis-plus官网.那么它是怎么增强的

  • Springboot之修改启动端口的两种方式(小结)

    Springboot启动的时候,端口的设定默认是8080,这肯定是不行的,我们需要自己定义端口,Springboot提供了两种方式,第一种,我们可以通过application.yml配置文件配置,第二种,可以通过代码里面指定,在开发中,建议使用修改application.yml的方式来修改端口. 代码地址 #通过yml配置文件的方式指定端口地址 https://gitee.com/yellowcong/springboot-demo/tree/master/springboot-demo2 #硬

  • Springboot+rabbitmq实现延时队列的两种方式

    什么是延时队列,延时队列应用于什么场景 延时队列顾名思义,即放置在该队列里面的消息是不需要立即消费的,而是等待一段时间之后取出消费. 那么,为什么需要延迟消费呢?我们来看以下的场景 网上商城下订单后30分钟后没有完成支付,取消订单(如:淘宝.去哪儿网) 系统创建了预约之后,需要在预约时间到达前一小时提醒被预约的双方参会 系统中的业务失败之后,需要重试 这些场景都非常常见,我们可以思考,比如第二个需求,系统创建了预约之后,需要在预约时间到达前一小时提醒被预约的双方参会.那么一天之中肯定是会有很多个

  • 详解Springboot之接收json字符串的两种方式

    第一种方式.通过关键字段@RequestBody,标明这个对象接收json字符串.还有第二种方式,直接通过request来获取流.在spring中,推荐使用. 代码地址 https://gitee.com/yellowcong/springboot-demo/tree/master/springboot-json 项目结构 其实项目里面没啥类容,就是一个控制器和pom.xml配置 配置fastjson 添加fastjson的依赖到pom.xml中 <dependency> <groupI

  • SpringBoot 中实现跨域的5种方式小结

    一.为什么会出现跨域问题 出于浏览器的同源策略限制.同源策略(Sameoriginpolicy)是一种约定,它是浏览器最核心也最基本的安全功能,如果缺少了同源策略,则浏览器的正常功能可能都会受到影响.可以说Web是构建在同源策略基础之上的,浏览器只是针对同源策略的一种实现. 同源策略会阻止一个域的javascript脚本和另外一个域的内容进行交互.所谓同源(即指在同一个域)就是两个页面具有相同的协议(protocol),主机(host)和端口号(port) 二.什么是跨域 当一个请求url的协议

随机推荐