Request异步请求处理PART2

本文探讨了如何在过滤器和Servlet中开启Request的异步处理,通过实现AsyncListener来监听AsyncContext的起始、完成、超时和错误事件。核心代码展示了解决方案的关键步骤。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

为什么需要异步请求处理?

当客户端发送一个请求,服务端接受到这个请求,由于该请求在服务器端需要复杂或耗时比较长的处理,有时候需要开启其他线程进行请求的处理。
此时就可以通过使用异步请求处理的功能。

如何在Servlet中使用异步请求处理?

1.首先,在web.xml中的servlet中配置,如果有filter拦截到的servlet需要异步请求处理,则filter配置中也需要配置这个参数。这样才能支持异步请求处理。
 <async-supported>true</async-supported>
 
2.通过request.startAsync()或request.startAsync(request, response)来初始化异步请求上下文对象AsyncContext。
AsyncContext asyncContextOriginal =request.startAsync();
AsyncContext asyncContextWapper =request.startAsync(request, response);
上面两种的取得AsyncContext的区别:
1.request.startAsync();使用的是原始的ServeltReqeust和ServletResponse对象,即没有使用ServletRequestWrapper或ServletResponseWrapper
实例,这样即使过滤器或者其他方面使用了包装器,得到的AsyncContext也是用原始的ServeltReqeust和ServletResponse对象,不会产生包装效果。
2.request.startAsync(request, response);则不一样,参数request,response可以是包装后的,产生包装效果。

3.AsyncContext 介绍
如果Servlet或过滤器的asyncSupported被标示为true,则它们支持异步请求处理,在不支持异步处理的Servlet或过滤器中调用startAsync(),会抛出
IllegalStateException.
当在支持异步处理的Servlet或过滤器中调用请求对象的startAsync()方法时,该次请求会离开容器所分配的线程,这意味这必须响应处理流程会返回,也就是若有
过滤器,也会依序返回(也就是各自完成的doFilter()方法),但最终的响应被延迟。
可以调用AsyncContext的complete()方法完成响应,而后调用forward()方法,将响应转发给别的Servlet/JSP处理。
或者直接使用AsyncContext.dispatcher()或AsyncContext.dispatcher(String path)方法转发。
不可同时调用complete和dispatcher方法

如果对AsyncContext的起始、完成、超时或错误发生等事件有兴趣,可以实现AsyncListener,使用AsyncContext的addListener()方法加入该监听器。

开启异步请求处理

1.filter中设置

<async-supported>true</async-supported>

 <filter-mapping>
    <filter-name>myFilter</filter-name>
    <url-pattern>/*</url-pattern>
  </filter-mapping>
  <filter>
    <filter-name>myFilter1</filter-name>
    <filter-class>com.zghw.servlet.demo.MyFilter1</filter-class>
    <async-supported>true</async-supported>
  </filter>

2.servlet中设置

<async-supported>true</async-supported>
<servlet>
    <servlet-name>myRequestServlet2</servlet-name>
    <servlet-class>com.zghw.servlet.demo.MyRequestServlet2</servlet-class>
    <load-on-startup>1</load-on-startup>
    <async-supported>true</async-supported>
  </servlet>
  <servlet-mapping>
    <servlet-name>myRequestServlet2</servlet-name>
    <url-pattern>/myrs2/*</url-pattern>
  </servlet-mapping>

主要功能代码

package com.zghw.servlet.demo;

import java.io.IOException;

import javax.servlet.AsyncContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * 请求异步处理 可以先释放容器分配给请求的线程与相关资源,减轻系统负担,原先释放了容器所分配线程的请求。
 * 其响应将被延后,可以在处理完成(例如长时间运算完成、所需资源以获得)时在对客户端进行响应。
 */
public class MyRequestServlet2 extends HttpServlet {
	private static final long serialVersionUID = 1L;

