Spring Boot QuickStart (3) - Web & Restful

环境:Spring Boot 1.5.4

基于 Spring Boot 可以快速创建一个Web & Restful 应用,在开始应用之前,至少要了解以下用法:

  • 定义路由,定义 HTTP 方法
  • 获取Header、GET、POST、路径等参数
  • Cookie、Session操作
  • 应用一个模板引擎,选择 Thymeleaf
  • 获取表单数据,以及文件上传数据
  • 完成一个登陆、登出、注册流程

增加以下两个依赖即可完成构建:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

注意:当前版本默认选择的 Thymeleaf 是 2.x 版本的,对html 标签闭合性要求比较高,虽然可以通过设置 mode,改变解析方式,但是还要引入额外的 nekoHTML,所以很蛋疼。不过可以切换到 3.x 版本,兼容性未知。

<properties>
    <thymeleaf.version>3.0.2.RELEASE</thymeleaf.version>
    <thymeleaf-layout-dialect.version>2.1.1</thymeleaf-layout-dialect.version>
</properties>

注解

一旦添加了 spring-boot-starter-web 依赖 Spring Boot 会判断这是一个Web 应用,并启动一个内嵌的Servlet容器(默认是Tomcat)用于处理HTTP请求。

Spring 框架中关于 Web 应用有大量的注解:

@Controller
注解一个 Web 控制器类,框架会将 Servlet 容器里收到的 HTTP 请求根据路径分发给对应的 Controller 类进行处理

@RestController
注解一个 Restful 控制器,默认会自动返回 JSON

@RequestMapping
注解一个路由,如果定义在类上,相当于一个路由组,最终路由是类+方法路由,参数有路由规则和 HTTP 方法,同时还有一些简写形式如:@GetMapping@PutMapping

@PathVariable
注解路径参数,如 /user/{id}

@RequestParam
注解请求参数,如 ?user=a 或 post['user'] = a

@RequestBody
注解请求体

@RequestHeader
注解请求header头

@CookieValue
注解一个Cookie值

@SessionAttribute
注解一个Session值

路由,方法

下面代码对应了两个路由:

/ 使用 @ResponseBody 注解,所以返回了文本串
/hello 返回表示模板的字符串,将会使用 resources/templates/hello.html 模板渲染

@Controller
public class IndexController {

    @RequestMapping("/")
    @ResponseBody
    public String index() {
        return "Spring Boot Index";
    }

    @RequestMapping("/hello")
    public String hello(Model model) {
        model.addAttribute("title", "Spring Boot");
        return "hello";
    }
}

这段代码对应了 /rest/ 路由。注意 @RequestMapping 中空字符串与 "/" 的区别。
使用空,则 /rest 与 /rest/ 都可以访问,使用 / 则必须通过 /rest 访问:

@RestController
@RequestMapping("/rest")
public class RestfulController {

    @RequestMapping("")
    public String index() {
        return "Hello Rest";
    }
}    

hello.html:

<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8" />
    <title th:text="'Hello, ' + ${title}">title</title>
</head>
<body>
<h1 th:text="'Hello, ' + ${title}">h1</h1>
</body>
</html>

请求参数

单一参数

@RequestParam 可以用来注解一个请求参数,默认会合并 GET、POST 请求名相同的参数,变成一个数组,所以下面的 text2 会进行数组 -> String 的转型。

@RequestMapping(value = "/get", method = {RequestMethod.GET, RequestMethod.POST})
@ResponseBody
public String get(@RequestParam("text1") String text1, @RequestParam("text2") String text2) {

   return text1 + "/" + text2;
}

在这里不使用注解同样可以获得数据,那为什么要使用注解呢? 因为 @RequestParam 主要提供了一些额外的功能:
如参数名->变量名的映射,required 检查等,更严谨一些。

@RequestMapping(value = "/getsimple", method = {RequestMethod.GET, RequestMethod.POST})
@ResponseBody
public String getSimple(String text1, String text2) {
   return text1 + "/" + text2;
}

参数列表

除了使用 @RequestParam 注解获取单个参数意外,还可以获取一个参数列表,但这种方式,POST 不会合并 GET 中的同名参数:

@RequestMapping("/getmap")
@ResponseBody
public String getMap(@RequestParam Map<String, Object> gets) {
   return gets.toString();
}

额外参数列表

除了注解获取数据以外,我们还可以通过向 Controller 中的方法,注入 Servlet 相关的类,来获取一些额外的参数,比如客户端 IP 地址

@RequestMapping("/request")
@ResponseBody
public String request(HttpServletRequest request) {
   HashMap<String, String> requests = new HashMap<>();

   requests.put("Method", request.getMethod());
   requests.put("QueryString", request.getQueryString());
   requests.put("RequestURI", request.getRequestURI());
   requests.put("getRequestURL", request.getRequestURL().toString());
   requests.put("RemoteAddr", request.getRemoteAddr());

   return requests.toString();
}

Cookie

Cookie 的操作其实和 Spring 没有太大的关系,不过Spring 提供了一个 @CookieValue 注解用来快速获取 Cookie 值

通过 HttpServletResponse 设置页面的 Cookie:

@RequestMapping("/setcookie")
@ResponseBody
public String setCookie(HttpServletResponse response) {
   Cookie cookie1 = new Cookie("cookie1", "value1");
   cookie1.setMaxAge(1800);
   Cookie cookie2 = new Cookie("cookie2", "value2");
   cookie2.setMaxAge(3600);

   response.addCookie(cookie1);
   response.addCookie(cookie2);
   return "cookie set ok";
}

