还是老样子,废话不多说,这里直接讲代码,后面讲故事。
1、在Controller类中添加【@EnableAsync】注解,注意不是方法上哦。
- package cn.renkai721.controller;
-
- import cn.renkai721.bean.*;
- import cn.renkai721.common.BaseController;
- import cn.renkai721.service.*;
- import io.swagger.annotations.Api;
- import io.swagger.annotations.ApiResponse;
- import io.swagger.annotations.ApiResponses;
- import lombok.extern.slf4j.Slf4j;
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.scheduling.annotation.EnableAsync;
- import org.springframework.web.bind.annotation.*;
- import javax.servlet.http.HttpServletRequest;
-
-
- @EnableAsync
- @RestController
- @RequestMapping("/async")
- @Slf4j
- @Api(value = "AsyncTestController",tags = {"测试异步"})
- public class AsyncTestController extends BaseController {
-
- @Autowired
- private AsyncTestService asyncTestService;
-
-
- @GetMapping("/testAsync")
- @ResponseBody
- @ApiResponses(value = {
- @ApiResponse(code=200,message="返回对象",response=LoginRespBean.class)
- })
- public String testAsync(HttpServletRequest request) throws Exception {
- asyncTestService.getLoginIpAndAddress(request);
- return "success";
- }
-
-
- @GetMapping("/sendEmailToUser")
- @ResponseBody
- @ApiResponses(value = {
- @ApiResponse(code=200,message="返回对象",response=LoginRespBean.class)
- })
- public String sendEmailToUser(UserReqBean vo) throws Exception {
- asyncTestService.sendEmailToUser(vo);
- return "success";
- }
-
- }
2、在你的Service的方法中【@Async】注解。这两步就可以实现异步操作了。
- package cn.renkai721.service;
-
- import cn.renkai721.bean.*;
- import lombok.extern.slf4j.Slf4j;
- import org.springframework.context.annotation.Configuration;
- import org.springframework.scheduling.annotation.Async;
- import org.springframework.stereotype.Service;
-
- import javax.servlet.AsyncContext;
- import javax.servlet.http.HttpServletRequest;
- import java.io.IOException;
- import java.net.InetAddress;
- import java.net.UnknownHostException;
-
-
- @Service
- @Slf4j
- @Configuration
- public class AsyncTestService {
-
- @Async
- public void getLoginIpAndAddress(HttpServletRequest request){
- AsyncContext asyncContext = request.startAsync();
- // 这个方法必须写
- asyncContext.setTimeout(0);
- this.getLoginIpAndAddress(asyncContext);
- }
-
- public String getLoginIpAndAddress(AsyncContext asyncContext){
- StringBuffer sb = new StringBuffer();
- try {
- HttpServletRequest request = (HttpServletRequest)asyncContext.getRequest();
- String userAgent = request.getHeader("User-Agent");
- String device = this.getLoginDevice(userAgent);
- String ip = this.getIpAddress(request);
- sb.append("device="+device+",");
- sb.append("ip="+ip+",");
- log.info("device={},ip={}",device,ip);
- } catch (Exception e) {
- log.error("getLoginIpAndAddress e={}",e);
- e.printStackTrace();
- }
- return sb.toString();
- }
-
- @Async
- public String getIpAddress(HttpServletRequest request) throws IOException {
- // 获取请求主机IP地址,如果通过代理进来,则透过防火墙获取真实IP地址
- String ip = request.getHeader("X-Forwarded-For");
- if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
- if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
- ip = request.getHeader("Proxy-Client-IP");
- }
- if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
- ip = request.getHeader("WL-Proxy-Client-IP");
- }
- if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
- ip = request.getHeader("HTTP_CLIENT_IP");
- }
- if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
- ip = request.getHeader("HTTP_X_FORWARDED_FOR");
- }
- if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
- ip = request.getRemoteAddr();
- // 以下是后期添加的 要是不想在数据库看到 0:0:0.....或者 127.0.0.1的 数字串可用下边方法 亲测
- if(ip.equals("127.0.0.1") || ip.equals("0:0:0:0:0:0:0:1")){
- //根据网卡取本机配置的IP
- InetAddress inet=null;
- try {
- inet = InetAddress.getLocalHost();
- } catch (UnknownHostException e) {
- e.printStackTrace();
- }
- ip= inet.getHostAddress();
- }
- }
- } else if (ip.length() > 15) {
- String[] ips = ip.split(",");
- for (int index = 0; index < ips.length; index++) {
- String strIp = (String) ips[index];
- if (!("unknown".equalsIgnoreCase(strIp))) {
- ip = strIp;
- break;
- }
- }
- }
- return ip;
- }
-
- public String getLoginDevice(String userAgent){
- log.info("userAgent={}",userAgent);
- String device = "未知型号";
- if(userAgent.indexOf("Windows") !=-1){
- device = "Windows PC";
- }else if(userAgent.indexOf("Mac") !=-1 && userAgent.indexOf("CPU") == -1){
- device = "MAC PC";
- }else if(userAgent.indexOf("iPhone") !=-1){
- device = "iPhone";
- }else if(userAgent.indexOf("Android") !=-1){
- device = "Android";
- }
- return device;
- }
-
-
- @Async
- public void sendEmailToUser(UserReqBean vo) {
- log.info("sendEmailToUser vo={}",vo);
- this.sendMessageToUserByPhone(vo.getPhone());
- this.sendEmailToUserByEmail(vo);
- }
-
- public void sendMessageToUserByPhone(String phone) {
- log.info("sendMessageToUserByPhone phone={}",phone);
- }
-
-
- public void sendEmailToUserByEmail(UserReqBean vo) {
- log.info("sendEmailToUserByEmail email={}",vo.getEmail());
- }
- }
3、经过上面两步就可以实现了,下面给出DEMO中的【UserReqBean】类。
- package cn.renkai721.bean;
-
- import io.swagger.annotations.ApiModel;
- import io.swagger.annotations.ApiModelProperty;
- import lombok.AllArgsConstructor;
- import lombok.Builder;
- import lombok.Data;
- import lombok.NoArgsConstructor;
- import lombok.experimental.Accessors;
- import lombok.extern.slf4j.Slf4j;
-
- import java.io.Serializable;
-
-
- @Data
- @Slf4j
- @Builder
- @NoArgsConstructor
- @AllArgsConstructor
- @Accessors(chain = true)
- @ApiModel(
- description = "测试异步用户对象"
- )
- public class UserReqBean implements Serializable {
-
- @ApiModelProperty("用户ID主键")
- private String userId;
-
- @ApiModelProperty("手机号码")
- private String phone;
-
- @ApiModelProperty("邮箱")
- private String email;
-
-
- }
1、仔细看DEMO的人会发现,在传递request的时候,居然和普通的方法不一样,不能直接使用。而是使用了AsyncContext。
2、这里还是有一个注意的点,就是asyncContext.setTimeout(0);这个方法不能少,否则会报错,提示NullPointerException。
- java.lang.NullPointerException
- at org.apache.coyote.http11.InternalOutputBuffer.realWriteBytes(InternalOutputBuffer.java:215)
- at org.apache.tomcat.util.buf.ByteChunk.flushBuffer(ByteChunk.java:480)
- at org.apache.coyote.http11.InternalOutputBuffer.flush(InternalOutputBuffer.java:119)
- at org.apache.coyote.http11.AbstractHttp11Processor.action(AbstractHttp11Processor.java:799)
- at org.apache.coyote.Response.action(Response.java:174)
- at org.apache.catalina.connector.OutputBuffer.doFlush(OutputBuffer.java:366)
- at org.apache.catalina.connector.OutputBuffer.flush(OutputBuffer.java:333)
- at org.apache.catalina.connector.CoyoteWriter.flush(CoyoteWriter.java:98)
- at cn.cloud.action.CounterThread.printPage(LoginServlet.java:69)
- at cn.cloud.action.CounterThread.run(LoginServlet.java:53)
- 九月 06, 2022 13:03:16 上午 org.apache.catalina.core.StandardContext reload
- 信息: Reloading Context with name [/myshop] has started
- 九月 06, 2022 13:03:16 上午 org.apache.catalina.core.StandardContext reload
- 信息: Reloading Context with name [/myshop] is completed
1、我们在实际的工作中,有时候会遇到一些非核心的附加功能,比如短信或微信模板消息通知,或者一些耗时比较久,但主流程不需要立即获得其结果反馈的操作,比如保存图片、同步数据到其它合作方等等。如果将这些操作都置于主流程中同步处理,势必会对核心流程的性能造成影响,甚至由于第三方服务的问题导致自身服务不可用。这时候就应该将这些操作异步化,以提高主流程的性能,并与第三方解耦,提高主流程的可用性。在Spring Boot中,或者说在Spring中,我们实现异步处理一般有以下几种方式:
| 通过 @EnableAsync 与 @Asyc 注解结合实现 | 参照本文DEMO |
| 通过异步事件实现 | |
| 通过消息队列实现 |
2、一般都是同步调用的,但经常有特殊业务需要做异步来处理。比如:注册用户、需要送积分、发短信和邮件、或者下单成功、发送消息等等。
| 1 | 容错问题,如果送积分出现异常,不能因为送积分而导致用户注册失败。 |
| 2 | 提升性能,比如注册用户花了30毫秒,送积分划分50毫秒,如果同步的话一共耗时:70毫秒,用异步的话,无需等待积分,故耗时是:30毫秒就完成了业务。 |
3、异步,同步的区别
同步:按顺序执行,前面结束了,后面才开始,是一个串行调用。
异步:按顺序执行,是否现在执行异步中的方法不一定,但是一定会先顺序执行异步方法外的代码。