	protected void doGet(HttpServletRequest request,
			HttpServletResponse response) throws ServletException, IOException {

		boolean isAsyncStarted = request.isAsyncStarted();
		f("当前请求是否开启异步?" + isAsyncStarted);
		boolean isAsyncSupported = request.isAsyncSupported();
		f("当前请求是否支持异步?" + isAsyncSupported);
		if (!isAsyncStarted && isAsyncSupported) {
			// 把这个请求放入到异步模式中,如多线程,使用原始的request和response即没有包装过的来初始化一个AsyncContext
			// AsyncContext asyncContextOriginal =request.startAsync();
			// 使用包装后的request和response初始化AsyncContext对象
			final AsyncContext asyncContextWapper = request.startAsync(request,
					response);
			// asyncContextOrigin = request.getAsyncContext();
			// 当请求参数时name=张三,使用request.startAsync();取得AsyncContext使用了没有包装的原始的ServeltReqeust和ServletResponse
			// 得到的参数"张三"是乱码,没有使用MyServletRequestWrapper,虽然filter使用了但并不会起作用
			// String
			// name=asyncContextOriginal.getRequest().getParameter("name");

			// 而使用request.startAsync(request,
			// response);取得AsyncContext使用了包装后的ServeltReqeust和ServletResponse
			// 解析中文参数"张三"时,使用了MyServletRequestWrapper来转换编码
			String name = asyncContextWapper.getRequest().getParameter("name");

			// 为请求异步处理添加一个事件监听器
			asyncContextWapper.addListener(new MyAsyncListener(), request,
					response);
			// 设置超时时间单位毫秒
			asyncContextWapper.setTimeout(5000);
			// 设置一个线程任务到线程池中,并启动该线程异步处理任务
			asyncContextWapper.start(new Runnable() {
				@Override
				public void run() {
					try {
						System.out.println("处理任务开始");
						// 模拟任务
						Thread.sleep(3000);
						System.out.println("任务完成");

						// 代表request对应的request中请求url或分发的url进行转发
						// 如请求/url/A则转向这个URL,了解详情看这个方法上的注释
						// asyncContextWapper.dispatch();

						// 转发到对应的路径
						asyncContextWapper
								.dispatch("/myAsyncDispatcherServlet");

						// 转发的目标路径在设置的ServletContext中
						// asyncContextWapper.dispatch(getServletContext(),
						// "/myAsyncDispatcherServlet");

						// 设置异步请求完成,当进行了dispatch转发,这里就不能在使用complete()方法。
						// asyncContextWapper.complete();
					} catch (InterruptedException e) {
						e.printStackTrace();
					}

				}
			});
			// 创建AsyncContext是否使用原始的ServletRequest和ServletResponse对象
			boolean hasOriginal = asyncContextWapper
					.hasOriginalRequestAndResponse();
			f(hasOriginal);

			f("" + name);
		}
	}

	protected void doPost(HttpServletRequest request,
			HttpServletResponse response) throws ServletException, IOException {
		doGet(request, response);
	}

	static void f(Object obj) {
		System.out.println(obj);
	}
}

异步处理监听器

package com.zghw.servlet.demo;

import java.io.IOException;

import javax.servlet.AsyncContext;
import javax.servlet.AsyncEvent;
import javax.servlet.AsyncListener;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;

/**
 * 请求异步处理监听器,监听异步处理启动、完成、超时或错误事件。
 * 
 * @author zghw
 *
 */
public class MyAsyncListener implements AsyncListener {
	/**
	 * 请求异步处理启动时触发
	 */
	@Override
	public void onStartAsync(AsyncEvent event) throws IOException {
		System.out.println("MyAsyncListener onStartAsync");
		ServletRequest req = event.getSuppliedRequest();
		ServletResponse res = event.getSuppliedResponse();
		AsyncContext asyncContext = event.getAsyncContext();
	}

	/**
	 * 请求异步完成后触发
	 */
	@Override
	public void onComplete(AsyncEvent event) throws IOException {
		System.out.println("MyAsyncListener onComplete");
		AsyncContext asyncContext = event.getAsyncContext();
		ServletRequest req = event.getSuppliedRequest();
		ServletResponse res = event.getSuppliedResponse();
		String contextPath = (String) req
				.getAttribute(AsyncContext.ASYNC_CONTEXT_PATH);
		f(contextPath);
		String pathInfo = (String) req
				.getAttribute(AsyncContext.ASYNC_PATH_INFO);
		f(pathInfo);
		String queryString = (String) req
				.getAttribute(AsyncContext.ASYNC_QUERY_STRING);
		f(queryString);
		String requestURI = (String) req
				.getAttribute(AsyncContext.ASYNC_REQUEST_URI);
		f(requestURI);
		String servletPath = (String) req
				.getAttribute(AsyncContext.ASYNC_SERVLET_PATH);
		f(servletPath);
	}

	/**
	 * 请求异步超时触发
	 */
	@Override
	public void onTimeout(AsyncEvent event) throws IOException {
		System.out.println("MyAsyncListener onTimeout");
	}

	/**
	 * 请求异步处理失败完成
	 */
	@Override
	public void onError(AsyncEvent event) throws IOException {
		System.out.println("MyAsyncListener onError");
	}

	static void f(Object obj) {
		System.out.println(obj);
	}
}

转发后的Servlet

package com.zghw.servlet.demo;

import java.io.IOException;

import javax.servlet.AsyncContext;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@WebServlet("/myAsyncDispatcherServlet")
public class MyAsyncDispatcherServlet extends HttpServlet{
	static void f(Object obj) {
		System.out.println(obj);
	}
	private static final long serialVersionUID = 1L;
	protected void doGet(HttpServletRequest request,
			HttpServletResponse response) throws ServletException, IOException {
		AsyncContext asyncContext=request.getAsyncContext();
		//请求异步dipatcher转发过来的无法获得AsyncContext中的request和response
		//ServletRequest req = asyncContext.getRequest();
		//ServletResponse res = asyncContext.getResponse();
	}
}



