• Springboot 异步Async教程


    还是老样子,废话不多说,这里直接讲代码,后面讲故事。

    第一章 代码实现

    1、在Controller类中添加【@EnableAsync】注解,注意不是方法上哦。

    1. package cn.renkai721.controller;
    2. import cn.renkai721.bean.*;
    3. import cn.renkai721.common.BaseController;
    4. import cn.renkai721.service.*;
    5. import io.swagger.annotations.Api;
    6. import io.swagger.annotations.ApiResponse;
    7. import io.swagger.annotations.ApiResponses;
    8. import lombok.extern.slf4j.Slf4j;
    9. import org.springframework.beans.factory.annotation.Autowired;
    10. import org.springframework.scheduling.annotation.EnableAsync;
    11. import org.springframework.web.bind.annotation.*;
    12. import javax.servlet.http.HttpServletRequest;
    13. @EnableAsync
    14. @RestController
    15. @RequestMapping("/async")
    16. @Slf4j
    17. @Api(value = "AsyncTestController",tags = {"测试异步"})
    18. public class AsyncTestController extends BaseController {
    19. @Autowired
    20. private AsyncTestService asyncTestService;
    21. @GetMapping("/testAsync")
    22. @ResponseBody
    23. @ApiResponses(value = {
    24. @ApiResponse(code=200,message="返回对象",response=LoginRespBean.class)
    25. })
    26. public String testAsync(HttpServletRequest request) throws Exception {
    27. asyncTestService.getLoginIpAndAddress(request);
    28. return "success";
    29. }
    30. @GetMapping("/sendEmailToUser")
    31. @ResponseBody
    32. @ApiResponses(value = {
    33. @ApiResponse(code=200,message="返回对象",response=LoginRespBean.class)
    34. })
    35. public String sendEmailToUser(UserReqBean vo) throws Exception {
    36. asyncTestService.sendEmailToUser(vo);
    37. return "success";
    38. }
    39. }

    2、在你的Service的方法中【@Async】注解。这两步就可以实现异步操作了。

    1. package cn.renkai721.service;
    2. import cn.renkai721.bean.*;
    3. import lombok.extern.slf4j.Slf4j;
    4. import org.springframework.context.annotation.Configuration;
    5. import org.springframework.scheduling.annotation.Async;
    6. import org.springframework.stereotype.Service;
    7. import javax.servlet.AsyncContext;
    8. import javax.servlet.http.HttpServletRequest;
    9. import java.io.IOException;
    10. import java.net.InetAddress;
    11. import java.net.UnknownHostException;
    12. @Service
    13. @Slf4j
    14. @Configuration
    15. public class AsyncTestService {
    16. @Async
    17. public void getLoginIpAndAddress(HttpServletRequest request){
    18. AsyncContext asyncContext = request.startAsync();
    19. // 这个方法必须写
    20. asyncContext.setTimeout(0);
    21. this.getLoginIpAndAddress(asyncContext);
    22. }
    23. public String getLoginIpAndAddress(AsyncContext asyncContext){
    24. StringBuffer sb = new StringBuffer();
    25. try {
    26. HttpServletRequest request = (HttpServletRequest)asyncContext.getRequest();
    27. String userAgent = request.getHeader("User-Agent");
    28. String device = this.getLoginDevice(userAgent);
    29. String ip = this.getIpAddress(request);
    30. sb.append("device="+device+",");
    31. sb.append("ip="+ip+",");
    32. log.info("device={},ip={}",device,ip);
    33. } catch (Exception e) {
    34. log.error("getLoginIpAndAddress e={}",e);
    35. e.printStackTrace();
    36. }
    37. return sb.toString();
    38. }
    39. @Async
    40. public String getIpAddress(HttpServletRequest request) throws IOException {
    41. // 获取请求主机IP地址,如果通过代理进来,则透过防火墙获取真实IP地址
    42. String ip = request.getHeader("X-Forwarded-For");
    43. if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
    44. if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
    45. ip = request.getHeader("Proxy-Client-IP");
    46. }
    47. if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
    48. ip = request.getHeader("WL-Proxy-Client-IP");
    49. }
    50. if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
    51. ip = request.getHeader("HTTP_CLIENT_IP");
    52. }
    53. if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
    54. ip = request.getHeader("HTTP_X_FORWARDED_FOR");
    55. }
    56. if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
    57. ip = request.getRemoteAddr();
    58. // 以下是后期添加的 要是不想在数据库看到 0:0:0.....或者 127.0.0.1的 数字串可用下边方法 亲测
    59. if(ip.equals("127.0.0.1") || ip.equals("0:0:0:0:0:0:0:1")){
    60. //根据网卡取本机配置的IP
    61. InetAddress inet=null;
    62. try {
    63. inet = InetAddress.getLocalHost();
    64. } catch (UnknownHostException e) {
    65. e.printStackTrace();
    66. }
    67. ip= inet.getHostAddress();
    68. }
    69. }
    70. } else if (ip.length() > 15) {
    71. String[] ips = ip.split(",");
    72. for (int index = 0; index < ips.length; index++) {
    73. String strIp = (String) ips[index];
    74. if (!("unknown".equalsIgnoreCase(strIp))) {
    75. ip = strIp;
    76. break;
    77. }
    78. }
    79. }
    80. return ip;
    81. }
    82. public String getLoginDevice(String userAgent){
    83. log.info("userAgent={}",userAgent);
    84. String device = "未知型号";
    85. if(userAgent.indexOf("Windows") !=-1){
    86. device = "Windows PC";
    87. }else if(userAgent.indexOf("Mac") !=-1 && userAgent.indexOf("CPU") == -1){
    88. device = "MAC PC";
    89. }else if(userAgent.indexOf("iPhone") !=-1){
    90. device = "iPhone";
    91. }else if(userAgent.indexOf("Android") !=-1){
    92. device = "Android";
    93. }
    94. return device;
    95. }
    96. @Async
    97. public void sendEmailToUser(UserReqBean vo) {
    98. log.info("sendEmailToUser vo={}",vo);
    99. this.sendMessageToUserByPhone(vo.getPhone());
    100. this.sendEmailToUserByEmail(vo);
    101. }
    102. public void sendMessageToUserByPhone(String phone) {
    103. log.info("sendMessageToUserByPhone phone={}",phone);
    104. }
    105. public void sendEmailToUserByEmail(UserReqBean vo) {
    106. log.info("sendEmailToUserByEmail email={}",vo.getEmail());
    107. }
    108. }

    3、经过上面两步就可以实现了,下面给出DEMO中的【UserReqBean】类。

    1. package cn.renkai721.bean;
    2. import io.swagger.annotations.ApiModel;
    3. import io.swagger.annotations.ApiModelProperty;
    4. import lombok.AllArgsConstructor;
    5. import lombok.Builder;
    6. import lombok.Data;
    7. import lombok.NoArgsConstructor;
    8. import lombok.experimental.Accessors;
    9. import lombok.extern.slf4j.Slf4j;
    10. import java.io.Serializable;
    11. @Data
    12. @Slf4j
    13. @Builder
    14. @NoArgsConstructor
    15. @AllArgsConstructor
    16. @Accessors(chain = true)
    17. @ApiModel(
    18. description = "测试异步用户对象"
    19. )
    20. public class UserReqBean implements Serializable {
    21. @ApiModelProperty("用户ID主键")
    22. private String userId;
    23. @ApiModelProperty("手机号码")
    24. private String phone;
    25. @ApiModelProperty("邮箱")
    26. private String email;
    27. }

    第二章 唠嗑

    1、仔细看DEMO的人会发现,在传递request的时候,居然和普通的方法不一样,不能直接使用。而是使用了AsyncContext。

    2、这里还是有一个注意的点,就是asyncContext.setTimeout(0);这个方法不能少,否则会报错,提示NullPointerException。

    1. java.lang.NullPointerException
    2. at org.apache.coyote.http11.InternalOutputBuffer.realWriteBytes(InternalOutputBuffer.java:215)
    3. at org.apache.tomcat.util.buf.ByteChunk.flushBuffer(ByteChunk.java:480)
    4. at org.apache.coyote.http11.InternalOutputBuffer.flush(InternalOutputBuffer.java:119)
    5. at org.apache.coyote.http11.AbstractHttp11Processor.action(AbstractHttp11Processor.java:799)
    6. at org.apache.coyote.Response.action(Response.java:174)
    7. at org.apache.catalina.connector.OutputBuffer.doFlush(OutputBuffer.java:366)
    8. at org.apache.catalina.connector.OutputBuffer.flush(OutputBuffer.java:333)
    9. at org.apache.catalina.connector.CoyoteWriter.flush(CoyoteWriter.java:98)
    10. at cn.cloud.action.CounterThread.printPage(LoginServlet.java:69)
    11. at cn.cloud.action.CounterThread.run(LoginServlet.java:53)
    12. 九月 06, 2022 13:03:16 上午 org.apache.catalina.core.StandardContext reload
    13. 信息: Reloading Context with name [/myshop] has started
    14. 九月 06, 2022 13:03:16 上午 org.apache.catalina.core.StandardContext reload
    15. 信息: Reloading Context with name [/myshop] is completed

    第三章 课外知识 

    1、我们在实际的工作中,有时候会遇到一些非核心的附加功能,比如短信或微信模板消息通知,或者一些耗时比较久,但主流程不需要立即获得其结果反馈的操作,比如保存图片、同步数据到其它合作方等等。如果将这些操作都置于主流程中同步处理,势必会对核心流程的性能造成影响,甚至由于第三方服务的问题导致自身服务不可用。这时候就应该将这些操作异步化,以提高主流程的性能,并与第三方解耦,提高主流程的可用性。在Spring Boot中,或者说在Spring中,我们实现异步处理一般有以下几种方式:

    通过 @EnableAsync 与 @Asyc 注解结合实现

    参照本文DEMO

    通过异步事件实现

    通过消息队列实现

    2、一般都是同步调用的,但经常有特殊业务需要做异步来处理。比如:注册用户、需要送积分、发短信和邮件、或者下单成功、发送消息等等。

    1

    容错问题,如果送积分出现异常,不能因为送积分而导致用户注册失败。

    2

    提升性能,比如注册用户花了30毫秒,送积分划分50毫秒,如果同步的话一共耗时:70毫秒,用异步的话,无需等待积分,故耗时是:30毫秒就完成了业务。

    3、异步,同步的区别

    同步:按顺序执行,前面结束了,后面才开始,是一个串行调用。

    异步:按顺序执行,是否现在执行异步中的方法不一定,但是一定会先顺序执行异步方法外的代码。

  • 相关阅读:
    【12月海口】2022年第六届船舶,海洋与海事工程国际会议(NAOME 2022)
    uni-app点击复制指定内容(点击复制)
    水平基准和垂直基准
    c#简易学生管理系统
    Unicode和UTF-8的关系
    两个线程交替执行的几种方式
    YOLOv4 NCNN 量化模型和实时推理
    2022年最新四川建筑八大员(劳务员)模拟题库及答案
    mlflow详细安装部署
    操作符的优先级、结合性、是否控制求值顺序【详解】
  • 原文地址:https://blog.csdn.net/renkai721/article/details/126724294