java - Java Spring Request 在异步方法完成之前变为非活动状态
问题描述
我有一个带有调用异步方法的 RestController 的 Java Spring 服务:
@RestController
public class SomeController {
@Autowired
//this is the service that contains the async-method
OtherService otherService;
@GetMapping
public void someFunctionWithinTheMainRequestThread() {
otherService.asyncMethod(RequestContextHolder.getRequestAttributes());
}
}
该异步方法需要使用 RequestContextAttributes,因为它正在使用 linkTo(...) 构建链接。问题是无论我如何将 RequestAttributes 传递给方法,我总是得到错误
java.lang.IllegalStateException: Cannot ask for request attribute - request is not active anymore!
这是异步方法上的注释:
public class OtherService {
@Async
@Transactional(readOnly = true)
public void asyncMethod(RequestAttributes context) {
RequestContextHolder.setRequestAttributes(context);
//doing a lot of stuff that takes a while
linkTo(methodOn(...)) //-> here the error occurs
}
我尝试了什么:
- 手动将 RequestAttributes 作为参数传递(如上面的代码片段所示)
- 使用此答案中描述的上下文感知池执行器:如何在异步任务执行器中启用请求范围- 这基本上似乎与我将上下文作为仅全局配置的变量传递相同
- 更新 servlet 配置并将 ThreadContextInheritable 设置为 true
- 将 RequestAttributes 分配给最终变量以尝试获取原始对象的副本,该副本被主线程标记为非活动
无论我做什么,请求似乎总是在我的异步方法之前完成,而且我显然从来没有属性的深层副本,所以它们总是在异步方法完成之前被主线程标记为非活动状态,然后我不能不再使用它们-> 至少这是我对错误的理解。
我只想能够在我的异步方法中获取 linkTo 方法所需的 requestAttributes,即使在主线程完成请求之后,有人能指出我正确的方向吗?
解决方案
我找到了一个有效的解决方案并消除了错误。因为我不认为这真的很干净,所以我希望有更多的答案,但万一它对某人有帮助:
首先我添加了这个类。它创建了一个自定义且非常简单的 RequestAttributes-Implementation,使我们能够使属性保持活动的时间比通常更长:
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Map;
public class AsyncRequestScopeAttr extends ServletRequestAttributes {
private Map<String, Object> requestAttributeMap = new HashMap<>();
public AsyncRequestScopeAttr(HttpServletRequest request) {
super(request);
}
@Override
public void requestCompleted() {
//keep the request active, normally here this.requestActive would be set to false -> we do that in the completeRequest()-method which is manually called after the async method is done
}
/**
* This method should be called after your async method is finished. Normally it is called when the
* request completes but since our async method can run longer we call it manually afterwards
*/
public void completeRequest() {
super.requestCompleted();
}
@Override
public Object getAttribute(String name, int scope) {
if(scope== RequestAttributes.SCOPE_REQUEST) {
return this.requestAttributeMap.get(name);
}
return null;
}
@Override
public void setAttribute(String name, Object value, int scope) {
if(scope== RequestAttributes.SCOPE_REQUEST){
this.requestAttributeMap.put(name, value);
}
}
@Override
public void removeAttribute(String name, int scope) {
if(scope== RequestAttributes.SCOPE_REQUEST) {
this.requestAttributeMap.remove(name);
}
}
@Override
public String[] getAttributeNames(int scope) {
if(scope== RequestAttributes.SCOPE_REQUEST) {
return this.requestAttributeMap.keySet().toArray(new String[0]);
}
return new String[0];
}
@Override
public void registerDestructionCallback(String name, Runnable callback, int scope) {
// Not Supported
}
@Override
public Object resolveReference(String key) {
// Not supported
return null;
}
@Override
public String getSessionId() {
return null;
}
@Override
public Object getSessionMutex() {
return null;
}
@Override
protected void updateAccessedSessionAttributes() {
}
}
然后在调用async方法之前的RestController中:
@Autowired
//this is the service that contains the async-method
OtherService otherService;
public void someFunctionWithinTheMainRequestThread(){
otherService.asyncMethod(getIndependentRequestAttributesForAsync());
}
private RequestAttributes getIndependentRequestAttributesForAsync(){
RequestAttributes requestAttributes = new AsyncRequestScopeAttr(((ServletRequestAttributes)RequestContextHolder.getRequestAttributes()).getRequest());
for (String attributeName : RequestContextHolder.getRequestAttributes().getAttributeNames(RequestAttributes.SCOPE_REQUEST)) {
RequestContextHolder.getRequestAttributes().setAttribute(attributeName, RequestContextHolder.getRequestAttributes().getAttribute(attributeName, RequestAttributes.SCOPE_REQUEST), RequestAttributes.SCOPE_REQUEST);
}
return requestAttributes;
}
然后在异步函数中:
public class OtherService {
@Async
@Transactional(readOnly=true)
public void asyncMethod(RequestAttributes context) {
//set the RequestAttributes for this thread
RequestContextHolder.setRequestAttributes(context);
// do your thing .... linkTo() etc.
//cleanup
((AsyncRequestScopeAttr)context).completeRequest();
RequestContextHolder.resetRequestAttributes();
}
}
推荐阅读
- mysql - 安装旧版本的 MySQL brew 错误
- python - 程序不断输出奇怪的结果
- ios - Firebase 下载/上传完成后进行 Segue
- jquery - 在下一个 i 标签上更改字体真棒图标
- objection.js - 如何在 Objection.js 的选择查询中构建 JSON 对象
- java - Java将方法添加到类
- javascript - 如何获取 JSON 密钥并添加额外字段?
- scala - Scala 控制台:OutOfMemoryError:超出 GC 开销限制
- docker - 我的 Windows 容器可以在 swarm 中的 linux 工作节点上工作吗?
- javascript - 传单:将折线隐藏在标记的透明部分后面