Redis-短信登录02-实现登录校验拦截器

  1. 1. tomcat的运行原理
  2. 2. 使用ThreadLocal保存用户登录信息
    1. 2.1. 1. 为什么使用ThreadLocal
    2. 2.2. 2. 使用ThreadLocal替代Session的好处
    3. 2.3. 3. ThreadLocal类:
  3. 3. 自定义LoginInterceptor登录拦截器实例配置
    1. 3.1. LoginInterceptor.java
  4. 4. WebMvcConfigurer自定义拦截器配置
  5. 5. 登录成功:

tomcat的运行原理

​ 当用户发起请求时,会访问我们像tomcat注册的端口,任何程序想要运行,都需要有一个线程对当前端口号进行监听,tomcat也不例外。

当监听线程知道用户想要和tomcat连接连接时,那会由监听线程创建socket连接,socket都是成对出现的,用户通过socket像互相传递数据。

​ 当tomcat端的socket接受到数据后,此时监听线程会从tomcat的线程池中取出一个线程执行用户请求。

​ 在我们的服务部署到tomcat后,线程会找到用户想要访问的工程,然后用这个线程转发到工程中的controller,service,dao中,并且访问对应的DB,在用户执行完请求后,再统一返回。

​ 再找到tomcat端的socket,再将数据写回到用户端的socket,完成请求和响应。

​ 通过以上讲解,我们可以得知 每个用户其实对应都是去找tomcat线程池中的一个线程来完成工作的, 使用完成后再进行回收,既然每个请求都是独立的,所以在每个用户去访问我们的工程时,我们可以使用threadlocal来做到线程隔离,每个线程操作自己的一份数据。

使用ThreadLocal保存用户登录信息

1. 为什么使用ThreadLocal

​ 很多项目中需要在代码中使用当前登录用户的信息,但是又不方便把保存用户信息的session对象传来传去,这种情况下,就可以考虑使用 ThreadLocal。ThreadLocal是一个依附于本地线程的变量,按照我的理解,每次对服务器请求,都会使用到一个线程,ThreadLocal的作用就是在这个线程的使用过程中只为这个线程所用。

​ 通常情况下,我们创建的变量是可以被任何一个线程访问并修改的。如果想实现每一个线程都有自己的专属本地变量该如何解决呢?

​ JDK 中自带的ThreadLocal类正是为了解决这样的问题。 ThreadLocal类主要解决的就是让每个线程绑定自己的值,可以将ThreadLocal类形象的比喻成存放数据的盒子,盒子中可以存储每个线程的私有数据。

2. 使用ThreadLocal替代Session的好处

​ 在同一个线程中可以很方便的获取用户信息,不需要频繁的传递session。

​ threadlocal本质上是以线程为key存储元素,用户信息保存在线程中,用户的每一次请求,就是一个线程,保存了用户信息,方便我们在后续操作获取用户登录信息。

​ 当请求结束,我们会把保存的用户信息清除掉,防止内存泄漏。

3. ThreadLocal类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/**
*持有用户信息,用于代替session对象
*/
@Component
public class HostHolder {

//ThreadLocal本质是以线程为key存储元素
private ThreadLocal<User> users = new ThreadLocal<>();

public void setUser(User user){
users.set(user);
}

public User getUser(){
return users.get();
}

public void clear(){
users.remove();
}
}

温馨小贴士:关于threadlocal

在threadLocal中,无论是他的put方法和他的get方法, 都是先从获得当前用户的线程,然后从线程中取出线程的成员变量map,只要线程不一样,map就不一样,所以可以通过这种方式来做到线程隔离

自定义LoginInterceptor登录拦截器实例配置

自定义拦截器需要实现HandleInterceptor接口

三个方法的运行顺序为: preHandle -> postHandle -> afterCompletion;

  • ​ 如果preHandle返回值为false,三个方法仅运行preHandle;
  • ​ 如果运行拦截放行后的代码出错,则不会执行postHandle;
  • ​ 自定义拦截器实例需要实现HandleInterceptor接口;
  • ​ 注入到 ioc 自定义拦截器配置需要使用到 拦截器实例;

LoginInterceptor.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public class LoginInterceptor implements HandlerInterceptor {

@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 1. 获取session
HttpSession session= request.getSession();
// 2. 获取session中的用户
Object user = session.getAttribute("user");
// 3. 判断用户是否存在
if (user==null) {
// 4. 不存在,拦截, 返回401状态码
response.setStatus(401);
return false;
}
// 5. 存在,保存用户信息到 ThreadLocal
UserHolder1.saveUser((User) user);
// 6. 放行
return true;
}

@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
// 移除用户
UserHolder.removeUser();
}
}

WebMvcConfigurer自定义拦截器配置

让拦截器生效:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

@Configuration
public class MvcConfig implements WebMvcConfigurer {

// 这个方法用来注册拦截器,我们自己写好的拦截器需要通过这里添加注册才能生效
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 登录拦截器
registry.addInterceptor(new LoginInterceptor()).excludePathPatterns(
"/shop/**",
"/voucher/**",
"/shop-type/**",
"/upload/**",
"/blog/hot",
"/user/code",
"/user/login"
);

}
}

登录成功: