Network Security Internet Technology Development Database Servers Mobile Phone Android Software Apple Software Computer Software News IT Information

In addition to Weibo, there is also WeChat

Please pay attention

WeChat public account

Shulou

Sample method of how Tomcat manages Session

2025-04-16 Update From: SLTechnology News&Howtos shulou NAV: SLTechnology News&Howtos > Servers >

Share

Shulou(Shulou.com)06/02 Report--

Editor to share with you the example of how Tomcat manages Session. I hope you will get something after reading this article. Let's discuss it together.

Session structure

No more nonsense, just go straight to the picture.

Looking closely at the above picture, we can come to the following conclusion

HttpSession is the interface class that operates on Session in the JavaEE standard, so we are actually operating on the StandardSessionFacade class.

The data structure used by Session to store the data is ConcurrentHashMap. As you can see in the figure, we have saved a msg in Session.

Why do you need to use ConcurrentHashMap? The reason is that when dealing with Http requests, not only one thread will access the Session. Modern Web applications usually need to execute multiple requests at the same time when accessing a page, and these requests may be executed by different threads in the Web container at the same time, so if you use HashMap, it is easy to cause thread safety problems.

Let's first take a look at HttpSession's wrapper class.

StandardSessionFacade

In this class, we can learn the practical application of appearance pattern (Facde). Its definition is as follows.

Public class StandardSessionFacade implements HttpSession

So how does this class implement the functions of Session? Looking at the following code, it is not difficult to see that this class is not the real implementation class of HttpSession, but rather wraps the real HttpSession implementation class, exposing only the methods in the HttpSession interface, that is, the Facde pattern in the design pattern.

Private final HttpSession session; public StandardSessionFacade (HttpSession session) {this.session = session;}

So why don't we just use HttpSession's implementation class?

According to figure 1, we can know that the real implementation class of HttpSession is StandardSession. Assuming that some methods are defined in this class that should be called by Tomcat rather than by the program, then because of Java's type system, we will be able to manipulate the class directly, which will cause some unforeseen problems, as shown in the following code.

If we wrap the StandardSession in another layer, an error will occur during the execution of the code in the figure above. As shown in the following figure, a cast exception will be thrown to prevent illegal operations here.

Further, do we access StandardSession directly around the appearance class?

Actually, we can get the StandardSession through the reflection mechanism, but you'd better know what you're doing. The code is as follows

@ GetMapping ("/ s") public String sessionTest (HttpSession httpSession) throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException {StandardSessionFacade session = (StandardSessionFacade) httpSession; Class targetClass = Class.forName (session.getClass (). GetName ()); / / modify visibility Field standardSessionField = targetClass.getDeclaredField ("session"); standardSessionField.setAccessible (true); / / get StandardSession standardSession = (StandardSession) standardSessionField.get (session); return standardSession.getManager (). ToString ();}

StandardSession

The definition of this class is as follows

Public class StandardSession implements HttpSession, Session, Serializable

Through its interface, we can see that this class not only has the functions required by HttpSession in the JavaEE standard, but also has serialization functions.

In figure 1, we already know that StandardSession is data stored in ConcurrentHashMap, so next we focus on the serialization and deserialization of StandardSession, as well as the functionality of listeners.

Serialization

Remember in the last section we got StandardSession through the reflection mechanism? Using the following code, we can directly see what the deserialized StandardSession looks like.

@ GetMapping ("/ s") public void sessionTest (HttpSession httpSession, HttpServletResponse response) throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException, IOException {StandardSessionFacade session = (StandardSessionFacade) httpSession; Class targetClass = Class.forName (session.getClass (). GetName ()); / / modify visibility Field standardSessionField = targetClass.getDeclaredField ("session"); standardSessionField.setAccessible (true); / / get StandardSession standardSession = (StandardSession) standardSessionField.get (session); / / save point data to observe standardSession.setAttribute ("msg", "hello,world") StandardSession.setAttribute ("user", "kesan"); standardSession.setAttribute ("password", "like"); standardSession.setAttribute ("tel", 10086L); / / write the serialized result directly to the response of Http ObjectOutputStream objectOutputStream = new ObjectOutputStream (response.getOutputStream ()); standardSession.writeObjectData (objectOutputStream);}

If nothing happens, the browser that accesses this interface will download and get a file.

Using WinHex to open the analysis, as shown in the figure shows the result after serialization, mainly a large number of delimiters, as well as type information and values, such as the red box standard information in the figure.

It is not recommended that you obsess over how serialized files organize data, because it doesn't make much sense.

If you are really interested, I suggest you read the following code org.apache.catalina.session.StandardSession.doWriteObject

Listener

In the JavaEE standard, we can listen for changes in Session by configuring HttpSessionAttributeListener, so how is it implemented in StandardSession? if you know the observer mode, you must already know the answer. Take setAttribute as an example, immediately after calling this method, the listener's method is called for processing on this thread, which means that we should not perform operations in the listener that have been blocked for too long.

