catalogue
3. Scenario analysis of dynamically modifying user Session
4 Introduction to the principle of dynamically modifying Session
5. Dynamically modify the Session implementation
6 performance optimization under high concurrency
7 complete source code + supporting video tutorial sharing
1 Session introduction
Session is another mechanism to record the session state of server and client, and session is implemented based on cookie s. The server needs to know who the current request is sent to. In order to make this distinction, the server needs to assign a different "identity" to each client, and then the client will bring this "identity" every time it sends a request to the server.
Cookie is a data storage technology implemented by browser. It is generally generated by the server and sent to the browser (the client can also set cookies) for storage. The next time the same website is requested, the cookie will be sent to the server.
2 simple example preparation
We make a simple example to simulate user login and obtain login user information;
Create a new springboot project modify session. The final directory structure is as follows:
Project POM XML, just introduce a web dependency;
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.5.3</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.java1234</groupId> <artifactId>modify-session</artifactId> <version>0.0.1-SNAPSHOT</version> <name>modify-session</name> <description>Demo project for Spring Boot</description> <properties> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
Start class ModifySessionApplication:
package com.java1234; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class ModifySessionApplication { public static void main(String[] args) { SpringApplication.run(ModifySessionApplication.class, args); } }
Project configuration file application yml
server: port: 80 servlet: context-path: /
User entity class user
package com.java1234.entity; /** * User information * @author java1234_Xiao Feng * @site www.java1234.com * @company Nantong Xiaofeng Network Technology Co., Ltd * @create 2021-08-03 21:14 */ public class User { public Integer id; public String userName; private String password; private String level="common"; // common VIP public User() { } public User(Integer id, String userName, String password) { this.id = id; this.userName = userName; this.password = password; } public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getUserName() { return userName; } public void setUserName(String userName) { this.userName = userName; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public String getLevel() { return level; } public void setLevel(String level) { this.level = level; } }
Create a new UserController and provide two interface methods: simulate user login and obtain user information:
package com.java1234.controller; import com.java1234.entity.User; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import javax.servlet.http.HttpSession; /** * User controller * @author java1234_Xiao Feng * @site www.java1234.com * @company Nantong Xiaofeng Network Technology Co., Ltd * @create 2021-08-03 21:10 */ @RestController @RequestMapping("/user") public class UserController { /** * Simulate user login * @return */ @RequestMapping("/login") public String login(HttpSession session){ User uesr=new User(1,"java1234","123456"); session.setAttribute("currentUser",uesr); System.out.println(session.getId()); return "success"; } /** * Get current user information * @param session * @return */ @RequestMapping("/getUserInfo") public User getUserInfo(HttpSession session){ return (User)session.getAttribute("currentUser"); } }
We started the project;
Browser address bar input: http://localhost/user/login
Browser address bar input: http://localhost/user/getUserInfo Get user information
The sessionId is printed out at the back end
We use Google developer tools, F12;
The backend also returns SessionId as a Cookie;
Browser address bar input: http://localhost/user/getUserInfo
The user information is returned. At the same time, we see that the request carries the SessionId in the Cookie. Go to the back end to query the specified Session information;
3. Scenario analysis of dynamically modifying user Session
The current user can use session The setAttribute method modifies the session information.
However, in some cases, we require non users to modify the Session;
For example, after the administrator recharges the VIP in the background, the data is modified, but the Session of the logged in user has not changed. What the user sees is still non VIP. You need to log in again to see the VIP information, and the user experience is poor; If we can dynamically modify the Session information of any user, the user can immediately see the VIP information without logging in and refreshing the web page, and the user experience will come up.
4 Introduction to the principle of dynamically modifying Session
Our ultimate solution is shown below:
We can create a Session listener to listen to user Session creation and destruction events. Therefore, here, we can maintain a storage medium with the relationship between sessionId and Session object. In general, we can use HashMap, which is exactly a key value pair. In case of high concurrency, it can also be stored in the tell cache Redis. Of course, for the object, Pay attention to serialization;
At the same time, after each user logs in, we can get userId and sessionId, and we also use a storage medium to maintain it. Here, for the convenience of testing, we use servletContext global context storage, high parallel distribution, and Redis storage is still used;
With the above two core storage media and the session listener, we can dynamically modify the session;
The specific steps are as follows:
Step 1: log in and get sessionId and userId;
Step 2: store sessionId and userId in servletContext global context in the format {userId: sessionId};
Step 3: the login request triggers the sessioncreated method of the session listener;
Step 4: the sessionCreated method adds session information to the HashMap in the format {sessionid: session object};
Step 5: the administrator logs in and goes to servletContext to query sessionId according to userId;
Step 6: after getting the sessionId, go to the hashMap to query the session object;
After obtaining the Session object of the specified user through the above steps, you can operate arbitrarily;
5. Dynamically modify the Session implementation
Let's implement the following code:
We define a custom session context MySessionContext, which defines the HashMap attribute to store session information in the format sessionId: session object;
package com.java1234.custom; import javax.servlet.http.HttpSession; import java.util.HashMap; /** * Custom session context * @author java1234_Xiao Feng * @site www.java1234.com * @company Nantong Xiaofeng Network Technology Co., Ltd * @create 2021-08-05 10:39 */ public class MySessionContext { private static MySessionContext instance; // The session map stores sessions. If there are many sessions that affect the system performance, you can serialize the session object with redis, key value, sessionid - > session object public static HashMap<String,HttpSession> sessionMap; private MySessionContext() { sessionMap = new HashMap<String,HttpSession>(); } /** * Single case * @return */ public static MySessionContext getInstance() { if (instance == null) { instance = new MySessionContext(); } return instance; } /** * Add session * @param session */ public synchronized void addSession(HttpSession session) { if (session != null) { System.out.println("session Successfully added!"); sessionMap.put(session.getId(), session); } } /** * Delete session * @param session */ public synchronized void delSession(HttpSession session) { if (session != null) { System.out.println("session Delete succeeded!"); sessionMap.remove(session.getId()); } } /** * Get session according to sessionId * @param sessionID * @return */ public synchronized HttpSession getSession(String sessionID) { if (sessionID == null) { return null; } return sessionMap.get(sessionID); } }
Create a new session listener SessionListener to listen to the creation and destruction of sessions;
When a session is created, the session information is stored in the custom session context; When a session is destroyed, the session will also be deleted from the custom session context;
Note that the @ WebListener annotation should be added
package com.java1234.listener; import com.java1234.custom.MySessionContext; import javax.servlet.annotation.WebListener; import javax.servlet.http.HttpSession; import javax.servlet.http.HttpSessionEvent; import javax.servlet.http.HttpSessionListener; /** * session monitor * @author java1234_Xiao Feng * @site www.java1234.com * @company Nantong Xiaofeng Network Technology Co., Ltd * @create 2021-08-05 10:43 */ @WebListener public class SessionListener implements HttpSessionListener { // Get custom session context instance private MySessionContext msc = MySessionContext.getInstance(); /** * session Create event * @param se */ @Override public void sessionCreated(HttpSessionEvent se) { System.out.println("session establish"); HttpSession session = se.getSession(); msc.addSession(session); // Add current session to custom session context } /** * session Destruction event * @param se */ @Override public void sessionDestroyed(HttpSessionEvent se) { System.out.println("session Destroy"); HttpSession session = se.getSession(); //todo needs to delete the user session information of the specified sessionId from the database or redis cache msc.delSession(session); // Delete the current session from the custom session context } }
In the springboot project, to make the listener effective, we need to add @ ServletComponentScan annotation to the startup class
@ServletComponentScan automatically scans classes annotated with (@ WebServlet, @WebFilter, and @WebListener) to complete registration
Here, let's test whether the listener is effective;
Start the project and enter in the browser address bar: http://localhost/user/login
It indicates that the listener is triggered successfully and the session is added successfully!
The session destruction method is triggered in the following three ways:;
1. Timeout (generally, the timeout time set by the server is 30 minutes) the server will destroy the session;
2. Click the red button on the console to shut down the server abnormally and destroy the session
3. Manually call the invalidate method of session invalidate();
For demonstration, I set the session validity to 30 seconds;
server: port: 80 servlet: context-path: / session: timeout: 30s
After 30 seconds of simulated Login, as long as we do not continue the requested operation, the session destruction event will be triggered and the session will be deleted after 30 seconds;
Modify UserController, obtain servletContext context through session, and store user session information in the format of {userid: sessionid}
/** * Simulate user login * @return */ @RequestMapping("/login") public String login(HttpSession session){ User uesr=new User(1,"java1234","123456"); session.setAttribute("currentUser",uesr); System.out.println(session.getId()); ServletContext servletContext = session.getServletContext(); // Simulate the storage of user session information to the database and simulate with application servletContext.setAttribute(String.valueOf(uesr.getId()),session.getId()); // Key value user ID sessionid return "success"; }
To create a ManagerController test:
package com.java1234.controller; import com.java1234.custom.MySessionContext; import com.java1234.entity.User; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import javax.servlet.ServletContext; import javax.servlet.http.HttpSession; import java.util.HashMap; /** * Administrator Controller * @author java1234_Xiao Feng * @site www.java1234.com * @company Nantong Xiaofeng Network Technology Co., Ltd * @create 2021-08-07 23:05 */ @RequestMapping("/manager") @RestController public class ManagerController { /** * Simulate user login * @return */ @RequestMapping("/modifySession") public String modifySession(HttpSession session){ ServletContext servletContext = session.getServletContext(); String userId="1"; // Modify user with userId=1 String sessionId = (String)servletContext.getAttribute(userId); // Get sessionId according to userId from servletContext context System.out.println("sessionId:"+sessionId); HashMap<String, HttpSession> sessionMap = MySessionContext.sessionMap; // Get sessionMap HttpSession currentSession = sessionMap.get(sessionId); // Get user session according to sessionId User user = (User)currentSession.getAttribute("currentUser"); // Get user information according to session user.setLevel("vip"); // Modification content return "success"; } }
Browser address bar: http://localhost/user/login Simulate login
Browser address bar: http://localhost/user/getUserInfo Get user information
We have another browser address bar: http://localhost/manager/modifySession Simulate the administrator to modify the session
Then refresh getUserInfo;
We found that the session information changed. Test successful!
6 performance optimization under high concurrency
In HPF, there will be many login users at the same time. If you put all sessions in memory, it will affect performance and even memory overflow; So the session can be stored in redis;
In addition, the two storage media shown in the figure below should also be stored in redis to achieve the best performance;
7 complete source code + supporting video tutorial sharing
I put the source code and documents + videos on Github and code cloud. If you need to refer to the source code, directly pull the IDEA tool;
Code cloud address: https://gitee.com/java_1234/modify-session
Github address: https://github.com/java1234/modify-session
8 left a little homework
If you are an ideal programmer, you might as well upgrade the example of xiafeng brother;
First, build a highly available redis cluster environment;
Second: springboot+redis enables the session to be stored in the redis cache;
Third, as shown in the figure above, the two storage media are also stored in redis;
Original address: The boss gave me a 2000 bonus because I realized that the administrator can modify the Session function of any user in Java