一.使用场景
应用部署在A,B两台服务器上时,此时若一用户在A服务器上登录后,登录信息会存放在A服务器上的session中,之后若该用户的请求被分配到B服务器上,会出现请求错误,因为B服务器上没有该用户的登录信息,因此考虑将session放在缓存中,实现session在多个服务器间的共享。(其他方案:将session存放在cookie[不安全],或者数据库[速度慢]中)
二. 解决方案
将session存到缓存中,封装HttpSessionWrapper类,对session的使用还和之前一样
1.HttpServletRequestWrapper类
//处理session共享 将session存到mdb中 封装HttpServletRequestWrapper
public class HttpServletRequestWrapper extends javax.servlet.http.HttpServletRequestWrapper{
//sessionId
private String sid;
//session实例
private SessionService sessionService;
public HttpServletRequestWrapper(String sid,HttpServletRequest request
, SessionService sessionService){
super(request);
this.sid = sid;
this.sessionService = sessionService;
}
@Override
public HttpSession getSession(boolean create){
return new HttpSessionWrapper(this.sid,super.getSession(create),this.sessionService);
}
@Override
public HttpSession getSession() {
return new HttpSessionWrapper(this.sid,super.getSession(),this.sessionService);
}
}
2.HttpSessionWrapper类
public class HttpSessionWrapper implements HttpSession{
private final Logger logger = LoggerFactory.getLogger(HttpSessionWrapper.class);
//session过期时间 单位是秒
private static final Integer EXPIRE_TIME = 3600;
//sessionID 自定义sessionId 否则会出现两个服务器生成的sessionid不一样的情况
private String sid;
//HashMap 对应session里的属性值
private Map map = new HashMap();
private SessionService sessionService;
private HttpSession session;
public HttpSessionWrapper(String sid, HttpSession session, SessionService sessionService){
this.sid = sid;
this.sessionService = sessionService;
this.session = session;
//根据session对map进行初始化
Map memSession = null;
String sessionJsonStr = this.sessionService.getSessionBySID(this.sid);
if(sessionJsonStr == null || sessionJsonStr.equals("") ||sessionJsonStr.equals("{}")){
memSession = null;
}else {
memSession = JSON.parseObject(sessionJsonStr, Map.class);
}
//sid没有加入到session中,需要初始化session,替换为自定义session
if(memSession == null){
memSession = new HashMap();
if(session != null){
Enumeration<String> names = session.getAttributeNames();
while(names.hasMoreElements()){
String key = names.nextElement();
memSession.put(key,session.getAttribute(key));
}
}
}//if
this.map = memSession;
//logger.info("initial HttpSessionWrapper");
//不能在这里更新 会出错 session内容会被置为null
//attributeChange();
}
//tair value需要是可序列化的 因此这里将map转化为了json 每访问一次session 需更新session有效期
private void attributeChange() {
String sessionId = this.sid;
String content = JSON.toJSONString(this.map);
HashMap<String, Object> param = new HashMap<String, Object>();
param.put("sessionId", sessionId);
param.put("content", content);
param.put("expireTime", HttpSessionWrapper.EXPIRE_TIME);
Integer count = this.sessionService.updateSession(param);
if(count <= 0){
logger.error("attributeChange put sid: " + sessionId + " failure");
}
}
@Override
public Object getAttribute(String key){
logger.info("HttpSessionWrapper getAttribute name: " + key);
//这里不能加更新语句 因为构造函数循环中调用getAttribute时 this.map还没赋值完
//attributeChange();
if(this.map != null && this.map.containsKey(key)){
Object value = this.map.get(key);
logger.info("value: " + value);
if(value != null){
logger.info(value.getClass().toString()); //class com.alibaba.fastjson.JSONObject
}
if(key.equals(Constants.SESSION_LOGIN_USER)){//登录用户
TUser userObj = JSON.parseObject(value.toString(),TUser.class);
return userObj;
}else if(key.equals(Constants.SESSION_MENU_LIST)){//菜单列表
List<TMenu> menuList = JSON.parseArray(value.toString(),TMenu.class);
return menuList;
}else if(key.equals("javax.security.auth.subject")){
//20180327 add
Subject object = JSON.parseObject(value.toString(), Subject.class);
logger.info(object.getClass().toString());
return object;
}else{
return value;
}
}else{
return null;
}
}
@Override
public Enumeration<String> getAttributeNames(){
logger.info("HttpSessionWrapper getAttributeNames");
Set temp = this.map.keySet();
//attributeChange();
return new Vector(temp).elements();
}
@Override
public void removeAttribute(String name){
logger.info("HttpSessionWrapper removeAttribute name: " + name);
this.map.remove(name);
attributeChange();
}
@Override
public void setAttribute(String name, Object value){
logger.info("HttpSessionWrapper setAttribute name: " + name);
this.map.put(name,value);
attributeChange();
}
@Override
public void invalidate(){
logger.info("HttpSessionWrapper invalidate");
this.map.clear();
long s1= System.currentTimeMillis();
try {
Integer count = this.sessionService.deleteSessionBySID(this.sid);
logger.info("removeSession sid is:" + this.sid + "; count: " + count);
}
finally{
logger.info("used time: " + (System.currentTimeMillis() - s1));
}
}
//以下没有用缓存mdb实现
@Override
public long getCreationTime() {
return this.session.getCreationTime();
}
@Override
public String getId() {
return this.sid;
}
@Override
public long getLastAccessedTime() {
return this.session.getLastAccessedTime();
}
@Override
public ServletContext getServletContext() {
return this.session.getServletContext();
}
@Override
public void setMaxInactiveInterval(int i) {
this.session.setMaxInactiveInterval(i);
}
@Override
public int getMaxInactiveInterval() {
return this.session.getMaxInactiveInterval();
}
@Override
public HttpSessionContext getSessionContext() {
return this.session.getSessionContext();
}
@Override
public boolean isNew() {
return this.session.isNew();
}
@Override
public Object getValue(String s) {
return this.session.getValue(s);
}
@Override
public String[] getValueNames() {
return this.session.getValueNames();
}
@Override
public void removeValue(String s) {
this.session.removeValue(s);
}
@Override
public void putValue(String s, Object o) {
this.session.putValue(s,o);
}
}
3.filter配置
@Component
public class ApplicationFilterConfig {
@Bean
public FilterRegistrationBean filterRegistrationBean(){
FilterRegistrationBean registrationBean = new FilterRegistrationBean();
MdbSessionFilter sessionFilter = new MdbSessionFilter();
registrationBean.setFilter(sessionFilter);
List<String> urlPatterns = new ArrayList<String>();
urlPatterns.add("/*");
registrationBean.setUrlPatterns(urlPatterns);
return registrationBean;
}
}
4.filter定义
//为实现session共享 创建Filter 重新封装request
public class MdbSessionFilter implements Filter {
private static final Logger logger = LoggerFactory.getLogger(MdbSessionFilter.class);
//这里不可使用autowired注解注入
private SessionService sessionService;
private static final Set<String> noValidRoutes = new HashSet();
static {
//必须加/
// noValidRoutes.add("/");
// noValidRoutes.add("/index");
// noValidRoutes.add("/login");
// noValidRoutes.add("/verifyCode");
noValidRoutes.add("/checkpreload.htm");
}
@Override
public void init(FilterConfig filterConfig) throws ServletException {
logger.info("init MdbSessionFilter");
ServletContext context = filterConfig.getServletContext();
ApplicationContext ac = WebApplicationContextUtils.getWebApplicationContext(context);
sessionService = (SessionService) ac.getBean("sessionService");
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
throws IOException, ServletException {
String sessionId = Constants.SESSION_COOKIE_NAME;
//logger.info("sessionId: " + sessionId);
HttpServletRequest request = (HttpServletRequest) servletRequest;
HttpServletResponse response = (HttpServletResponse) servletResponse;
String uri = request.getRequestURI();
if(noValidRoutes.contains(uri)){
filterChain.doFilter(request,response);
return;
}else{
logger.info("MdbSessionFilter uri: " + uri);
}
Cookie cookies[] = request.getCookies();
Cookie sCookie = null;
String sid = "";
//sessionId已经存放在cookie中 直接取出 放入sid中
if (cookies != null && cookies.length > 0) {
for (int i = 0; i < cookies.length; i++) {
sCookie = cookies[i];
if (sCookie.getName().equals(sessionId)) {
sid = sCookie.getValue();
}
}
}
if (sid == null || sid.length() == 0) {
//生成sessionID
sid = java.util.UUID.randomUUID().toString();
logger.info("sid: " + sid);
Cookie mycookie = new Cookie(sessionId, sid);
//设置生命周期为1天,秒为单位
mycookie.setMaxAge(-1);
mycookie.setPath("/");
mycookie.setHttpOnly(true);
response.addCookie(mycookie);
}
//logger.info("sessionId: " + sessionId + " -- sid: " + sid);
filterChain.doFilter(new HttpServletRequestWrapper(sid, request, sessionService), response);
}
@Override
public void destroy() {
}
}
5.SessionService
public interface SessionService {
/**
* 根据sid获取session内容
* @param sid sessionId
* @return session内容 json字符串
* */
String getSessionBySID(String sid);
/**
* 根据sessionId更新session
* @param param -- sessionId content expireTime
* @return 影响行数
* */
Integer updateSession(HashMap<String, Object> param);
/**
* 根据sessionId删除session
* @param sid sessionId
* @return 影响行数
* */
Integer deleteSessionBySID(String sid);
}