概述
在Spring
应用中,将一个较大作用域的bean
注入到一个较小作用域的bean
中是很自然的一件事情,因为往较小作用域bean
中注入较大作用域bean
时,较大作用域的bean
已经存在了。但是,如果反过来,将一个较小作用域的bean
往一个较大作用域的bean
中注入时,较小作用域的bean
可能根本不存在,比如Spring MVC
应用中,如果要将一个ServletRequest
注入到一个单例作用域的Web
控制器组件中,则该注入动作会在该单例Web
控制器组件初始化时发生(容器对单例bean
的创建过程决定的),而通常此时容器正在启动,任何用户请求尚未到达,所以注入任何一个ServletRequest
听起来都是不正确的。以此类推,将一个较小作用域的bean
往一个较大作用域的bean
中注入概念上讲是不可行的。
但通过实践,我们明确知道,在一个Spring MVC
应用中,将一个ServletRequest
注入到一个单例作用域的Web
控制器中是可以的。这又是为什么呢 ?这里面,我们有两个方面的问题要解答 :
- 注入的
bean
是什么?为什么ServletRequest
能够注入成功 ? - 注入的
bean
如何工作的?为什么请求过程中访问注入的ServletRequest
能访问到正确的信息?
本文我们将通过分析回答上面的第二个问题。
本文所使用的例子如下 :
@Controller public class HomeController { ServletRequest request; @Autowired public void setRequest(ServletRequest request){ this.request=request; } @RequestMapping("/") @ResponseBody public String home() { return "Hello "+ request.getParameter("name"); } }
工作原理分析
1. 观察注入的ServletRequest
对象
我们在上面例子中行
this.request=request;
设置一个断点,然后启动程序。我们发现,bean HomeController
创建时,request
属性被注入了如下对象 :
request = {$Proxy54@5166} "Current HttpServletRequest"
h = {AutowireUtils$ObjectFactoryDelegatingInvocationHandler@5174}
objectFactory = {WebApplicationContextUtils$RequestObjectFactory@5175} "Current HttpServletRequest"
从这些信息不难看出,request
是一个Java
动态代理对象,该动态代理对象的InvocationHandler
实现类是AutowireUtils$ObjectFactoryDelegatingInvocationHandler
。我们先来看看这个实现类。
1.1 AutowireUtils$ObjectFactoryDelegatingInvocationHandler
ObjectFactoryDelegatingInvocationHandler
是AutowireUtils
的静态嵌套类,实现如下 :
private static class ObjectFactoryDelegatingInvocationHandler implements InvocationHandler, Serializable {
// 这是一个对象工厂,该对象工厂用于获取目标调用方法的所属对象
private final ObjectFactory<?> objectFactory;
// 构造函数,参数objectFactory表示一个对象工厂,目标调用方法的所属对象将会从该对象工厂中获取
public ObjectFactoryDelegatingInvocationHandler(ObjectFactory<?> objectFactory) {
this.objectFactory = objectFactory;
}
// 接口 InvocationHandler 所约定的 invoke 方法,对Java动态代理对象的调用会调用该方法,
// 然后该方法又会从objectFactory中获取目标对象,然后将最终的方法调用应用在目标对象上。
@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 {
return method.invoke(this.objectFactory.getObject(), args);
}
catch (InvocationTargetException ex) {
throw ex.getTargetException();
}
}
}
从该类的实现我们可以看到,代理对象request
一旦生成,其属性objectFactory
对象就确定且不再更改了,但是该代理对象的每次调用,却作用在了objectFactory.getObject()
返回的对象上。我们知道,getObject()
方法其实是一段逻辑,而一段逻辑是可以每次调用时返回不同的对象的。那么我们可以试想,每次用户请求处理时,我们对预先注入的request
代理对象的方法调用,比如上面例子中的getParameter("name")
,之所以能够应用到正确的用户请求上,会不会就是这里getObject()
每次调用时返回了正确的用户请求对象呢 ?
从上面我们调试观察到的request
代理对象的内存结构我们可以看到,它的objectFactory
实现类是WebApplicationContextUtils$RequestObjectFactory
。我们接下来分析一个这个类。
1.2 WebApplicationContextUtils$RequestObjectFactory
RequestObjectFactory
是WebApplicationContextUtils
的静态嵌套类,实现如下 :
/**
* 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";
}
}
RequestObjectFactory
的实现很简单,它是一个对象工厂,用于从currentRequestAttributes()
中获取一个请求对象ServletRequest
交给调用者。我们再来看一下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;
}
该方法的实现也很简单直观,主要就是执行RequestContextHolder.currentRequestAttributes()
返回一个ServletRequestAttributes
对象。它具体是什么,我们就得看RequestContextHolder
的实现了。
1.3 RequestContextHolder
package org.springframework.web.context.request;
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<>("Request attributes");
// 在当前线程中保存请求属性,适用于指定需要跨线程继承请求属性的情形
private static final ThreadLocal<RequestAttributes> inheritableRequestAttributesHolder =
new NamedInheritableThreadLocal<>("Request context");
/**
* Reset the RequestAttributes for the current thread.
* 清除当前线程中保存的请求属性,
* 对 requestAttributesHolder 和 inheritableRequestAttributesHolder 都会执行删除动作
*/
public static void resetRequestAttributes() {
requestAttributesHolder.remove();
inheritableRequestAttributesHolder.remove();
}
/**
* Bind the given RequestAttributes to the current thread,
* not exposing it as inheritable for child threads.
* 记录请求属性到当前线程,假定当前情形是不要跨线程继承请求属性的情形
* @param attributes the RequestAttributes to expose
* @see #setRequestAttributes(RequestAttributes, boolean)
*/
public static void setRequestAttributes(@Nullable RequestAttributes attributes) {
setRequestAttributes(attributes, false);
}
/**
* Bind the given RequestAttributes to the current thread.
* 记录请求属性到当前线程,参数 inheritable 指定是何种情形 :
* true - 需要跨线程继承请求属性的情形 => 信息记录到 inheritableRequestAttributesHolder
* false - 不要跨线程继承请求属性的情形 => 信息记录到 requestAttributesHolder
* 如果参数 attributes 为 null,则从当前线程中清除所记录的请求属性
* @param attributes the RequestAttributes to expose,
* or null to reset the thread-bound context
* @param inheritable whether to expose the RequestAttributes as inheritable
* for child threads (using an 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();
}
}
}
/**
* Return the RequestAttributes currently bound to the thread.
* 获取当前线程中绑定的请求属性
* @return the RequestAttributes currently bound to the thread,
* or null if none bound
*/
@Nullable
public static RequestAttributes getRequestAttributes() {
RequestAttributes attributes = requestAttributesHolder.get();
if (attributes == null) {
attributes = inheritableRequestAttributesHolder.get();
}
return attributes;
}
/**
* Return the RequestAttributes currently bound to the thread.
* Exposes the previously bound RequestAttributes instance, if any.
* Falls back to the current JSF FacesContext, if any.
* 获取当前线程中绑定的请求属性,对 JSF 也提供了支持,如果当前线程中没有
* 绑定请求属性,则抛出异常 IllegalStateException
* @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: " +
"In this case, use RequestContextListener or RequestContextFilter to expose the current request.");
}
}
return attributes;
}
/**
* Inner class to avoid hard-coded JSF dependency.
*/
private static class FacesRequestAttributesFactory {
@Nullable
public static RequestAttributes getFacesRequestAttributes() {
FacesContext facesContext = FacesContext.getCurrentInstance();
return (facesContext != null ? new FacesRequestAttributes(facesContext) : null);
}
}
}
从上面的代码实现不难看出,RequestContextHolder
是一个静态工具类,它的作用主要在当前线程中保存请求属性 :
- 供使用者使用
RequestContextHolder.setRequestAttributes
将请求属性记录到当前线程, - 这样即使逻辑执行到了其他地方,使用者仍然可以通过
RequestContextHolder.currentRequestAttributes
获取当前请求属性。
结合上面的分析,我们可以看出,bean HomeController
属性request
代理对象最终作用在了从RequestContextHolder
中获取到的请求对象上。进一步讲,如果每次调用request
代理对象的方法时,如果RequestContextHolder
在当前线程中所记录的请求对象是正确的,那么通过bean HomeController
属性request
代理对象就能访问到正确的请求数据。
那么,RequestContextHolder
有没有被正确设置呢 ?
我们在RequestContextHolder
方法setRequestAttributes
上增加一个断点,可以观察到,在Spring MVC
的前端控制器DispatcherServlet
处理该请求的主逻辑开始前,它被调用了,具体的代码如下 :
// DispatcherServlet 基类 FrameworkServlet
protected final void processRequest(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// ...
// 构建当前请求属性对象 requestAttributes
RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();
ServletRequestAttributes requestAttributes = buildRequestAttributes(request,
response, previousAttributes);
// ...
//
initContextHolders(request, ..., requestAttributes);
// ...
// 请求处理主逻辑
doService(request, response);
// ...
}
private void initContextHolders(HttpServletRequest request,
@Nullable LocaleContext localeContext, @Nullable RequestAttributes requestAttributes) {
// ...
// 记录当前请求对象到RequestContextHolder,也就是当前线程
if (requestAttributes != null) {
RequestContextHolder.setRequestAttributes(requestAttributes,
this.threadContextInheritable);
}
}
到此,一切就很清楚了:每个请求到达后,被处理前,请求属性会被记录到当前线程,也就是RequestContextHolder
所做的事情,这样随后在整个请求处理过程的任何地方,当然也包括上面bean HomeController
的任何控制器方法,比如home()
中,访问代理对象request
时,真正访问到的就会是RequestContextHolder
中保存的当前请求对象了。
总结
基于上述分析,我们可以总结出使bean HomeController
的注入属性ServletRequest request
可以正确工作的关键点 :
- 注入属性
ServletRequest request
是一个代理对象,它所代理的真正请求对象是RequestContextHolder
请求上下文持有器中的请求对象; RequestContextHolder
用于在当前线程中保持请求属性;Spring MVC
前端控制器DispatcherServlet
每处理一个请求前,先将请求属性记录在RequestContextHolder
中。
相关文章
Spring MVC : ServletRequest 注入原理分析
Spring MVC : ServletRequest 注入原理分析 1 - 为什么可以注入成功?