SpringBoot自定义RedisHttpSession实现Session共享

最近发现,SpringSession的好像有点不对我的胃口,有些难控制,于是我就自己照着它写了自己的Session实现。

首先,我们需要复习 Java Web 的知识,想想我们是如何操作 Session 的: session = request.getSession(),很明显是通过HttpServletRequest获取的,因为每次用户请求过来,我们服务端都会获取到请求携带的唯一Cookie SessionId。根据这点来看,我们必然要通过 HttpServletRequest 获取到我们自己的 Session,这样我们对于 Session 的操作才是使用我们自定义的。

但是想想, HttpServletRequest是不可能返回我们自定的 HttpSession的,所以我们还要自定义一个HttpServletRequest的包装类,使得每次请求获取的都是我们自己的HttpSession

但是,又有一个难点,如何确保HttpServletRequest是我们定义的呢?
这里就需要Filter了,我们还需要自定义一个 Filter,这个Filter不干其它的事情,就负责把HttpServletRquest 换成我们自定义的包装类。

 整理一下,我们需要三件利器,帮我们完成自定义的 Session 操作,
 1: 首当其冲的便是我们自己的HttpSession,它需要实现HttpSession这个接口,
    我叫他 CustomRedisHttpSession
    
 2: 我们需要自己的HttpServletRquest帮我们获取到自定义的 CustomRedisHttpSession
    我叫他 CustomSessionHttpServletRequestWrapper, 继承了 HttpServletRequestWrapper
    
 3:  还需要一个只负责将HttpServletRequest包装成 CustomSessionHttpServletRequestWrapper 
     的Fitler,我叫他 CustomSessionRequestFilter ,实现了 Filter 接口

好的,有了思路,就开始实现,但是有许多坑需要注意,HttpSession 是不能注入属性的,所以我们需要手动创建一个类,来获取 bean:

//此类作为 获取 bean 的工具类
@Component(value="applicationContextUtil")
public class ApplicationContextUtil implements ApplicationContextAware {

    //IOC 上下文对象
    private static ApplicationContext applicationContext;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        ApplicationContextUtil.applicationContext  = applicationContext;
    }
    public static ApplicationContext getApplicationContext() {
        assertApplicationContext();
        return applicationContext;
    }

    /**
     * 三种获取 Bean 的方法
     */


    @SuppressWarnings("unchecked")
    public static <T> T getBean(String beanName) {
        assertApplicationContext();
        return (T) applicationContext.getBean(beanName);
    }
    public static <T> T getBean(Class<T> clazz) {
        assertApplicationContext();
        return applicationContext.getBean(clazz);
    }
    public static <T> T getBean(String penName,Class<T> clazz){
        assertApplicationContext();
        return applicationContext.getBean(penName,clazz);
    }


    //判断上下文对象是否未注入
    private static void assertApplicationContext() {
        if (ApplicationContextUtil.applicationContext == null) {
            throw new RuntimeException("没有注入 applicationContext");
        }
    }
}

既然是实现 Redis 的Session,那么我们就需要有 RedisTemplate:

@SpringBootConfiguration
 public class RedisConfig{
    /**
     *  配置Session RedisTemplate,用于Session的操作
     * @return  RedisTemplate<String,Serializable>r
     */
     //两个参数LettuceConnectionFactory,Jackson2JsonRedisSerializer 不了解的先学习关于
     //Redis和SpringBoot的整合

    @Bean(name="sessionRedisTemplate")
    public RedisTemplate<String,Object> getSessionRedisTemplate(
            @Qualifier(value="sessionLettuceConnectionFactory")
                    LettuceConnectionFactory sessionLettuceConnectionFactory,
            @Qualifier(value="jackson2JsonRedisSerializer")
                    Jackson2JsonRedisSerializer jackson2JsonRedisSerializer){
                    
        //构造 sessionRedisTemplate
        RedisTemplate<String,Object> sessionRedisTemplate = new RedisTemplate<>();
        sessionRedisTemplate.setConnectionFactory(sessionLettuceConnectionFactory);
        /**
         * 使用 String 作为 Key 的序列化器,使用 jackson2JsonRedisSerializer 作为 Value 的序列化器
         */
        StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
        // key采用String的序列化方式
        sessionRedisTemplate.setKeySerializer(stringRedisSerializer);
        // hash的key也采用String的序列化方式
        sessionRedisTemplate.setHashKeySerializer(stringRedisSerializer);
        // value序列化方式采用 jackson2JsonRedisSerializer
        sessionRedisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
        // hash的value序列化方式采用 jackson2JsonRedisSerializer
        sessionRedisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);
        sessionRedisTemplate.afterPropertiesSet();
        return sessionRedisTemplate;
    }
}

