Spring源码解读:Spring注入Request原理

文章目录

  • 前言
  • 一、来个例子
  • 二、解析一下
    • 1. 为啥会打印Current HttpServletRequest
    • 2. 既然是Spring注入的对象,为啥能做到动态变化
    • 3. 为啥此ObjectFactory.getObject()能够拿到不停变化的对象
  • 总结


前言

之前我在这篇文章说过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对象。

二、解析一下

1. 为啥会打印Current HttpServletRequest

想想,我们给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()方法的返回,也就是咱们的打印

2. 既然是Spring注入的对象,为啥能做到动态变化

其实此处很容易想到动态代理,毕竟整个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()又是怎么做到对象不停变化的

3. 为啥此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对象在变化的。

你可能感兴趣的:(spring,源码分析,spring,java,aop)