对于在service层使用RequestContextHolder的方法要小心,除了controller层外,调度任务等也可能调用service层的方法。
分析
该错误信息在RequestContextHolder这个类中,详细如下:
spring-web-4.3.7.RELEASE-sources.jar!/org/springframework/web/context/request/RequestContextHolder.java
public abstract class RequestContextHolder {
private static final boolean jsfPresent =
ClassUtils.isPresent("javax.faces.context.FacesContext", RequestContextHolder.class.getClassLoader());
private static final ThreadLocal<RequestAttributes> requestAttributesHolder =
new NamedThreadLocal<RequestAttributes>("Request attributes");
private static final ThreadLocal<RequestAttributes> inheritableRequestAttributesHolder =
new NamedInheritableThreadLocal<RequestAttributes>("Request context");
/**
* Return the RequestAttributes currently bound to the thread.
* <p>Exposes the previously bound RequestAttributes instance, if any.
* Falls back to the current JSF FacesContext, if any.
* @return the RequestAttributes currently bound to the thread
* @throws IllegalStateException if no RequestAttributes object
* is bound to the current thread
* @see #setRequestAttributes
* @see ServletRequestAttributes
* @see FacesRequestAttributes
* @see javax.faces.context.FacesContext#getCurrentInstance()
*/
public static RequestAttributes currentRequestAttributes() throws IllegalStateException {
RequestAttributes attributes = getRequestAttributes();
if (attributes == null) {
if (jsfPresent) {
attributes = FacesRequestAttributesFactory.getFacesRequestAttributes();
}
if (attributes == null) {
throw new IllegalStateException("No thread-bound request found: " +
"Are you referring to request attributes outside of an actual web request, " +
"or processing a request outside of the originally receiving thread? " +
"If you are actually operating within a web request and still receive this message, " +
"your code is probably running outside of DispatcherServlet/DispatcherPortlet: " +
"In this case, use RequestContextListener or RequestContextFilter to expose the current request.");
}
}
return attributes;
}
/**
* Return the RequestAttributes currently bound to the thread.
* @return the RequestAttributes currently bound to the thread,
* or {@code null} if none bound
*/
public static RequestAttributes getRequestAttributes() {
RequestAttributes attributes = requestAttributesHolder.get();
if (attributes == null) {
attributes = inheritableRequestAttributesHolder.get();
}
return attributes;
}
//...
}
解决
在非web下访问了RequestContextHolder.currentRequestAttributes()导致,因此在service层方法里头调用该方法要慎重,为了避免出错,可以再封装一下
package com.yzy.common.core.interceptors;
import cn.hutool.core.util.StrUtil;
import com.yzy.common.constant.AuthConstant;
import com.yzy.common.core.local.LocalUser;
import com.yzy.common.core.local.UserIdentityBO;
import feign.RequestInterceptor;
import feign.RequestTemplate;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
/**
* 当拿不到request时,创建NonWebRequestAttributes来实现RequestAttributes,然后加一个非空判断绕过强转的地方,这样就能正常的调用其他服务了
*/
public class FeignRequestHeaderInterceptor implements RequestInterceptor {
public FeignRequestHeaderInterceptor(){
}
@Override
public void apply(RequestTemplate requestTemplate) {
//修改前
// RequestAttributes requestAttributes = RequestContextHolder.currentRequestAttributes();
//修改后
HttpServletRequest httpServletRequestSafely = getHttpServletRequestSafely();
if (httpServletRequestSafely != null) {
HttpServletRequest request = ((ServletRequestAttributes) httpServletRequestSafely).getRequest();
requestTemplate.header(AuthConstant.USER_TOKEN_HEADER, request.getHeader(AuthConstant.USER_TOKEN_HEADER));
}
UserIdentityBO userIdentityBO = LocalUser.getLocalUser();
if (userIdentityBO==null){
return;
}
if(!StrUtil.isEmpty(userIdentityBO.getScope())){
requestTemplate.header(AuthConstant.CURRENT_SCOPE, userIdentityBO.getScope());
}
if (null != userIdentityBO.getUid() && 0 != userIdentityBO.getUid()) {
requestTemplate.header(AuthConstant.CURRENT_USER, String.valueOf(userIdentityBO.getUid()));
}
if(null != userIdentityBO.getIdentity() ){
requestTemplate.header(AuthConstant.CURRENT_IDENTITY, String.valueOf(userIdentityBO.getIdentity()));
}
}
public HttpServletRequest getHttpServletRequestSafely() {
try {
RequestAttributes requestAttributesSafely = this.getRequestAttributesSafely();
return requestAttributesSafely instanceof NonWebRequestAttributes ? null : ((ServletRequestAttributes)requestAttributesSafely).getRequest();
} catch (Exception var2) {
return null;
}
}
public RequestAttributes getRequestAttributesSafely() {
Object requestAttributes = null;
try {
requestAttributes = RequestContextHolder.currentRequestAttributes();
} catch (IllegalStateException var3) {
requestAttributes = new NonWebRequestAttributes();
}
return (RequestAttributes)requestAttributes;
}
}
NonWebRequestAttributes实现RequestAttributes接口,重写的方法都为空即可
NonWebRequestAttributes
package com.yzy.common.core.interceptors;
import org.springframework.web.context.request.RequestAttributes;
public class NonWebRequestAttributes implements RequestAttributes {
@Override
public Object getAttribute(String s, int i) {
return null;
}
@Override
public void setAttribute(String s, Object o, int i) {
}
@Override
public void removeAttribute(String s, int i) {
}
@Override
public String[] getAttributeNames(int i) {
return new String[0];
}
@Override
public void registerDestructionCallback(String s, Runnable runnable, int i) {
}
@Override
public Object resolveReference(String s) {
return null;
}
@Override
public String getSessionId() {
return null;
}
@Override
public Object getSessionMutex() {
return null;
}
}