重头戏,先实现三大利器中的 Filter 吧,多一句嘴,在SpringBoot中使用Filter的方法需要大家自己有相关知识,把 Filter注册到 SpringBoot中 :

	/**
     * 此过滤器拦截所有请求,也放行所有请求,但是只要与Session操作的有关的请求都换被
     * 替换成:CustomSessionHttpServletRequestWrapper包装请求,
     * 这个请求会获取自定义的HttpSession
     */
    public class CustomSessionRequestFilter implements Filter {
        @Override
        public void doFilter(ServletRequest servletRequest, 
 					         ServletResponse servletResponse,
 					          FilterChain filterChain) 
 					          throws IOException, ServletException {
 					          
            //将请求替换成自定义的 CustomSessionHttpServletRequestWrapper 包装请求
            HttpServletRequest customSessionHttpServletRequestWrapper =
                    new CustomSessionHttpServletRequestWrapper
	((HttpServletRequest)servletRequest,(HttpServletResponse)servletResponse);
       
            //获取请求的路径
            String sessionPath=
            customSessionHttpServletRequestWrapper.getServletPath();
            
            //如果是与Session操作有关的请求,就替换成自定请求包装类,SESSION_PATH是我程序中的
            //一个常量,也就是你需要操作Session的那个RequestMapping路径
            //并继续调用doFilter执行责任链
            if(sessionPath.equals(SESSION_PATH)){
                HttpServletResponse httpServletResponse =
                 (HttpServletResponse)servletResponse;
                 //这里的责任链就是使用了自定义的 HttpServletRquest执行下去,非常的巧妙。
                filterChain.doFilter(customSessionHttpServletRequestWrapper,
                httpServletResponse);
                return;
            }
            //如果是普通请求就放行
            filterChain.doFilter(servletRequest,servletResponse);
        }

        /**
         * init 和 destroy 是管理 Filter的生命周期的,与逻辑无关,所以无需实现
         */
        @Override
        public void init(FilterConfig filterConfig) throws ServletException {}
        @Override
        public void destroy() {}
    }

然后自定义 HttpServletRequest :


public class CustomSessionHttpServletRequestWrapper extends HttpServletRequestWrapper{
    private HttpServletRequest httpServletRequest;
    private HttpServletResponse httpServletResponse;
    //自定义Session
    private CustomRedisHttpSession customRedisHttpSession;

    public CustomSessionHttpServletRequestWrapper(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse){
          super(httpServletRequest);
          this.httpServletRequest = httpServletRequest;
          this.httpServletResponse = httpServletResponse;
          Cookie[] cookies = httpServletRequest.getCookies();
          this.customRedisHttpSession = new 
          CustomRedisHttpSession(httpServletRequest,httpServletResponse,cookies);
    }

	//这个方法就是最重要的,通过它获取自定义的 HttpSession
    @Override
    public HttpSession getSession() {
        return this.customRedisHttpSession;
    }
}

核心:RedisHttpSession ,有些方法我没有实现,也就不在代码中贴了, 说一下,session在 Redis中我选择的 Hash 结构存储,以 sessionId 作为Key:

