如何解决Java Spring 请求在异步方法完成之前变为非活动状态
我有一个带有调用异步方法的 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 作为参数(如上面的代码片段所示)
- 使用此答案中描述的上下文感知池执行程序:How to enable request scope in async task executor - 这基本上似乎与我将上下文作为仅全局配置的变量传递一样
- 更新 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() {
}
}
然后在调用异步方法之前的 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();
}
}
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。