Springboot 异步Async教程

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

第一章 代码实现

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、异步,同步的区别

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

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

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

renkai721

谢谢您的打赏!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值