Spring web开发之Request 获取三种方式

在开发 Java Web 项目中,我们经常使用 HttpServletRequest 获取请求参数、请求头等信息。在Spring项目,我们通常会使用 Spring 提供的注解获取参数,如 @RequestParam、@RequestHeader。

不过在某些场景下,我们可能需要从 HttpServletRequest 对象中取得更多的能力,如获取请求 IP,获取请求域名等。这篇我们来学习如何在 Spring MVC 环境下获取 HttpServletRequest,以及它们的实现方式,做到知其所以然。

Controller 方法参数

使用注解后的 Spring MVC controller 方法可以作为 handler 处理请求,如果想获取 request 对象,只需要在方法中添加 ServletRequest 或 HttpServletRequest 类型参数即可。代码如下

@RestController
public class UserController {

    @GetMapping("/getUser")
    public String getUser(HttpServletRequest request) {
        return "request ip is : " + request.getRemoteHost();
    }

}

扩展:如何要获取reponse,同例只要在方法中增加 ServletResponse 或 HttpServletResponse 类型参数即可。

Controller 方法参数实现原理

通过上面的代码我们很容易就实现了,那spring是怎么帮我们搞定的呢?

  1. 在spring mvc中,所有浏览器发起的请求都会先交给DispatcherServlet 处理。
  2. DispatcherServlet 根据用户或默认的配置使用 HandlerMapping 查找可处理请求的处理器。
  3. DispatcherServlet 拿到 HandlerMapping 返回的处理器链 HandlerExecutionChain。整个处理器链包含拦截器和处理。
  4. DispatcherServlet 将处理器适配为 HandlerAdapter。
  5. DispatcherServlet 使用拦截器进行请求前置处理。
  6. DispatcherServlet 使用处理器进行请求处理。
  7. DispatcherServlet 使用拦截器进行请求后置处理。
  8. DispatcherServlet 从拦截器或处理器中提取到模型及视图 ModelAndView。
  9. DispatcherServlet 使用视图解析器 ViewResolver 解析视图出视图 View。
  10. DispatcherServlet 渲染视图,响应请求返回给浏览器。

在上面第6步【DispatcherServlet 使用处理器进行请求处理】时,在调用我们自己的controller方法之前,Spring通过
HandlerMethodArgumentResolver向我们的controller方法注入对应的参数。

静态方法

除了通过 controller 方法参数获取 HttpServletRequest 对象,Spring 还允许通过其提供的工具类的静态方法来获取 HttpServletRequest。示例如下。

HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest();

静态方法实现原理

上面的示例中,RequestContextHolder 表示一个请求上下文的持有者,内部将每个请求上下文信息存储到 ThreadLocal 中。

public abstract class RequestContextHolder {

  /**
   * 线程上下文 RequestAttributes
   */
  private static final ThreadLocal<RequestAttributes> requestAttributesHolder =
      new NamedThreadLocal<>("Request attributes");

  /**
   * 支持继承的线程上下文 RequestAttributes
   */
  private static final ThreadLocal<RequestAttributes> inheritableRequestAttributesHolder =
      new NamedInheritableThreadLocal<>("Request context");

}

DispatcherServlet 处理请求前会将 request 存至 ServletRequestAttributes,然后放到 RequestContextHolder 中,具体可见DispatcherServlet的父类
FrameworkServlet.processRequest()。

直接注入

还可以将 HttpServletRequest 当做普通的 bean 注入。代码如下

@RestController
public class UserController {

    @Autowired
    private HttpServletRequest request;

    @GetMapping("/getIP")
    public String getIP() {
        return "request ip is : " + request.getRemoteHost();
    }

}

直接注入分析

通过 @Autowired 的方式引入 request 也很简单,想想这里会有问题吗?.......

Controller 不是一个单例 bean 对象吗?在一个 Spring 容器内只有一个实例,而每次请求都对应一个 request 对象,Spring 是怎样做到使用一个 request 表示多个请求的?

经过仔细分析,我们可以发现 Spring 注入 bean 时使用了底层的 
DefaultListableBeanFactory 获取 bean 实例,相关代码如下。

