首页 > 解决方案 > 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
  }

我尝试了什么:

无论我做什么,请求似乎总是在我的异步方法之前完成,而且我显然从来没有属性的深层副本,所以它们总是在异步方法完成之前被主线程标记为非活动状态,然后我不能不再使用它们-> 至少这是我对错误的理解。

我只想能够在我的异步方法中获取 linkTo 方法所需的 requestAttributes,即使在主线程完成请求之后,有人能指出我正确的方向吗?

标签: javaspringasynchronousrequestcontext

解决方案


我找到了一个有效的解决方案并消除了错误。因为我不认为这真的很干净,所以我希望有更多的答案,但万一它对某人有帮助:

首先我添加了这个类。它创建了一个自定义且非常简单的 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();

  }

}

推荐阅读