springboot项目异步处理获取不到header中的token
controller方法中调用service的方法,service方法上@Async代表异步执行这个方法,此时方法中如果获取请求头中的token是获取不到的,获取方式如下:
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) requestAttributes;
HttpServletRequest request = servletRequestAttributes.getRequest();
String tokenValue = request.getHeader(tokenHeaderKey);
具体原因:
public static RequestAttributes getRequestAttributes() {RequestAttributes attributes = (RequestAttributes)requestAttributesHolder.get();if (attributes == null) {attributes = (RequestAttributes)inheritableRequestAttributesHolder.get();}return attributes;
}
private static final ThreadLocal<RequestAttributes> requestAttributesHolder = new NamedThreadLocal("Request attributes");
private static final ThreadLocal<RequestAttributes> inheritableRequestAttributesHolder = new NamedInheritableThreadLocal("Request context");
可以看到采用的ThreadLocal存储的request对象,所以子线程魂总获取不到主线程的request。解决办法重新创建request对象,传参给service中异步方法。首先自定义request对象
package com.erbaoge.common.utils;import javax.servlet.*;
import javax.servlet.http.*;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.security.Principal;
import java.util.*;/*** 实现 javax.servlet.http.HttpServletRequest 方法主要用于在子线程中获取参数* 目前只实现headers 和 parameters 其他属性可自定义* @since 2025/9/15 15:44*/
public class ServletRequestCustom implements HttpServletRequest {private Map<String, Vector<String>> headersMap;private Map<String, String[]> paraMap;public ServletRequestCustom() {headersMap = new HashMap<>();paraMap = new HashMap<>();}public ServletRequestCustom(HttpServletRequest request){this();setHeaders(request);setParameters(request);}private void setParameters(HttpServletRequest request) {Enumeration<String> parameterNames = request.getParameterNames();while (parameterNames.hasMoreElements()){String key = parameterNames.nextElement();paraMap.put(key, request.getParameterValues(key));}}private void setHeaders(HttpServletRequest request) {Enumeration<String> headerNames = request.getHeaderNames();while (headerNames.hasMoreElements()) {String key = headerNames.nextElement();Enumeration<String> headers = request.getHeaders(key);Vector<String> vector = new Vector<>();while (headers.hasMoreElements()) {vector.add(headers.nextElement());}headersMap.put(key, vector);}}@Overridepublic String getAuthType() {return "";}@Overridepublic Cookie[] getCookies() {return new Cookie[0];}@Overridepublic long getDateHeader(String s) {return 0;}@Overridepublic String getHeader(String s) {Vector<String> vector = headersMap.get(s);return vector.isEmpty() ? null : vector.get(0);}@Overridepublic Enumeration<String> getHeaders(String s) {return headersMap.get(s.toLowerCase()).elements();}@Overridepublic Enumeration<String> getHeaderNames() {Iterator<String> iterator = headersMap.keySet().iterator();return new Enumeration<String>() {@Overridepublic boolean hasMoreElements() {return iterator.hasNext();}@Overridepublic String nextElement() {return iterator.next();}};}@Overridepublic int getIntHeader(String s) {return 0;}@Overridepublic String getMethod() {return "";}@Overridepublic String getPathInfo() {return "";}@Overridepublic String getPathTranslated() {return "";}@Overridepublic String getContextPath() {return "";}@Overridepublic String getQueryString() {return "";}@Overridepublic String getRemoteUser() {return "";}@Overridepublic boolean isUserInRole(String s) {return false;}@Overridepublic Principal getUserPrincipal() {return null;}@Overridepublic String getRequestedSessionId() {return "";}@Overridepublic String getRequestURI() {return "";}@Overridepublic StringBuffer getRequestURL() {return null;}@Overridepublic String getServletPath() {return "";}@Overridepublic HttpSession getSession(boolean b) {return null;}@Overridepublic HttpSession getSession() {return null;}@Overridepublic String changeSessionId() {return "";}@Overridepublic boolean isRequestedSessionIdValid() {return false;}@Overridepublic boolean isRequestedSessionIdFromCookie() {return false;}@Overridepublic boolean isRequestedSessionIdFromURL() {return false;}@Overridepublic boolean isRequestedSessionIdFromUrl() {return false;}@Overridepublic boolean authenticate(HttpServletResponse httpServletResponse) throws IOException, ServletException {return false;}@Overridepublic void login(String s, String s1) throws ServletException {}@Overridepublic void logout() throws ServletException {}@Overridepublic Collection<Part> getParts() throws IOException, ServletException {return Collections.emptyList();}@Overridepublic Part getPart(String s) throws IOException, ServletException {return null;}@Overridepublic <T extends HttpUpgradeHandler> T upgrade(Class<T> aClass) throws IOException, ServletException {return null;}@Overridepublic Object getAttribute(String s) {return null;}@Overridepublic Enumeration<String> getAttributeNames() {return null;}@Overridepublic String getCharacterEncoding() {return "";}@Overridepublic void setCharacterEncoding(String s) throws UnsupportedEncodingException {}@Overridepublic int getContentLength() {return 0;}@Overridepublic long getContentLengthLong() {return 0;}@Overridepublic String getContentType() {return "";}@Overridepublic ServletInputStream getInputStream() throws IOException {return null;}@Overridepublic String getParameter(String s) {String[] values = paraMap.get(s);return values.length > 0 ? values[0] : null;}@Overridepublic Enumeration<String> getParameterNames() {final Iterator<String> iterator = paraMap.keySet().iterator();return new Enumeration<String>() {@Overridepublic boolean hasMoreElements() {return iterator.hasNext();}@Overridepublic String nextElement() {return iterator.next();}};}@Overridepublic String[] getParameterValues(String s) {return paraMap.get(s);}@Overridepublic Map<String, String[]> getParameterMap() {return new HashMap<>(paraMap);}@Overridepublic String getProtocol() {return "";}@Overridepublic String getScheme() {return "";}@Overridepublic String getServerName() {return "";}@Overridepublic int getServerPort() {return 0;}@Overridepublic BufferedReader getReader() throws IOException {return null;}@Overridepublic String getRemoteAddr() {return "";}@Overridepublic String getRemoteHost() {return "";}@Overridepublic void setAttribute(String s, Object o) {}@Overridepublic void removeAttribute(String s) {}@Overridepublic Locale getLocale() {return null;}@Overridepublic Enumeration<Locale> getLocales() {return null;}@Overridepublic boolean isSecure() {return false;}@Overridepublic RequestDispatcher getRequestDispatcher(String s) {return null;}@Overridepublic String getRealPath(String s) {return "";}@Overridepublic int getRemotePort() {return 0;}@Overridepublic String getLocalName() {return "";}@Overridepublic String getLocalAddr() {return "";}@Overridepublic int getLocalPort() {return 0;}@Overridepublic ServletContext getServletContext() {return null;}@Overridepublic AsyncContext startAsync() throws IllegalStateException {return null;}@Overridepublic AsyncContext startAsync(ServletRequest servletRequest, ServletResponse servletResponse) throws IllegalStateException {return null;}@Overridepublic boolean isAsyncStarted() {return false;}@Overridepublic boolean isAsyncSupported() {return false;}@Overridepublic AsyncContext getAsyncContext() {return null;}@Overridepublic DispatcherType getDispatcherType() {return null;}
}
contoller中方法注入HttpServletRequest对象,并创建自定义request对象
@GetMapping("/test")
public Result<?> test (HttpServletRequest request) {ServletRequestCustom servletRequestCustom = new ServletRequestCustom(request);lotteryService.test(servletRequestCustom);return Result.success();
}
service中异步方法需要将request放入
@Async
@Override
public void test(HttpServletRequest request) {RequestContextHolder.setRequestAttributes(new ServletRequestAttributes(request));try {......} finally {RequestContextHolder.resetRequestAttributes();}
}
上述方法是spring的异步执行方法,方法中就能成功使用开始的那种方式获取到token。
两点疑问
- 为什么不直接传参从controller中注入的request对象
- 为什么不在controller线程中获取ServletRequestAttributes后,再放入,子线程获取参数传参true。使用代码
RequestContextHolder.setRequestAttributes(new ServletRequestAttributes(request), true);
答案是以上两种方式都不能成功在异步方法中获取token,详细原因还未深究。