精华内容
下载资源
问答
  • memcache缓存共享session实现

    千次阅读 2018-03-25 16:52:33
    一、session共享问题介绍 session主要用于服务端存储用户会话信息,cookie用于浏览器存储用户会话信息。单系统服务session都存在同一个web容器中,例如tomcat中,用户请求都只访问这个容器中的session信息,除非...

    一、session共享问题介绍

        session主要用于服务端存储用户会话信息,cookie用于浏览器存储用户会话信息。单系统服务session都存在同一个web容器中,例如tomcat中,用户请求都只访问这个容器中的session信息,除非容器挂了,否者不存在session取不到的情况。随着业务的扩展,应用用户的增加,当个容器存放系统应用消耗服务的cup和内存会不断增加,导致应用性能下降。此时考虑用nginx集群做应用的负载均衡请求分发,假设用ngnix集群三个服务,分别用A、B、C表示。按照未做session共享,仍然使用Servlet中HttpSession情景,假设此时访问的是A服务,那么session将存储在A服务中,此处如果A服务宕机,ngnix会将用户的请求分发到B或者C服务,但是B和C服务中没有存A存放的Session信息,那么用户访问的数据将会丢失。为了解决session数据丢失,需要将session共享,主流做法是将session存储在nosql数据库中,例如memcache、redis等。也有很多人通过spring session 实现共享,原理大致一样,下面主要实现了memcache缓存session共享。

    二、核心代码:

    1、ShareSession.java 

    package com.gccode.sso.session;
    
    import java.util.HashMap;
    import java.util.Map;
    
    import javax.servlet.http.HttpServletRequest;
    
    import org.springframework.web.context.WebApplicationContext;
    import org.springframework.web.context.support.WebApplicationContextUtils;
    
    import com.gccode.sso.cache.BaseCache;
    import com.gccode.sso.common.CookieUtils;
    
    /**
     * Title: 缓存共享session实现类
     *
     */
    public class ShareSession {
    	
    	/**
    	 * 存到缓存中的共享session容器
    	 */
    	private Map<String,Object> sessionWrapper;
    	
    	/**
    	 * session对象
    	 */
    	private static ShareSession session;
    	
    	/**
    	 * 浏览器请求回话id
    	 */
    	private String sessionId;
    	
    	/**
    	 * 缓存
    	 */
        private BaseCache cache;
        
        /**
         * 构造函数初始化参数
         * @param request
         */
    	public ShareSession(HttpServletRequest request){
    		WebApplicationContext wac = WebApplicationContextUtils.getRequiredWebApplicationContext(request.getServletContext());
    		this.cache = (BaseCache) wac.getBean("cache");
    		this.sessionId =  CookieUtils.getCookieId(request);
    		this.sessionWrapper = new HashMap<String, Object>();
    	}
    	
    	/**
    	 * 获取session对象
    	 * @param request
    	 * @return
    	 */
    	public static synchronized ShareSession get(HttpServletRequest request){
    		if(session == null){
    			session = new ShareSession(request);
    		}
    		return session;
    	}
    
    	/**
    	 * 设置session,带超时时间
    	 * @param key
    	 * @param value
    	 * @param outTime
    	 * @return sessionId
    	 */
    	public String setSession(String key,Object value,int outTime) {
    		sessionWrapper.put(key, value);
    		cache.put(sessionId,sessionWrapper,outTime);
    		return sessionId;
    	}
    	
    	/**
    	 * 设置session
    	 * @param key
    	 * @param value
    	 * @return
    	 */
    	public String setSession(String key,Object value) {
    		sessionWrapper.put(key, value);
    		cache.put(sessionId, sessionWrapper);
    		return sessionId;
    	}
    
    	/**
    	 * 设置session,不带超时时间
    	 * @return
    	 */
    	@SuppressWarnings("unchecked")
    	public  Map<String, Object> getSession() {
    		return (Map<String, Object>)cache.get(sessionId);
    	}
    
    	/**
    	 * 删除session中的值
    	 * @param key
    	 */
    	public void removeSession(String key) {
    		Map<String,Object> session = getSession();
    		session.remove(key);
    		cache.put(sessionId,session);
    	}
    
    }
    

    2、ShareSessionIntercept.java

    package com.gccode.sso.session;
    
    import java.util.HashMap;
    import java.util.Map;
    
    import javax.servlet.http.HttpServletRequest;
    
    import org.springframework.web.context.WebApplicationContext;
    import org.springframework.web.context.support.WebApplicationContextUtils;
    
    import com.gccode.sso.cache.BaseCache;
    import com.gccode.sso.common.CookieUtils;
    
    /**
     * Title: 缓存共享session实现类
     *
     */
    public class ShareSession {
    	
    	/**
    	 * 存到缓存中的共享session容器
    	 */
    	private Map<String,Object> sessionWrapper;
    	
    	/**
    	 * session对象
    	 */
    	private static ShareSession session;
    	
    	/**
    	 * 浏览器请求回话id
    	 */
    	private String sessionId;
    	
    	/**
    	 * 缓存
    	 */
        private BaseCache cache;
        
        /**
         * 构造函数初始计划参数
         * @param request
         */
    	public ShareSession(HttpServletRequest request){
    		WebApplicationContext wac = WebApplicationContextUtils.getRequiredWebApplicationContext(request.getServletContext());
    		this.cache = (BaseCache) wac.getBean("cache");
    		this.sessionId =  CookieUtils.getCookieId(request);
    		this.sessionWrapper = new HashMap<String, Object>();
    	}
    	
    	/**
    	 * 获取session对象
    	 * @param request
    	 * @return
    	 */
    	public static synchronized ShareSession get(HttpServletRequest request){
    		if(session == null){
    			session = new ShareSession(request);
    		}
    		return session;
    	}
    
    	/**
    	 * 设置session,带超时时间
    	 * @param key
    	 * @param value
    	 * @param outTime
    	 * @return sessionId
    	 */
    	public String setSession(String key,Object value,int outTime) {
    		sessionWrapper.put(key, value);
    		cache.put(sessionId,sessionWrapper,outTime);
    		return sessionId;
    	}
    	
    	/**
    	 * 设置session
    	 * @param key
    	 * @param value
    	 * @return
    	 */
    	public String setSession(String key,Object value) {
    		sessionWrapper.put(key, value);
    		cache.put(sessionId, sessionWrapper);
    		return sessionId;
    	}
    
    	/**
    	 * 设置session,不带超时时间
    	 * @return
    	 */
    	@SuppressWarnings("unchecked")
    	public  Map<String, Object> getSession() {
    		return (Map<String, Object>)cache.get(sessionId);
    	}
    
    	/**
    	 * 删除session中的值
    	 * @param key
    	 */
    	public void removeSession(String key) {
    		Map<String,Object> session = getSession();
    		session.remove(key);
    		cache.put(sessionId,session);
    	}
    
    }
    

    3、SSOUserFilter.java

    package com.gccode.sso.session;
    
    import java.io.IOException;
    import java.util.Map;
    
    import javax.servlet.Filter;
    import javax.servlet.FilterChain;
    import javax.servlet.FilterConfig;
    import javax.servlet.ServletException;
    import javax.servlet.ServletRequest;
    import javax.servlet.ServletResponse;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    
    import org.apache.commons.lang3.StringUtils;
    import org.springframework.cache.Cache;
    import org.springframework.web.context.WebApplicationContext;
    import org.springframework.web.context.support.WebApplicationContextUtils;
    
    import com.gccode.sso.common.Constant;
    import com.gccode.sso.common.CookieUtils;
    
    
    /**
     * Title: 用户请求过滤
     *
     */
    public class SSOUserFilter implements Filter{
    
    	@Override
    	public void init(FilterConfig filterConfig) throws ServletException {
    		// TODO Auto-generated method stub
    		
    	}
    
    	@Override
    	public void doFilter(ServletRequest req, ServletResponse res,
    			FilterChain chain) throws IOException, ServletException {
            HttpServletRequest request = (HttpServletRequest) req;
            HttpServletResponse response = (HttpServletResponse) res;
    		String sessionId = CookieUtils.getCookieId(request);
    		WebApplicationContext wac = WebApplicationContextUtils.getRequiredWebApplicationContext(req.getServletContext());
    		Cache cache = (Cache) wac.getBean("cache");
    		if (StringUtils.isEmpty(sessionId) || null == cache.get(sessionId)) {
    			request.getRequestDispatcher("/logout.html").forward(request,
    					response);
    		} else {
    			@SuppressWarnings("unchecked")
    			Map<String, Object> session = (Map<String, Object>) cache.get(
    					sessionId).get();
    			if (session.get(Constant.SESSION_USER_KEY) == null) {
    				request.getRequestDispatcher("/logout.html").forward(request,
    						response);
    			} else {
    				chain.doFilter(req, res);
    			}
    		}
    	}
    
    	@Override
    	public void destroy() {
    		// TODO Auto-generated method stub
    		
    	}
    
    }
    


    展开全文
  • spring session实现session共享
  • spring-session实现session共享
    HttpSession是通过Servlet容器创建和管理的,像Tomcat/Jetty都是保存在内存中的。但是我们把应用搭建成分布式的集群,然后利用LVS或Nginx做负载均衡,那么来自同一用户的Http请求将有可能被分发到多个不同的应用中,这样问题来了,如何保证不同的应用能够共享同一份session数据呢?最简单的想法,就是把session数据保存到内存以外的一个统一的地方,例如Memcached/Redis等数据库中。那问题又来了,如何替换掉Servlet容器创建和管理的HttpSession的实现呢?
    


    1)利用Servlet容器提供的插件功能,自定义HttpSession的创建和管理策略,并通过配置的方式替换掉默认的策略。这方面其实早就有开源项目了,例如memcached-session-manager(可以参考负载均衡+session共享(memcached-session-manager实现),以及tomcat-redis-session-manager。不过这种方式有个缺点,就是需要耦合Tomcat/Jetty等Servlet容器的代码。


    2)设计一个Filter,利用HttpServletRequestWrapper,实现自己的 getSession()方法,接管创建和管理Session数据的工作。spring-session就是通过这样的思路实现的。


    1、pom.xml依赖:

    <dependency>
        <groupId>org.springframework.session</groupId>
        <artifactId>spring-session-data-redis</artifactId>
        <version>1.2.0.RELEASE</version>
    </dependency>

    2、spring.xml配置:

    <!-- 将session放入redis -->
        <context:annotation-config/>
        <bean id="redisHttpSessionConfiguration"  class="org.springframework.session.data.redis.config.annotation.web.http.RedisHttpSessionConfiguration" >
            <property name="maxInactiveIntervalInSeconds" value="120" />
         </bean>
        <bean
            class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory">
            <!-- redis 配置 -->
            <property name="hostName" value="192.168.0.48" />
            <property name="port" value="6379" />
        </bean>


    3、web.xml:

    <context-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:spring-mvc.xml</param-value>
        </context-param>
        <!-- 分布式Session共享Filter -->
        <filter>
            <filter-name>springSessionRepositoryFilter</filter-name>
            <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
        </filter>
        <filter-mapping>
            <filter-name>springSessionRepositoryFilter</filter-name>
            <url-pattern>/*</url-pattern>
        </filter-mapping>


    这样就可以实现分布式Session了。

    注意:

    1.spring的这个配置文件一定要写在web.xml的<context-param>部分,写在其他地方不行。
    2.filter的名字必须是springSessionRepositoryFilter
    3.如果使用了shiro,web.xml中一定要把<context-param>放在最前面,然后写shiro的Filter配置,再写spring-session的Filter配置。后面就是其他的编码和servlet配置了。


    个人理解:由于spring-session 只是将原生的session以指定的方式进行存储,至于session的操作,它没有什么修改,无侵入,装饰者模式。理解的人自然理解,不理解的人就自行体会吧。

    展开全文
  • NULL 博文链接:https://justcoding.iteye.com/blog/747398
  • spring-session实现分布式session共享及自定义sessionid

    官方文档地址:http://projects.spring.io/spring-session/
    http://docs.spring.io/spring-session/docs/current/reference/html5/guides/httpsession.html

    Spring Session为企业级Java应用的session管理带来了革新,使得以下的功能更加容易实现:

    • 将session所保存的状态卸载到特定的外部session存储中,如Redis、mongo或gemfire中,它们能够以独立于应用服务器的方式提供高质量的集群。
    • 当用户使用WebSocket发送请求的时候,能够保持HttpSession处于活跃状态。
    • 在非Web请求的处理代码中,能够访问session数据,比如在JMS消息的处理代码中。
    • 支持每个浏览器上使用多个session,从而能够很容易地构建更加丰富的终端用户体验。
    • 控制session id如何在客户端和服务器之间进行交换,这样的话就能很容易地编写Restful API,因为它可以从HTTP 头信息(或者参数)中获取session id,而不必再依赖于cookie。
    话不多说,上集成代码吧(*^__^*) …

    首先加入maven引用:
    <dependency>
                    <groupId>org.springframework.session</groupId>
                    <artifactId>spring-session-data-redis</artifactId>
                    <version>1.2.2.RELEASE</version>
                    <type>pom</type>
    </dependency>
    <dependency>
                    <groupId>org.springframework</groupId>
                    <artifactId>spring-web</artifactId>
                    <version>4.2.5.RELEASE</version>
    </dependency>
    HttpSessionConfig:
    //maxInactiveIntervalInSeconds为session过期时间,这里注意session过期时间配置在web.xml里面是不起作用的
    @Configuration
    @EnableRedisHttpSession(maxInactiveIntervalInSeconds=999)
    public class HttpSessionConfig {
    
            //这里有个小坑,如果服务器用的是云服务器,不加这个会报错
    		@Bean
    		public static ConfigureRedisAction configureRedisAction() {
    			return ConfigureRedisAction.NO_OP;
    		}
    
            //这里是reids连接配置
            @Bean
            public JedisConnectionFactory connectionFactory() {
            	JedisConnectionFactory connection = new JedisConnectionFactory();
                connection.setPort(6379);
                connection.setHostName("127.0.0.1");
                connection.setPassword("ps123456");
                connection.setDatabase(8);
                return connection;
            }
    
            //session策略,这里配置的是Header方式(有提供Header,Cookie等方式),可自定义,后面会详细讲
            @Bean
            public HttpSessionStrategy httpSessionStrategy() {
                    return new HeaderHttpSessionStrategy();
            }
    
    }
    Initializer:
    public class Initializer extends AbstractHttpSessionApplicationInitializer {
                    
    }
    到这里就可以把工程跑起来了,上面提到使用的session策略是HeaderHttpSessionStrategy,restful可以使用这种方式。看 HeaderHttpSessionStrategy源码可以知道,HeaderHttpSessionStrategy实现了HttpSessionStrategy接口。哈~也就是说,如果要自定义策略的话,也实现HttpSessionStrategy就可以了。
    getRequestedSessionId()方法,获取客户端传过来的sessionid,如果没有传spring会通过UUID的方式分配一个(这里springsession没有支持自定义sessionid微笑),
    然后onNewSession()和onInvalidateSession()就不用说了,创建和销毁嘛~
    public class HeaderHttpSessionStrategy implements HttpSessionStrategy {
    	private String headerName = "x-auth-token";
    
    	public String getRequestedSessionId(HttpServletRequest request) {
    		return request.getHeader(this.headerName);
    	}
    
    	public void onNewSession(Session session, HttpServletRequest request,
    			HttpServletResponse response) {
    		response.setHeader(this.headerName, session.getId());
    	}
    
    	public void onInvalidateSession(HttpServletRequest request,
    			HttpServletResponse response) {
    		response.setHeader(this.headerName, "");
    	}
    
    	/**
    	 * The name of the header to obtain the session id from. Default is "x-auth-token".
    	 *
    	 * @param headerName the name of the header to obtain the session id from.
    	 */
    	public void setHeaderName(String headerName) {
    		Assert.notNull(headerName, "headerName cannot be null");
    		this.headerName = headerName;
    	}
    }
    好啦,下面就来说 自定义sessionid吧~
    一些特别的鉴权场景,需要由应用层自己控制生成sessionid,那么先看看源码~
    public final class MapSession implements ExpiringSession, Serializable {
    	/**
    	 * Default {@link #setMaxInactiveIntervalInSeconds(int)} (30 minutes).
    	 */
    	public static final int DEFAULT_MAX_INACTIVE_INTERVAL_SECONDS = 1800;
    
    	private String id;
    	private Map<String, Object> sessionAttrs = new HashMap<String, Object>();
    	private long creationTime = System.currentTimeMillis();
    	private long lastAccessedTime = this.creationTime;
    
    	/**
    	 * Defaults to 30 minutes.
    	 */
    	private int maxInactiveInterval = DEFAULT_MAX_INACTIVE_INTERVAL_SECONDS;
    
    	/**
    	 * Creates a new instance with a secure randomly generated identifier.
    	 */
    	public MapSession() {
    		this(UUID.randomUUID().toString());
    	}
    
    	/**
    	 * Creates a new instance with the specified id. This is preferred to the default
    	 * constructor when the id is known to prevent unnecessary consumption on entropy
    	 * which can be slow.
    	 *
    	 * @param id the identifier to use
    	 */
    	public MapSession(String id) {
    		this.id = id;
    	}
    看org.springframework.session.MapSession发现,新建session的时候,构造 方法默认就是使用UUID做id,并调用MapSession(String id)构造方法给id赋值。
    在看 org.springframework.session.SessionRepository接口:
    public interface SessionRepository<S extends Session> {
    
    	S createSession();
    
    	void save(S session);
    	
    	S getSession(String id);
    
    	void delete(String id);
    }
    发现并没有S createSession(String id);的方法,后面通过改源码的方式实现了自定义id的功能。加了一个 S createSession(String id);的方法,并通过getRequestedSessionId();把id拿过来传参。这里就不详讲了,因为这种方式特别不友好。
    Another way:
    可以自定义一个session策略,在用户id和sessionid之间加一个映射保存在redis,在onNewSession()创建映射关系,在getRequestedSessionId()时找用户id对应的sessionid返回。
    下面是MyHttpSessionStrategy代码,HttpSessionConfig里面的httpSessionStrategy()改为自己写的就可以了。
    public class MyHttpSessionStrategy implements HttpSessionStrategy {
    
        private final Logger logger = LoggerFactory.getLogger(WlwHttpSessionStrategy.class);
    
    	//这用Qualifier注解,如果你的工程还集成了spring-data-redis,需要指定一下用哪一个
        @Qualifier("sessionRedisTemplate")
        @Autowired
        private RedisTemplate redisTemplate;
    //过期时间,与session过期时间保持一致
        private Long maxInactiveIntervalInSeconds = 999L;
        private String xxxRedisName = "spring:session:xxx:";
    	//当客户端没有传xxx参数的时候,避免创建多个无用的session占用redis空间
        private String defaultSessionId = "default-sessionid";
    
        /**
         * 客户端传过来的是xxx,需要通过xxx查找映射关系,拿到sessionid返回
         */
        public String getRequestedSessionId(HttpServletRequest request) {
            String xxx = request.getParameter("xxx");
            ValueOperations<String, String> vops = redisTemplate.opsForValue();
            if (xxx != null && !xxx.equals("")) {
                String sessionid = vops.get(xxxRedisName + xxx);
                if(sessionid!=null){
                    redisTemplate.expire(xxxRedisName + xxx, maxInactiveIntervalInSeconds, TimeUnit.SECONDS);
                }
                return sessionid;
            } else {
                return vops.get(xxxRedisName+defaultSessionId);
            }
        }
    
        /**
         * 创建session时,保存xxx和sessionid的映射关系
         */
        public void onNewSession(Session session, HttpServletRequest request,
                                 HttpServletResponse response) {
            String xxx = request.getParameter("xxx");
            String sessionid = session.getId();
            ValueOperations<String, String> vops = redisTemplate.opsForValue();
            if (xxx != null && !xxx.equals("")) {
                //保存xxx和sessionid映射关系
                vops.set(xxxRedisName + xxx, sessionid);
                redisTemplate.expire(xxxRedisName + xxx, maxInactiveIntervalInSeconds, TimeUnit.SECONDS);
            }else{
                //没有传xxx时,保存为默认
                vops.set(xxxRedisName+defaultSessionId, sessionid);
                redisTemplate.expire(xxxRedisName+defaultSessionId, maxInactiveIntervalInSeconds, TimeUnit.SECONDS);
            }
        }
    
        public void onInvalidateSession(HttpServletRequest request,
                                        HttpServletResponse response) {
            String xxx = request.getParameter("xxx");
            redisTemplate.expire(xxxRedisName + xxx, 0, TimeUnit.SECONDS);
        }
    
    }
    好了,完工了。。。

     心如止水,把自己学习的一点一滴全都放到了csdn上面, 
     我知道我是写给自己看的, 
     我欣赏安安静静、心无旁骛的自己, 
     尽管有些人戏谑般的说是自恋, 
     我相信, I don't care ...  尴尬


    展开全文
  • 自己亲自试验,亲测有效。实现父子网站共享session。可实现单点登录的效果,具体还是发挥各位大佬的才能了,此文只是实现共享session的亲测案例
  • 主要介绍了Spring boot集成spring session实现session共享的方法,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
  • 用PHP实现多服务器共享SESSION数据的方法
  • Session共享 tomcat7+redis的session共享实现,无需重新打包,直接放入tomcat lib中就可以使用,记得修改tomcat的content.xml
  • Session共享 tomcat8+redis的session共享实现,支持tomcat8
  • 实现 session 共享

    2013-06-13 17:15:03
    同一个主域名,不同服务器之间共享session信息。
  • 主要介绍了SpringBoot Session共享实现图解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
  • 本篇介绍Spring-Session的整个实现的原理。以及对核心的源码进行简单的介绍! 实现原理介绍 实现原理这里简单说明描述: 就是当Web服务器接收到http请求后,当请求进入对应的Filter进行过滤,将原本...

    做一个积极的人

    编码、改bug、提升自己

    我有一个乐园,面向编程,春暖花开!

    =================================================

    对人工智能感兴趣的伙伴,分享一个我朋友的人工智能教程。零基础!通俗易懂!风趣幽默!大家可以看看是否对自己有帮助,点击这里查看教程

    =================================================

    知其然,还要知其所以然 !

    本篇介绍Spring-Session的整个实现的原理。以及对核心的源码进行简单的介绍!

    实现原理介绍

    实现原理这里简单说明描述:

    就是当Web服务器接收到http请求后,当请求进入对应的Filter进行过滤,将原本需要由web服务器创建会话的过程转交给Spring-Session进行创建,本来创建的会话保存在Web服务器内存中,通过Spring-Session创建的会话信息可以保存第三方的服务中,如:redis,mysql等。Web服务器之间通过连接第三方服务来共享数据,实现Session共享!

    实现原理结构草图

    整个实现流程和源码详细介绍

    本次源码介绍基于上一篇内容,并且在保存Session的时候只会分析使用JedisConnectionFactory实现的RedisConnectionFactory !

    1.SessionRepositoryFilter和JedisConnectionFactory注册过程

    流程:

    SessionRepositoryFilter和JedisConnectionFactory注册过程

    说明:

    1.、启动WEB项目的时候,会读取web.xml,读取顺序content-param --> listener --> filter --> servlet
    
    2.、ContextLoaderListener监听器的作用就是启动Web容器时,自动装配ApplicationContext的配置信息
    
    3、初始化根web应用程序上下文。
    
    4、SpringHttpSessionConfiguration注册 springSessionRepositoryFilter :bean,RedisHttpSessionConfiguration 注册 sessionRedisTemplate : bean  和 sessionRepository : bean
    
    5、配置文件配置JedisConnectionFactory implements RedisConnectionFactory ,创建 jedisConnectionFactory bean
    
    

    代码分析如下:

    1. web.xml ,加载了xml配置文件,并初始化web应用上下文
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath*:spring/*xml</param-value>
    </context-param>
    
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
      
    

    2.application-session.xml中,配置 RedisHttpSessionConfiguration的bean和JedisConnectionFactory的bean,web应用初始化加载bean!

    <!--创建一个Spring Bean的名称springSessionRepositoryFilter实现过滤器。
        筛选器负责将HttpSession实现替换为Spring会话支持。在这个实例中,Spring会话得到了Redis的支持。-->
        <bean class="org.springframework.session.data.redis.config.annotation.web.http.RedisHttpSessionConfiguration"/>
    
        <!--创建了一个RedisConnectionFactory,它将Spring会话连接到Redis服务器。我们配置连接到默认端口(6379)上的本地主机!-->
        <!--集群Redis-->
        <bean id="jedisConnectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory">
            <!--Redis-CLuster-->
            <constructor-arg index="0" ref="redisClusterConfig"/>
    
            <!--配置Redis连接池 ,可以不配置,使用默认就行!-->
            <constructor-arg index="1" ref="jedisPoolConfig"/>
        </bean>
        
    
    1. ContextLoaderListener
    /**
    * 初始化根web应用程序上下文。
    */
    @Override
    public void contextInitialized(ServletContextEvent event) {
    initWebApplicationContext(event.getServletContext());
    }
    	
    

    4.RedisHttpSessionConfiguration类图

    RedisHttpSessionConfiguration类图

    RedisHttpSessionConfiguration注释
    RedisHttpSessionConfiguration继承了SpringHttpSessionConfiguration

    public class RedisHttpSessionConfiguration extends SpringHttpSessionConfiguration
    		implements EmbeddedValueResolverAware, ImportAware {
    		
    

    4.1 SpringHttpSessionConfiguration 创建一个名称为springSessionRepositoryFilter的bean

    @Bean
    public <S extends ExpiringSession> SessionRepositoryFilter<? extends ExpiringSession> springSessionRepositoryFilter(
        SessionRepository<S> sessionRepository) {
        SessionRepositoryFilter<S> sessionRepositoryFilter = new SessionRepositoryFilter<S>(
            sessionRepository);
        sessionRepositoryFilter.setServletContext(this.servletContext);
        if (this.httpSessionStrategy instanceof MultiHttpSessionStrategy) {
            sessionRepositoryFilter.setHttpSessionStrategy(
                (MultiHttpSessionStrategy) this.httpSessionStrategy);
        }
        else {
            sessionRepositoryFilter.setHttpSessionStrategy(this.httpSessionStrategy);
        }
        return sessionRepositoryFilter;
    }
    
    
    

    4.2 创建RedisHttpSessionConfiguration#RedisTemplate bean的名称为sessionRedisTemplate

    @Bean
    public RedisTemplate<Object, Object> sessionRedisTemplate(
        RedisConnectionFactory connectionFactory) {
        //实例化 RedisTemplate 
        RedisTemplate<Object, Object> template = new RedisTemplate<Object, Object>();
        //设置key序列化 StringRedisSerializer
        template.setKeySerializer(new StringRedisSerializer());
        //设置Hash key  StringRedisSerializer
        template.setHashKeySerializer(new StringRedisSerializer());
        if (this.defaultRedisSerializer != null) {
            template.setDefaultSerializer(this.defaultRedisSerializer);
        }
        //设置 connectionFactory。第五步创建的(实际connectionFactory加载过程和讲解过程顺序不一样)
        template.setConnectionFactory(connectionFactory);
        return template;
    }
    
    

    4.3 创建RedisHttpSessionConfiguration#RedisOperationsSessionRepository bean的名称为sessionRepository

    @Bean
    public RedisOperationsSessionRepository sessionRepository(
        //使用sessionRedisTemplate bean
        @Qualifier("sessionRedisTemplate") RedisOperations<Object, Object> sessionRedisTemplate,
        ApplicationEventPublisher applicationEventPublisher) {
    
        //實例化RedisOperationsSessionRepository
        RedisOperationsSessionRepository sessionRepository = new RedisOperationsSessionRepository(
            sessionRedisTemplate);
        //設置applicationEventPublisher
        sessionRepository.setApplicationEventPublisher(applicationEventPublisher);
        //設置最大的失效時間 maxInactiveIntervalInSeconds = 1800
        sessionRepository
            .setDefaultMaxInactiveInterval(this.maxInactiveIntervalInSeconds);
        if (this.defaultRedisSerializer != null) {
            sessionRepository.setDefaultSerializer(this.defaultRedisSerializer);
        }
    
        String redisNamespace = getRedisNamespace();
        if (StringUtils.hasText(redisNamespace)) {
            sessionRepository.setRedisKeyNamespace(redisNamespace);
        }
    
        sessionRepository.setRedisFlushMode(this.redisFlushMode);
        return sessionRepository;
    }
    
    
    1. 创建 RedisConnectionFactory bean为 jedisConnectionFactory

    JedisConnectionFactory类图

    <bean id="jedisConnectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory">
    
    

    2.SessionRepositoryFilter添加到FilterChain

    流程:

    SessionRepositoryFilter添加到FIlterChain

    说明:

    1 和 2、在Servlet3.0规范中,Servlet容器启动时会自动扫描javax.servlet.ServletContainerInitializer的实现类,在实现类中我们可以定制需要加载的类。 通过注解@HandlesTypes(WebApplicationInitializer.class),让Servlet容器在启动该类时,会自动寻找所有的WebApplicationInitializer实现类。
    
    2.1、insertSessionRepositoryFilter 方法通过filterName获取 SessionRepositoryFilter ,并创建了 new DelegatingFilterProxy(filterName);
    
    3 和 4、然后将filter添加到FilterChain中
    
    
    

    1.ServletContainerInitializer的实现类加载和通过注解@HandlesTypes(WebApplicationInitializer.class)实现类的加载

    //加载实现类
    @HandlesTypes(WebApplicationInitializer.class)
    //SpringServletContainerInitializer实现ServletContainerInitializer
    public class SpringServletContainerInitializer implements ServletContainerInitializer {
    
    //------------
    
    

    2.AbstractHttpSessionApplicationInitializer实现WebApplicationInitializer进行加载

    @Order(100)
    public abstract class AbstractHttpSessionApplicationInitializer
        implements WebApplicationInitializer {
    
    

    2.1 onStartup

    public void onStartup(ServletContext servletContext) throws ServletException {
        beforeSessionRepositoryFilter(servletContext);
        if (this.configurationClasses != null) {
            AnnotationConfigWebApplicationContext rootAppContext = new AnnotationConfigWebApplicationContext();
            rootAppContext.register(this.configurationClasses);
            servletContext.addListener(new ContextLoaderListener(rootAppContext));
        }
        //添加Filter
        insertSessionRepositoryFilter(servletContext);
        afterSessionRepositoryFilter(servletContext);
    }
    
    

    2.1.1.insertSessionRepositoryFilter

    /**
    	 * 注册springSessionRepositoryFilter
    	 * @param servletContext the {@link ServletContext}
    	 */
    private void insertSessionRepositoryFilter(ServletContext servletContext) {
        // DEFAULT_FILTER_NAME = "springSessionRepositoryFilter"
        String filterName = DEFAULT_FILTER_NAME;
        //通过filterName创建 DelegatingFilterProxy
        DelegatingFilterProxy springSessionRepositoryFilter = new DelegatingFilterProxy(
            filterName);
        String contextAttribute = getWebApplicationContextAttribute();
        if (contextAttribute != null) {
            springSessionRepositoryFilter.setContextAttribute(contextAttribute);
        }
        //根据filterName和上下文添加Filter到FilterChain
        registerFilter(servletContext, true, filterName, springSessionRepositoryFilter);
    }
    
    
    1. registerFilter
    private void registerFilter(ServletContext servletContext,
                                boolean insertBeforeOtherFilters, String filterName, Filter filter) {
        Dynamic registration = servletContext.addFilter(filterName, filter);
        if (registration == null) {
            throw new IllegalStateException(
                "Duplicate Filter registration for '" + filterName
                + "'. Check to ensure the Filter is only configured once.");
        }
        //是否支持异步,默认 true
        registration.setAsyncSupported(isAsyncSessionSupported());
        //得到DispatcherType springSessionRepositoryFilter
        EnumSet<DispatcherType> dispatcherTypes = getSessionDispatcherTypes();
        //添加一个带有给定url模式的筛选器映射和由这个FilterRegistration表示的过滤器的分派器类型。 过滤器映射按照添加它们的顺序进行匹配。
        registration.addMappingForUrlPatterns(dispatcherTypes, !insertBeforeOtherFilters,
                                              "/*");
    }
    
    1. addFilter将Filter添加到ServletContext中
    public FilterRegistration.Dynamic addFilter(
        String filterName, Filter filter);
    
    

    3.SessionRepositoryFilter拦截过程

    流程:

    SessionRepositoryFilter拦截过程

    说明:

    1、请求被DelegatingFilterProxy : 拦截到,然后执行doFilter方法,在doFilter中找到执行的代理类。
    2、OncePerRequestFilter : 代理Filter执行doFilter方法,然后调用抽象方法doFilterInternal
    3、SessionRepositoryFilter 继承了OncePerRequestFilter,实现了doFilterInternal,这个方法一个封装一个wrappedRequest,通过执行commitSession保存session信息到redis
    
    

    1请求进来,被DelegatingFilterProxy 拦截到,在web.xml中进行了配置
    1.1 执行doFilter

    如果没有指定目标bean名称,请使用筛选器名称。
    @Override
    	public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)
        throws ServletException, IOException {
    
        // 如果需要,延迟初始化委托。 necessary.
        Filter delegateToUse = this.delegate;
        if (delegateToUse == null) {
            synchronized (this.delegateMonitor) {
                if (this.delegate == null) {
                    WebApplicationContext wac = findWebApplicationContext();
                    if (wac == null) {
                        throw new IllegalStateException("No WebApplicationContext found: " +
                                                        "no ContextLoaderListener or DispatcherServlet registered?");
                    }
                    this.delegate = initDelegate(wac);
                }
                delegateToUse = this.delegate;
            }
        }
    
        // 让委托执行实际的doFilter操作
        invokeDelegate(delegateToUse, request, response, filterChain);
    }
    

    1.2 initDelegate

    protected Filter initDelegate(WebApplicationContext wac) throws ServletException {
        //可以获取到SessionRepositoryFilter [备注1]
        Filter delegate = wac.getBean(getTargetBeanName(), Filter.class);
        if (isTargetFilterLifecycle()) {
            delegate.init(getFilterConfig());
        }
        return delegate;
    }
    
    //[备注1] 因为 :SessionRepositoryFilter是一个优先级最高的javax.servlet.Filter
    /*
    @Order(SessionRepositoryFilter.DEFAULT_ORDER)
    public class SessionRepositoryFilter<S extends ExpiringSession>
    		extends OncePerRequestFilter {
    
    */
    
    1. delegate.doFilter();
    protected void invokeDelegate(
        Filter delegate, ServletRequest request, ServletResponse response, FilterChain filterChain)
        throws ServletException, IOException {
        //代理去执行doFilter,代理为SessionRepositoryFilter
        delegate.doFilter(request, response, filterChain);
    }
    
    

    2.1 OncePerRequestFilter#doFilter

    public final void doFilter(ServletRequest request, ServletResponse response,
                               FilterChain filterChain) throws ServletException, IOException {
    
        if (!(request instanceof HttpServletRequest)
            || !(response instanceof HttpServletResponse)) {
            throw new ServletException(
                "OncePerRequestFilter just supports HTTP requests");
        }
        HttpServletRequest httpRequest = (HttpServletRequest) request;
        HttpServletResponse httpResponse = (HttpServletResponse) response;
        boolean hasAlreadyFilteredAttribute = request
            .getAttribute(this.alreadyFilteredAttributeName) != null;
    
        if (hasAlreadyFilteredAttribute) {
    
            //在不调用此过滤器的情况下进行…
            filterChain.doFilter(request, response);
        }
        else {
            // 调用这个过滤器…
            request.setAttribute(this.alreadyFilteredAttributeName, Boolean.TRUE);
            try {
                //doFilterInternal是个抽象方法
                doFilterInternal(httpRequest, httpResponse, filterChain);
            }
            finally {
                // 删除此请求的“已过滤”请求属性。
                request.removeAttribute(this.alreadyFilteredAttributeName);
            }
        }
    }
    
    
    1. 执行SessionRepositoryFilter#doFilterInternal
    @Override
    protected void doFilterInternal(HttpServletRequest request,
                                    HttpServletResponse response, FilterChain filterChain)
        throws ServletException, IOException {
        request.setAttribute(SESSION_REPOSITORY_ATTR, this.sessionRepository);
        //使用HttpServletRequest 、HttpServletResponse和servletContext创建一个SessionRepositoryRequestWrapper
    
        SessionRepositoryRequestWrapper wrappedRequest = new SessionRepositoryRequestWrapper(
            request, response, this.servletContext);
        SessionRepositoryResponseWrapper wrappedResponse = new SessionRepositoryResponseWrapper(
            wrappedRequest, response);
    
        //使用CookieHttpSessionStrategy重新包装了 HttpServletRequest
        HttpServletRequest strategyRequest = this.httpSessionStrategy
            .wrapRequest(wrappedRequest, wrappedResponse);
        HttpServletResponse strategyResponse = this.httpSessionStrategy
            .wrapResponse(wrappedRequest, wrappedResponse);
    
        try {
            //执行其他过滤器
            filterChain.doFilter(strategyRequest, strategyResponse);
        }
        finally {
            //保存session信息
            wrappedRequest.commitSession();
        }
    }
    
    

    4 .wrappedRequest.commitSession() 看下第四大点分析

    4.SessionRepository保存session数据

    流程:
    SessionRepository保存session数据

    说明:

    1、提交session保存
    2、获取当前session,这一步比较重要,获取了一个HttpSessionWrapper,这个HttpSessionWrapper替换了HTTPSession
    3、wrappedSession获取当前的Session
    4、使用 RedisTemplate 保存Session内容,并通过调用RedisConnection 使用它的实现类JedisClusterConnection获取redis连接
    
    

    1.commitSession

    /**
    *使用HttpSessionStrategy将会话id写入响应。 *保存会话。
    */
    private void commitSession() {
        HttpSessionWrapper wrappedSession = getCurrentSession();
        if (wrappedSession == null) {
            if (isInvalidateClientSession()) {
                SessionRepositoryFilter.this.httpSessionStrategy
                    .onInvalidateSession(this, this.response);
            }
        }
        else {
            S session = wrappedSession.getSession();
            SessionRepositoryFilter.this.sessionRepository.save(session);
            if (!isRequestedSessionIdValid()
                || !session.getId().equals(getRequestedSessionId())) {
                SessionRepositoryFilter.this.httpSessionStrategy.onNewSession(session,
                                                                              this, this.response);
            }
        }
    }
    

    2.getCurrentSession

    会话存储库请求属性名。
    public static final String SESSION_REPOSITORY_ATTR = SessionRepository.class
        .getName();
    
    private static final String CURRENT_SESSION_ATTR = SESSION_REPOSITORY_ATTR
    			+ ".CURRENT_SESSION";
    
    private HttpSessionWrapper getCurrentSession() {
        return (HttpSessionWrapper)
            //获取session
            getAttribute(CURRENT_SESSION_ATTR);
    }
    
    /**
         * 此方法的默认行为是在包装请求对象上调用getAttribute(字符串名称)。
         */
    public Object getAttribute(String name) {
        //这里的request就是上面封装的
        return this.request.getAttribute(name);
    }
    

    3 .wrappedSession.getSession

    //返回 RedisSession
    S session = wrappedSession.getSession();
    //-------------------------
    public S getSession() {
        return this.session;
    }
    class ExpiringSessionHttpSession<S extends ExpiringSession> implements HttpSession {
        private S session;
    
    
     final class RedisSession implements ExpiringSession {
    
    

    4.save,实际是调用 RedisOperationsSessionRepository的 RedisOperations 操作

    SessionRepositoryFilter.this.sessionRepository.save(session);
    
    //this.sessionRepository =  SessionRepository<S> sessionRepository;
    
    //--------------------------------
    //这个RedisOperationsSessionRepository是之前就创建好的
    public class RedisOperationsSessionRepository implements
        FindByIndexNameSessionRepository<RedisOperationsSessionRepository.RedisSession>,
    MessageListener {
    
    
        public interface FindByIndexNameSessionRepository<S extends Session>
            extends SessionRepository<S> {
    
            //---------------------------
    
    
            public void save(RedisSession session) {
                //4.1saveDelta
                session.saveDelta();
                if (session.isNew()) {
                    //4.2调用
                    String sessionCreatedKey = getSessionCreatedChannel(session.getId());
                    //4.3convertAndSend
                    //RedisOperations = this.sessionRedisOperations
                    this.sessionRedisOperations.convertAndSend(sessionCreatedKey, session.delta);
                    session.setNew(false);
                }
            }
    	
    	
    

    其中RedisOperationsSessionRepository 里面介绍保存的详细过程,具体请看文档说明:

    Class RedisOperationsSessionRepository

    因为 RedisTemplate implements RedisOperations,实际进行操作的是RedisTemplate,RedisTemplate通过RedisConnection进行数据add和remove等

    public class RedisTemplate<K, V>
    extends RedisAccessor
    implements RedisOperations<K, V>, BeanClassLoaderAware
    

    总结

    本系列到这里也就结束了,本次话的整个流程图,会上传到github上,使用Jude打开就可以看!

    如果有什么地方写的不对或者有想和我一起探讨一下的,欢迎加我的QQ或者QQ群!

    记录一个小点:

    Spring Session + Redis实现分布式Session共享 有个非常大的缺陷, 无法实现跨域名共享session , 只能在单台服务器上共享session , 因为是依赖cookie做的 , cookie 无法跨域 pring Session一般是用于多台服务器负载均衡时共享Session的,都是同一个域名,不会跨域。你想要的跨域的登录,可能需要SSO单点登录。

    参考博文

    【Spring】Spring Session的简单搭建与源码阅读

    利用spring session解决共享Session问题

    Spring Session解决分布式Session问题的实现原理

    spring-session简介、使用及实现原理


    本系列教程

    【入门】分布式Session一致性入门简介

    【第一篇】Spring-Session实现Session共享入门教程

    【第二篇】Spring-Session实现Session共享Redis集群方式配置教程

    【第三篇】Spring-Session实现Session共享实现原理以及源码解析

    本系列的源码下载地址:learn-spring-session-core


    谢谢你的阅读,如果您觉得这篇博文对你有帮助,请点赞或者喜欢,让更多的人看到!祝你每天开心愉快!



    不管做什么,只要坚持下去就会看到不一样!在路上,不卑不亢!

    愿你我在人生的路上能都变成最好的自己,能够成为一个独挡一面的人

    © 每天都在变得更好的阿飞云

    展开全文
  • 主要介绍了SpringBoot2.x 整合Spring-Session实现Session共享功能,本文给大家介绍的非常详细,具有一定的参考借鉴价值,需要的朋友可以参考下
  • 主要介绍了PHP使用Redis实现Session共享实现示例,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
  • 作为一个刚入行的小白,在公司接手了一个springboot的项目,项目中我们完全使用session,管理用户的登陆信息及权限,项目完成时,我们需要集成某公司的nigix服务器,甲方要求,并且由甲方提供,一开始,我们接入之后...
  • 主要介绍了Zend Framework实现多服务器共享SESSION数据的方法,详细分析了SESSION数据共享的原理与实现技巧,需要的朋友可以参考下
  • 主要介绍了实现Asp与Asp.Net共享Session的方法,需要的朋友可以参考下
  • java实现多次HttpURLConnection共享session,发送两次请求共享同一个session,这样做爬虫的时候就可以爬网站登录后能看到的内容了
  • shiro框架---多项目登录访问共享session实现

    万次阅读 多人点赞 2018-08-04 17:55:57
    shiro框架—多项目登录访问共享session实现 公司需要这样的需求:   有两个项目master 主项目、suiteone 项目,两个项目各自由shiro 安全框架管理,当不能登录时,都无法访问,但当登录了其中一个,再访问另...
  • 主要介绍了thinkPHP多域名情况下使用memcache方式共享session数据的实现方法,较为详细的分析了session的原理及多服务器共享session的相关技巧,需要的朋友可以参考下
  • Spring Session + redis实现session共享
  • nginx实现session共享

    千次阅读 多人点赞 2021-01-14 16:17:43
    nginx实现session共享
  • 使用redis实现共享session

    千次阅读 2018-06-05 22:58:18
    为什么要实现共享session 分布式应用需要用户在某一点登录,记录用户登录状态在其他应用实例中也能获取到已经登录的状态 实现流程 用户登录 1.用户登录时,生成一个全局唯一uuid, 2.将uuid:用户信息以key:...
  • 一、在pom.xml添加springSession &lt;!--springSession--&gt; &lt;dependency&gt; &lt;groupId&gt;org.springframework.session&lt;/groupId&gt; &lt;artifactId&gt;...
  • 此文档只是用于实现spring redis session共享的文档,并没有其他代码,都是配置,配置加入到spring配置中即可实现session共享,有不懂的可以私信我,有时间会为您解答
  • SpringSession+Redis实现Session共享案例,可参考运行,
  • cas实现共享session

    万次阅读 热门讨论 2015-02-15 11:51:42
    随着用户量的增加,web应用需要部署多个实例,要实现不同应用、多实例的共享session,需要先了解cas的logout机制。简单的说,web应用在接入cas的时候需要继承cas-client-core,这个模块完成的事情如下: 拦截到web...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 186,417
精华内容 74,566
关键字:

共享session的实现