Three ways to get Request from Spring web development

In developing Java Web projects, we often use HttpServletRequest to obtain information such as request parameters and request headers. In Spring projects, we usually use annotations provided by Spring to get parameters, such as @RequestParam, @RequestHeader.

However, in some scenarios, we may need to obtain more capabilities from the HttpServletRequest object, such as obtaining the request IP, obtaining the request domain name, and so on. In this article, we will learn how to obtain HttpServletRequest in the Spring MVC environment, and how they are implemented, so as to know why.

Controller method parameters

The annotated Spring MVC controller method can be used as a handler to process the request. If you want to get the request object, you only need to add the ServletRequest or HttpServletRequest type parameter to the method. code show as below

@RestController
public class UserController {

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

}

Extension: How to get the response, just add ServletResponse or HttpServletResponse type parameter in the method in the same example.

Implementation Principle of Controller Method Parameters

We can easily achieve it through the above code, so how does spring help us get it done?

  1. In spring mvc, all browser-initiated requests will be handed over to DispatcherServlet for processing.
  2. DispatcherServlet uses HandlerMapping to find handlers that can handle requests based on user or default configuration.
  3. DispatcherServlet gets the handler chain HandlerExecutionChain returned by HandlerMapping. The entire processor chain consists of interceptors and processing.
  4. DispatcherServlet adapts the handler as a HandlerAdapter.
  5. DispatcherServlet uses interceptors for request pre-processing.
  6. DispatcherServlet uses handlers for request processing.
  7. DispatcherServlet uses interceptors for request post-processing.
  8. DispatcherServlet extracts the model and view ModelAndView from the interceptor or handler.
  9. DispatcherServlet uses the ViewResolver to resolve the View out of the View.
  10. DispatcherServlet renders the view and returns it to the browser in response to the request.

In step 6 above [DispatcherServlet uses a processor for request processing], before calling our own controller method, Spring
injects corresponding parameters into our controller method through HandlerMethodArgumentResolver.

static method

In addition to obtaining the HttpServletRequest object through the controller method parameter, Spring also allows obtaining the HttpServletRequest through the static method of the tool class it provides. Examples are as follows.

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

Static method implementation principle

In the above example, RequestContextHolder represents a request context holder, and internally stores each request context information in 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 will save the request to ServletRequestAttributes before processing the request, and then put it in RequestContextHolder, see the parent class
FrameworkServlet.processRequest() of DispatcherServlet for details.

direct injection

It is also possible to inject HttpServletRequest as a normal bean. code show as below

@RestController
public class UserController {

    @Autowired
    private HttpServletRequest request;

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

}

direct injection analysis

It is also very simple to introduce request through @Autowired. Think there will be problems here?  …

Isn't Controller a singleton bean object? There is only one instance in a Spring container, and each request corresponds to a request object. How does Spring use one request to represent multiple requests?

After careful analysis, we can find that Spring uses the underlying 
DefaultListableBeanFactory to obtain bean instances when injecting beans. The relevant codes are as follows.

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;
        }
      }
    }
    //部分代码省略
    }
}


When DefaultListableBeanFactory searches for candidate beans, it will first search from resolvableDependencies, and after finding it, call the AutowireUtils.resolveAutowiringValue method to resolve it again.

The object in resolvableDependencies is a special existence in Spring and does not belong to the bean managed by Spring, so it needs to be manually registered to 
DefaultListableBeanFactory.

We continue to track the source code.

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;
  }
}

When the object in resolvableDependencies is of ObjectFactory type and does not match the required type, Spring uses ObjectFactory to create a JDK proxy object:

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();
      }
    }
  }

The implementation of the agent is simple. Whenever the method of the required type is called, the corresponding method of the instance object obtained in ObjectFactory is called.

So how to associate it with HttpServletRequest?

Spring will register dependent objects related to the Web environment at startup

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);
    }
  }
  
}

It can be seen that Spring injects the RequestObjectFactory type into ServletRequest, then look at its implementation:

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";
    }
  }
}

As you can see, it is the same idea as the [static method] introduced earlier.

The above are 3 ways to get the request in the spring scene. Have you got it?

Guess you like

Origin blog.csdn.net/lzzyok/article/details/128437151