Public void setAttribute (String name, Object value, boolean notify) {/ / omit extraneous code / / get the event listener Object listeners [] = context.getApplicationEventListeners () configured above; if (listeners = = null) {return;} for (int I = 0; I)

< listeners.length; i++) { //只有HttpSessionAttributeListener才可以执行 if (!(listeners[i] instanceof HttpSessionAttributeListener)) { continue; } HttpSessionAttributeListener listener = (HttpSessionAttributeListener) listeners[i]; try { //在当前线程调用监听器的处理方法 if (unbound != null) { if (unbound != value || manager.getNotifyAttributeListenerOnUnchangedValue()) { //如果是某个键的值被修改则调用监听器的attributeReplaced方法 context.fireContainerEvent("beforeSessionAttributeReplaced", listener); if (event == null) { event = new HttpSessionBindingEvent(getSession(), name, unbound); } listener.attributeReplaced(event); context.fireContainerEvent("afterSessionAttributeReplaced", listener); } } else { //如果是新添加某个键则执行attributeAdded方法 context.fireContainerEvent("beforeSessionAttributeAdded", listener); if (event == null) { event = new HttpSessionBindingEvent(getSession(), name, value); } listener.attributeAdded(event); context.fireContainerEvent("afterSessionAttributeAdded", listener); } } catch (Throwable t) { //异常处理 } } } Sesssion生命周期 如何保存Session 在了解完Session的结构之后,我们有必要明确 StandardSession 是在何时被创建的,以及需要注意的点。 首先我们来看看 StandardSession 的构造函数, 其代码如下所示。 public StandardSession(Manager manager) { //调用Object类的构造方法,默认已经调用了 //此处再声明一次,不知其用意,或许之前此类有父类? super(); this.manager = manager; //是否开启访问计数 if (ACTIVITY_CHECK) { accessCount = new AtomicInteger(); } } 在创建 StandardSession 的时候都必须传入 Manager 对象以便与此 StandardSession 关联,因此我们可以将目光转移到 Manager ,而 Manager 与其子类之间的关系如下图所示。

We turn our attention to ManagerBase and find the following code.

Protected Map sessions = new ConcurrentHashMap ()

Session is a custom interface for Tomcat. StandardSession implements the HttpSession and Session interfaces, which are more functional, but are not available to programmers.

Looking for this property, we can find that the operations related to Session are implemented by manipulating sessions, so we can explicitly save the data structure of Session is ConcurrentHashMap.

How to create a Session

So how exactly was Session created? I found the following method, ManagerBase.creaeSession, and summarized the process as follows.

Check whether the number of session exceeds the limit, and if so, throw an exception

Create a StandardSession object

Set various required properties of session (legitimacy, maximum timeout, sessionId)

To generate SessionId, Tomcat supports different SessionId algorithms. The SessionId generation algorithm used in my debugging process is LazySessionIdGenerator. (this algorithm is different from other algorithms in that it does not load random arrays at the beginning, but only when they are used. The random arrays here are not ordinary random arrays but SecureRandom. For related information, please read the article of the boss.)

Increase the count of session, because Tomcat's policy is to calculate the creation rate of only 100 session, so sessionCreationTiming is a linked list with a fixed size of 100 (initially 100 elements with a value of null), so when adding new data to the linked list, the old data must be removed from the linked list to ensure its fixed size. The formula for calculating session creation rate is as follows

(1000*60*counter) / (int) (now oldest)

Among them

Now is the time when getting statistics System.currentTimeMillis ()

Oldest is the earliest time the session was created in the queue

Counter is the number of elements in the queue whose value is not null

Since the rate per minute is calculated, 1000 must be multiplied by 60 here (60000 milliseconds in a minute)

Public Session createSession (String sessionId) {/ / checks whether the Session exceeds the limit, and if so, throws an exception if ((maxActiveSessions > = 0) & & (getActiveSessions () > = maxActiveSessions)) {rejectedSessions++; throw new TooManyActiveSessionsException (sm.getString ("managerBase.createSession.ise"), maxActiveSessions);} / / this method creates the StandardSession object Session session = createEmptySession (); / / initializes the necessary attribute session.setNew (true) in Session / / whether session.setValid (true) is available for session; / / creation time session.setCreationTime (System.currentTimeMillis ()); / / set session maximum timeout session.setMaxInactiveInterval (getContext (). GetSessionTimeout () * 60); String id = sessionId; if (id = = null) {id = generateSessionId ();} session.setId (id); sessionCounter++ / / record the time when the session was created, used to count the creation rate of the data session / / similarly, there is the expiration rate of ExpireRate that is Session / / because there may be other threads operating on sessionCreationTiming, it needs to be locked SessionTiming timing = new SessionTiming (session.getCreationTime (), 0); synchronized (sessionCreationTiming) {/ / sessionCreationTiming is LinkedList / / so poll removes the data from the chain header, that is, the oldest data sessionCreationTiming.add (timing); sessionCreationTiming.poll () } return session;}

Destruction of Session

To destroy the Session, it is necessary to remove the Session from the ConcurrentHashMap, and we can find the code to remove the session as follows.

@ Override public void remove (Session session, boolean update) {/ / check whether it is necessary to send statistics expired session information if (update) {long timeNow = System.currentTimeMillis (); int timeAlive = (int) (timeNow-session.getCreationTimeInternal ()) / 1000; updateSessionMaxAliveTime (timeAlive); expiredSessions.incrementAndGet (); SessionTiming timing = new SessionTiming (timeNow, timeAlive); synchronized (sessionExpirationTiming) {sessionExpirationTiming.add (timing); sessionExpirationTiming.poll () }} / / remove session from Map if (session.getIdInternal ()! = null) {sessions.remove (session.getIdInternal ());}}

The timing of being destroyed.

Active destruction

We can perform the session destroy operation by calling the HttpSession.invalidate () method. This method finally calls the StandardSession.invalidate () method, and its code is as follows. You can see that the key method to destroy session is StandardSession.expire ().

Public void invalidate () {if (! isValidInternal () throw new IllegalStateException (sm.getString ("standardSession.invalidate.ise")); / / Cause this session to expire expire ();}

The code for the expire method is as follows

@ Override public void expire () {expire (true);} public void expire (boolean notify) {/ / omit the code / / remove the session from the ConcurrentHashMap manager.remove (this, true); / / the omitted code is mainly to notify / / to each listener that the session has been destroyed.

Time-out destruction

In addition to active destruction, we can set an expiration time for the session. When the time arrives, the session will be actively destroyed by the background thread. We can set a short expiration time for session, and then use JConsole to track its call stack, which object and which thread performed the destroy operation.

As shown in the following figure, we set a timeout of 30 seconds for session.

And then we're in ManagerBase.remove.

Method and wait for 30 seconds, as shown in the following figure

Tomcat starts a background thread to periodically execute the backgroundProcess method of the subcomponent (provided that the subcomponent is managed by Tomcat and implements the Manager interface)

@ Override public void backgroundProcess () {count = (count + 1)% processExpiresFrequency; if (count = = 0) processExpires ();} public void processExpires () {long timeNow = System.currentTimeMillis (); Session sessions [] = findSessions (); int expireHere = 0; if (log.isDebugEnabled ()) log.debug ("Start expire sessions" + getName () + "at" + timeNow + "sessioncount" + sessions.length); / / you can see from JConsole's diagram that isValid may cause expire method to be called for (int I = 0) I < sessions.length; iTunes +) {if (sessions [I]! = null & &! sessions [I] .isValid ()) {expireHere++;}} long timeEnd = System.currentTimeMillis (); if (log.isDebugEnabled ()) log.debug ("End expire sessions" + getName () + "processingTime" + (timeEnd-timeNow) + "expired sessions:" + expireHere); processingTime + = (timeEnd-timeNow);}

We can take a look at the comments in Manager.backgroundProcess in the API. The brief translation means that backgroundProcess is executed periodically by the container and can be used to perform session cleanup tasks.

/ * This method will be invoked by the context/container on a periodic * basis and allows the manager to implement * a method that executes periodic tasks, such as expiring sessions etc. * / public void backgroundProcess ()

Summary

The data structure of Session is shown in the following figure, which simply uses ConcurrentHashMap to save Session, while Session uses ConcurrentHashMap to store key-value pairs, and its structure is shown in the following figure. .jpg

This means that instead of desperately adding discrete data to the Session, encapsulating the discrete data into an object will perform better as follows

/ / badhttpSession.setAttribute ("user", "kesan"); httpSession.setAttribute ("nickname", "like"); httpSession.setAttribute ("sex", "male");.... / goodUser kesan = userDao.getUser () httpSession.setAttribute ("user", kesan)

If you configure a listener for Session, any changes to Session will execute the listener's methods directly on the current thread, so it is best not to execute methods that may block in the listener.

Tomcat starts a background thread to periodically execute the ManagerBase.backgroundProcess method to detect expired Session and destroy it.

Thought transfer

Object generation rate algorithm this algorithm design is interesting, and can also be applied to other projects, so make the following summary.

First generate a fixed-size linked list (say 100) and then populate it with the null element. When creating a new object, the creation time is added to the end of the linked list (the encapsulated object, of course), and then the header node of the linked list is removed, and the removed object is either the null node or the earliest node to join the linked list. When you want to calculate the object generation rate, you can calculate the number of elements in the linked list that are not null divided by the difference between the current time and the earliest time to create the object. (pay attention to the conversion of time units)

After reading this article, I believe you have a certain understanding of "how Tomcat manages Session". If you want to know more about it, you are welcome to follow the industry information channel. Thank you for reading!

Welcome to subscribe "Shulou Technology Information " to get latest news, interesting things and hot topics in the IT industry, and controls the hottest and latest Internet news, technology news and IT industry trends.

Views: 0

*The comments in the above article only represent the author's personal views and do not represent the views and positions of this website. If you have more insights, please feel free to contribute and share.

Share To

Servers

Wechat

© 2024 shulou.com SLNews company. All rights reserved.

12
Report