This article is participating in “Java Theme Month – Java Debug Notes Event”, see < Event link > for more details.

A message is displayed indicating that an online user has no permission to view data

preface

  • I wonder when I was young and thought that Java was easy, that there was no requirement I couldn’t implement, no bug I couldn’t solve

  • Until I came across a bug I still remember. A message is displayed indicating that an online user has no permission to view data

For the first time positioning

  • Why do you not have permission to add data? At first, I was confident that there was something wrong with our customer operation or our permission configuration
  • However, after taking myself to verify it, I found that this problem is not present, which is an accidental problem. This is really not easy to find in the development phase.

Problem to upgrade

  • Having run my own tests has made me even more skeptical about life. You either have a problem or you’re okay. What does it mean to have a while and not a while? My problem is really hard to solve. I’m exhausted at this point. And then he gave up on positioning

  • But the problem had to be solved, and the next day I went back to work. Maybe the next day WHEN I was more sober, I found that when inserting data in our system, the current login user would be automatically obtained and the creator of the second data and the latest modifier would be recorded in the database. All the more reason we’re off base. But the problem is that we have a problem getting the current logged-in user

  • Yes, I tracked down the problem and finally found the essence of the problem. We get the current logged-in user through ThreadLocal. The problem is that ThreadLocal is having trouble getting users

  • Our distributed development system. We will add an AOP interceptor to each module to query basic user information through the request header token to the user module. Then drop it into a ThreadLocal. This allows us to retrieve our log-in users from ThreadLocal objects everywhere in our system.

  • Don’t ask me why DO I do this in every module? Don’t ask me why ThreadLocal? Don’t ask me why I’m doing this when I’m distributed. Because today we’re going to focus on fixing bugs

Cut to the chase

  • The problem is in the getUser piece of logic. Because our design is to get User objects everywhere in the system. Of course we’re talking about any request. There is definitely no User in MQ, timer modules. Because these can’t be intercepted by AOP

The user information obtained by ThreadLocal is inconsistent, causing an exception in new data permissions

The final location

  • ourThreadLocalIs an object whose properties are obtained in our system through a utility class. We provide set and get methods on this object.

  • The above process shows that the factory is joined after the User User is acquired. If the factory already exists, it won’t join. Otherwise they will join our users
  • This also prevents us from constantly adding duplicate user information. Because the same thread can only correspond to one user.

thinking

public static UserInfo getUser(a) {
    return userThreadLocal.get();
}
Copy the code
  • Above is the get method of our utility class. This is toThreadLocalThe contents of the object store are returned. This step should not be a problem.
  • In getUser there is obviously no problem, and we are left with setUser using elimination. I can’t see anything wrong with setUser even though I’ve ruled him out. After a bit of debug breakpoint tracking, I found that our setUser logic did have a problem
  • SetUser saves user information to a ThreadlocalObject, but the premise isThreadLocal ‘has no users. Yeah, that’s the question. What if you already have users? Then our real users won’t be able to add it
  • At this point the problem becomes clear. To make ourThreadLocalThe object management is abnormal. The last user information is saved and the user information is jumbled

To solve the problem

  • Now that we’ve located itThreadLocalThen we’ll be in a good position.

ThreadLocal simple comb

  • ThreadLocalStore objects in a thread. In other words, each thread’s data is isolated from each other. Based on this feature we can store user information here, so that we can ensure that our current thread execution sub-methods can obtain user information through it
  • ThreadLocalInternally, it stores itself as a key and the storage object as a value into a map in the current thread. The map is reclaimed by the JVM as the thread is destroyed.
  • However, thread pools are often used in our actual development to avoid repeated thread creation and destruction. Threads are usually not destroyed
  • Web containers such as Tomcat and JBoss integrated in Spring use a certain number of threads by default. The thread reuse feature that we use in Spring causes us to get the user of the current thread because the thread is used by someone else and the user information is never updated successfully. Which raises the strange question we mentioned above
  • So since it is not updated, we can solve the problem at this point, or after each use is completedThreadLocalRemove data from. Because it is internally weak, it will recycle the object the next time it is collected so that it does not cause a memory leak
  • Or we could set the user before setUser in our AOPThreadLocalEmpty. Either way is a perfect solution to our problem

Concrete code implementation

/** * The last step in the request lifecycle destruction is a callback event * used to destroy online user information to prevent online user information from interfering with each other (when multithreading) */
@WebListener
@Primary
public class SysServletRequestListener implements ServletRequestListener {
    @Override
    public void requestDestroyed(ServletRequestEvent requestEvent) {
        UserInfoUtil.clearUserInfo();
    }

    @Override
    public void requestInitialized(ServletRequestEvent sre) {}}Copy the code
  • Spring provides listeners that listen for the life of a request and remove our ThreadLocal after the request completes. Why do I recommend it. Because emptying at the end of a request quickly frees up memory for more useful things to do.

  • If it’s the second way then if we don’t have anyone logging in, or until the next time we log in this piece of memory that we don’t need is always occupied

Summarize the advice

  • This problem is very strange, once made me doubt life, but always believe that the program will not go wrong for no reason.
  • The only problem is that our code has a problem, we should be good at parsing the problem, refine the problem to our code level rather than the business level
  • The best way to use a technology is to understand its internal principles. Or at least understand his general logic
  • Although this article solved our problem in a few words, I actually suffered a lot in the process of solving him. I spent a lot of nights fighting with him
  • I’m on locationThreadLocalI spent an hour learning his logic and tracking his source code. Finally, combining with our business, I found out
  • In short, problems are a good thing, with problems we can grow. At least I learned from this problemThreadLocal. My problem this time is also the typical problem of using him, there is also a memory leak problem which is learned in the process of learning his source code. We’ll have time to look at memory leaks. Problem solved. Finally, I can continue happy.