Prefacio
Este artículo introducirá varios métodos para obtener objetos de solicitud en el sistema web desarrollado por Spring MVC y discutirá la seguridad de sus subprocesos. No diré mucho a continuación, echemos un vistazo a la introducción detallada juntos.
Descripción general
Al desarrollar un sistema web que usa Spring MVC, a menudo debe usar un objeto de solicitud al procesar solicitudes, como obtener la dirección IP del cliente, la URL solicitada, los atributos en el encabezado (como las cookies, la información de autorización), los datos en el cuerpo, etc., ya que en la primavera MVC, el controlador, el servicio y otros objetos que se manejan, se mananjan los tiselizos, se le importan la solicitud de la solicitud que se les pide a la solicitud, se les pide que la solicitud sea la solicitud, la solicitud es la solicitud de solicitud, se trata de la solicitud, la solicitud es la solicitud, la solicitud es la solicitud, la solicitud es la solicitud de la solicitud. Una gran cantidad de solicitudes concurrentes, ¿se puede garantizar que se usan diferentes objetos de solicitud en diferentes solicitudes/hilos?
Hay otra pregunta que tener en cuenta aquí: ¿dónde uso el objeto de solicitud "Al procesar una solicitud" mencionada anteriormente? Teniendo en cuenta que existen ligeras diferencias en los métodos para obtener objetos de solicitud, pueden dividirse aproximadamente en dos categorías:
1) Use objetos de solicitud en frijoles de primavera: incluya tanto los frijoles MVC, como el controlador, el servicio, el repositorio y los frijoles de resorte ordinarios, como el componente. Para conveniencia de explicación, los frijoles en primavera en el siguiente texto se denominan frijoles para abreviar.
2) Use los objetos de solicitud en no temas: como en los métodos de objetos Java ordinarios, o en métodos estáticos de clases.
Además, este artículo analiza sobre el objeto de solicitud que representa la solicitud, pero el método utilizado también es aplicable al objeto de respuesta, InputStream/Reader, OutputStream/Writer, etc.; Donde InputStream/Reader puede leer los datos en la solicitud, y OutputStream/Writer puede escribir datos en la respuesta.
Finalmente, el método para obtener el objeto de solicitud también está relacionado con la versión de Spring y MVC; Este artículo se discute en base a Spring 4, y los experimentos realizados están utilizando la versión 4.1.1.
Cómo probar la seguridad del hilo
Dado que los problemas de seguridad del hilo del objeto de solicitud necesitan atención especial, para facilitar la discusión a continuación, primero expliquemos cómo probar si el objeto de solicitud es seguro de hilo.
La idea básica de las pruebas es simular una gran cantidad de solicitudes concurrentes en el cliente y luego determinar si las solicitudes se usan en el servidor.
La forma más intuitiva de determinar si el objeto de solicitud es el mismo es imprimir la dirección del objeto de solicitud. Si es lo mismo, significa que se usa el mismo objeto. Sin embargo, en casi todas las implementaciones del servidor web, se utilizan grupos de subprocesos, lo que conduce a dos solicitudes que llegan en sucesión, que pueden ser procesadas por el mismo hilo: después de procesar la solicitud anterior, el grupo de subprocesos reclama el hilo y reasigna el hilo a la solicitud posterior. En el mismo hilo, es probable que el objeto de solicitud utilizado sea el mismo (la dirección es la misma, los atributos son diferentes). Por lo tanto, incluso para los métodos seguros de hilo, las direcciones del objeto de solicitud utilizadas por diferentes solicitudes pueden ser las mismas.
Para evitar este problema, un método es dejar que el hilo duerma durante unos segundos durante el proceso de procesamiento de solicitudes, lo que puede hacer que cada hilo funcione el tiempo suficiente para evitar la misma asignación de hilo a diferentes solicitudes; El otro método es utilizar otros atributos de la solicitud (como parámetros, encabezado, cuerpo, etc.) como base para si la solicitud es segura de subproceso, porque incluso si diferentes solicitudes usan el mismo hilo uno tras otro (la dirección del objeto de solicitud es la misma), siempre que el objeto de solicitud se construya dos veces utilizando diferentes atributos, el uso del objeto de solicitud es el subproceso de hilo-Safe. Este documento utiliza el segundo método para las pruebas.
El código de prueba del cliente es el siguiente (cree 1000 hilos para enviar solicitudes por separado):
Prueba de clase pública {public static void main (string [] args) lanza la excepción {string prefix = uuid.randomuuid (). toString (). replaceAll ("-", ") +" :: "; for (int i = 0; i <1000; i ++) {value de cadena final = prefix+i; new Thread () {@Override public void run () {try {cerrableHttpClient httpClient = httpclients.createDefault (); Httpget httpget = new httpget ("http: // localhost: 8080/test? Key =" + valor); httpclient.execute (httpget); httpclient.close (); } catch (ioException e) {E.PrintStackTrace (); } } } } } }.comenzar(); }}}El código del controlador en el servidor es el siguiente (el código para obtener el objeto de solicitud se omite temporalmente):
@ControllerPublic Class TestController {// almacene los parámetros existentes para determinar si los parámetros están duplicados, determinando así si el subproceso es seguro SETATIC SETATIC SET <String> set = new Hashset <> (); @RequestMapping ("/Test") public void test () lanza interruptedException {// ………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………… …………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………… ………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………… …………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………… "/t aparece repetidamente, la concurrencia de solicitud no es segura!"); } else {System.out.println (valor); set.add (valor); } // El programa de simulación se ha ejecutado por un período de tiempo, Thread.sleep (1000); }}Si el objeto de solicitud es seguro de hilo, el resultado de impresión en el servidor es el siguiente:
Si hay un problema de seguridad de subprocesos, el resultado de impresión en el servidor puede verse así:
Si no hay una descripción especial, el código de prueba se omitirá de los códigos más adelante en este artículo.
Método 1: Agregar parámetros al controlador
Ejemplo de código
Este método es el más simple de implementar e ingrese directamente el código del controlador:
@ControllerPublic de clase TestController {@RequestMapping ("/Test") Public Void Test (HTTPServletRequest Solicitud) arroja interruptedException {// El programa de simulación se ha ejecutado por un período de tiempo Thread.sleep (1000); }}El principio de este método es que cuando el método del controlador comienza a procesar la solicitud, Spring asignará el objeto de solicitud a los parámetros del método. Además del objeto de solicitud, hay muchos parámetros que se pueden obtener a través de este método. Para obtener más detalles, consulte: https://docs.spring.io/spring/docs/current/springframework-reference/web.html#mvc-ann-methods
Después de obtener el objeto de solicitud en el controlador, si desea utilizar el objeto de solicitud en otros métodos (como métodos de servicio, métodos de clase de herramienta, etc.), debe pasar el objeto de solicitud como parámetro al llamar a estos métodos.
Seguridad
Resultados de la prueba: seguridad de hilo
Análisis: en este momento, el objeto de solicitud es un parámetro de método, que es equivalente a una variable local, y sin duda es segura de hilo.
Pros y contras
La principal desventaja de este método es que el objeto de solicitud es demasiado redundante para escribir, lo que se refleja principalmente en dos puntos:
1) Si el objeto de solicitud se requiere en múltiples métodos del controlador, entonces el parámetro de solicitud debe agregarse a cada método.
2) La adquisición del objeto de solicitud solo puede comenzar desde el controlador. Si el lugar donde se usa el objeto de solicitud está en un lugar más profundo del nivel de llamadas de función, entonces todos los métodos en toda la cadena de llamadas deben agregar el parámetro de solicitud.
De hecho, durante todo el proceso de procesamiento de solicitudes, el objeto de solicitud se ejecuta a través de toda la solicitud; Es decir, a excepción de casos especiales como temporizadores, el objeto de solicitud es equivalente a una variable global dentro del hilo. Este método es equivalente a pasar esta variable global.
Método 2: inyección automática
Ejemplo de código
Primero cargue el código:
@ControllerPublic Class TestController {@aUtowired private httpservletRequest solicitud; // Solicitud automática @RequestMapping ("/Test") public void test () lanza interruptedException {// El programa de simulación se ha ejecutado por un período de tiempo Thread.sleep (1000); }} Seguridad
Resultados de la prueba: seguridad de hilo
Análisis: En la primavera, el alcance del controlador es Singleton (Singleton), lo que significa que en todo el sistema web, solo hay un TestController; Pero la solicitud inyectada es segura de hilo, porque:
De esta manera, cuando se inicializa Bean (TestController en este ejemplo), Spring no inyecta un objeto de solicitud, sino un proxy; Cuando el bean necesita usar el objeto de solicitud, el objeto de solicitud se obtiene a través del proxy.
La siguiente es una descripción de esta implementación a través de un código específico.
Agregue los puntos de interrupción al código anterior y vea las propiedades del objeto de solicitud como se muestra en la figura a continuación:
Como se puede ver en la figura, la solicitud es en realidad un proxy: la implementación del proxy se muestra en la clase interna de AutowireUtils
ObjectFactoryDelegatingInVocationHandler: /*** InvocationHandler reflectante para el acceso perezoso al objeto de destino actual. */@SupressWarnings ("serial") clase estática privada ObjectFactoryDelegatingInVocationHandler implementa InvocationHandler, serializable {private final ObjectFactory <?> ObjectFactory; Public ObjectFactoryDelegatingInVocationHandler (ObjectFactory <?> ObjectFactory) {this.ObjectFactory = ObjectFactory; } @Override public Object Invoke (Object Proxy, Method Method, Object [] args) lanza lanzable {// ... omitir código irrelevante try {return Method.invoke (this.ObjectFactory.getObject (), args); // Código central de implementación del agente} Catch (InvocationTargetException ex) {Throw Ex.GetTargetException (); }}}En otras palabras, cuando llamamos el método de la solicitud, en realidad llamamos al método del método del objeto generado por objectFactory.getObject (); El objeto generado por ObjectFactory.getObject () es el objeto de solicitud real.
Continúe observando la figura anterior y descubra que el tipo ObjectFactory es la clase interna RequestObjectFactory de WebApplicationContextUtils; y el código requestObjectFactory es el siguiente:
/*** Fábrica que expone el objeto de solicitud actual bajo demanda. */ @SupressWarnings ("serial") de clase estática privada que se requillaBjectFactory implementa ObjectFactory <ServLetRequest>, serializable {@Override public ServLetRequest getObject () {return CurrentRequestatTributes (). GetRequest (); } @Override public string toString () {return "actual httpservletRequest"; }}Entre ellos, para obtener el objeto de solicitud, debe llamar al método CurrentRequestAtTributes () para obtener el objeto SolicitarTributes. La implementación de este método es la siguiente:
/*** Devuelva la instancia actual de request Tributes como ServLetRequestAttributes. */Private static ServLetRequestatTributes CurrentRequestatTributes () {requestTributes requestATTR = requestContexTholder.CurrentRequestatTributes (); if (! (requestattr instanceOf ServLetRequestAttributes)) {tire nueva ilegalStateException ("la solicitud actual no es una solicitud de servlet"); } return (ServLetRequestAttributes) requestattr;}El código central que genera el objeto requestattributes está en la clase requestContexTholder, donde el código relevante es el siguiente (se omite el código no relacionado en la clase):
Public Abstract Class RequestContexTholder {public static shaticTributes CurrentRequestatTributes () lanza ilegalStateException {requestTributes atributes = getRequestatTributes (); // La lógica irrelevante se omite aquí ...... Atributos de retorno; } public static requestTributes getRequestatTributes () {requestAttributes atributes = requestAttributesSholder.get (); if (attributes == null) {attributes = hereTeTableRequestatTributesSholder.get (); } Atributos de retorno; } Private Static final ThreadLocal <SquitleAttributes> requestTributesSholder = new NamedThreadLocal <SquitingAttributes> ("Atributos de solicitud"); Private static final ThreadLocal <RequestAttributes> HereThableSequestatTributesSholder = new namedinhereTablethreadLocal <requestTributes> ("contexto de solicitud");}A partir de este código, podemos ver que el objeto request de trebes generado es una variable local de hilo (ThreadLocal), por lo que el objeto de solicitud también es una variable local de subprocesos; Esto asegura la seguridad del hilo del objeto de solicitud.
Pros y contras
Las principales ventajas de este método:
1) La inyección no se limita al controlador: en el método 1, solo el parámetro de solicitud se puede agregar al controlador. Para el Método 2, no solo se puede inyectar en el controlador, sino también en cualquier frijol, incluido el servicio, el repositorio y los frijoles ordinarios.
2) El objeto inyectado no se limita a la solicitud: además de inyectar el objeto de solicitud, este método también puede inyectar otros objetos con alcance como solicitud o sesión, como objetos de respuesta, objetos de sesión, etc.; y garantizar la seguridad del hilo.
3) Reduzca la redundancia del código: solo inyecte el objeto de solicitud en el frijol que requiere el objeto de solicitud, y puede usarse en varios métodos del bean, lo que reduce en gran medida la redundancia del código en comparación con el método 1.
Sin embargo, este método también tiene redundancia de código. Considere este escenario: hay muchos controladores en el sistema web, y cada controlador usa un objeto de solicitud (este escenario es realmente muy frecuente). En este momento, debe escribir código para inyectar solicitudes muchas veces; Si también necesita inyectar respuesta, el código será aún más engorroso. A continuación se describe la mejora del método de inyección automática y analiza la seguridad y las ventajas y desventajas de su hilo.
Método 3: Inyección automática en la clase base
Ejemplo de código
En comparación con el método 2, coloque la parte inyectada del código en la clase base.
Código de clase base:
Class pública BaseController {@aUtowired HTTPServletRequest solicitud; }El código del controlador es el siguiente; Aquí hay dos clases derivadas de basecontrolador. Dado que el código de prueba será diferente en este momento, el código de prueba del servidor no se omite; El cliente también debe realizar modificaciones correspondientes (envíe una gran cantidad de solicitudes concurrentes a las dos URL al mismo tiempo).
@ControllerPublic La clase TestController extiende BaseController {// almacenar los parámetros existentes para determinar si el valor del parámetro se repite, determinando así si el subproceso es seguro público static set <string> set = new Hashset <> (); @RequestMapping ("/test") public void test () lanza interruptedException {string value = requit.getParameter ("Key"); // Verifique la seguridad de los subprocesos if (set.contains (valor)) {system.out.println (valor + "/t aparece repetidamente, la concurrencia de solicitud no es segura!"); } else {System.out.println (valor); set.add (valor); } // El programa de simulación se ha ejecutado por un período de tiempo Thread.sleep (1000); }} @ControllerPublic La clase test2Controller extiende BASECONTROLLER {@RequestMapping ("/test2") public void test2 () lanza InterruptedException {String Value = request.getParameter ("Key"); // Juez de seguridad del hilo (use un conjunto con TestController para juzgar) if (testController.set.contains (valor)) {system.out.println (valor + "/t aparece repetidamente, la concurrencia de solicitud no es segura!"); } else {System.out.println (valor); TestController.set.add (valor); } // El programa de simulación se ha ejecutado por un período de tiempo, Thread.sleep (1000); }} Seguridad
Resultados de la prueba: seguridad de hilo
Análisis: Basado en la comprensión de la seguridad del hilo del método 2, es fácil entender que el método 3 es seguro de subprocesos: al crear diferentes objetos de clase derivados, los dominios en la clase base (aquí hay la solicitud inyectada) ocuparán diferentes espacios de memoria en diferentes objetos de clase derivados, es decir, colocar la solicitud inyectada de código en la clase base no tiene impacto en la seguridad de los hilos; Los resultados de la prueba también lo demuestran.
Pros y contras
En comparación con el método 2, se evita la inyección repetida de solicitudes en diferentes controladores; Sin embargo, teniendo en cuenta que Java solo permite la herencia de una clase base, si el controlador necesita heredar otras clases, este método ya no es fácil de usar.
Ya sea que sea el método 2 o el método 3, solo puede inyectar solicitudes en el frijol; Si otros métodos (como métodos estáticos en la clase de herramienta) deben usar objetos de solicitud, debe aprobar los parámetros de solicitud al llamar a estos métodos. El método 4 introducido a continuación se puede usar directamente en métodos estáticos, como clases de herramientas (por supuesto, también se puede usar en varios frijoles).
Método 4: Llame manualmente
Ejemplo de código
@ControllerPublic Class TestController {@RequestMapping ("/Test") public void test () lanza interruptedException {httpservletRequest request = ((ServLetRequestatTributes) (requestContexTholder.CurrentRequestAtTributes ())). GetRequest ();; // El programa de simulación se ha ejecutado por un período de tiempo Thread.sleep (1000); }} Seguridad
Resultados de la prueba: seguridad de hilo
Análisis: Este método es similar al método 2 (inyección automática), excepto que se implementa a través de la inyección automática en el método 2, y este método se implementa a través de la llamada del método manual. Por lo tanto, este método también es seguro de hilo.
Pros y contras
Ventajas: se pueden obtener directamente en noianas. Desventajas: si usa más lugares, el código es muy engorroso; Por lo tanto, se puede usar junto con otros métodos.
Método 5: @modelattribute Método
Ejemplo de código
El siguiente método y sus variantes (mutación: poner solicitud y bindRequest en subclases) a menudo se ven en línea:
@ControllerPublic Class TestController {solicitud privada httpservletRequest; @Modelattribute public void bindRequest (solicitud httpservletrequest) {this.request = request; } @RequestMapping ("/test") public void test () lanza interruptedException {// El programa de simulación se ha ejecutado por un período de tiempo Thread.sleep (1000); }} Seguridad
Resultado de la prueba: el hilo no es seguro
Análisis: Cuando se usa la anotación @ModelAttribute para modificar el método en el controlador, su función es que el método se ejecutará antes de que se ejecute cada método @RequestMapping en el controlador. Por lo tanto, en este ejemplo, la función de bindRequest () es asignar un valor al objeto de solicitud antes de que se ejecute test (). Aunque la solicitud de parámetros en BindRequest () en sí es segura de hilo, ya que TestController es Singleton, la solicitud, como un campo de TestController, no puede garantizar la seguridad de los subprocesos.
Resumir
Para resumir, agregar parámetros (método 1), inyección automática (método 2 y método 3) y las llamadas manuales (método 4) en el controlador son todos seguros y se pueden usar para obtener objetos de solicitud. Si el objeto de solicitud se usa menos en el sistema, se puede utilizar cualquier método; Si se usa más, se recomienda utilizar la inyección automática (Método 2 y Método 3) para reducir la redundancia del código. Si necesita usar un objeto de solicitud en un no excluyente, puede pasarlo a través de los parámetros al llamar a la capa superior, o puede obtenerlo directamente a través de llamadas manuales (método 4).
De acuerdo, lo anterior es todo el contenido de este artículo. Espero que el contenido de este artículo tenga cierto valor de referencia para el estudio o el trabajo de todos. Si tiene alguna pregunta, puede dejar un mensaje para comunicarse. Gracias por su apoyo a Wulin.com.
Referencias