两大传统艺能学习完毕了,其中Spring Security还需要深入一下,呃,其实是整个Spring都还需要再深入学习。
光有了前边的两大传统艺能,如果还是使用JSP来开发页面,还没有做到前后端分离,还是拿着传统艺能在做传统的事情。
REST具体是什么就不多说了,在Django时代已经用过一次了。现在来看看Spring Web开发里如何使用REST,并且要把原来的增删改查项目,升级到REST风格的项目。
REST的部分将学习如下内容:
- 使用Spring 创建 REST 服务
- REST的理念,JSON和HTTP messaging
- 使用Postman REST客户端工具
- 使用@RestController开发REST API/Web 服务
- 最后使用上述概念写一个CRUD应用
Spring的文档地址:https://docs.spring.io/spring/docs/current/spring-framework-reference/index.html
REST基础概念
REST指的是REpresentational State Transfer,由于都是老Web开发了,具体就不介绍了,可以看其他文章。
REST的数据可以使用XML或者JSON,现在JSON使用的比较普遍也更加现代。
其实REST数据主要是给其他程序用的,而页面是给人看的,记住这点就可以了。
天气网站经常使用REST服务,比如https://openweathermap.org/,可以以api.openweathermap.org/data/2.5/weather?q={city name}
的方式访问这个网站,当然,直接访问不行,还得有API KEY,这只是举一个例子。
通常这个网站会返回一个JSON,里边有所查询的城市的一系列天气的信息,这些信息实际上是给其他程序提供的,如果我们编写一个程序,让用户输入城市名,然后通过JS调用这个API,得到JSON之后展示给用户,此时这个天气网站就变成了我们页面的后端服务。这就是真正的前后端分离,只交换数据,页面不会由后端进行渲染。
在Java中使用JSON
JSON的基础概念对于我们学过前端的,就不细细学习了JSON,一句话,相当于一个键值对的组合。
这里要学的是如何用Java处理Json,由于Java基于类,所以很显然,需要一个POJO对应一种JSON,以便将Json解析成类对象,或者将类对象转换成JSON字符串。我们称之Java POJO和JSON的绑定,用英文来说,一般称作binding,Mapping映射,Serialization/Deserialization序列化/反序列化,Marshalling/Unmarshalling。看到这些单词,基本都是指同样的意思。
Spring使用Jackson Project
来处理Java POJO和JSON的绑定。这个项目的GIT地址在这里。
Jackson Project
的包名是com.fasterxml.jackson.databind,Maven配置如下:
<dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.9.0</version> </dependency>
来通过代码学习一下Jackson的使用,其实用过JSON的都知道,JSON处理库基本上就两个大功能,一个是把数据转换成JSON字符串,一个是把JSON字符串解析成对应的数据类型。
新建一个Maven quickstart项目,然后把Jackson的依赖添加进去。
从JSON中读取数据
在src目录下创建data目录,编写两个JSON文件如下:
不嵌套的sample-lite.json:
{ "id": 14, "firstName": "Mario", "lastName": "Rossi", "active": true }
嵌套的sample-full.json:
{ "id": 14, "firstName": "Mario", "lastName": "Rossi", "active": true, "address": { "street": "100 Main St", "city": "Philadelphia", "state": "Pennsylvania", "zip": "19103", "country": "USA" }, "languages" : ["Java", "C#", "Python", "Javascript"] }
之后第一步是先来创建映射的POJO,先从不嵌套的JSON开始:
package cc.conyli.json; public class Student { private int id; private String firstName; private String lastName; boolean active; public Student() { } public Student(int id, String firstName, String lastName, boolean active) { this.id = id; this.firstName = firstName; this.lastName = lastName; this.active = active; } public int getId() { return id; } public void setId(int id) { this.id = id; } public String getFirstName() { return firstName; } public void setFirstName(String firstName) { this.firstName = firstName; } public String getLastName() { return lastName; } public void setLastName(String lastName) { this.lastName = lastName; } public boolean isActive() { return active; } public void setActive(boolean active) { this.active = active; } @Override public String toString() { return "Student{" + "id=" + id + ", firstName='" + firstName + '\'' + ", lastName='" + lastName + '\'' + ", active=" + active + '}'; } }
类本身很简单,这里要注意的是,类的域名称必须要和JSON字符串里的键名相匹配,在实际操作中,Jackson不像Spring那样装配类,所以不会直接去访问域,而是调用getter和setter方法来写入和读取数据,所以getter和setter方法也要设置好。
然后来使用Jackson从JSON中载入数据到Student对象:
package cc.conyli.json; import com.fasterxml.jackson.databind.ObjectMapper; import java.io.File; public class JsonTest { public static void main(String[] args) { try { // 创建新的映射对象 ObjectMapper mapper = new ObjectMapper(); //尝试载入JSON文件和对应的POJO类 Student student = mapper.readValue(new File("src/data/sample-lite.json"), Student.class); //使用Student对象 System.out.println("From JSON load a student: ---> " + student); } catch (Exception ex) { ex.printStackTrace(); } } }
这个其实是样板代码,需要创建一个映射对象,通过从一个文件对象中和对应的POJO类,自动加载对应的数据。
再来看一下那个嵌套的JSON,实际上JSON字符串中复杂结构映射到Java的类上边只有两种,要么还是一个普通对象,要么是一个List<String>对象。所以其中的address对应一个Address对象,而languages对应一个List<String>对象。知道了这些,就可以编写对应的类了:
package cc.conyli.json; public class Address { private String street; private String city; private String state; private String zip; private String country; public String getStreet() { return street; } public void setStreet(String street) { this.street = street; } public String getCity() { return city; } public void setCity(String city) { this.city = city; } public String getState() { return state; } public void setState(String state) { this.state = state; } public String getZip() { return zip; } public void setZip(String zip) { this.zip = zip; } public String getCountry() { return country; } public void setCountry(String country) { this.country = country; } public Address() { } public Address(String street, String city, String state, String zip, String country) { this.street = street; this.city = city; this.state = state; this.zip = zip; this.country = country; } @Override public String toString() { return "Address{" + "street='" + street + '\'' + ", city='" + city + '\'' + ", state='" + state + '\'' + ", zip='" + zip + '\'' + ", country='" + country + '\'' + '}'; } }
这个是纯POJO,没什么好说的,剩下就是要新建一个StudentComplext类来对应整个JSON,设置两个域为Address类型和List<String>类型。
package cc.conyli.json; import java.util.List; public class StudentComplex { ...... private Address address; private List<String> languages; public Address getAddress() { return address; } public void setAddress(Address address) { this.address = address; } public List<String> getLanguages() { return languages; } public void setLanguages(List<String> languages) { this.languages = languages; } ...... }
也是很简单的一个POJO,其他域、构造器和toString()都省略了。然后来加载这个新的JSON文件。
public class App { public static void main(String[] args) { try { ObjectMapper mapper = new ObjectMapper(); //加载复杂的类 StudentComplex student = mapper.readValue(new File("src/data/sample-full.json"), StudentComplex.class); System.out.println("From lite JSON load a student: ---> " + student); } catch (Exception ex) { ex.printStackTrace(); } } }
这样就完成了将JSON数据加载到对应的POJO类的过程,可见其中的关键是创建合理的POJO类来对应JSON文件。
仅加载需要的JSON数据
在日常开发中经常会遇到,仅需要JSON的部分数据,为此每次都用完整的类读入全部数据,再进行分析有些浪费。
现在我们不再需要JSON中的lastName数据,如果直接尝试把Student类中的lastName字段和getter/setter方法去掉,会报错Unrecognized field "lastName"
。
正确的做法是给Student类加上一个注解如下:
@JsonIgnoreProperties(ignoreUnknown = true) public class Student { private int id; private String firstName; boolean active; ... }
此时再运行,就发现仅取出了所需要的数据。
将POJO转换成JSON字符串
还记得很久之前学过的知识,想要把一个东西进行序列化,需要实现一个标志性接口叫做Serializable。
现在我们修改一下Student类,加上这个接口(代码省略)。Jackson序列化的方式依然操作ObjectMapper
对象,直接调用write方法,传入一个输出流和数据对象即可。
public static void main(String[] args) { try { ObjectMapper mapper = new ObjectMapper(); //新建一个学生数据 Student student = new Student(1, "cony", "li", true); //向指定的流中写入转换后的字符串 mapper.writeValue(System.out, student); } catch (Exception ex) { ex.printStackTrace(); } }
注意.writeValue()方法调用一次之后流就关闭了,无法再写入,写入Map对象的例子如下:
public class App { public static void main(String[] args) { try { ObjectMapper mapper = new ObjectMapper(); //创建Map对象 Map<String, String> address = new HashMap<String, String>(); address.put("street", "100 Main St"); address.put("city", "Philadelphia"); address.put("state", "Pennsylvania"); address.put("zip", "19103"); address.put("country", "USA"); //写入转换后的JSON字符串到输出流 mapper.writeValue(System.out, address); } catch (Exception ex) { ex.printStackTrace(); } } }
单独这么转换JSON字符串没有什么大用处,我们想要的是将字符流写入Http响应对象中,也就是在Spring中使用Jackson。
Spring对于Jackson的支持是集成式的,无需向上边一样手动操作,Spring会自动将传递给REST控制器的JSON字符串转换成对应的POJO,将REST控制器返回的对象转换成JSON字符串,所以先要了解Spring基础的@RestController
,再来看如何将JSON字符串以HTTP响应的形式返回。