<think>嗯,用户想了解如何在OkHttpClient中异步上传MultipartFile文件。首先,我需要确认用户的使用场景,可能是在Android开发或者Java后端处理文件上传。MultipartFile通常是Spring框架中用于接收上传文件的接口,所以用户可能是在Spring Boot环境下使用OkHttpClient发送异步请求。 接下来,我需要回忆OkHttpClient处理多部分表单请求的方式。OkHttp使用MultipartBody来构建请求体,每个部分可以是不同的内容类型,比如文件、文本等。用户需要将MultipartFile转换为OkHttp能够处理的格式,比如RequestBody或者更具体的Part。 然后,考虑异步请求的实现。OkHttp的异步调用是通过enqueue方法,并传入Callback回调接口来处理响应或失败的情况。需要确保在回调中正确处理线程切换,比如在Android中更新UI需要回到主线程,但如果是后端服务可能不需要。 另外,需要注意MultipartFile的转换。可能需要将MultipartFile的InputStream读取到文件中,或者直接使用字节数组。考虑到内存问题,如果文件较大,可能需要使用流式上传,避免内存溢出。 同时,检查依赖是否正确引入。OkHttpClient需要相应的依赖,比如在Maven或Gradle中添加对应的库。可能还需要处理文件类型和名称,确保服务器端能正确接收。 用户可能需要具体的代码示例,所以需要分步骤说明:构建MultipartBody,创建请求异步执行,并处理响应或异常。同时,提醒常见的坑,比如忘记关闭流,或者文件路径问题,以及异步回调中的线程处理。 最后,确认是否需要处理其他表单字段,比如在文件上传时附带其他参数,这需要在MultipartBody中添加更多的part。确保每个部分的名称和服务器端接收的参数名一致。</think>在Android或Java后端开发中,通过OkHttpClient异步上传MultipartFile文件需要以下步骤: ### 一、核心实现步骤 1. **构建MultipartBody** ```java RequestBody requestBody = new MultipartBody.Builder() .setType(MultipartBody.FORM) // 添加文件参数(Spring的MultipartFile转OkHttp格式) .addFormDataPart( "file", file.getOriginalFilename(), RequestBody.create( file.getBytes(), MediaType.parse("application/octet-stream") ) ) // 可添加其他表单参数 .addFormDataPart("param1", "value1") .build(); ``` 2. **创建Request对象** ```java Request request = new Request.Builder() .url("https://api.example.com/upload") .post(requestBody) .build(); ``` 3. **异步执行请求** ```java OkHttpClient client = new OkHttpClient(); client.newCall(request).enqueue(new Callback() { @Override public void onResponse(Call call, Response response) throws IOException { if (response.isSuccessful()) { String result = response.body().string(); // 处理成功响应(注意:此处处于后台线程) } } @Override public void onFailure(Call call, IOException e) { // 处理失败情况 } }); ``` ### 二、关键注意事项 1. **文件处理优化** - 大文件建议使用流式上传: ```java RequestBody fileBody = RequestBody.create( file.getInputStream().readAllBytes(), // 实际需分块读取 MediaType.parse("multipart/form-data") ); ``` 2. **内存管理** - 使用`File`对象代替直接读取字节数组: ```java File tempFile = new File(file.getOriginalFilename()); file.transferTo(tempFile); RequestBody fileBody = RequestBody.create(tempFile, MediaType.get("application/octet-stream")); ``` 3. **参数对应关系** - `addFormDataPart`第一个参数必须与服务器端接收参数的名称一致 - 例如SpringBoot接口参数:`@RequestParam("file") MultipartFile file` ### 三、完整异步处理示例 ```java public void uploadFileAsync(MultipartFile file) { // 构建请求RequestBody requestBody = new MultipartBody.Builder() .setType(MultipartBody.FORM) .addFormDataPart("file", file.getOriginalFilename(), RequestBody.create(file.getBytes(), MediaType.parse("multipart/form-data"))) .build(); // 创建请求 Request request = new Request.Builder() .url("https://api.example.com/upload") .post(requestBody) .build(); // 异步执行 new OkHttpClient().newCall(request).enqueue(new Callback() { @Override public void onFailure(Call call, IOException e) { // 处理网络错误 e.printStackTrace(); } @Override public void onResponse(Call call, Response response) throws IOException { if (!response.isSuccessful()) { // 处理HTTP错误(如404/500等) throw new IOException("Unexpected code " + response); } // 解析响应数据(注意:此处在IO线程) String responseData = response.body().string(); // 更新UI需切到主线程(Android场景) } }); } ``` ### 四、常见问题排查 1. **413错误**:检查服务器配置的`max-file-size`和`max-request-size` 2. **文件损坏**:确保MediaType与实际文件类型匹配 3. **超时设置**:建议添加自定义配置 ```java OkHttpClient client = new OkHttpClient.Builder() .connectTimeout(30, TimeUnit.SECONDS) .readTimeout(60, TimeUnit.SECONDS) .build(); ``` 实际开发中建议结合RxJava或Kotlin协程进行更优雅的异步处理,并注意在Android平台处理线程切换问题。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值