//首先我说过,HttpSession是不能注入属性的,所以就需要依赖 上面定义的那个 工具类,获取bean
//如果不加此注解,你的属性就会为空,获取不到
@DependsOn("applicationContextUtil")
@SpringBootConfiguration
public class CustomRedisHttpSession implements HttpSession {
	
    private HttpServletRequest httpServletRequest;
    private HttpServletResponse httpServletResponse;
    private Cookie[] cookies;
    //sessionId
    private String sessionId;
    public CustomRedisHttpSession(){}

    public CustomRedisHttpSession(HttpServletRequest httpServletRequest,
  		  HttpServletResponse httpServletResponse,Cookie[] cookies){
    
        this.httpServletRequest = httpServletRequest;
        this.httpServletResponse = httpServletResponse;
        this.cookies = cookies;
        this.sessionId = getCookieSessionId(cookies);
    }

    /**
     * 获取指定属性值
     * @param key 属性key
     * @return  key对应的value
     */
    @Override
    public Object getAttribute(String key) {
        if(sessionId != null){
            return sessionRedisTemplate.opsForHash().get(sessionId,key);
        }
        return null;
    }
    

    /**
     * 之前说过了,此类属性不能注入,只能通过手动获取
     */
    @SuppressWarnings("unchecked")
    private final RedisTemplate<String,Object> sessionRedisTemplate =
    =ApplicationContextUtil.getBean("sessionRedisTemplate",RedisTemplate.class);

    //sessionId 的前缀
    private static final String SESSIONID_PRIFIX="yangxiaoguang";

    /**
     * 设置属性值
     * @param key           key
     * @param value         value
     */
    @Override
    public void setAttribute(String key, Object value) {
        if (sessionId != null) {
            sessionRedisTemplate.opsForHash().put(sessionId, key, value);
        }else{
            //如果是第一次登录,那么生成 sessionId,将属性值存入redis,设置过期时间,并设置浏览器cookie
            this.sessionId = SESSIONID_PRIFIX + UUID.randomUUID();
            setCookieSessionId(sessionId);
            sessionRedisTemplate.opsForHash().put(sessionId, key, value);
            sessionRedisTemplate.expire(sessionId, sessionTimeout, TimeUnit.SECONDS);
        }
    }
   //session的过期时间,8小时
    private final int sessionTimeout=28800;

    //将sessionId存入浏览器
    private void setCookieSessionId(String sessionId){
        Cookie cookie = new Cookie(SESSIONID,sessionId);
        cookie.setPath("/");
        cookie.setMaxAge(sessionTimeout);
        this.httpServletResponse.addCookie(cookie);
    }

    /**
     * 移除指定的属性
     * @param key  属性 key
     */
    @Override
    public void removeAttribute(String key) {
        if(sessionId != null){
            sessionRedisTemplate.opsForHash().delete(sessionId,key);
        }
    }

    /**
     * session失效,删除当前用户的所有信息
     */
    @Override
    public void invalidate() {
        //删除在 redis 中的信息
        if(sessionId != null){
            sessionRedisTemplate.delete(sessionId);
            sessionId = null;
            if(cookies != null){
                for(Cookie cookie : cookies){
                    if(SESSIONID.equals(cookie.getName())){
                        //使cookie无效
                        cookie.setMaxAge(0);
                        cookie.setPath("/");
                        httpServletResponse.addCookie(cookie);
                        cookies = null;
                        break;
                    }
                }
            }
        }
    }

    //浏览器的cookie key
    private static final String SESSIONID="xyzlycimanage";


    //从浏览器获取SessionId
    private String getCookieSessionId(Cookie[] cookies){
        if(cookies != null){
            for(Cookie cookie : cookies){
                if(SESSIONID.equals(cookie.getName())){
                    return cookie.getValue();
                }
            }
        }
        return null;
    }
}

其实又明白了一件事,那就是基础基础很重要呀,JavaWeb的知识,能够与SpringMVC这种框架完美契合,解决我们使用框架解决不了的事情,更加证明了 所有天上飞的理念,都离不开地基。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值