第二章 Spring Boot MVC

本章是《 Spring Boot 快速入门 》系列教程的第二章,若要查看本系列的全部章节,请点击 这里

目录

  • 简介
  • 源码下载
  • 软件版本
  • 编写 Controller 类
  • 自动装载请求参数
  • 处理复杂类型的返回值
  • 自定义返回值转化类 HttpMessageConverter
  • 总结说明

简介

在上一章《Hello Spring Boot》中,我们从一个最简单的应用程序入手,体验了 Spring Boot 的开发流程。 本章我们会带着大家继续 Spring Boot 体验之旅,将 Hello Spring Boot 程序升级成 Web 版。 现在WEB开发比较流行的开发模式是前后端分离,也就是前端工程师专注于写前端的代码,用 ajax 调用后端的 Http Restful API 获取数据,而后台工程师则专注于实现 Http Restful API 即可,前后端在代码工程和部署上都是完全分离的,这样只要接口定义好了就可以各自开发相互不干扰,协作效率更高。 因此,本文的重点是描述如何用 Spring Boot 开发 Http Restful API 服务。

源码下载

本章的示例代码放在“码云”上,大家可以免费下载或浏览:

https://git.oschina.net/terran4j/springboot/tree/master/springboot-web

软件版本

相关软件使用的版本:

  • Java: 1.8
  • Maven: 3.3.9

程序在以上版本均调试过,可以正常运行,其它版本仅作参考。

编写 Controller 类

Spring Boot 默认使用 Spring MVC 作为WEB框架,处理HTTP请求需要编写 Controller 类,如下代码所示:

package com.terran4j.springboot.web;

import java.io.IOException;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

@Controller
public class HelloController {

	@Autowired
	private HelloService helloService;
	
	@RequestMapping(value = "/hello", method = RequestMethod.GET)
	public void sayHello(HttpServletRequest request, HttpServletResponse response) throws IOException {
		String name = request.getParameter("name");
		String msg = helloService.hello(name);
		response.getWriter().println(msg);
	}
	
}

首先, 要在类上面加 @Controller 注解,然后处理 HTTP 请求的方法上要加 @RequestMapping 注解,如:

@RequestMapping(value = "/hello", method = RequestMethod.GET)

其中 value 是匹配的路径,method 是匹配的方法,主要有GET, POST, PUT, DELETE这么几种,上面这行的意思是路径为 /hello 的 GET 请求,都会让这个方法来处理,如:

http://localhost:8080/hello?name=terran4j

这里方法参数我们先用原始的 HttpServletRequest 和 HttpServletResponse 作为入参,如:

sayHello(HttpServletRequest request, HttpServletResponse response)

有的场景下我们直接使用 HttpServletRequest 和 HttpServletResponse 对象会更灵活,但大多数实际场景下我们用 @RequestParam 直接自动装载参数会更简单一些(这点后面的例子会讲到)。

扫描二维码关注公众号,回复: 130334 查看本文章

然后我们写一个 main 函数并运行它:

package com.terran4j.springboot.web;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class HelloWebApp {

	public static void main(String[] args) {
		SpringApplication.run(HelloWebApp.class, args);
	}

}

运行起来后,我们在浏览器输入URL:

http://localhost:8080/hello?name=terran4j

访问结果为:

Spring Boot MVC 1.png

自动装载请求参数

上面的示例中我们直接从 HttpServletRequest 对象中读取参数,然而Spring MVC提供了更简单的方式,就是通过注解 @RequestParam 自动装载请求参数,如下代码所示:

	// URL示例:  http://localhost:8080/hello2/param?name=terran4j
	@RequestMapping(value = "/hello2/param", method = RequestMethod.GET)
	@ResponseBody
	public String sayHelloByParam(@RequestParam("name") String name) {
		return helloService.hello(name);
	}

代码 @RequestParam("name") String name 的意思是自动读取key为 name 的 HTTP 请求参数,值装载到方法入参 name 中。 但这样写的话,如果HTTP请求中没有 name 参数时Spring MVC就会报错,解决的方法是这样写 @RequestParam(value = "name", defaultValue = "") 意思是给它设置一个默认值,当没有请求参数时就用默认值,默认值为空串。

这种自动装载的方式很智能,它还会根据参数对象的类型自动解析,比如还要一个 count 参数是数字类型的,可以直接定义成Integer 或 Long 类型的,如:

sayHello(@RequestParam("count") Integer count, @RequestParam("name") String name)

Spring MVC 会根据方法入参的类型自动解析成 Integer 类型,当然如果对应的 HTTP 参数不是数字就会报错。

