1: บทนำสู่ฤดูใบไม้ผลิ
1. บทนำ
เซสชั่นเป็นปัญหาที่ยากเสมอที่เราต้องแก้ไขเมื่อทำกลุ่ม ในอดีตเราสามารถแก้ปัญหาได้จากคอนเทนเนอร์ Servlet เช่น Tomcat-Redis-Session-Manager และ Memcached-Session-Manager ที่จัดทำโดย Open Servlet Container-Tomcat
หรือทำ ip_hash ผ่านการปรับสมดุลโหลดเช่น Nginx และการกำหนดเส้นทางไปยังเซิร์ฟเวอร์เฉพาะ ..
แต่ทั้งสองวิธีมีข้อเสีย
Spring-Session เป็นโครงการภายใต้ฤดูใบไม้ผลิ มันแทนที่ httpsession ที่ดำเนินการโดยคอนเทนเนอร์ servlet ด้วย Spring-session โดยมุ่งเน้นไปที่การแก้ปัญหาการจัดการเซสชัน มันสามารถรวมเข้ากับแอปพลิเคชันของเราได้อย่างรวดเร็วและราบรื่น
2. ฟังก์ชั่นสนับสนุน
1) จัดเก็บเซสชันในภาชนะเก็บของบุคคลที่สามได้อย่างง่ายดาย เฟรมเวิร์กให้บริการ Redis, JVM, Mongo, Gemfire, Hazelcast, JDBC และภาชนะอื่น ๆ สำหรับการจัดเก็บเซสชัน
2) เบราว์เซอร์เดียวกันและเว็บไซต์เดียวกันสนับสนุนปัญหาหลายเซสชัน
3) Restfulapi ไม่พึ่งพาคุกกี้ Jessionid สามารถส่งผ่านส่วนหัวได้
4) การรวม WebSocket และ Spring-Session เพื่อซิงโครไนซ์การจัดการวงจรชีวิต
3. วิธีการรวม
วิธีการรวมนั้นง่ายมากเพียงดูตัวอย่างและคำแนะนำในเว็บไซต์ทางการ http://docs.spring.io/spring-session/docs/1.3.0.release/reference/html5/
ส่วนใหญ่แบ่งออกเป็นขั้นตอนการรวมดังต่อไปนี้:
1) แนะนำแพ็คเกจ Jar Dependency
2) วิธีการเพิ่มความคิดเห็นหรือวิธี XML เพื่อกำหนดค่าวิธีการจัดเก็บข้อมูลสำหรับคอนเทนเนอร์ที่เก็บข้อมูลเฉพาะเช่นวิธีการกำหนดค่า XML ของ Redis
<บริบท: Annotation-config/>/** เริ่มต้นการเตรียมการฤดูใบไม้ผลิทั้งหมดและใส่ SpringsessionFilter ลงใน IOC **/<beanclass = "org.springframework.session.data.edis.config.annotation. <beanclass = "org.springframework.data.redis.connection.lettuce.lettuceconnectionfactory"/>
3) กำหนดค่า web.xml ในวิธี XML กำหนดค่า SpringsessionFilter เป็นห่วงโซ่ตัวกรอง
<Tilter> <Tilter-Name> SpringsessionRepositoryFilter </filter-name> <tilter-class> org.springframework.web.filter.delegatingFilterproxy </filter-class> </filter> <url-pattern>/*</url-pattern> <dispatcher> คำขอ </dispatcher> <ispatcher> ข้อผิดพลาด </dispatcher> </ตัวกรอง-การแมป>
สอง: การวิเคราะห์ภายในของกรอบฤดูใบไม้ผลิ
1. เฟรมไดอะแกรมโครงสร้างบทคัดย่อระดับสูง
2. สปริงเซสชันเขียนคำร้องขอ servlet และปัญหาที่เกี่ยวข้องกับการจัดเก็บ Redis
หลักการทั่วไปของ Spring-Session แทนที่คำขอของแอปพลิเคชันเซิร์ฟเวอร์อย่างราบรื่นคือ:
1. ปรับแต่งตัวกรองและใช้วิธี Dofilter
2. สืบทอดคลาส HTTPSERVLETREQUESTWRAPPER และ HTTPSERVLETRESPONSEWRAPPER และแทนที่ GETSESSION และวิธีการอื่น ๆ ที่เกี่ยวข้อง
3. ในขั้นตอน dofilter ในขั้นตอนแรกใหม่คลาสคำขอและการตอบกลับในขั้นตอนที่สอง และส่งผ่านไปยังห่วงโซ่ตัวกรองแยกกัน
4. กำหนดค่าตัวกรองไปยังตำแหน่งแรกของห่วงโซ่ตัวกรอง
/** คลาสนี้เป็นซอร์สโค้ด 1.30 ของ Spring-Session และยังเป็นคลาสคีย์ที่ใช้ขั้นตอนแรกไปจนถึงขั้นตอนที่สามด้านบน **/คลาสสาธารณะ SessionRepositoryFilter <S ขยายการหมดอายุ> ขยายตัวครั้งหนึ่ง servletContext ส่วนตัว servletContext; /** อินเทอร์เฟซการจัดส่ง SessionID ปัจจุบัน Spring-Session มาพร้อมกับสองคลาสการใช้งาน 1. วิธีการ Cookie: CookiehttpsessionStrategy 2. วิธีการส่วนหัว http: headerhttpsessionsTrategy แน่นอนเรายังสามารถปรับแต่งวิธีอื่น ๆ **/ ส่วนตัว multihttpsessionsTrategy httpsessionStrategy = ใหม่ cookiehttpsessionsTrategy (); Public SessionRepositoryFilter (SessionRepository <S> SessionRepository) {ถ้า (SessionRepository == NULL) {โยน unlegalArgumentException ใหม่ ("SessionRepository ไม่สามารถเป็นโมฆะ"); } this.sessionRepository = sessionRepository; } โมฆะสาธารณะ sethttpsessionsTrategy (httpsessionsTrategy httpsessionsTrategy) {ถ้า (httpsessionStrategy == null) {โยน unlegalargumentException ใหม่ ( } /** ผ่านการแนะนำฟังก์ชั่นฤดูใบไม้ผลิก่อนหน้านี้เรารู้ว่า Spring-Session สามารถรองรับหลายเซสชันในเบราว์เซอร์เดียวซึ่งทำได้ผ่าน MultiHttpSessionStrateGyAdapter แต่ละเบราว์เซอร์มี sessionID แต่ SessionID นี้มีนามแฝงหลายรายการ (ตามแท็บของเบราว์เซอร์) ตัวอย่างเช่น: นามแฝง 1 SessionId alias 2 SessionId ... และนามแฝงนี้จะผ่าน URL ซึ่งเป็นหลักการของหลายเซสชันโดยเบราว์เซอร์เดียว **/ this.httpsessionStrategy = new MultihttpsessionStrateGyadapter } โมฆะสาธารณะ sethttpsessionsTrategy (multihttpsessionsTrategy httpsessionsTrategy) {ถ้า (httpsessionStrategy == null) {โยน unlegalargumentException ใหม่ ( } this.httpsessionsTrategy = httpsessionStrategy; } /** วิธีนี้เทียบเท่ากับการเขียน Dofilter ใหม่ แต่ Spring-Session มีชั้นของการห่อหุ้มอีกชั้นหนึ่ง สร้างคำขอและการตอบสนองที่กำหนดเองในวิธีนี้จากนั้นส่งผ่านไปยังตัวกรองตัวกรองตัวกรอง filterchain **/ @Override void dofilterInternal (httpservletrequest Request, HttpservletResponse Response /** ServletRequest เขียนใหม่โดย Spring-Session คลาสนี้สืบทอด httpservletRequestWrapper **/ sessionRepositoryRequestWrapper wraptedRequest = ใหม่ SessionRepositoryRequestWrapper (คำขอ, การตอบสนอง, this.servletContext); SessionRepositoryResponSewrapper wrappedResponse = ใหม่ SessionRepositoryResponSewRapper (wrappedRequest, การตอบสนอง); httpservletRequest strategyRequest = this.httpsessionstrategy.wraprequest (wrappedRequest, wrappedResponse); httpservletResponse strategyResponse = this.httpsessionsTrategy .wrapResponse (wrappedRequest, wrappedResponse); ลอง { /** ส่งคำขอที่กำหนดเองและการตอบกลับไปยังห่วงโซ่ ลองนึกภาพว่า Spring-SessionFilter ตั้งอยู่ที่ห่วงโซ่ตัวกรองแรกหรือไม่นั้นเป็นตัวกรองที่ตามมาเช่นเดียวกับคำขอและการตอบสนองที่ได้จากการเข้าถึงเลเยอร์ควบคุมล่าสุดเรากำลังปรับแต่งมัน? **/ filterchain.dofilter (strategyRequest, strategyResponse); } ในที่สุด {wrappedRequest.Commitsession (); }} โมฆะสาธารณะ setServletContext (servletContext servletContext) {this.servletContext = servletContext; } / ** นี่คือคลาสที่เขียนใหม่ของการตอบสนอง servlet* / คลาสสุดท้ายของคลาสสุดท้าย repositoryResponSewrapper ขยาย onCommittedResponSewrapper {private sessionRepositoryRequestWrapper คำขอ; SessionRepositoryResponSewRapper (คำขอ SessionRepositoryRequestWrapper, การตอบกลับ httpservletResponse) {super (การตอบสนอง); ถ้า (คำขอ == null) {โยน unleglArgumentException ใหม่ ("คำขอไม่สามารถเป็นโมฆะ"); } this.request = คำขอ; } /** ขั้นตอนนี้คือการคงอยู่เซสชันไปยังคอนเทนเนอร์ที่เก็บข้อมูล เราอาจเรียกวิธีการดำเนินการของเซสชันหลายครั้งในเลเยอร์ควบคุม หากการดำเนินการแต่ละครั้งของเซสชันยังคงอยู่ในคอนเทนเนอร์ที่เก็บข้อมูลมันจะมีผลกระทบต่อประสิทธิภาพอย่างแน่นอน ตัวอย่างเช่น Redis ดังนั้นเราจึงสามารถดำเนินการเลเยอร์ควบคุมทั้งหมดและการตอบกลับส่งคืนข้อมูลไปยังเบราว์เซอร์และเซสชันจะคงอยู่เฉพาะเมื่อการตอบกลับส่งคืนข้อมูลไปยังเบราว์เซอร์ **/ @Override ป้องกันโมฆะ onResponsecommitted () {this.request.Commitsession (); }} /** การร้องขอคลาสการเขียนของ Spring-Session เกือบจะเป็นคลาสที่สำคัญที่สุดในการเขียนใหม่ วิธีการเขียนใหม่เช่น getSession เซสชันและวิธีการอื่น ๆ รวมถึงคลาส */ คลาสสุดท้ายคลาสสุดท้ายเซสชัน repositoryRequestWrapper ขยาย httpservletrequestwrapper {บูลีนส่วนตัวร้องขอ บูลีนส่วนตัวร้องขอ การตอบสนอง httpservletResponse ครั้งสุดท้ายส่วนตัว; servletContext สุดท้ายส่วนตัว servletContext; Private SessionRepositoryRequestWrapper (คำขอ httpservletRequest, การตอบสนอง httpservletResponse, servletContext servletContext) {super (คำขอ); this.response = การตอบสนอง; this.servletContext = servletContext; } /** * ใช้ httpsessionsTrategy เพื่อเขียน ID เซสชันไปยังการตอบสนองและ * คงอยู่เซสชัน */ โมฆะส่วนตัวการเดินทาง () {httpsessionWrapper wrappedSession = getCurrentSession (); if (wrappingSession == null) {// เซสชันหมดอายุให้ลบคุกกี้หรือส่วนหัวถ้า (isinvalidateClientsession ()) {sessionRepositoryFilter.HttpsessionStrategy .oninvalidatesession (นี่คือการตอบสนอง); }} else {s เซสชัน = wrappedSession.getSession (); SessionRepositoryFilter.S.SessionRepository.save (เซสชัน); if (! isRequestedSessionIdValid () ||! session.getId (). เท่ากับ (getRequestedSessionId ())) {// เขียนคุกกี้หรือส่วนหัวกลับไปที่เบราว์เซอร์เพื่อบันทึกเซสชัน }}} @suppresswarnings ("ไม่ได้ตรวจสอบ") ส่วนตัว httpsessionWrapper getCurrentSession () {return (httpsessionWrapper) getAttribute (current_session_attr); } โมฆะส่วนตัว setCurrentsession (httpsessionWrapper currentsession) {ถ้า (currentsession == null) {removeattribute (current_session_attr); } else {setAttribute (current_session_attr, currentsession); }} @suppresswarnings ("ไม่ได้ใช้") การเปลี่ยนแปลงสตริงสาธารณะ () {httpsession session = getSession (false); if (session == null) {โยนใหม่ legenalStateException ("ไม่สามารถเปลี่ยนรหัสเซสชันไม่มีเซสชันที่เกี่ยวข้องกับคำขอนี้"); } // ได้รับแอตทริบิวต์เซสชันอย่างกระตือรือร้นในกรณีที่การใช้งาน Lazy โหลดพวกเขาแผนที่ <สตริง, วัตถุ> attrs = new hashmap <string, object> (); การแจงนับ <String> iattrNames = session.getAttributenames (); ในขณะที่ (iattrNames.hasmoreElements ()) {string attrName = iattrNames.nextElement (); ค่า object = session.getAttribute (attrName); attrs.put (attrName, value); } SessionRepositoryFilter.his.sessionRepository.delete (session.getId ()); httpsessionWrapper Original = getCurrentSession (); SetCurrentsession (NULL); httpsessionWrapper newsession = getSession (); Original.setsession (Newsession.getSession ()); Newsession.setMaxInactiveInterval (session.getMaxinactiveInterval ()); สำหรับ (map.entry <string, object> attr: attrs.entryset ()) {string attrName = attr.getKey (); Object attrvalue = attr.getValue (); Newsession.setAttribute (attrName, attrvalue); } return newsession.getId (); } // พิจารณาว่าเซสชันนั้นถูกต้อง @Override บูลีนสาธารณะ isRequestedSessionIdValid () {ถ้า (this.requestedSessionIdValid == null) {String sessionId = getRequestedSessionId (); s เซสชัน = sessionId == null? NULL: GETSESSION (SessionID); return isrequestedsessionidvalid (เซสชั่น); } ส่งคืนสิ่งนี้ requestedsessionidvalid; } บูลีนส่วนตัว isRequestedSessionIdValid (S เซสชัน) {ถ้า (this.RequestedSessionIdValid == null) {this.RequestedSessionIdValid = เซสชัน! = null; } ส่งคืนสิ่งนี้ requestedsessionidvalid; } บูลีนส่วนตัว isinvalidateClientsession () {return getCurrentSession () == null && this.requestedSessionInvalidated; } ส่วนตัว s getSession (String sessionID) {// รับเซสชันจากคอนเทนเนอร์ที่เก็บเซสชันตามเซสชัน S Session = SessionRepositoryFilter.his.sessionRepository .GetSession (SessionId); if (session == null) {return null; } // ตั้งเวลาการเข้าถึงครั้งสุดท้ายของเซสชันเพื่อป้องกันไม่ให้เซสชันจากเซสชันหมดอายุ SetLastAccessedTime (System.currentTimeMillis ()); เซสชั่นกลับ; } /** วิธีนี้คุ้นเคยมากหรือไม่? นอกจากนี้ยังมี getSession () ด้านล่างเพื่อให้คุ้นเคยมากขึ้น ถูกต้องเพียงแค่รับเมธอดเซสชันที่นี่ **/@Override สาธารณะ httpsessionWrapper getSession (สร้างบูลีน) {// ได้รับเซสชันอย่างรวดเร็วซึ่งสามารถเข้าใจได้ว่าเป็นความสัมพันธ์ระหว่างแคชระดับแรกและแคชระดับสอง httpsessionWrapper ถ้า (currentsession! = null) {return currentsession; } // รับ SessionId จาก httpsessionsTret requestsessionId = getRequestedSessionId (); if (requestedSessionId! = null && getAttribute (invalid_session_id_attr) == null) {// รับเซสชันจากคอนเทนเนอร์ที่เก็บข้อมูลและตั้งค่าแอตทริบิวต์การเริ่มต้นปัจจุบัน s เซสชัน = getSession if (เซสชัน! = null) {this.requestedSessionIdValid = true; currentsession = ใหม่ httpsessionWrapper (เซสชัน, getServletContext ()); currentsession.setNew (เท็จ); setcurrentsession (currentsession); ส่งคืนกระแส; } else {if (session_logger.isdebugenabled ()) {session_logger.debug ("ไม่พบเซสชันโดย ID: ผลการแคชสำหรับ getSession (เท็จ) สำหรับ httpservletrequest นี้"); } setAttribute (invalid_session_id_attr, "true"); }} if (! create) {return null; } if (session_logger.isdebugenabled ()) {session_logger.debug ("เซสชันใหม่ถูกสร้างขึ้นเพื่อช่วยคุณแก้ไขปัญหาที่เซสชันถูกสร้างขึ้นเราให้ stacktrace (นี่ไม่ใช่ข้อผิดพลาด) ข้อผิดพลาด)")); } // หากเบราว์เซอร์หรือผู้เยี่ยมชม HTTP อื่น ๆ กำลังเข้าถึงเซิร์ฟเวอร์เป็นครั้งแรกให้สร้างเซสชันเซสชันใหม่สำหรับเขา = เซสชัน repositoryFilter.S.SessionRepository.createSession (); session.setLastaccessedTime (System.CurrentTimeMillis ()); currentsession = ใหม่ httpsessionWrapper (เซสชัน, getServletContext ()); setcurrentsession (currentsession); ส่งคืนกระแส; } @Override สาธารณะ servletContext getServletContext () {ถ้า (this.servletContext! = null) {return this.servletContext; } // servlet 3.0+ return super.getServletContext (); } @Override สาธารณะ httpsessionWrapper getSession () {return getSession (จริง); } @Override String สาธารณะ getRequestedSessionId () {return sessionRepositoryFilter.this.httpsessionsTrategy .getRequestedSessionId (นี่); } / ** คลาสการเขียนใหม่ของ httpsession* / คลาสสุดท้ายคลาสสุดท้าย httpsessionwrapper ขยาย Expiringsessionhttpsession <s> {httpsessionWrapper (เซสชัน s, servletContext servletContext) {super (เซสชัน, servletContext); } @Override โมฆะสาธารณะเป็นโมฆะ () {super.invalidate (); SessionRepositoryRequestWrapper.his.HisquestedSessionInvalidated = true; SetCurrentsession (NULL); SessionRepositoryFilter.S.SessionRepository.delete (getId ()); -สรุป
ข้างต้นเป็นเนื้อหาทั้งหมดของบทความนี้เกี่ยวกับการแนะนำ Spring-Session และการวิเคราะห์ซอร์สโค้ดของหลักการดำเนินการ ฉันหวังว่ามันจะเป็นประโยชน์กับทุกคน เพื่อนที่สนใจสามารถอ้างถึงหัวข้ออื่น ๆ ที่เกี่ยวข้องในเว็บไซต์นี้ต่อไป หากมีข้อบกพร่องใด ๆ โปรดฝากข้อความไว้เพื่อชี้ให้เห็น!