Spring异步调用
异步调用应用场景
在请求一个Controller时,需要进行操作A、B、C,默认情况下,属于同步调用,三个操作依次进行。如果把B设置成一个异步方法,在执行完操作A之后,必用等待B执行完成,直接开始执行C,等到C执行完成后,整个方法就会返回,不管操作B是否已经执行完成。
以上描述有点干,来个实际的应用。大家在某东下单之后,会收到一封订单确认邮件。如果用同步调用的方法,当按下确认下单的时候,后台需要在处理完数据库,发完邮件之后,客户端页面才会跳转到下单成功页面,这会导致用户体验下降,甚至可能导致用户多次点击下单按钮。若使用异步调用,就可以做到读写数据库和发送邮件异步操作了。当然这里有个前提,发送邮件延迟一会是不会导致用户体验下降的。
<br />
代码
那么就来看一下,在基于Spring MVC的项目中,异步调用是怎么实现的。
首先,注意,异步调用在Spring 3.x之后使用比较方便,建议使用3.x版本。
第一步:配置web.xml
让在web.xml中配置的servelet和filter都支持异步调用。
<async-supported>true</async-supported>
<servlet>
<description>spring mvc servlet</description>
<servlet-name>springMvc</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<description>spring mvc 配置文件</description>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring-mvc.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
<async-supported>true</async-supported>
</servlet>
第二步:异步调用注解支持
在Spring配置中加上对异步调用注解的支持。
<!-- 支持异步方法执行 -->
<task:executor id="myexecutor" pool-size="5" />
<task:annotation-driven executor="myexecutor"/>
第三步:创建异步调用类和方法
这里模拟异步发邮件,所以就创建异步发送邮件类。
- 把邮件发送类注册成bean,加上@Service即可。
- 在该类上加上@EnableAsync,表示该类支持异步调用。
- 在对应异步方法上加上@Async注解。
@Service
@EnableAsync
public class SendMailUtil {
private final static String SEND_HOST_NAME = "smtp.163.com";
private final static String SEND_FROM = "-----------";
private final static String SEND_AUTH_ACCOUNT = "-----------";
private final static String SEND_AUTH_PASSWORD = "-----------";
@Async
public void SEND_SUBMIT_EMAIL(String sendToAccount, String sendToName) {
//为了体现效果,邮件发送延迟5秒钟
try {
Thread.sleep(5 * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Mail is sent!");
//使用commons-mail发送邮件
SimpleEmail email = new SimpleEmail();
try {
email.setHostName(SEND_HOST_NAME);
email.addTo(sendToAccount, sendToName);
email.setFrom(SEND_AUTH_ACCOUNT, SEND_FROM);
email.setAuthentication(SEND_AUTH_ACCOUNT, SEND_AUTH_PASSWORD);
email.setSubject("This is a @Async eamil!");
email.setMsg("This is a simple test of commons-email");
//需要对发送错误情况做处理
email.send();
} catch (EmailException e) {
// 这里需要对邮件发送做异常处理
e.printStackTrace();
}
}
}
第四步:在Controller中调用异步方法即可
@Controller
@RequestMapping("/asyncController")
public class AsyncController extends BaseController {
//注入邮件类
@Autowired
private SendMailUtil sendMailUtil;
//注入其他服务类
@Autowired
private OtherService otherService;
@RequestMapping(params = "async")
@ResponseBody
public Json async(HttpServletRequest request) {
Json j = new Json();
Other other = OtherService.other();
if (other != null) {
j.setObj(other);
j.setSuccess(true);
//异步发送邮件
sendMailUtil.SEND_SUBMIT_EMAIL("-----------", "-----------");
System.out.println("Execute before sending mail!");
//在邮件发送之前就返回了
return j;
}
}
}
<br />
错误处理
- 如果邮件发送失败了该怎么处理?很多网站都会在用户注册的时候发送激活邮件,如果一段时间没收到,网站会提供一个再次发送邮件的按钮。
- 类似的,如果做其他异步请求,需要考虑请求失败的情况。
<br />
异步调用实现原理
Spring会为异步调用创建一个线程。
在没有异步调用之前,可以手工创建一个线程来实现。
[转]Spring 在扫描bean的时候会扫描方法上是否包含@async的注解,如果包含的,spring会为这个bean动态的生成一个子类(那应该是用cglib代理),我们称之为代理类,代理类是继承我们所写的bean的,然后把代理类注入进来。
那此时,在执行此方法的时候,会到代理类中,代理类判断了此方法需要异步执行,就不会调用父类(我们原本写的bean)的对应方法。
Spring自己维护了一个队列,他会把需要执行的方法,放入队列中,等待线程池去读取这个队列,完成方法的执行,从而完成了异步的功能。
我们可以关注到再配置task的时候,是有参数让我们配置线程池的数量的。