通过 HttpServletRequest 或 @CookieValue 注解获取 Cookie:

@RequestMapping("/getcookie")
@ResponseBody
public String getCookie(HttpServletRequest request,
       @CookieValue(value = "cookie1", required = false) String cookie1) {

   HashMap<String, String> map = new HashMap<>();
   Cookie[] cookies = request.getCookies();
   if (cookies != null) {
       for (Cookie cookie : cookies) {
           map.put(cookie.getName(), cookie.getValue());
       }
   }

   logger.info(cookie1);

   return map.toString();
}

清空Cookie,就是重新设置cookie的值与过期时间:

@RequestMapping("/delcookie")
@ResponseBody
public String delCookie(HttpServletRequest request, HttpServletResponse response) {
   Cookie[] cookies = request.getCookies();
   if (cookies != null) {
       for (Cookie cookie : cookies) {
           // setValue只是清空了value,cookie还在
           cookie.setValue(null);
           cookie.setMaxAge(0);
           response.addCookie(cookie);
       }
   }

   return "delete ok";
}

Session

Session的相关操作,通过 HttpSession、HttpServletRequest、@SessionAttribute 来完成。

设置 Session:

@RequestMapping("/setsession")
@ResponseBody
public String setSession(HttpSession session) {
   session.setAttribute("session1", "value1");
   session.setAttribute("session2", "value2");
   return "";
}

获取Session:

@RequestMapping("/getsession")
@ResponseBody
public String getSession(
       HttpServletRequest request,
       HttpSession httpSession,
       @SessionAttribute(value = "session1", required = false) String session1) {
   HttpSession session = request.getSession();
   String session2 = (String)session.getAttribute("session2");
   String http_session1 = (String)httpSession.getAttribute("session1");

   logger.info(http_session1);
   logger.info(session1);
   logger.info(session2);

   HashMap<String, String> sessionMap = new HashMap<>();
   Enumeration<String> sessions = session.getAttributeNames();
   while(sessions.hasMoreElements()) {
       String key = sessions.nextElement();
       sessionMap.put(key, (String)session.getAttribute(key));
   }

   return sessionMap.toString();
}

删除Session:

@RequestMapping("/delsession")
@ResponseBody
public String delSession(HttpSession httpSession) {
   httpSession.removeAttribute("session1");
   httpSession.removeAttribute("session2");

   return "delete session ok";
}

模板引擎

在模板引擎之前,要了解怎么向模板中传递数据,于是有这三种姿势:

Map,这是一个Java原生类型
ModelMap,这是一个类
Model,这是一个接口,其实现类为 ExtendedModelMap,继承了 ModelMap 类

这三个都可以在方法参数中直接注入使用,暂时不知道这三个有什么区别,用起来差不多。

@RequestMapping("/model")
public String model(Model model, ModelMap modelMap, Map<String, Object> map) {
   model.addAttribute("title1", "model_title");
   modelMap.addAttribute("title2", "modelMap_title");
   map.put("title2", "map_title");

   User user = new User(1, "test");
   model.addAttribute("user", user);

   return "model";
}

除了上面的用法,还可以使用 ModelAndView 手动渲染模板,效果是一样的:

@RequestMapping("/modelandview")
public ModelAndView modelAndView() {
   ModelAndView modelAndView = new ModelAndView();
   modelAndView.setViewName("model");
   modelAndView.addObject("title1", "title1");
   modelAndView.addObject("title2", "title2");

   User user = new User(1, "test");
   modelAndView.addObject("user", user);

   return modelAndView;
}

最后 SpringBoot 默认可以集成好几种模板引擎,现在主要使用 thymeleaf。

@ModelAttribute 注解

这个注解有点复杂。可以注解到方法上,也可以注解到方法参数上

  1. 注解到方法参数

大概意思是通过模型中获取,这个模型是什么呢?大概通过一些途径(可能来自Session?可能来自请求参数?可能来自控制器注解到方法上的 @ModelAttribute)

完成自动填充,并且自动传递到模板中,这是 Spring MVC 数据绑定。

下面是两个例子:

请求通过 /xxx?id=1&name=张三,能够自动进行映射,并且传到模板中,并且还能自动进行输出格式转换,如下面第一个例子,受 @ResponseBody 影响,直接输出了 JSON

@RequestMapping("/getmodel")
@ResponseBody
public User getModel(@ModelAttribute User user) {
   return user;
}

@RequestMapping("/modelattribute")
public String modelAttribute(@ModelAttribute User user) {
   return "model";
}
  1. 注解到方法

将这个控制器的方法,变成一个非请求处理的方法,在其它请求方法(RequestMapping)被调用前首先调用该方法,并将返回的数据放到 Model 中

有什么用呢?

估计是用来生成初始化数据的,比如生成一个带有一些默认数据的表单?在进入控制器之前对数据进行一些整理和清洗?

常用配置

自动 trim 参数

大多数 PHP 框架都有自动 trim GET/POST 参数的功能

在 Spring 里面,可以借助 @InitBinder 注解可以完成这种事情,我们定义一个控制器基类,方便接受请求的控制器继承

public class BaseController {
    @InitBinder
    protected void initBinder(WebDataBinder binder) {
        StringTrimmerEditor stringtrimmer = new StringTrimmerEditor(true);
        binder.registerCustomEditor(String.class, stringtrimmer);
    }
}

server

server.address 绑定地址
server.port 绑定端口
server.compression.enabled 是否开启压缩
server.compression.mime-types 压缩的类型
server.compression.min-response-size 压缩的阈值

server.tomcat.* access日志,日志目录,线程数等

server.session.cookie.* SessionCookie相关配置