之前我在这篇文章说过Spring往容器里管理了Request对象,所以你可以在spring的任何实例里面注入HTTPRequest对象。具体是怎么实现的呢?
今天就来更详细地分析下~
public class HealthController {
@Resource
private HttpServletRequest request;
@ApiOperation("健康检查")
@GetMapping("check")
public String health() {
System.out.println(request);
System.out.println(request.getRequestURI());
return "OK";
}
}
为了测试,在我们的健康检查接口类里注入了request对象,同时在健康检查的接口里打印信息
接口调用之后,打印结果如下
Current HttpServletRequest
/xxxxx/api/health/check
如果不想通过注入方式,也可以通过((ServletRequestAttributes)RequestContextHolder.currentRequestAttributes()).getRequest()拿到咱们的request对象。
想想,我们给Spring注入的ServletRequest对象是什么
beanFactory.registerResolvableDependency(ServletRequest.class, new RequestObjectFactory());
我们再来看看这个RequestObjectFactory类
@SuppressWarnings("serial")
private static class RequestObjectFactory implements ObjectFactory<ServletRequest>, Serializable {
@Override
public ServletRequest getObject() {
return currentRequestAttributes().getRequest();
}
@Override
public String toString() {
return "Current HttpServletRequest";
}
}
可以看到toString()方法的返回,也就是咱们的打印
其实此处很容易想到动态代理,毕竟整个Spring的AOP都是基于动态代理做的,但是这个动态代理却又是如此特殊,多数情况下,我们的动态代理不过是在实现invoke()的时候,在target.method调用前后加上咱们的私有逻辑。
但是注意此处Spring给该Request注入的是个RequestObjectFactory,也不是动态代理对象啊。别着急,咱们看看Spring给咱们实际在注入的时候是怎么处理的。
通过添加断点,我们可以发现,在request进行注入的时候,走了以下的逻辑:
/**
* Resolve the given autowiring value against the given required type,
* e.g. an {@link ObjectFactory} value to its actual object result.
* @param autowiringValue the value to resolve
* @param requiredType the type to assign the result to
* @return the resolved value
*/
public static Object resolveAutowiringValue(Object autowiringValue, Class<?> requiredType) {
if (autowiringValue instanceof ObjectFactory && !requiredType.isInstance(autowiringValue)) {
ObjectFactory<?> factory = (ObjectFactory<?>) autowiringValue;
if (autowiringValue instanceof Serializable && requiredType.isInterface()) {
// 会走入到这个逻辑,因此给我们的request对象,其实注入的是ObjectFactoryDelegatingInvocationHandler
autowiringValue = Proxy.newProxyInstance(requiredType.getClassLoader(),
new Class<?>[] {requiredType}, new ObjectFactoryDelegatingInvocationHandler(factory));
}
else {
return factory.getObject();
}
}
return autowiringValue;
}
通过以上代码解析,咱们就发现了,给request实际上注入的是一个咱们了解的动态代理的InvocationHandler对象,入参其实就是咱们的RequestObjectFactory对象,那么具体执行逻辑,咱们要去ObjectFactoryDelegatingInvocationHandler里面看。
private static class ObjectFactoryDelegatingInvocationHandler implements InvocationHandler, Serializable {
private final ObjectFactory<?> objectFactory;
public ObjectFactoryDelegatingInvocationHandler(ObjectFactory<?> objectFactory) {
this.objectFactory = objectFactory;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String methodName = method.getName();
//此处省略掉一些咱们不关心的代码
try {
// 所以当我们request对象调用的时候,他其实做的事儿,就是下面的逻辑。
// 而下面的逻辑,相当于每次调用request对象时,都重新获得新对象objectFactory.getObject()
// 因此保证了,不同的用户的request是不同的对象。
return method.invoke(this.objectFactory.getObject(), args);
}
catch (InvocationTargetException ex) {
throw ex.getTargetException();
}
}
}
上面这段代码里的注释也就解释了为什么request对象为何能够动态变化,不像我们印象中的spring管理对象,是一个不变的对象,而且该proxy,也没有在method.invoke前后做自己的逻辑处理,完全就是为了使用objectFactory.getObject()拿对象。那objectFactory.getObject()又是怎么做到对象不停变化的
毕竟对于普通的ObjectFactory,正常getObject拿到的对象也是一直相同的,不会一直变化的。那让我们再回过头看看RequestObjectFactory的getObject()方法逻辑:
@Override
public ServletRequest getObject() {
return currentRequestAttributes().getRequest();
}
看看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.currentRequestAttributes()).getRequest()来获取request。
/**
* 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.
* @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;
}
最终来到了getRequestAttributes()方法,看看变量名requestAttributesHolder,能够猜到这应该是个ThreadLocal的变量
@Nullable
public static RequestAttributes getRequestAttributes() {
// requestAttributesHolder这命名,一看就是个ThreadLocal
RequestAttributes attributes = requestAttributesHolder.get();
if (attributes == null) {
attributes = inheritableRequestAttributesHolder.get();
}
return attributes;
}
既然此时我们看到是从requestAttributesHolder这个ThreadLocal里面拿到的request对象数据,那么什么时候放入的呢,可以看到RequestContextHolder#setRequestAttributes
/**
* 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();
}
}
}
下面就该定位RequestContextHolder#setRequestAttributes方法是何时被调用了。
相信搞过web编程的都知道Servlet里面方法的运行机制,如果有service()方法,就会运行service方法,否则执行指定的doGet或者doPost。
此处简单写一下每次有request请求进来的处理调用链。
Spring的Web起作用主要靠的就是DispatcherServlet(相信之前配置过SpringMVC的同学对此不陌生)。DispatcherServlet并没有重写service(),所以实际运行的是其父类FrameworkServlet#service()
DispatherServlet#serivce() ->FrameworkServlet#service() ->
HttpServlet#service()->
FrameworkServlet#doGet()或者FrameworkServlet#doPost()->
FrameworkServlet#processRequest()-> 这个方法里生成了最终用的RequestAttributes对象
FrameworkServlet#initContextHolders->
RequestContextHolder#setRequestAttributes 这最后一步也就是我们关心的,至此我们便把RequestAttributes对象放进了ThreadLocal中
以上就是今天要讲的内容,主要分为三步:
一、在web工程中如何拿到request
1. 使用@Autowired或@Resource注解
2. ((ServletRequestAttributes)RequestContextHolder.currentRequestAttributes()).getRequest()
二、Spring是给Request注入的对象是什么,真正在运行时是怎么处理的;
三、Spring是如何做到注入的Request对象在变化的。