基于redis实现tomcat8及以上版本的tomcat集群的session持久化实现(tomcat-redis-session-manager二次开发)
前言:
本项目是基于jcoleman的tomcat-redis-session-manager二次开发版本
1、修改了小部分实现逻辑
2、去除对juni.jar包的依赖
3、去除无效代码和老版本tomcat操作API
4、支持tomcat 8 及以后的版本
感谢jcoleman的项目: https://github.com/jcoleman/tomcat-redis-session-manager,由于该项目已经停止更新,最新版本只支持tomcat7,对于tomcat7以后的版本都不支持。
源码提供:
github项目地址:https://github.com/eguid/tomcat-redis-sessioon-manager
下载目录:
tomcat-redis-session-manager-by-eguid.jar下载地址:http://download.csdn.net/detail/eguid_1/9638171
tomcat-redis-session-manager-by-eguid.jar+jedis-2.9.0.jar+commons-pool2-2.2.jar集合包下载
注意:本项目依赖5个jar包,tomcat-api.jar;catalina.jar;servlet-api.jar;jedis-2.9.0.jar;commons-pool-2.4.2.jar,其中tomcat-api.jar、catalina.jar和servlet-api.jar这三个包是tomcat原生jar包,本项目打包时不需要打入这三个包
一、主要代码实现
1、session管理器实现
该类用于实现session的基本增删改查操作,加入了redis实现持久化
package cn.eguid.redisSessionManager; import org.apache.catalina.Lifecycle; import org.apache.catalina.LifecycleException; import org.apache.catalina.LifecycleListener; import org.apache.catalina.util.LifecycleSupport; import org.apache.catalina.LifecycleState; import org.apache.catalina.Valve; import org.apache.catalina.Session; import org.apache.catalina.session.ManagerBase; import redis.clients.jedis.JedisPool; import redis.clients.jedis.JedisPoolConfig; import redis.clients.jedis.Jedis; import redis.clients.jedis.Protocol; import java.io.IOException; import java.util.Arrays; import java.util.Set; /** * * @author eguid * */ public class RedisSessionManager extends ManagerBase implements Lifecycle { protected byte[] NULL_SESSION = "null".getBytes(); protected String host = "localhost"; protected int port = 6379; protected int database = 0; protected String password = null; protected int timeout = Protocol.DEFAULT_TIMEOUT; protected JedisPool connectionPool = null; protected RedisSessionHandlerValve handlerValve; protected ThreadLocal<RedisSession> currentSession = new ThreadLocal<RedisSession>(); protected ThreadLocal<String> currentSessionId = new ThreadLocal<String>(); protected ThreadLocal<Boolean> currentSessionIsPersisted = new ThreadLocal<Boolean>(); protected Serializer serializer; protected static String name = "RedisSessionManager"; // 用于序列化的类 protected String serializationStrategyClass = "cn.eguid.redisSessionManager.JavaSerializer"; protected LifecycleSupport lifecycle = new LifecycleSupport(this); public String getHost() { return host; } public void setHost(String host) { this.host = host; } public int getPort() { return port; } public void setPort(int port) { this.port = port; } public int getDatabase() { return database; } public void setDatabase(int database) { this.database = database; } public int getTimeout() { return timeout; } public void setTimeout(int timeout) { this.timeout = timeout; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public void setSerializationStrategyClass(String strategy) { this.serializationStrategyClass = strategy; } @Override public int getRejectedSessions() { // Essentially do nothing. return 0; } public void setRejectedSessions(int i) { // Do nothing. } protected Jedis getConnection() { System.out.println("获取jedis连接"); Jedis jedis = connectionPool.getResource(); if (getDatabase() != 0) { jedis.select(getDatabase()); } return jedis; } protected void returnConnection(Jedis jedis) { System.out.println("注销jedis连接"); jedis.close(); } @Override public void load() throws ClassNotFoundException, IOException { } @Override public void unload() throws IOException { } /** * Add a lifecycle event listener to this component. * * @param listener * The listener to add */ @Override public void addLifecycleListener(LifecycleListener listener) { lifecycle.addLifecycleListener(listener); } /** * Get the lifecycle listeners associated with this lifecycle. If this * Lifecycle has no listeners registered, a zero-length array is returned. */ @Override public LifecycleListener[] findLifecycleListeners() { return lifecycle.findLifecycleListeners(); } /** * Remove a lifecycle event listener from this component. * * @param listener * The listener to remove */ @Override public void removeLifecycleListener(LifecycleListener listener) { lifecycle.removeLifecycleListener(listener); } /** * Start this component and implement the requirements of * {@link org.apache.catalina.util.LifecycleBase#startInternal()}. * * @exception LifecycleException * if this component detects a fatal error that prevents this * component from being used */ @Override protected synchronized void startInternal() throws LifecycleException { boolean isSucc=false; try { System.out.println("准备开启redis-session-Manager处理器 ... "); super.startInternal(); setState(LifecycleState.STARTING); Boolean attachedToValve = false; Valve[] values = getContainer().getPipeline().getValves(); for (Valve valve : values) { if (valve instanceof RedisSessionHandlerValve) { System.out.println("初始化redis-session-Manager处理器 ... "); this.handlerValve = (RedisSessionHandlerValve) valve; this.handlerValve.setRedisSessionManager(this); attachedToValve = true; break; } } if (!attachedToValve) { String error = "重大错误:redis-session-Manager无法添加到会话处理器,session在请求后不能正常启动处理器!"; throw new LifecycleException(error); } System.out.println("初始化序列化器和反序列化器 ... "); initializeSerializer(); initializeDatabaseConnection(); setDistributable(true); isSucc=true; } catch (ClassNotFoundException e) { throw new LifecycleException(e); } catch (InstantiationException e) { throw new LifecycleException(e); } catch (IllegalAccessException e) { throw new LifecycleException(e); } catch(Exception e){ throw e; }finally{ if(isSucc){ System.out.println("redis-session-manager初始化成功"); }else{ System.out.println("redis-session-manager初始化失败"); } } } /** * Stop this component and implement the requirements of * {@link org.apache.catalina.util.LifecycleBase#stopInternal()}. * * @exception LifecycleException * if this component detects a fatal error that prevents this * component from being used */ @Override protected synchronized void stopInternal() throws LifecycleException { System.err.println("停止redis-session-manager处理器!"); setState(LifecycleState.STOPPING); try { if (connectionPool != null) { connectionPool.destroy(); } } catch (Exception e) { System.err.println("注销redis连接池失败!"); connectionPool = null; } super.stopInternal(); } @Override public Session createSession(String sessionId) { System.out.println("根据sessionId创建session:" + sessionId); // 初始化设置并创建一个新的session返回 RedisSession session = (RedisSession) createEmptySession(); session.setNew(true); session.setValid(true); session.setCreationTime(System.currentTimeMillis()); session.setMaxInactiveInterval(getMaxInactiveInterval()); String jvmRoute = getJvmRoute(); Jedis jedis = null; try { jedis = getConnection(); do { if (null == sessionId) { // 重新生成一个sessionId sessionId = generateSessionId(); } if (jvmRoute != null) { sessionId += '.' + jvmRoute; } } while (jedis.setnx(sessionId.getBytes(), NULL_SESSION) == 1L); /* * Even though the key is set in Redis, we are not going to flag the * current thread as having had the session persisted since the * session isn't actually serialized to Redis yet. This ensures that * the save(session) at the end of the request will serialize the * session into Redis with 'set' instead of 'setnx'. */ session.setId(sessionId); session.tellNew(); currentSession.set(session); currentSessionId.set(sessionId); currentSessionIsPersisted.set(false); } finally { if (jedis != null) { jedis.close(); } } return session; } @Override public Session createEmptySession() { System.out.println("添加空的session"); return new RedisSession(this); } @Override public void add(Session session) { System.out.println("添加session到redis数据库"); try { save(session); } catch (IOException e) { throw new RuntimeException("保存session失败", e); } } @Override public Session findSession(String id) throws IOException { System.out.println("查找sessionId:" + id); RedisSession session = null; if (id == null) { session = null; currentSessionIsPersisted.set(false); } else if (id.equals(currentSessionId.get())) { session = currentSession.get(); } else { session = loadSessionFromRedis(id); if (session != null) { currentSessionIsPersisted.set(true); } } currentSession.set(session); currentSessionId.set(id); return session; } public void clear() { Jedis jedis = null; try { jedis = getConnection(); jedis.flushDB(); } finally { if (jedis != null) { jedis.close(); } } } public int getSize() throws IOException { Jedis jedis = null; try { jedis = getConnection(); int size = jedis.dbSize().intValue(); return size; } finally { if (jedis != null) { jedis.close(); } } } public String[] keys() throws IOException { Jedis jedis = null; try { jedis = getConnection(); Set<String> keySet = jedis.keys("*"); return keySet.toArray(new String[keySet.size()]); } finally { if (jedis != null) { jedis.close(); } } } public RedisSession loadSessionFromRedis(String id) throws IOException { RedisSession session; Jedis jedis = null; try { jedis = getConnection(); byte[] data = jedis.get(id.getBytes()); if (data == null) { session = null; } else if (Arrays.equals(NULL_SESSION, data)) { throw new IllegalStateException("Race condition encountered: attempted to load session[" + id + "] which has been created but not yet serialized."); } else { session = (RedisSession) createEmptySession(); serializer.deserializeInto(data, session); session.setId(id); session.setNew(false); session.setMaxInactiveInterval(getMaxInactiveInterval() * 1000); session.access(); session.setValid(true); session.resetDirtyTracking(); } return session; } catch (IOException e) { throw e; } catch (ClassNotFoundException ex) { throw new IOException("Unable to deserialize into session", ex); } finally { if (jedis != null) { jedis.close(); } } } /** * save session to redis * * @param session * @throws IOException */ public void save(Session session) throws IOException { System.out.println("保存session到redis"); Jedis jedis = null; try { RedisSession redisSession = (RedisSession) session; Boolean sessionIsDirty = redisSession.isDirty(); redisSession.resetDirtyTracking(); byte[] binaryId = redisSession.getId().getBytes(); jedis = getConnection(); if (sessionIsDirty || currentSessionIsPersisted.get() != true) { jedis.set(binaryId, serializer.serializeFrom(redisSession)); } currentSessionIsPersisted.set(true); jedis.expire(binaryId, getMaxInactiveInterval()); } catch (IOException e) { throw e; } finally { if (jedis != null) { jedis.close(); } } } @Override public void remove(Session session) { remove(session, false); } @Override public void remove(Session session, boolean update) { System.out.println("删除redis中的session,更新:"+update); Jedis jedis = null; try { jedis = getConnection(); jedis.del(session.getId()); } finally { if (jedis != null) { jedis.close(); } } } public void afterRequest() { System.out.println("删除缓存在内存中的session"); RedisSession redisSession = currentSession.get(); if (redisSession != null) { currentSession.remove(); currentSessionId.remove(); currentSessionIsPersisted.remove(); } } @Override public void processExpires() { // We are going to use Redis's ability to expire keys for session // expiration. // Do nothing. } private void initializeDatabaseConnection() throws LifecycleException { try { System.out.println("初始化redis连接池 ... "); // 初始化redis连接池 connectionPool = new JedisPool(new JedisPoolConfig(), getHost(), getPort(), getTimeout(), getPassword()); } catch (Exception e) { e.printStackTrace(); throw new LifecycleException("redis连接池初始化错误,redis不存在或配置错误!", e); } } private void initializeSerializer() throws InstantiationException, IllegalAccessException, ClassNotFoundException { System.out.println("准备初始化序列器 ... "); serializer = (Serializer) Class.forName(serializationStrategyClass).newInstance(); ClassLoader classLoader = null; if (getContainer() != null) { classLoader = getContainer().getClass().getClassLoader(); } System.out.println("初始化序列器完成!"); serializer.setClassLoader(classLoader); } }
2、redis的session实现
package cn.eguid.redisSessionManager; import java.security.Principal; import org.apache.catalina.Manager; import org.apache.catalina.session.StandardSession; import java.util.HashMap; public class RedisSession extends StandardSession { protected static Boolean manualDirtyTrackingSupportEnabled = false; public static void setManualDirtyTrackingSupportEnabled(Boolean enabled) { manualDirtyTrackingSupportEnabled = enabled; } protected static String manualDirtyTrackingAttributeKey = "__changed__"; public static void setManualDirtyTrackingAttributeKey(String key) { manualDirtyTrackingAttributeKey = key; } protected HashMap<String, Object> changedAttributes; protected Boolean dirty; public RedisSession(Manager manager) { super(manager); resetDirtyTracking(); } public Boolean isDirty() { return dirty || !changedAttributes.isEmpty(); } public HashMap<String, Object> getChangedAttributes() { return changedAttributes; } public void resetDirtyTracking() { changedAttributes = new HashMap<String, Object>(); dirty = false; } @Override public void setAttribute(String key, Object value) { if (manualDirtyTrackingSupportEnabled && manualDirtyTrackingAttributeKey.equals(key)) { dirty = true; return; } Object oldValue = getAttribute(key); if ( value == null && oldValue != null || oldValue == null && value != null || !value.getClass().isInstance(oldValue) || !value.equals(oldValue) ) { changedAttributes.put(key, value); } super.setAttribute(key, value); } @Override public void removeAttribute(String name) { dirty = true; super.removeAttribute(name); } @Override public void setId(String id) { this.id = id; } @Override public void setPrincipal(Principal principal) { dirty = true; super.setPrincipal(principal); } }
3、session处理器实现
该类可以用于在请求前后请求后做一些操作,不仅局限于session操作,可以做servlet中的所有操作
package cn.eguid.redisSessionManager; import org.apache.catalina.Session; import org.apache.catalina.connector.Request; import org.apache.catalina.connector.Response; import org.apache.catalina.valves.ValveBase; import javax.servlet.ServletException; import java.io.IOException; public class RedisSessionHandlerValve extends ValveBase { // redis-session-manager管理器操作 private RedisSessionManager manager; // 通过tomcat的context.xml可以注入该实例 public void setRedisSessionManager(RedisSessionManager manager) { this.manager = manager; } // 产生一个请求后 @Override public void invoke(Request request, Response response) throws IOException, ServletException { try { getNext().invoke(request, response); } finally { System.out.println("请求完毕后,redis-session-manager正在获取当前产生的session"); Session session = request.getSessionInternal(false); storeOrRemoveSession(session); System.out.println("redis-session-manager操作结束,正在清理内存中的session!"); // 删除内存中的session manager.afterRequest(); } } private void storeOrRemoveSession(Session session) { try { if (session!=null && session.isValid() && session.getSession() != null) { manager.save(session); } else { manager.remove(session); } } catch (Exception e) { System.err.println("提示一下:session操作失败"); } } }
二、如何配置该项目到tomcat
1、拷贝tomcat-redis-session-manager-by-eguid.jar,jedis-2.9.0.jar,commons-pool2-2.2.jar到tomcat/lib目录下
2、修改Tomcat context.xml (or the context block of the server.xml if applicable.)
<Valve className="cn.eguid.redisSessionManager.RedisSessionHandlerValve"/>
<Manager className="cn.eguid.redisSessionManager.RedisSessionManager"
host="192.168.30.21"
port="6379"
database="14"
maxInactiveInterval="1800"/>
本文来自博客园,作者:eguid,没有作者允许禁止转载,取得作者同意后转载需注明作者名和原文链接:https://www.cnblogs.com/eguid/p/6821590.html