关于session和cookie的历史渊源

http协议是没有状态的,也就是说你第一次访问www.taobao.com然后登录,之后第二次访问www.taobao.com的时候,服务器并不知道你已经登录了
这就像你去超市,超市的门卫是个健忘症晚期的老头,你第二天去,他就忘了你昨天已经来过了
那具体咱们办呢?

  • 第一个方法
    你每次去的时候,带一个小纸板,上面写着我叫dale,是陕西咸阳人,今年28岁
    但是这么做有一个问题,如果那天我不开心了,把纸板上面的信息换了,我改成我今年48岁
    那是不是就有问题了?
  • 第二个方法
    我第一次去超市,老头问了我的信息,然后他自己拿个小本,记录一下,之后给我发一个身份id,告诉我:小伙子,下次来拿上这个id哦
    下次我去的时候,依然拿个小纸板,上面就写着一个信息:我的id 然后老头看到我的id,就查一下他的小本,就知道了我叫dale,是陕西咸阳人,今年28岁

其实第一个方法就叫做cookie方法
第二种发放就叫做session方法

当然我上面说的例子并不十分准确,详细的资料大家可以参考​

分布式带来的挑战

多个服务器必须共享session
那么,为什么要共享session呢,我懒得说了
那多个服务器怎么共享session?

  • 方案一 直接让多个tomcat把session都保存到redis集群里面去
  • 方案二 稍微麻烦一些,我们手动把用户数据写入分布式缓存里
    那么方案二比方案一好在哪里呢?
    个人感觉,其实真的没有绝对的优势,只能说自己写入的话,各种细节更加可控吧

方案二的具体实现

我们猜想一下,代码应该是这样的:登录成功之后,给用户的cookie里面写入一个uuid,然后以这个uuid为key,把用户数据写入redis
下次用户来的时候,从cookie里面读出来那个uuid(如果有的话),然后去redis里面找,如果有就说明之前登录过了

public boolean login(HttpServletResponse response, LoginVo loginVo) {
// 省略验证密码的逻辑
User user = getById(Long.parseLong(loginVo.getUserId));
//生成cookie
String token = UUIDUtil.uuid();
addCookie(response, token, user);
return true;
}
//User 就是一个pojo类,里面就是username password等
private void addCookie(HttpServletResponse response, String token, User user){
redisService.set(token, user);
Cookie cookie = new Cookie("token", token);
cookie.setMaxAge(100);
cookie.setPath("/");
response.addCookie(cookie);
}

ok,那么需要使用用户信息的时候,自然就是从cookie里面拿到那个token,然后再去redis集群里面拿信息就OK
就以淘宝为例,我们试想一下,查看自己的购物车需要看用户的信息,查看我的订单也需要看用户的信息,查看我的订单详情还是需要验证用户信息的。每次都去redis拿,代码不是就显得很臃肿么?
怎么办?
我能不能再mvc层里,就把这个值拿到,然后直接用传参的方式交给后面的查看购物车,查看订单的逻辑呢?

@Service
public class UserArgumentResolver implements HandlerMethodArgumentResolver {

@Autowired
UserService userService;

public boolean supportsParameter(MethodParameter parameter) {
Class<?> clazz = parameter.getParameterType();
return clazz==User .class;
}

public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class);
HttpServletResponse response = webRequest.getNativeResponse(HttpServletResponse.class);
//有些手机端浏览器会把cookie信息直接加到request请求的参数里
String paramToken = request.getParameter("token");
String cookieToken = getCookieValue(request, "token");
if(StringUtils.isEmpty(cookieToken) && StringUtils.isEmpty(paramToken)) {
return null;
}
String token = StringUtils.isEmpty(paramToken)?cookieToken:paramToken;
//从redis里拿到信息
return userService.getByToken(response, token);
}

private String getCookieValue(HttpServletRequest request, String cookiName) {
Cookie[] cookies = request.getCookies();
for(Cookie cookie : cookies) {
if(cookie.getName().equals(cookiName)) {
return cookie.getValue();
}
}
return null;
}
}

然后再把UserArgumentResolver 注入Spring mvc里面

@Configuration
public class WebConfig extends WebMvcConfigurerAdapter{
@Autowired
UserArgumentResolver userArgumentResolver;
@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
argumentResolvers.add(userArgumentResolver);
}
}

最后我们使用的时候,在获取商品信息的逻辑里,直接用MiaoshaUser 即可,UserArgumentResolver 会把从redis里面取到的信息注入的GoodsController 的list方法里

@Controller
@RequestMapping("/goods")
public class GoodsController {
@RequestMapping("/to_list")
public String list(Model model,User user) {
model.addAttribute("user", user);
return "goods_list";
}

}

关于HandlerMethodArgumentResolver 的更多资料,大家需要自行查找