另外,还可以用 @PathVariable 来装载URL路径中的值作为参数,如:

	// URL示例:  http://localhost:8080/hello2/path/terran4j
	@RequestMapping(value = "/hello2/path/{name}", method = RequestMethod.GET)
	@ResponseBody
	public String sayHelloByPath(@PathVariable("name") String name) {
		return helloService.hello(name);
	}

它会自动从URL路径中 /hello2/path/ 后面的一节作为 name 参数的值。 如果读者比较了解 Restful 风格的 API 就知道这种方式非常适合 Restful API 。

注意,方法上一定不能少了 @ResponseBody 注解,它的意思是该方法的返回结果直接写入 HTTP Response Body 中。 至于写入的内容具体是什么,则由 HttpMessageConverter 来决定,Spring MVC中默认的 HttpMessageConverter 是将返回对象自动转成的json串,关于这一点下一节会讲到。

处理复杂类型的返回值

目前为止,我们的返回值只是简单的 String 类型,如果是一个自己定义的复杂的 java bean 会怎么样呢? 我们先自己定义一个名为 HelloBean 的java bean类,代码如下:

package com.terran4j.springboot.web;

import java.util.Date;

public class HelloBean {
	
	private String name;
	
	private String message;
	
	private Date time;

	public final String getName() {
		return name;
	}

	public final void setName(String name) {
		this.name = name;
	}

	public final String getMessage() {
		return message;
	}

	public final void setMessage(String message) {
		this.message = message;
	}

	public final Date getTime() {
		return time;
	}

	public final void setTime(Date time) {
		this.time = time;
	}

	@Override
	public String toString() {
		return "Hello [name=" + name + ", message=" + message + ", time=" + time + "]";
	}
	
}

这个 Bean 中有 name, msg 两个 String 类型的属性,还有 time 这个 Date 类型的属性,以及它们的 getter , setter 方法。

然后我们将之前的Controller类改造成这样:

package com.terran4j.springboot.web;

import java.util.Date;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class HelloController3 {
	
	private static final Logger log = LoggerFactory.getLogger(HelloController3.class);

	@Autowired
	private HelloService helloService;
	
	// URL示例:  http://localhost:8080/hello3?name=terran4j
	@RequestMapping(value = "/hello3", method = RequestMethod.GET)
	public HelloBean sayHello(@RequestParam("name") String name) {
		HelloBean hello = new HelloBean();
		hello.setName(name);
		hello.setMessage(helloService.hello(name));
		hello.setTime(new Date());
		if (log.isInfoEnabled()) {
			log.info("hello bean is: {}", hello);
		}
		return hello;
	}
	
}

注意: 上面代码中,类上的注解从 @Controller 变成 @RestController,而方法的注解少了个 @ResponseBody 。 其实 @RestController = @Controller + @ResponseBody,也就是说有了@RestController之后,相当于每个方法(有@RequestMapping的)自动加上了 @ResponseBody 注解。

上面代码中,方法返回一个 HelloBean 对象,我们运行main程序,然后访问URL:

http://localhost:8080/hello3?name=terran4j

结果如下:

Spring Boot MVC 3.1

可以看到Spring MVC 自动将 java bean 对象转成 json 串返回了。

但这还是有一个问题, 属性 Date time 显示成了 long 类型的时间戳格式,而不是人类容易理解的格式(如“2017-03-09 18:42:00”),还有这个 json 串被压缩成一行很不好看,通常正式的项目返回的数据是比较多的,如果也像这样挤成一坨,那调试程序时看着也费劲啊。

那有没有办法对返回的 json 串结果优化一下呢? 答案显然是有的,下一节我们将讲解如何通过注入自定义的 HttpMessageConverter 将返回值转化成我们希望的json串格式。

自定义返回值转化类 HttpMessageConverter

Spring MVC 在调用 Controller 的方法后,是用 HttpMessageConverter 类来将返回值转化成最终结果并写入到 Http Response Body 中的。 我们可以自己定义一个 HttpMessageConverter 的对象,来代替 Spring MVC 默认提供的 HttpMessageConverter 对象。

如下代码所示:

package com.terran4j.springboot.web;

import java.text.SimpleDateFormat;
import java.util.List;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;

import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.PropertyNamingStrategy;
import com.fasterxml.jackson.databind.SerializationFeature;

@EnableWebMvc
public class HelloWebMvcConfigurer extends WebMvcConfigurerAdapter {
	
	private static final Logger log = LoggerFactory.getLogger(HelloWebMvcConfigurer.class);

