1: Introduction à Spring-Session
1. Introduction
La session a toujours été un problème difficile que nous devons résoudre lors des clusters. Dans le passé, nous pourrions le résoudre à partir de conteneurs de servlets, tels que Tomcat-Redis-Session-Manager et Memcached-Session-Manager fournis par le contenant-contein-Tomcat open source.
Ou faire ip_hash via l'équilibrage de charge tel que Nginx et le routage vers un serveur spécifique.
Mais les deux méthodes ont des inconvénients.
Le printemps est un projet au printemps. Il remplace la HTTPSession mise en œuvre par le conteneur servlet par la séance de printemps, en se concentrant sur la résolution des problèmes de gestion de session. Il peut être intégré dans nos applications rapidement et de manière transparente.
2. Fonctions de support
1) Stockez facilement les séances dans des conteneurs de stockage tiers. Le cadre fournit Redis, JVM Map, Mongo, Gemfire, Hazelcast, JDBC et d'autres conteneurs pour stocker des séances.
2) Le même navigateur et le même site Web prennent en charge plusieurs problèmes de session.
3) Restulapi, ne compte pas sur les cookies. Le jessionid peut être passé par l'en-tête
4) combinant WebSocket et Spring-session pour synchroniser la gestion du cycle de vie.
3. Méthode d'intégration
La méthode d'intégration est très simple, il suffit de regarder les échantillons et le guide sur le site officiel. http://docs.spring.io/spring-sesion/docs/1.3.0.release/reference/html5/
Il est principalement divisé en étapes d'intégration suivantes:
1) Introduire un package de pot de dépendance
2) Méthode d'annotation ou méthode XML pour configurer les méthodes de stockage pour des conteneurs de stockage spécifiques, tels que la méthode de configuration XML de Redis
<Context: Annotation-Config /> / ** Initialisez toutes les préparatifs de Spring-Session et placez SpringSessionFilter dans IOC ** / <BeanClass = "org.springframework.session.data.redis.config.annotation.web.http.redishttpSessionConfiguration" /> / ** Il s'agit du groupe de liens pour le conteneur de stockage ** <BeanClass = "org.springframework.data.redis.connection.lettuce.LetTuceConnectionFactory" />
3) Configurez web.xml dans la méthode XML, configurez SpringSessionFilter dans la chaîne de filtre
<filter> <filter-name> springSessionRepositoryFilter </filter-name> <filter-Class> org.springframework.web.filter.delegatingfilterproxy </ filter-Class> </filter> <filter-mapping> <filter-name> springSesseRepositoryFilter </filter-name> <URL-Pattern> / * </ url-stern> <padipatcher> Demande </spatcher> <padipatcher> error </spatcher> </filter-mapping>
Deux: Analyse interne du cadre Spring-Session
1. Cadre du schéma de structure abstraite de haut niveau
2.Spring-session réécrit la demande de servlet et les problèmes liés au stockage redis
Le principe général de Spring-Session remplace de manière transparente la demande du serveur d'applications est:
1. Personnalisez un filtre et implémentez la méthode Dofilter
2. Hériter des classes httpservletRequestwrapper et httpservletResponsewrapper, et remplacer la getSession et d'autres méthodes connexes (la classe d'opération de conteneur de stockage de session pertinente est appelée dans ces méthodes).
3. Dans l'étape Dofilter dans la première étape, Nouveau les classes de demande et de réponse dans la deuxième étape. et les passer séparément à la chaîne de filtre
4. Configurez le filtre à la première position de la chaîne de filtre
/ ** Cette classe est le code source 1.30 de Spring-Session, et c'est également la classe clé qui met en œuvre les premières à les troisième étapes ci-dessus ** / classe publique SessionRepositoryFilter <S ExpiringSession> Interface, redis, monnfire et autres databases de la session; ServletContext privé ServletContext; / ** Interface de livraison SessionID. Actuellement, Spring-Session est livré avec deux classes d'implémentation 1.CooKie Méthode: COOKIEHTTPSESSIONSTRATEGY 2.HTTP Header Méthode: HeaderHTTPSessionStrategy Bien sûr, nous pouvons également personnaliser d'autres méthodes. ** / private MultiHTTPSessionStrategy httpSessionStrategy = new CookieHTTPSessionStrategy (); public SessionRepositoryFilter (SessionRepository <s> SessionRepository) {if (sessionRepository == null) {Throw New illégalArgumentException ("SessionRepository ne peut pas être null"); } this.SessionRepository = sessionRepository; } public void SethTTPSessionStrategy (httpSessionStrategy httpSessionStrategy) {if (httpSessionStrategy == null) {lancez new illégalargumentException ("httpSessionStrategy ne peut pas être nul"); } / ** Grâce à l'introduction de la fonction de Spring-Session précédente, nous savons que Spring-Session peut prendre en charge plusieurs sessions dans un seul navigateur, qui est réalisée via le MultiHTTPSessionStrateGyAdapter. Chaque navigateur a un sessionID, mais ce sessionID a plusieurs alias (selon l'onglet du navigateur). Par exemple: alias 1 sessiond alias 2 sessionid ... et cet alias est passé par l'URL, qui est le principe de plusieurs sessions par un seul navigateur ** / this.httpSessionStrategy = new MultiHttpSessionStrateGyAdapter (httpSessionStrategy); } public void SethTTPSessionStrategy (MultiHttpSessionStrategy httpSessionStrategy) {if (httpSessionStrategy == NULL) {Throw New illégalArgumentException ("httpSessionStrategy ne peut pas être nul"); } this.httpSessionStrategy = httpSessionStrategy; } / ** Cette méthode est équivalente à la réécriture de Dofilter, mais Spring-Session a une autre couche d'encapsulation. Créez une demande et une réponse personnalisées dans cette méthode, puis passez-la à la chaîne filtrante filterchain ** / @Override Protected void dofilterinternal (httpservletRequest request, httpservletResponse réponse, filterchain filterChain) servletException, ioexception {request.settribute (session_repository_attr, this.Session. / ** ServletRequest réécrit par le printemps-session. Cette classe hérite httpservletRequestwrapper ** / sessionRepositoryRequestWrapper Embappequest = new SessionRepositoryRequestwrapper (request, réponse, this.servletContext); SessionRopositoryResponseWrapper EmbappySponse = new SessionRopositoryResponseWrapper (EmbappedRequest, Response); HttpServLetRequest StrategyRequest = this.httpSessionStrategy.WrapRequest (enveloppequest, enveloppePonse); HttpServletResponse StrategyResponse = this.httpSessionStrategy .WrapResponse (enveloppequest, enveloppePonse); Essayez {/ ** Passez la demande personnalisée et la réponse à la chaîne. Imaginez-vous si le remplaçant de Spring-Session est situé à la première de la chaîne de filtre, alors les filtres suivants sont-ils, ainsi que la demande et la réponse obtenues en atteignant la dernière couche de contrôle, nous la personnalisons? ** / filterchain.dofilter (StrategyRequest, StrategyResponse); } enfin {embappyRequest.CommitSession (); }} public void setServletContext (servletContext servletContext) {this.servletContext = servletContext; } / ** Il s'agit de la classe réécrite de la réponse du servlet * / Session final privée SessionRepositoryResponseWrapper étend OnCommitteSponseWrapper {Private Final SessionrepositoryRequestWrapper Request; SessionRepositoryResponseWrapper (SessionRepositoryRequestWrapper Request, HttpServletResponse Response) {super (réponse); if (request == null) {lancer un nouveau IllégalArgumentException ("la demande ne peut pas être nul"); } this.request = request; } / ** Cette étape consiste à persister la session vers le conteneur de stockage. Nous pouvons appeler la méthode de fonctionnement de la session plusieurs fois dans une couche de contrôle. Si chaque opération de la session est persistée dans le conteneur de stockage, il aura certainement un impact sur les performances. Par exemple, Redis, afin que nous puissions exécuter l'intégralité de la couche de contrôle, et la réponse renvoie les informations au navigateur, et la session ne sera persistée que lorsque la réponse renvoie les informations au navigateur. ** / @Override Protected void onResponScommited () {this.Request.CommitSession (); }} / ** La classe de réécriture de la demande de Spring-Session est presque la classe de réécriture la plus importante. Il a réécrit des méthodes telles que Getcession, session et autres méthodes ainsi que les classes * / Session finale privée SessionRoSitoryRequestwrapper étend httpservletRequestwrapper {private booléen demandedSessionIdValid; Booléen privé a demandé sasion invalide; Réponse privée finale HttpServletResponse; Final privé ServletContext ServletContext; Session PrivateRepositoryRequestWrapper (demande httpservletRequest, réponse httpservletResponse, servletContext servletContext) {super (request); this.Response = réponse; this.servletContext = servletContext; } / ** * utilise la HttpSessionStrategy pour écrire l'ID de session dans la réponse et * persister la session. * / private void commitsession () {httSessionwrapper enveloppeSession = getCurrentession (); if (enveloppedSession == null) {// La session expire, supprime les cookies ou l'en-tête if (isInvalidateClientSession ()) {SessionRepositoryFilter.This.httpSessionStrategy .oninvalidaSession (this, this.response); }} else {s session = enveloppeSession.getSession (); SessionRepositoryFilter.This.SessionRepository.save (session); if (! IsRequetedSessionIdValid () ||! Session.getId (). Equals (getRequetedSessionId ())) {// Écrivez le cookie ou l'en-tête vers le navigateur pour enregistrer sessionRepositoryFilter.Chis. }}} @SuppressWarnings ("Unchecked") httpSessionwrapper privé GetCurrentESSE () {return (httpSessionwrapper) getAttribute (current_session_attr); } private void setCurrentession (httpSessionwrapper currentession) {if (currentession == null) {reposatTribute (current_session_attr); } else {setAttribute (current_session_attr, Currentession); }} @SuppressWarnings ("inutilisé") Public String changeSessionId () {httpSession session = getSession (false); if (session == null) {lancez new illégalStateException ("Impossible de modifier l'ID de session. Il n'y a pas de session associée à cette demande."); } // Obtenez avec impatience les attributs de session dans le cas de l'implémentation Lazy les charge map <string, objet> attrs = new hashmap <string, object> (); Énumération <string> iantrNames = session.getAttributeNames (); while (iantTrNames.hasmoreElements ()) {String attName = iAttrNames.NextElement (); Objet Value = session.getAttribute (attName); att.put (attName, valeur); } SessionRepositoryFilter.This.SessionRepository.Delete (session.getId ()); HttpSessionwrapper original = getCurrentSession (); SetCurrentession (null); HttpSessionwrapper newession = getSession (); Original.SetSession (Newsession.getSession ()); newession.setMaxInactiveInterval (session.getMaxInactiveInterval ()); for (map.entry <string, objet> attr: attrs.EntrySet ()) {String attRname = att.getKey (); Objet attRvalue = att.getValue (); newession.setAttribute (attrname, attvalue); } return newession.getId (); } // Déterminez si la session est valide @Override public booléen isRedestSessionIdValid () {if (this.requestSessionIdValid == null) {String SessionId = getRedestSessionId (); S session = sessionID == null? null: getSession (sessionId); Retour IsRedestSessionIdValid (session); } Renvoie ce.AuquéeSessionIdValid; } privé booléen isRequetedSessionIdValid (S session) {if (this.requestSessionIdValid == NULL) {this.requestSessionIdValid = session! = null; } Renvoie ce.AuquéeSessionIdValid; } privé boolean isInvalidateClientSession () {return getCurrentession () == null && this.requestSessionInvalidated; } private s getSession (string sessionID) {// Obtenez la session à partir du conteneur de stockage de session en fonction de sessionID S session = sessionRepositoryFilter.tthis.SessionRepository .getSession (sessionID); if (session == null) {return null; } // Définissez la dernière heure d'accès de la session pour empêcher la session de session expirée.SetLastAccessSedTime (System.CurrentTimemillis ()); Session de retour; } / ** Cette méthode est-elle très familière? Il y a aussi une getcession () ci-dessous pour être plus familier. C'est vrai, recueillez simplement la méthode de session ici ** / @Override public httpSessionwrapper getSession (boolean create) {// Get Session, qui peut être comprise comme une relation entre le cache de premier niveau et le cache de deuxième niveau httSessionwrapper Currentession = getCurrentession (); if (Currentession! = null) {return Currentession; } // Obtenez SessionId à partir de httpSessionStret demandedSessionId = getRequetedSessionId (); if (demandedSessionId! = null && getAttribute (invalid_session_id_attr) == null) {// Obtenez la session à partir du conteneur de stockage et définissez l'attribut d'initialisation actuel S session = getSession (demandedSessionId); if (session! = null) {this.requestSessionIdValid = true; CURRENTESSESE = NOUVEAU HTTPSESSESSEWRAPPER (Session, GetServletContext ()); CurrentSession.setNew (false); SetCurrentession (Currentession); Retour Currentession; } else {if (session_logger.isdebugeNabled ()) {session_logger.debug ("Aucune session trouvée par id: résultat de mise en cache pour getSession (false) pour ce httpsservletRequest."); } setAttribute (invalid_session_id_attr, "true"); }} if (! Create) {return null; } if (session_logger.isdebugeNabled ()) {session_logger.debug ("Une nouvelle session a été créée. Pour vous aider à résoudre que la session a été créée, nous avons fourni un stacktrace (ce n'est pas une erreur). } // Si le navigateur ou un autre visiteur HTTP accéde au serveur pour la première fois, créez une nouvelle session de session pour lui = sessionRopositoryFilter.this.SessionRepository.CreateSession (); session.SetLastAccessSedTime (System.CurrentTimemillis ()); CURRENTESSESE = NOUVEAU HTTPSESSESSEWRAPPER (Session, GetServletContext ()); SetCurrentession (Currentession); Retour Currentession; } @Override public ServletContext getServletContext () {if (this.servletContext! = Null) {return this.servletContext; } // servlet 3.0+ return super.getServletContext (); } @Override public httSessionwrapper getSession () {return getSession (true); } @Override public String getRequetedSessionId () {return sessionRepositoryFilter.this.httpSessionStrategy .getRequetedSessionId (this); } / ** Réécriture de la classe de httpSession * / classe finale privée httpSessionwrapper étend ExpiringSessionHttSession <s> {httSSessionwrapper (S session, servletContext servletContext) {super (session, servletContext); } @Override public void invalidate () {super.invalidate (); SessionRepositoryRequestwrapper.This.requestSessionInvalidated = true; SetCurrentession (null); SessionRepositoryFilter.This.SessionRepository.Delete (getID ()); }}}}Résumer
Ce qui précède est tout le contenu de cet article sur l'introduction de la séance de printemps et l'analyse du code source des principes de mise en œuvre. J'espère que ce sera utile à tout le monde. Les amis intéressés peuvent continuer à se référer à d'autres sujets connexes sur ce site. S'il y a des lacunes, veuillez laisser un message pour le signaler!