数据库+ThreadLocal+拦截器+注解替代Session

在分布式的环境下,使用session会出现共享数据的问题。那么可以将共享数据存入数据库中,然后应用服务器就可以去数据库获取共享数据。对于每一次请求,可以在一开始从数据库里取到数据,然后将其临时存放在本地的内存里。考虑到线程安全的问题,所以使用threadlocal进行线程隔离,这样在本次请求的过程中,就可以随时获取到这份共享数据了。所以,session的替代方案是数据库,ThreadLocal在这里起辅助的作用。

这里演示偷懒直接用的mysql,推荐使用redis作为替代,否则mysql数据库压力会很大

先搞一个用于存储用户信息的threadLocal

@Component
public class HostHolder {
    
    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();
    }
}

设计是在登录后将用户凭证存到了cookie中,所以制作一个cookie工具类

public class CookieUtil {

    public static String getValue(HttpServletRequest request, String name) {
        if (request == null || name == null) {
            throw new IllegalArgumentException("参数为空!");
        }

        Cookie[] cookies = request.getCookies();
        if (cookies != null) {
            for (Cookie cookie : cookies) {
                if (cookie.getName().equals(name)) {
                    return cookie.getValue();
                }
            }
        }

        return null;
    }
}

然后就可以制作拦截器

第一个是在方法请求前看有无用户凭证

@Component
public class LoginTicketInterceptor implements HandlerInterceptor {

    @Autowired
    private UserService userService;

    @Autowired
    private HostHolder hostHolder;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        //获取凭证
        String ticket = CookieUtil.getValue(request,"ticket");
        
        if(ticket!=null){
            LoginTicket loginTicket = userService.findLoginTicket(ticket);
            //loginTicket是否为空,status为0代表还没退出登录,看过期时间是不是在现在的时间之后
            if(loginTicket!=null&&loginTicket.getStatus()==0&&loginTicket.getExpired().after(new Date())){
                //条件都成立就放到threadLoccal中
                User user = userService.findUserById(loginTicket.getUserId());
                hostHolder.setUser(user);
            }
        }

        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        User user = hostHolder.getUser();
        //将用户信息放到modelAndView中
        if(user!=null&&modelAndView!=null){
            modelAndView.addObject("loginUser",user);
        }
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        //记得用完后清除一下threadLocal,否则可能内存泄露
        hostHolder.clear();
    }
}

第二个拦截器用于判断用户调用方法时是否登录。具体是看方法上是否是用了注解,以及用户是否处于登陆状态

@Component
public class LoginRequiredInterceptor implements HandlerInterceptor {

    @Autowired
    private HostHolder hostHolder;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        if(handler instanceof HandlerMethod){
            HandlerMethod handlerMethod = (HandlerMethod) handler;
            Method method = handlerMethod.getMethod();
            LoginRequired annotation = method.getAnnotation(LoginRequired.class);
            //看方法上有没有用到LoginRequired注解,以及用户是否为空,有用到且为空则代表用户没登陆,返回登录页你
            if(annotation!=null&&hostHolder.getUser()==null){
                response.sendRedirect(request.getContextPath()+"/login");
                return false;
            }
        }
        return true;
    }
}

判断有无登录的注解,这里不需要写任何具体内容,仅在方法上作标识作用,以供拦截器识别

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface LoginRequired {
}

使拦截器生效

注意细节,必须先注册loginTicketInterceptor,再注册loginRequiredInterceptor,因为两边同时都使用了preHandle()方法,执行顺序会有影响

@Configuration
public class WebMvcConfig implements WebMvcConfigurer {

    @Autowired
    private LoginTicketInterceptor loginTicketInterceptor;

    @Override
        public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(loginTicketInterceptor)
                .excludePathPatterns("/**/*.css", "/**/*.js", "/**/*.png", "/**/*.jpg", "/**/*.jpeg");
        registry.addInterceptor(loginRequiredInterceptor)
                .excludePathPatterns("/**/*.css", "/**/*.js", "/**/*.png", "/**/*.jpg", "/**/*.jpeg");
    }

}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值