我们在Controller中使用request对象的时候往往会直接Autowired到成员变量中,如下
@RestController
public class XController {
@Autowired
private HttpServletRequest request;
@PostMapping("/test")
public Object orderInfo() {
String o = request.getXxx();
// todo
return null;
}
}
controller作为默认单例的对象,其成员变量为共享变量,那么我们上述使用方式为什么没有引发线程安全问题呢?
我们使用的是SpringIOC注入,猜想就会被spring进行代理,但是代理和线程安全有何关联?
controller类也会被spring注入ioc容器中,而request作为controller所依赖的属性,必定会通过方法org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#populateBean来进行属性设置,断点跟进
整个流程为
> org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#populateBean
> org.springframework.beans.factory.support.DefaultListableBeanFactory#doResolveDependency
> org.springframework.beans.factory.support.DefaultListableBeanFactory#findAutowireCandidates
> org.springframework.beans.factory.support.AutowireUtils#resolveAutowiringValue
看下resolveAutowiringValue方法:
方法中传入了WebApplicationContextUtils.RequestObjectFactory对象来创建一个ObjectFactoryDelegatingInvocationHandler类对象,它实现了InvocationHandler接口,是一个代理类,注定会被调用invoke方法:
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String methodName = method.getName();
if (methodName.equals("equals")) {
// Only consider equal when proxies are identical.
return (proxy == args[0]);
}
else if (methodName.equals("hashCode")) {
// Use hashCode of proxy.
return System.identityHashCode(proxy);
}
else if (methodName.equals("toString")) {
return this.objectFactory.toString();
}
try {
// 调用factory的getObject方法返回需要的对象
return method.invoke(this.objectFactory.getObject(), args);
}
catch (InvocationTargetException ex) {
throw ex.getTargetException();
}
}
继续跟进RequestObjectFactory类:
/**
* Factory that exposes the current request object on demand.
*/
@SuppressWarnings("serial")
private static class RequestObjectFactory implements ObjectFactory<ServletRequest>, Serializable {
@Override
public ServletRequest getObject() {
return currentRequestAttributes().getRequest();
}
@Override
public String toString() {
return "Current HttpServletRequest";
}
}
进入currentRequestAttributes()方法:
/**
* Return the current RequestAttributes instance as ServletRequestAttributes.
* @see RequestContextHolder#currentRequestAttributes()
*/
private static ServletRequestAttributes currentRequestAttributes() {
RequestAttributes requestAttr = RequestContextHolder.currentRequestAttributes();
if (!(requestAttr instanceof ServletRequestAttributes)) {
throw new IllegalStateException("Current request is not a servlet request");
}
return (ServletRequestAttributes) requestAttr;
}
currentRequestAttributes()方法中会调用getRequestAttributes()方法:
/**
* Return the RequestAttributes currently bound to the thread.
* @return the RequestAttributes currently bound to the thread,
* or {@code null} if none bound
*/
@Nullable
public static RequestAttributes getRequestAttributes() {
RequestAttributes attributes = requestAttributesHolder.get();
if (attributes == null) {
attributes = inheritableRequestAttributesHolder.get();
}
return attributes;
}
里面两个holder都是threadlocal对象:
private static final ThreadLocal<RequestAttributes> requestAttributesHolder =
new NamedThreadLocal<>("Request attributes");
private static final ThreadLocal<RequestAttributes> inheritableRequestAttributesHolder =
new NamedInheritableThreadLocal<>("Request context");
既然有获取,那么一定会有设置方法吧:
/**
* Bind the given RequestAttributes to the current thread.
* @param attributes the RequestAttributes to expose,
* or {@code null} to reset the thread-bound context
* @param inheritable whether to expose the RequestAttributes as inheritable
* for child threads (using an {@link InheritableThreadLocal})
*/
public static void setRequestAttributes(@Nullable RequestAttributes attributes, boolean inheritable) {
if (attributes == null) {
resetRequestAttributes();
}
else {
if (inheritable) {
inheritableRequestAttributesHolder.set(attributes);
requestAttributesHolder.remove();
}
else {
requestAttributesHolder.set(attributes);
inheritableRequestAttributesHolder.remove();
}
}
}
我们知道SpringMVC框架入口在DispatchServlet,它的继承实现关系如下
本质就是一个servlet嘛,所以一定会通过service调用到doGet或doPost方法,具体实现位于类FrameworkServlet中,doPost和doGet等方法都会调用processRequest方法:
/**
* Process this request, publishing an event regardless of the outcome.
* <p>The actual event handling is performed by the abstract
* {@link #doService} template method.
*/
protected final void processRequest(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
long startTime = System.currentTimeMillis();
Throwable failureCause = null;
LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
LocaleContext localeContext = buildLocaleContext(request);
RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();
ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes);
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor());
initContextHolders(request, localeContext, requestAttributes);
try {
doService(request, response);
}
catch (ServletException | IOException ex) {
failureCause = ex;
throw ex;
}
catch (Throwable ex) {
failureCause = ex;
throw new NestedServletException("Request processing failed", ex);
}
finally {
resetContextHolders(request, previousLocaleContext, previousAttributes);
if (requestAttributes != null) {
requestAttributes.requestCompleted();
}
logResult(request, response, failureCause, asyncManager);
publishRequestHandledEvent(request, response, startTime, failureCause);
}
}
initContextHolders方法中会调用RequestContextHolder.setRequestAttributes方法将请求信息requestAttributes设置到线程本地变量中,从而解决了线程安全问题!
通过currentRequestAttributes()方法源码可知 RequestAttributes 是 ServletRequestAttributes,ServletRequestAttributes内部有一个request全局变量:
private final HttpServletRequest request;
RequestObjectFactory的getObject方法返回的就是这个request对象。再通过反射调用实际需要调用的方法,实现的效果就像是吧request变成了方法入参一样,不会有问题。
总结下:
- spring注入request对象的时候注入了一个单例工厂对象RequestObjectFactory,提供了getObject方法用于返回真正的对象实例
- getObject方法返回实例,取自线程本地变量中存储的ServletRequestAttributes中
- 接口调用时会设置ServletRequestAttributes到线程本地变量中,里面包含request;