Préface
Cet article présentera plusieurs méthodes pour obtenir des objets de demande dans le système Web développés par Spring MVC et discutera de sa sécurité de thread. Je ne dirai pas beaucoup ci-dessous, jetons un coup d'œil à l'introduction détaillée ensemble.
Aperçu
When developing a web system using Spring MVC, you often need to use a request object when processing requests, such as obtaining the client IP address, the requested URL, attributes in the header (such as cookies, authorization information), data in the body, etc. Since in Spring MVC, the Controller, Service and other objects that handle requests are singletons, the most important issue to be paid attention to when obtaining request objects is whether the request object is thread-safe: when there Un grand nombre de demandes simultanées sont-elles garanties que différents objets de demande sont utilisés dans différentes demandes / threads?
Il y a une autre question à noter ici: où dois-je utiliser l'objet de demande "lors du traitement d'une demande" mentionnée plus tôt? Étant donné qu'il existe de légères différences dans les méthodes d'obtention d'objets de demande, ils peuvent être à peu près divisés en deux catégories:
1) Utilisez des objets de demande dans les haricots à ressort: incluez les deux hanches MVC telles que le contrôleur, le service, le référentiel et les haricots de ressort ordinaires tels que le composant. Pour plus de commodité d'explication, les haricots au printemps dans le texte suivant sont tous appelés haricots pour faire court.
2) Utiliser des objets de demande en non-haricots: comme dans les méthodes d'objets Java ordinaires, ou dans des méthodes statiques de classes.
De plus, cet article discute autour de l'objet de demande représentant la demande, mais la méthode utilisée est également applicable à l'objet de réponse, InputStream / Reader, OutputStream / Writer, etc.; où InputStream / Reader peut lire les données de la demande, et OutputStream / Writer peut écrire des données dans la réponse.
Enfin, la méthode d'obtention de l'objet de demande est également liée à la version de Spring et MVC; Cet article est discuté sur la base du printemps 4, et les expériences effectuées utilisent toutes la version 4.1.1.
Comment tester la sécurité des filetages
Étant donné que les problèmes de sécurité des threads de l'objet de demande nécessitent une attention particulière, afin de faciliter la discussion ci-dessous, expliquons d'abord comment tester si l'objet de demande est en filetage.
L'idée de base du test est de simuler un grand nombre de demandes simultanées sur le client, puis de déterminer si les demandes sont utilisées sur le serveur.
Le moyen le plus intuitif de déterminer si l'objet de demande est le même est d'imprimer l'adresse de l'objet de demande. Si c'est le même, cela signifie que le même objet est utilisé. Cependant, dans presque toutes les implémentations de serveurs Web, des pools de threads sont utilisés, ce qui conduit à deux demandes qui arrivent successivement, qui peuvent être traitées par le même thread: une fois la demande précédente traitée, le pool de threads récupère le thread et réaffecte le thread à la demande suivante. Dans le même thread, l'objet de demande utilisé est probablement le même (l'adresse est la même, les attributs sont différents). Par conséquent, même pour les méthodes de filetage, les adresses d'objet de demande utilisées par différentes demandes peuvent être les mêmes.
Pour éviter ce problème, une méthode consiste à permettre au thread de dormir pendant quelques secondes pendant le processus de traitement de la demande, ce qui peut faire fonctionner chaque thread suffisamment longtemps pour éviter le même thread allouant à différentes demandes; L'autre méthode consiste à utiliser d'autres attributs de la demande (tels que les paramètres, l'en-tête, le corps, etc.) comme base pour savoir si la demande est fileuse, car même si différentes demandes utilisent le même thread les uns après les autres (l'adresse de l'objet de demande est la même), tant que l'objet de demande est construit deux fois en utilisant différents attributs, l'utilisation de l'objet de demande est le thread-sile. Cet article utilise la deuxième méthode pour les tests.
Le code de test client est le suivant (créer 1000 threads pour envoyer des demandes séparément):
Public Class Test {public static void main (String [] args) lève l'exception {String Prefix = uUid.randomuuid (). toString (). RempaceALL ("-", "") + "::"; for (int i = 0; i <1000; i ++) {final String value = prefix + i; nouveau thread () {@Override public void run () {try {clôturehttpclient httpclient = httpclients.createDefault (); Httpget httpget = new httpget ("http: // localhost: 8080 / test? Key =" + value); httpClient.Execute (httpget); httpclient.close (); } catch (ioException e) {e.printStackTrace (); } } } } } }.commencer(); }}}Le code du contrôleur du serveur est le suivant (le code pour obtenir l'objet de demande est temporairement omis):
@ControllerPublic Class TestController {// Stockez les paramètres existants pour déterminer si les paramètres sont dupliqués, déterminant ainsi si le thread est sûr public statique <string> set = new HashSet <> (); @RequestMapping ("/ test") public void test () lève InterruptedException {// ………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………. …………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………. ………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………. …………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………. "/ T apparaît à plusieurs reprises, la concurrence demande n'est pas sûre!"); } else {System.out.println (valeur); set.add (valeur); } // Le programme de simulation est exécuté pendant un certain temps, Thread.Sleep (1000); }}Si l'objet de demande est en file d'attente, le résultat de l'impression du serveur est le suivant:
S'il y a un problème de sécurité des threads, l'impression du serveur peut ressembler à ceci:
S'il n'y a pas de description spéciale, le code de test sera omis des codes plus loin dans cet article.
Méthode 1: Ajouter des paramètres au contrôleur
Exemple de code
Cette méthode est la plus simple à implémenter et entre directement le code du contrôleur:
@ControllerPublic class testController {@RequestMapping ("/ test") public void test (requête httpservletRequest) lance InterruptedException {// Le programme de simulation a été exécuté pendant une période de thread.Sleep (1000); }}Le principe de cette méthode est que lorsque la méthode du contrôleur commence à traiter la demande, Spring affectera l'objet de demande aux paramètres de la méthode. En plus de l'objet de demande, il existe de nombreux paramètres qui peuvent être obtenus via cette méthode. Pour plus de détails, veuillez consulter: https://docs.spring.io/spring/docs/current/spring-framework-reference/web.html#mvc-ann-methods
Après avoir obtenu l'objet de demande dans le contrôleur, si vous souhaitez utiliser l'objet de demande dans d'autres méthodes (telles que les méthodes de service, les méthodes de classe d'outils, etc.), vous devez passer l'objet de demande en tant que paramètre lorsque vous appelez ces méthodes.
Sécurité en fil
Résultats des tests: sécurité du fil
Analyse: À l'heure actuelle, l'objet de demande est un paramètre de méthode, qui équivaut à une variable locale, et est sans aucun doute file.
Pour les avantages et les inconvénients
Le principal inconvénient de cette méthode est que l'objet de demande est trop redondant pour écrire, ce qui se reflète principalement en deux points:
1) Si l'objet de demande est requis dans plusieurs méthodes de contrôleur, le paramètre de demande doit être ajouté à chaque méthode.
2) L'acquisition de l'objet de demande ne peut que commencer à partir du contrôleur. Si l'endroit où l'objet de demande est utilisé est dans un endroit plus profond du niveau d'appel de fonction, toutes les méthodes de toute la chaîne d'appel doivent ajouter le paramètre de demande.
En fait, pendant l'ensemble du processus de traitement de la demande, l'objet de demande passe par l'intégralité de la demande; Autrement dit, à l'exception des cas spéciaux tels que les minuteries, l'objet de demande équivaut à une variable globale à l'intérieur du thread. Cette méthode équivaut à passer cette variable globale.
Méthode 2: Injection automatique
Exemple de code
Téléchargez d'abord le code:
@ControllerPublic Class TestController {@autowired private httpServleRequest request; // request Autowired @RequestMapping ("/ test") public void test () lève InterruptedException {// Le programme de simulation a été exécuté pendant une période de temps thread.sleep (1000); }} Sécurité en fil
Résultats des tests: sécurité du fil
Analyse: Au printemps, la portée du contrôleur est Singleton (Singleton), ce qui signifie que dans l'ensemble du système Web, il n'y a qu'un seul test-coller; Mais la demande injectée est une file d'attente, car:
De cette façon, lorsque le bean (testController dans cet exemple) est initialisé, Spring n'injecte pas un objet de demande, mais un proxy; Lorsque le bean doit utiliser l'objet de demande, l'objet de demande est obtenu via le proxy.
Ce qui suit est une description de cette implémentation via un code spécifique.
Ajoutez des points d'arrêt au code ci-dessus et affichez les propriétés de l'objet de demande comme indiqué dans la figure ci-dessous:
Comme on peut le voir sur la figure, la demande est en fait un proxy: la mise en œuvre du proxy est indiquée dans la classe interne d'Autowireutils
ObjectFactoryDelegatingInvocationHandler: / ** * Reflective InvocationHandler pour un accès paresseux à l'objet cible actuel. * / @ SuppressWarnings ("Serial") classe statique privée objectFactoryDelegatingInvocationHandler implémente invocationHandler, serializable {private final objectFactory <?> ObjectFactory; public objectFactoryDelegatingInvocationHandler (objectFactory <?> ObjectFactory) {this.objectfactory = objectFactory; } @Override Public Object Invoke (Proxy d'objet, méthode de la méthode, objet [] args) lève le throwable {// ... omettre le code non pertinent try {return metheth.invoke (this.objectfactory.getObject (), args); // CODE DE CORE IMPPORTATION DE L'AGENT} Catch (invocationTargetException ex) {Throw Ex.getTargetException (); }}}En d'autres termes, lorsque nous appelons la méthode de la demande, nous appelons réellement la méthode de la méthode de l'objet généré par objectFactory.getObject (); L'objet généré par objectFactory.getObject () est l'objet de demande réel.
Continuez à observer la figure ci-dessus et constatez que le type d'objetFactory est la classe interne requestObjectFactory de webApplicationContextUtils; et le code de demandeObjectFactory est le suivant:
/ ** * usine qui expose l'objet de demande actuel à la demande. * / @ SuppressWarnings ("Serial") Classe statique privée requestObjectFactory implémente ObjectFactory <ServLetRequest>, Serializable {@Override public ServLetRequest getObject () {return currentRequestAttributes (). GetRequest (); } @Override public String toString () {return "actuel httpServLetRequest"; }}Parmi eux, pour obtenir l'objet de demande, vous devez appeler la méthode CurrentRequestAttributes () pour obtenir l'objet demandesAttributes. La mise en œuvre de cette méthode est la suivante:
/ ** * Renvoie l'instance de demande deTributes actuelle en tant que servletRequestAttributes. * / private static ServLetRequestAttributes CurrentRequestAttributes () {requestAttributes requestAtTr = requestContexTholder.currentRequestAttributes (); if (! (requestAtTr instanceof servLetRequestAtTributes)) {lancez new illégalStateException ("La demande actuelle n'est pas une demande de servlet"); } return (servletRequestAttributes) requestAtTr;}Le code de base qui génère l'objet de demandeAttribute est dans la classe RequestContexTholder, où le code pertinent est le suivant (le code non lié de la classe est omis):
Classe de résumé public DemandeContexTholder {public static requestAttributes CurrentRequestAttributes () lève illégalStateException {requestAttributes attributes = getRequestAttributes (); // Logique non pertinente est omise ici ...... Attributs de retour; } public static requestAttributes getRequestAtTributes () {requestAttributes Attributes = requestAttributeSholder.get (); if (attributes == null) {attributs = hheRiTableRequestAtTributSholder.get (); } Retour attributs; } private statique final threadLocal <demandesAttributes> requestAtTributSholder = new NamedThreadLocal <queandAttributes> ("Attributs de demande"); Final Final Static privé <quequestAttributes> HéritablerequestAtTrributeSholder = new NamedInHeritableThreAlLocal <DequestAttributes> ("Context" Request ");}À partir de ce code, nous pouvons voir que l'objet Generated RequestAttributes est une variable locale de thread (ThreadLocal), donc l'objet de demande est également une variable locale de thread; Cela garantit la sécurité du fil de l'objet de demande.
Pour les avantages et les inconvénients
Les principaux avantages de cette méthode:
1) L'injection n'est pas limitée au contrôleur: dans la méthode 1, seul le paramètre de demande peut être ajouté au contrôleur. Pour la méthode 2, il peut non seulement être injecté dans le contrôleur, mais aussi dans n'importe quel haricot, y compris le service, le référentiel et les haricots ordinaires.
2) L'objet injecté n'est pas limité à la demande: En plus d'injecter l'objet de demande, cette méthode peut également injecter d'autres objets avec Scope comme demande ou session, tels que les objets de réponse, les objets de session, etc.; et assurer la sécurité des filetages.
3) Réduisez la redondance du code: injectez simplement l'objet de demande dans le bean qui nécessite l'objet de demande, et il peut être utilisé dans diverses méthodes du bean, ce qui réduit considérablement la redondance du code par rapport à la méthode 1.
Cependant, cette méthode a également une redondance de code. Considérez ce scénario: il existe de nombreux contrôleurs dans le système Web, et chaque contrôleur utilise un objet de demande (ce scénario est en fait très fréquent). À l'heure actuelle, vous devez écrire du code pour injecter à plusieurs reprises une demande; Si vous devez également injecter une réponse, le code sera encore plus lourd. Ce qui suit décrit l'amélioration de la méthode d'injection automatique et analyse sa sécurité et ses avantages et ses inconvénients.
Méthode 3: Injection automatique dans la classe de base
Exemple de code
Par rapport à la méthode 2, placez la partie injectée du code dans la classe de base.
Code de classe de base:
classe publique BasEController {@autowired protected httpservletRequest request; }Le code du contrôleur est le suivant; Voici deux classes dérivées de BaseController. Étant donné que le code de test sera différent pour le moment, le code de test du serveur n'est pas omis; Le client doit également apporter des modifications correspondantes (envoyer un grand nombre de demandes simultanées aux deux URL en même temps).
@ControllerPublic Class TestController étend BaseController {// Stockez les paramètres existants pour déterminer si la valeur du paramètre est répétée, déterminant ainsi si le thread est un jeu statique public sûr <string> set = new HashSet <> (); @RequestMapping ("/ test") public void test () lève InterruptedException {String value = request.getParameter ("key"); // Vérifiez la sécurité du thread if (set.contains (valeur)) {System.out.println (valeur + "/ t apparaît à plusieurs reprises, la concurrence de demande n'est pas sûre!"); } else {System.out.println (valeur); set.add (valeur); } // Le programme de simulation a été exécuté pendant un certain temps Thread.Sleep (1000); }} @ControllerPublic Class Test2Controller étend BasEController {@RequestMapping ("/ Test2") public void test2 () lance InterruptedException {String Value = request.getParAmter ("key"); // Juge Thread Safety (utilisez un ensemble avec TestController pour juger) if (testController.set.contains (valeur)) {System.out.println (valeur + "/ t apparaît à plusieurs reprises, demandez la concurrence n'est pas sûre!"); } else {System.out.println (valeur); TestController.set.add (valeur); } // Le programme de simulation est exécuté pendant un certain temps, Thread.Sleep (1000); }} Sécurité en fil
Résultats des tests: sécurité du fil
Analyse: Basé sur la compréhension de la sécurité du thread de la méthode 2, il est facile de comprendre que la méthode 3 est un filetage: lors de la création d'objets de classe dérivés différents, les domaines de la classe de base (voici la demande injectée) occupera un espace mémoire différent dans différents objets de classe dérivés, c'est-à-dire que la demande de code injectée dans la classe de base n'a pas d'impact sur la sécurité des threads; Les résultats des tests le prouvent également.
Pour les avantages et les inconvénients
Par rapport à la méthode 2, une injection répétée de demandes dans différents contrôleurs est évitée; Cependant, étant donné que Java n'autorise que l'héritage d'une classe de base, si le contrôleur doit hériter d'autres classes, cette méthode n'est plus facile à utiliser.
Que ce soit la méthode 2 ou la méthode 3, vous ne pouvez injecter que des demandes dans le haricot; Si d'autres méthodes (telles que les méthodes statiques de la classe d'outils) doivent utiliser des objets de demande, vous devez transmettre les paramètres de demande lorsque vous appelez ces méthodes. La méthode 4 introduite ci-dessous peut être utilisée directement dans des méthodes statiques telles que les classes d'outils (bien sûr, il peut également être utilisé dans divers haricots).
Méthode 4: Appelez manuellement
Exemple de code
@ControllerPublic Class TestController {@RequestMapping ("/ test") public void test () lance InterruptedException {httpServletRequest request = ((servLetRequestAttributes) (requestContexTholder.CurrentRequestAttributes ())). GetRequest (); // Le programme de simulation a été exécuté pendant un certain temps Thread.Sleep (1000); }} Sécurité en fil
Résultats des tests: sécurité du fil
Analyse: Cette méthode est similaire à la méthode 2 (injection automatique), sauf qu'elle est implémentée par injection automatique dans la méthode 2, et cette méthode est implémentée via l'appel de la méthode manuelle. Par conséquent, cette méthode est également en file d'attente.
Pour les avantages et les inconvénients
Avantages: Peut être obtenu directement en non-haricots. Inconvénients: si vous utilisez plus de places, le code est très lourd; Par conséquent, il peut être utilisé conjointement avec d'autres méthodes.
Méthode 5: Méthode @ModeLatTribute
Exemple de code
La méthode suivante et ses variantes (mutation: demande de put et bindRequest dans les sous-classes) sont souvent vues en ligne:
@ControllerPublic classe TestController {requête privée httpservletRequest; @ModelAttribute Public void bindRequest (requête httpServLeRequest) {this.request = request; } @RequestMapping ("/ test") public void test () lève InterruptedException {// Le programme de simulation a été exécuté pendant un certain temps thread.sleep (1000); }} Sécurité en fil
Résultat du test: le thread n'est pas sûr
Analyse: Lorsque l'annotation @ModeLattribute est utilisée pour modifier la méthode dans le contrôleur, sa fonction est que la méthode sera exécutée avant l'exécution de chaque méthode @RequestMapping dans le contrôleur. Par conséquent, dans cet exemple, la fonction de bindRequest () est d'attribuer une valeur à l'objet de demande avant l'exécution de test (). Bien que la demande de paramètre dans bindRequest () elle-même soit lui-même, puisque TestController est singleton, la demande, en tant que champ de TestController, ne peut garantir une file d'assistance.
Résumer
Pour résumer, l'ajout de paramètres (méthode 1), l'injection automatique (méthode 2 et méthode 3) et les appels manuels (méthode 4) dans le contrôleur sont tous en file et peuvent tous être utilisés pour obtenir des objets de demande. Si l'objet de demande est moins utilisé dans le système, l'une ou l'autre méthode peut être utilisée; S'il est davantage utilisé, il est recommandé d'utiliser l'injection automatique (méthode 2 et méthode 3) pour réduire la redondance du code. Si vous avez besoin d'utiliser un objet de demande dans un non-haring, vous pouvez soit le transmettre par des paramètres lorsque vous appelez la couche supérieure, soit vous pouvez l'obtenir directement via des appels manuels (méthode 4).
D'accord, ce qui précède est l'intégralité du contenu de cet article. J'espère que le contenu de cet article a une certaine valeur de référence pour l'étude ou le travail de chacun. Si vous avez des questions, vous pouvez laisser un message pour communiquer. Merci pour votre soutien à wulin.com.
Références