public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFactory
    implements ConfigurableListableBeanFactory, BeanDefinitionRegistry, Serializable {
  // 不依赖关系
  private final Map<Class<?>, Object> resolvableDependencies = new ConcurrentHashMap<>(16);
   // 查找候选 bean
  protected Map<String, Object> findAutowireCandidates(
      @Nullable String beanName, Class<?> requiredType, DependencyDescriptor descriptor) {  
    //部分代码省略
    Map<String, Object> result = new LinkedHashMap<>(candidateNames.length);
    
    for (Map.Entry<Class<?>, Object> classObjectEntry : this.resolvableDependencies.entrySet()) {
      Class<?> autowiringType = classObjectEntry.getKey();
      if (autowiringType.isAssignableFrom(requiredType)) {
        Object autowiringValue = classObjectEntry.getValue();
        // 解析 ObjectFactory
        autowiringValue = AutowireUtils.resolveAutowiringValue(autowiringValue, requiredType);
        if (requiredType.isInstance(autowiringValue)) {
          result.put(ObjectUtils.identityToString(autowiringValue), autowiringValue);
          break;
        }
      }
    }
    //部分代码省略
    }
}


DefaultListableBeanFactory 查找候选 bean 时会先从 resolvableDependencies 中查找,找到后调用 AutowireUtils.resolveAutowiringValue方法再次解析。

resolvableDependencies中对象是 Spring 中特殊的存在,不属于 Spring 管理的 bean,需要手动注册到 
DefaultListableBeanFactory。

我们继续跟踪源码。

abstract class AutowireUtils {
  public static Object resolveAutowiringValue(Object autowiringValue, Class<?> requiredType) {
    if (autowiringValue instanceof ObjectFactory && !requiredType.isInstance(autowiringValue)) {
      // ObjectFactory 类型值和所需类型不匹配,创建代理对象
      ObjectFactory<?> factory = (ObjectFactory<?>) autowiringValue;
      if (autowiringValue instanceof Serializable && requiredType.isInterface()) {
        // 创建代理对象,可用于处理 HttpServletRequest 注入等问题
        autowiringValue = Proxy.newProxyInstance(requiredType.getClassLoader(),
            new Class<?>[]{requiredType}, new ObjectFactoryDelegatingInvocationHandler(factory));
      } else {
        return factory.getObject();
      }
    }
    return autowiringValue;
  }
}

当resolvableDependencies中对象是ObjectFactory 类型,并且与所需的类型不匹配,Spring 使用 ObjectFactory 创建了一个 JDK 代理对象:

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 {
       try {
        return method.invoke(this.objectFactory.getObject(), args);
      } catch (InvocationTargetException ex) {
        throw ex.getTargetException();
      }
    }
  }

代理的实现简单,每当所需类型的方法调用时,就调用 ObjectFactory 中获取的实例对象的对应方法。

那怎么与HttpServletRequest关联启来呢?

Spring 在启动时会注册 Web 环境相关的依赖对象

public abstract class WebApplicationContextUtils {

  public static void registerWebApplicationScopes(ConfigurableListableBeanFactory beanFactory,
                          @Nullable ServletContext sc) {

    beanFactory.registerScope(WebApplicationContext.SCOPE_REQUEST, new RequestScope());
    beanFactory.registerScope(WebApplicationContext.SCOPE_SESSION, new SessionScope());
    if (sc != null) {
      ServletContextScope appScope = new ServletContextScope(sc);
      beanFactory.registerScope(WebApplicationContext.SCOPE_APPLICATION, appScope);
      // Register as ServletContext attribute, for ContextCleanupListener to detect it.
      sc.setAttribute(ServletContextScope.class.getName(), appScope);
    }
    // ServletRequest 类型对应 ObjectFactory 注册
    beanFactory.registerResolvableDependency(ServletRequest.class, new RequestObjectFactory());
    beanFactory.registerResolvableDependency(ServletResponse.class, new ResponseObjectFactory());
    beanFactory.registerResolvableDependency(HttpSession.class, new SessionObjectFactory());
    beanFactory.registerResolvableDependency(WebRequest.class, new WebRequestObjectFactory());
    if (jsfPresent) {
      FacesDependencyRegistrar.registerFacesDependencies(beanFactory);
    }
  }
  
}

可以看到:Spring 为 ServletRequest 注入的是 RequestObjectFactory 类型,那再看看它的实现:

public abstract class WebApplicationContextUtils {

  private static class RequestObjectFactory implements ObjectFactory<ServletRequest>, Serializable {

    @Override
    public ServletRequest getObject() {
      return currentRequestAttributes().getRequest();
    }
	/**
	 * 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;
	}
    @Override
    public String toString() {
      return "Current HttpServletRequest";
    }
  }
}

可以看到,和前面介绍的【静态方法】思路一样。

以上就是3种在spring场景中,获取request的方法,get到了吗?

猜你喜欢

转载自blog.csdn.net/lzzyok/article/details/128437151