补习系列-springboot中的几种scope

发布日期:2019-03-19

目标

    了解HTTP 请求/响应头及常见的属性;了解如何使用SpringBoot处理头信息 ;了解如何使用SpringBoot处理Cookie ;学会如何对 Session 进行读写;了解如何在不同请求间传递 flash参数

一、Http 头信息

HTTP 头(Header)是一种附加内容,独立于请求内容和响应内容。HTTP 协议中的大量特性都通过Header信息交互来实现,比如内容编解码、缓存、连接保活等等。如下面的一个请求响应:Request

Accept: text/htmlapplication/xhtml+xmlapplication/xmlq=0.9image/webpimage/apng*/*q=0.8Accept-Encoding: gzip deflateAccept-Language: zh-CNzhq=0.9Cache-Control: max-age=0Connection: keep-aliveHost: www.cnblogs.comIf-Modified-Since: Wed 18 Jul 2018 13:47:45 GMTUpgrade-Insecure-Requests: 1User-Agent: Mozilla/5.0 (Windows NT 6.1 Win64 x64) AppleWebKit/537.36 (KHTML like Gecko) Chrome/65.0.3325.181 Safari/537.36

名称用途
Accept客户端期望的MIME 类型列表
Accept-Encoding客户端期望的编解码方式
Accept-Language客户端期望的语言
Cache-Control缓存控制
Connection连接行为(keep-alive)
Host请求访问的主机
If-Modified-Since缓存控制
Upgrade-Insecure-Requests支持安全加密标记
User-Agent用户代理(客户端标识)

Response

Cache-Control: private max-age=10Connection: keep-aliveContent-Encoding: gzipContent-Type: text/html charset=utf-8Date: Wed 18 Jul 2018 13:47:51 GMTExpires: Wed 18 Jul 2018 13:48:01 GMTLast-Modified: Wed 18 Jul 2018 13:47:51 GMTTransfer-Encoding: chunkedVary: Accept-EncodingX-Frame-Options: SAMEORIGINX-UA-Compatible: IE=10

名称用途
Cache-Control缓存控制
Connection连接行为(keep-alive)
Content-Encoding编解码方式
Content-Type内容类型(MIME)
Date当前响应时间
Expires文档过期时间
Last-Modified最后一次更新时间
Transfer-Encoding传输编码方式
Vary需要刷新的请求Header
X-Frame-OptionsFRAME展示策略(用于同源控制)
X-UA-CompatibleIE兼容属性

更多的** Http Header **可以从这里找到

二、SpringBoot 处理头信息

前面的内容中已经讲过如何完成Controller方法及请求的映射。在SpringBoot可通过@RequestHeader注解方式将请求头信息映射到参数,如下面的片段:

@GetMapping("/some") @ResponseBody public String someHeader(@RequestHeader(value = "Host") String host @RequestHeader(value = "User-Agent") String userAgent @RequestHeader(value = "Cache-Control" required = false) String cacheControl HttpServletResponse response) { logger.info("host:{}" host) logger.info("User-Agent:{}" userAgent) logger.info("Cache-Control:{}" cacheControl) // 设置响应头 response.setHeader("Cache-Control" "no-cacheno-storemust-revalidate") response.setHeader("Pragma" "no-cache") response.setDateHeader("Expires" 0) return "OK" }

而响应头呢,可以通过声明一个HttpServletResponse参数后,通过该对象进行设置,上面的代码非常容易理解。

如果希望获得全部的请求头,可以使用HttpHeaders对象:

@GetMapping("/all") public ResponseEntity<Map<String List<String>>> allHeaders(@RequestHeader HttpHeaders headers) { Map<String List<String>> valueMap = new HashMap<String List<String>>() for (String header : headers.keySet()) { valueMap.put(header headers.get(header)) logger.info("header[{}]={}" header headers.get(header)) } // 通过ResponseEntity设置响应头 ResponseEntity<Map<String List<String>>> entity = ResponseEntity.status(HttpStatus.OK) .header("new header" UUID.randomUUID().toString()).body(valueMap) return entity }

上面的一段代码中,可以将所有请求头信息全部打印出来。此外还须注意到,返回响应使用了ResponseEntity对象,这是一个用于直接表示响应信息头、内容的对象,利用ResponseEntity可以很方便的设置响应头信息。

三、Cookie处理

Cookie一开始服务器用于辨别用户信息而记录在浏览器上的信息。到目前为止Cookie作为客户端的存储有了非常多的应用场景。

SpringBoot 提供了@CookieValue以支持参数方式注入,如下:

@GetMapping("/some") @ResponseBody public String someCookie(@CookieValue(value = "counter" defaultValue = "0") int counter HttpServletResponse response) { logger.info("counter:{}" counter) counter += 1 String newValue = counter + "" // 设置Cookie response.addCookie(new Cookie("counter" newValue)) return newValue }

上述代码中,访问/some 可以获得一个counter的cookie值,且每访问一次则自增一次,这是一个简单的访问计数器功能。

如果希望获取全部的Cookie,可以参考以下代码:

@GetMapping("/all") public ResponseEntity<Map<String String>>allCookies(HttpServletRequest request HttpServletResponse response) { Map<String String> valueMap = new HashMap<String String>() for (Cookie cookie : request.getCookies()) { valueMap.put(cookie.getName() cookie.getValue()) logger.info("cookie[{}]={}" cookie.getName() cookie.getValue()) } // 设置Cookie response.addCookie(new Cookie("key" UUID.randomUUID().toString())) return new ResponseEntity<Map<String String>>(valueMap HttpStatus.OK) }

清理全部Cookie

@GetMapping("/clear") public ResponseEntity<Map<String String>> clearCookies(HttpServletRequest request HttpServletResponse response) { Map<String String> valueMap = new HashMap<String String>() for (Cookie cookie : request.getCookies()) { valueMap.put(cookie.getName() cookie.getValue()) logger.info("cookie[{}]={}" cookie.getName() cookie.getValue()) // 清除 cookie.setMaxAge(0) response.addCookie(cookie) } return new ResponseEntity<Map<String String>>(valueMap HttpStatus.OK) }

Cookie机制存在一定的缺陷,尽可能在考虑一些风险后使用

    安全性无法保证,除非使用HTTPS;浏览器端只有4KB大小的存储上限;

四、Session处理

Session 指的是会话,是建立于Cookie机制上的一种身份识别方式。由于Cookie自身的安全性和容量限制,大多数应用中是在Cookie中存放一个唯一凭证;服务侧通过凭证再进行身份信息的存取,这就是会话的由来。不同的语言、框架采用的实现方式有些差异,比如JavaEE采用JSESSION_ID,而PHP则是PHPSESSID

Session的交互原理可以参考下面一个图:

Springboot 内嵌了Servlet容器,则是沿用的JSESSION_ID。因此在浏览一些JavaWeb站点时会发现该Cookie。使用@SessionAttribute可以将会话中的属性映射到方法参数;

如果希望对Session属性进行操作,可以在Controller上声明@SessionAttributes注解以指定想要变更的属性;之后,通过Model参数进行写入即可(由框架自动检测并修改Session)

@SessionAttributes("seed")public class SessionController { private static final Logger logger = LoggerFactory.getLogger(SessionController.class) @GetMapping("/some") @ResponseBody public String someSession(@SessionAttribute(value = "seed" required = false) Integer seed Model model) { logger.info("seed:{}" seed) if (seed == null) { seed = (int) (Math.random() * 10000) } else { seed += 1 } model.addAttribute("seed" seed) return seed + "" }

上面的例子与Cookie实现访问计数器的功能是一样的!如果希望获取全部会话,可以使用HttpSession

@GetMapping("/all") public ResponseEntity<Map<String Object>> allSessions(HttpSession session) { Map<String Object> valueMap = new HashMap<String Object>() Enumeration<String> iSession = session.getAttributeNames() while (iSession.hasMoreElements()) { String sessionName = iSession.nextElement() Object sessionValue = session.getAttribute(sessionName) valueMap.put(sessionName sessionValue) logger.info("sessoin[{}]={}" sessionName sessionValue) } // 写入session session.setAttribute("timestmap" new Date()) return new ResponseEntity<Map<String Object>>(valueMap HttpStatus.OK) }

清除会话

@GetMapping("/clear") public ResponseEntity<Map<String Object>> clearSessions(HttpSession session) { Map<String Object> valueMap = new HashMap<String Object>() Enumeration<String> iSession = session.getAttributeNames() while (iSession.hasMoreElements()) { String sessionName = iSession.nextElement() Object sessionValue = session.getAttribute(sessionName) valueMap.put(sessionName sessionValue) logger.info("sessoin[{}]={}" sessionName sessionValue) session.removeAttribute(sessionName) } return new ResponseEntity<Map<String Object>>(valueMap HttpStatus.OK) }

五、Flash参数传递

Flash的意思是一瞬间,一闪而过的,因此很好理解,这是一类仅用来消费一次的参数,有些类似阅后即焚试想这样的场景,你确认完购物车,完成订单支付后进入订单管理界面,而此时界面上提示你"下单成功,请等待发货"。这便可以通过Flash传参来实现。

Flash的意义是用作请求之间的瞬时参数传递,仅消费一次后便不再用。以下是一个示例:

/** * 执行跳转,并设置传值 * * @param counter * @param response * @return */ @GetMapping("/first") public String first(final RedirectAttributes redirectAttrs) { logger.info("redirect start:{}") redirectAttrs.addFlashAttribute("flash" UUID.randomUUID().toString()) return "redirect:/flash/second" } /** * 获取传值 * * @param session * @param response * @return */ @GetMapping("/second") @ResponseBody public String second(@ModelAttribute("flash") String flash) { logger.info("redirect receive {}" flash) return flash }

交互原理

Sprintboot中Flash机制也是利用Session实现的,其中FlashMapManager接口实现了Flash参数的管理。默认的实现是SessionFlashMapManager,可以通过RequestContextUtils获得上下文中的FlashMapManager对象。

RequestContextUtils通过Request Scope(请求上下文)存取对象这也是一个本文未提及的scope域,Request上下文是利用线程变量实现的,通常用于线程内业务处理的数据交互。

小结

HTTP 头信息是一种附加内容,用于实现HTTP协议中的各种特性,在开始部分介绍了常见的头信息定义。本文主要介绍了几种常见的HTTP scope信息的存取方法,包括如何对header、cookie进行读取及修改。springboot 内嵌了Servlet容器,会话处理机制上沿用了JSESSIONID,通过代码示例介绍了会话的处理方法;Flash参数是一种阅后即焚的数据,其底层实现也用了session的实现方案。欢迎继续关注"美码师的补习系列-springboot篇" ,期待更多精彩内容^-^