	public static final ObjectMapper createObjectMapper() {
		ObjectMapper objectMapper = new ObjectMapper();

		objectMapper.setPropertyNamingStrategy(PropertyNamingStrategy.LOWER_CAMEL_CASE);

		// 属性为空时(包括 null, 空串,空集合,空对象),不参与序列化。
		objectMapper.setSerializationInclusion(JsonInclude.Include.NON_EMPTY);

		// Date 对象在序列化时,格式为 yyyy年MM月dd日 HH时mm分ss秒 。
		objectMapper.setDateFormat(new SimpleDateFormat("yyyy年MM月dd日 HH时mm分ss秒"));

		objectMapper.configure(JsonParser.Feature.ALLOW_COMMENTS, true);
		objectMapper.configure(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES, true);
		objectMapper.configure(JsonParser.Feature.ALLOW_SINGLE_QUOTES, true);
		objectMapper.configure(JsonParser.Feature.ALLOW_UNQUOTED_CONTROL_CHARS, true);

		// json串以良好的格式输出。
		objectMapper.configure(SerializationFeature.INDENT_OUTPUT, true);

		// 当属性为空或有问题时不参与序列化。
		objectMapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);

		// 未知的属性不参与反序列化。
		objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
		
		if (log.isInfoEnabled()) {
			log.info("create objectMapper done.");
		}
		return objectMapper;
	}

	@Override
	public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
		ObjectMapper objectMapper = createObjectMapper();
		MappingJackson2HttpMessageConverter convertor = new MappingJackson2HttpMessageConverter(objectMapper);
		converters.add(0, convertor);
	}

}

首先我们用createObjectMapper()方法自己创建一个 ObjectMapper 对象,ObjectMapper 是由开源项目 Jackson 提供的一个高性能 json 处理工具,它提供了 json 与 java 对象互相转化能力,并且有非常强大的可配置性,比如下面这行代码:

        // Date 对象在序列化时,格式为 yyyy年MM月dd日 HH时mm分ss秒 。
        objectMapper.setDateFormat(new SimpleDateFormat("yyyy年MM月dd日 HH时mm分ss秒"));

它的意思是说,可以把 Date 类型的对象,转成自己希望的日期格式。 当然 ObjectMapper 还有很多配置项,就不一一详述了,有兴趣的朋友们可以自行在网上查找相关资料了解。

然后我们将自定义的 HttpMessageConverter 注入到 Spring MVC中,如下代码所示:

	@Override
	public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
		ObjectMapper objectMapper = createObjectMapper();
		MappingJackson2HttpMessageConverter convertor = new MappingJackson2HttpMessageConverter(objectMapper);
		converters.add(0, convertor);
	}

Spring MVC 中可能有很多 HttpMessageConverter 对象,运行时是从 converters 列表中遍历,直到找到了一个 HttpMessageConverter 对象能处理当前的HTTP请求为止。 我们要把自己的 HttpMessageConverter 对象加到 converters 列表的第1位,以便获得最高优先权,如代码: converters.add(0, convertor); 我们使用了 jackson 项目提供的现成的 MappingJackson2HttpMessageConverter 类(它实现了 HttpMessageConverter 接口),我们只要把自己创建的 ObjectMapper 对象传进去就可以了。

最后,我们用 @Import 注解在主程序中引入这个类,代码如下:

package com.terran4j.springboot.web;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Import;

@Import(HelloWebMvcConfigurer.class)
@SpringBootApplication
public class HelloWebApp {

	public static void main(String[] args) {
		SpringApplication.run(HelloWebApp.class, args);
	}

}

上面的 @Import(HelloWebMvcConfigurer.class) 就引入了我们定义的 HelloWebMvcConfigurer 类到主程序中。

最后,我们运行主程序,在浏览器输入URL:

http://localhost:8080/hello3?name=terran4j

结果如下:

引入 HelloWebMvcConfigurer 之后的结果

与之前的 json 串结果相比,格式变优雅了,Date 的显示格式也易读了: 引入 HelloWebMvcConfigurer 之前的结果

总结说明

本章是《 Spring Boot 快速入门 》系列的第二章,本章我们讲解了如何使用 Spring Boot 开发 Http Restful 服务,下一章我们会讲解在 Spring Boot 中如何访问数据库,欢迎大家继续学习下一章《 Spring Boot JPA 》。

点击 这里 可以查看本系列的全部章节。 (本系列的目标是帮助有 Java 开发经验的程序员们快速掌握使用 Spring Boot 开发的基本技巧,感受到 Spring Boot 的极简开发风格及超爽编程体验。)

另外,我们有一个名为 SpringBoot及微服务 的微信公众号,感兴趣的同学请扫描下面的二维码关注下吧,关注后就可以收到我们定期分享的技术干货哦! SpringBoot及微服务-公众号二维码

猜你喜欢

转载自my.oschina.net/u/3017144/blog/1503450