赞
踩
在开发 Java Web 项目中,我们经常使用 HttpServletRequest 获取请求参数、请求头等信息。在Spring项目,我们通常会使用 Spring 提供的注解获取参数,如 @RequestParam、@RequestHeader。
不过在某些场景下,我们可能需要从 HttpServletRequest 对象中取得更多的能力,如获取请求 IP,获取请求域名等。这篇我们来学习如何在 Spring MVC 环境下获取 HttpServletRequest,以及它们的实现方式,做到知其所以然。
使用注解后的 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 类型参数即可。
通过上面的代码我们很容易就实现了,那spring是怎么帮我们搞定的呢?
在上面第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到了吗?
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。