Tres formas de obtener la solicitud del desarrollo web de Spring

Al desarrollar proyectos web de Java, a menudo usamos HttpServletRequest para obtener información como parámetros de solicitud y encabezados de solicitud. En los proyectos de Spring, generalmente usamos anotaciones proporcionadas por Spring para obtener parámetros, como @RequestParam, @RequestHeader.

Sin embargo, en algunos escenarios, es posible que necesitemos obtener más capacidades del objeto HttpServletRequest, como obtener la IP de la solicitud, obtener el nombre de dominio de la solicitud, etc. En este artículo, aprenderemos cómo obtener HttpServletRequest en el entorno Spring MVC y cómo se implementan, para saber por qué.

Parámetros del método del controlador

El método de controlador Spring MVC anotado se puede usar como controlador para procesar la solicitud. Si desea obtener el objeto de solicitud, solo necesita agregar el parámetro de tipo ServletRequest o HttpServletRequest al método. el código se muestra a continuación

@RestController
public class UserController {

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

}

Extensión: cómo obtener la respuesta, simplemente agregue el parámetro de tipo ServletResponse o HttpServletResponse en el método en el mismo ejemplo.

Principio de implementación de los parámetros del método del controlador

Podemos lograrlo fácilmente a través del código anterior, entonces, ¿cómo nos ayuda Spring a hacerlo?

  1. En spring mvc, todas las solicitudes iniciadas por el navegador se entregarán a DispatcherServlet para su procesamiento.
  2. DispatcherServlet usa HandlerMapping para encontrar controladores que puedan manejar solicitudes según el usuario o la configuración predeterminada.
  3. DispatcherServlet obtiene la cadena de controladores HandlerExecutionChain devuelta por HandlerMapping. Toda la cadena de procesadores consta de interceptores y manipuladores.
  4. DispatcherServlet adapta el controlador como un HandlerAdapter.
  5. DispatcherServlet utiliza interceptores para el preprocesamiento de solicitudes.
  6. DispatcherServlet utiliza controladores para el procesamiento de solicitudes.
  7. DispatcherServlet utiliza interceptores para el posprocesamiento de solicitudes.
  8. DispatcherServlet extrae el modelo y la vista ModelAndView del interceptor o controlador.
  9. DispatcherServlet usa ViewResolver para resolver la Vista fuera de la Vista.
  10. DispatcherServlet representa la vista y la devuelve al navegador en respuesta a la solicitud.

En el paso 6 anterior [DispatcherServlet usa un procesador para el procesamiento de solicitudes], antes de llamar a nuestro propio método de controlador, Spring
inyecta los parámetros correspondientes en nuestro método de controlador a través de HandlerMethodArgumentResolver.

método estático

Además de obtener el objeto HttpServletRequest a través del parámetro del método del controlador, Spring también permite obtener el HttpServletRequest a través del método estático de la clase de herramienta que proporciona. Los ejemplos son los siguientes.

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

Principio de implementación del método estático

En el ejemplo anterior, RequestContextHolder representa un titular de contexto de solicitud y almacena internamente cada información de contexto de solicitud en 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 guardará la solicitud en ServletRequestAttributes antes de procesarla y luego la colocará en RequestContextHolder; consulte la clase principal
FrameworkServlet.processRequest() de DispatcherServlet para obtener más detalles.

inyección directa

También es posible inyectar HttpServletRequest como un bean normal. el código se muestra a continuación

@RestController
public class UserController {

    @Autowired
    private HttpServletRequest request;

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

}

análisis de inyección directa

También es muy sencillo presentar una solicitud a través de @Autowired. ¿Crees que habrá problemas aquí?  …

¿No es Controller un objeto de bean singleton? Solo hay una instancia en un contenedor de Spring, y cada solicitud corresponde a un objeto de solicitud.¿Cómo usa Spring una solicitud para representar múltiples solicitudes?

Después de un análisis cuidadoso, podemos encontrar que Spring usa el 
DefaultListableBeanFactory subyacente para obtener instancias de beans al inyectar beans.Los códigos relevantes son los siguientes.

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


Cuando DefaultListableBeanFactory busca beans candidatos, primero busca desde resolvableDependencies y, después de encontrarlo, llama al método AutowireUtils.resolveAutowiringValue para resolverlo nuevamente.

El objeto en resolvableDependencies es una existencia especial en Spring y no pertenece al bean administrado por Spring, por lo que debe registrarse manualmente en 
DefaultListableBeanFactory.

Seguimos rastreando el código fuente.

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

Cuando el objeto en resolvableDependencies es del tipo ObjectFactory y no coincide con el tipo requerido, Spring usa ObjectFactory para crear un objeto proxy 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();
      }
    }
  }

La implementación del agente es sencilla, cada vez que se llama al método del tipo requerido, se llama al método correspondiente del objeto instancia obtenido en ObjectFactory.

Entonces, ¿cómo asociarlo con HttpServletRequest?

Spring registrará objetos dependientes relacionados con el entorno web al inicio

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

Se puede ver que Spring inyecta el tipo RequestObjectFactory en ServletRequest, luego observa su implementación:

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

Como puede ver, es la misma idea que el [método estático] presentado anteriormente.

Las anteriores son 3 formas de obtener la solicitud en la escena de primavera. ¿Lo tienes?

Supongo que te gusta

Origin blog.csdn.net/lzzyok/article/details/128437151
Recomendado
Clasificación