Spring Boot从零入门5_五脏俱全的RESTful Web Service构建

Stella981
• 阅读 811

本文属于原创,转载注明出处,欢迎关注微信小程序小白AI博客 和微信公众号别打名名或者网站 https://xiaobaiai.net 或者我的CSDN http://blog.csdn.net/freeape

Spring Boot从零入门5_五脏俱全的RESTful Web Service构建

  • 1 前言

  • 2 名词术语

  • 3 一分钟了解RESTful API

  • 4 MVC/Model 2

  • 5 简单 RESTful Web Service 构建

  • 5.1 功能和API设计

  • 5.2 项目实现

  • 5.3 编译测试

  • 5.4 代码分析

  • 6 五脏俱全的 RESTful WEB Service 构建

  • 6.1 工程实现

  • 6.2 代码分析

  • 6.3 延伸

  • 7 总结

  • 8 参考资料

1 前言

这一节我们正式进入Spring Boot的WEB服务开发,在WEB服务中,不可缺少的我们需要去提供API出来,那么就少不了设计API,而当前流行的一套API设计风格就是REST API ,接下来我们会介绍什么是RESTful API以及它的特点和如何去设计。完成设计后,我们会使用Spring Boot + MVC架构去实现一个RESTful Web Service。本文的所有内容都是经过多方面考察和参考官方资料,本着严谨的态度为自己也为一起学习的同学们负责,由浅入深,层层展开,让自己有不一样的收获。一起加油吧!

2 名词术语

名词术语

释义

RESTful

RESTFUL是一种网络应用程序的设计风格和开发方式,是目前流行的 API 设计规范,用于 Web 数据接口的设计。通过使用事先定义好的接口与不同的服务联系起来,浏览器使用POST,DELETE,PUT和GET四种主要请求方式分别对指定的URL资源进行增删改查操作。因此,RESTful是通过URI实现对资源的管理及访问,具有扩展性强、结构清晰的特点。

Java Web

Java Web有很多成熟的框架,主要可以分为两类Web Application和Web Services。用于Web Application的框架包括官方的Servlet/JSP, JSTL/JSF以及第三方Struts/Spring MVC(action-based)。Web Services的项目又可以分为基于XML的(SOAP/WSDL)的和基于JSON的,Java Communitiy为这两种方式都定义了标准,Java EE5引入了JAX-WS(Java API for XML Web Services)-JSR224,Java EE6引入了JAX-RS(Java API for RESTful Web Services)-JSR331。RESTful Service由于轻量,好测试,有弹性等特点,越来越受欢迎。Jersey,RESTEasy都是JAX-RS标准的具体实现。

JAX-RS

JAX-RS和所有JAVA EE的技术一样,只提供了技术标准,允许各个厂家有自己的实现版本,实现版本有:RESTEasy(JBoss), Jersey(Sun提供的参考实现), Apache CXF, Restlet(最早的REST框架,先于JAX-RS出现), Apache Wink。JAX-RS基于JavaEE的Servlet。标准中定义的注解大大简化资源位置和参数的描述,仅仅使用注解就可以将一个POJO java类封装成一个Web资源。JAX-RS也有类似于Spring依赖注入的方式,减少类之间的耦合度。

3 一分钟了解RESTful API

RESTful 是目前流行的 API 设计规范,用于 Web 数据接口的设计。

RESTful 对 URL 或者 API 的设计总的原则就是将所有操作对象都看作一个资源,操作这个(些)资源(名词)的方法通过 HTTP的方法类型(动词)去实现:

# GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS, TRACEGET:读取(Read)资源信息POST:新建(Create)资源PUT:更新(Update)资源PATCH:更新(Update)资源,通常是部分更新DELETE:删除(Delete)资源

通过对于上述概念的理解,我们举一些常用示例来判断设计是否符合RESTful规范。

POST /api/v1/users/login             # 否,具体分析见后面POST /api/v1/users                   # 是,创建一个新用户GET  /api/v1/users/:username         # 是,获取所有用户信息或者指定用户名的信息DELETE /api/v1/users/:username       # 是,删除所有用户或者删除指定用户GET /api/v1/getUserInfo              # 否,本身就是利用HTTP的方法做动词,无需另外添加

更多的 RESTful API 示例可以参考主流网站的开发API,如码云(https://gitee.com/api/v5/swagger)

「特例讲述:用户登录和登出的 RESTful API 怎么设计呢?」

  1. 登录(login)和登出(logout)是两个动作,本身也是两个动词,因此从表面上看我们是无法设计成RESTful API的
  • POST /api/v1/users/login body参数为username和password,这样既不安全也不是RESTful API
  1. 登录和登出本质上就是获取一个具有时间限定的会话(session),其中保持这个会话的枢纽就是token,而REST中是没有session的,RESTful架构中的原则就是无状态,本身的释义就是状态转移。

  2. 实际在 API 上还有通过 OAuth 来实现授权操作

因为,这里的结论就是登录登出仅作为URL设计,并不作为RESTful API设计。

注意:一些HTTP方法,例如HEAD,GET,OPTIONS和TRACE被定义为安全的,这意味着它们仅用于信息获取,而没有更改服务器的状态。而POST、PUT、DELETE就不是定义为安全的,因为会更新信息状态。关于无状态:无状态意味着每个HTTP请求都是完全隔离的。客户端发出HTTP请求时,它包含服务器完成该请求所需的所有信息。服务器从不依赖先前请求中的信息。如果该信息很重要,则客户端将不得不在后续请求中再次发送该信息。无状态也带来了新功能。在负载平衡的服务器之间分发无状态应用程序更加容易。无状态应用程序也易于缓存。

4 MVC/Model 2

在MVC/Model 2 中, 将Web 应用划分为模型、视图与控制器三个部分:

  • 控制器(Controller)的职责,桥梁

  • 接受请求

  • 验证请求

  • 判断要转发请求给哪个模型

  • 判断要转发请求给哪个视图

  • 模型(Model)的职责

  • 保存应用程式状态

  • 执行应用程序业务逻辑(Business logic)

  • 视图(View)的职责

  • 提取模型状态

  • 执行呈现回应画面

下图框架是Model2的结构。MVC框架有两个版本,一个是Model1,也就是MVC的第一个版本,它的视图中存在着大量的流程控制和代码开发,也就是控制器和视图还具有部分的耦合。

Spring Boot从零入门5_五脏俱全的RESTful Web Service构建

MVC/Model2跟我这一篇的讲述有什么关联呢?因为使用Spring Boot构建WEB应用依赖的就是spring-boot-starter-web,而这个依赖项里就是使用的spring-webmvc,采用MVC结构。接下来我们就讲述如何去创建WEB服务。分两部分来讲述,一部分就是只有控制器和视图的简单RESTful WEB Service,另一部分利用@Service Spring Boot应用中完整的呈现MVC结构。

5 简单 RESTful Web Service 构建

5.1 功能和API设计

我们实现的功能就是对用户实现简单的管理,如查询,新增,删除,更新操作。设计的API如下:

# 获取所有用户信息GET /api/v1/users# 新增一个用户POST /api/v1/users# 删除指定用户DELETE /api/v1/users/{id}# 更新指定用户信息PUT /api/v1/users/{id}

5.2 项目实现

同样地,我们建立一个Spring Starter Project项目,将Spring Boot Starter Web依赖项添加到构建配置文件pom.xml(使用Marven构建)中:

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

添加用户属性类 User.java :

package com.xiaobaiai;public class User { private String id; private String name; public String getId() {  return id; } public void setId(String id) {  this.id = id; } public String getName() {  return name; } public void setName(String name) {  this.name = name; }}

添加控制器类 UserServiceController.java :

package com.xiaobaiai;import java.util.HashMap;import java.util.Map;import org.springframework.http.HttpStatus;import org.springframework.http.ResponseEntity;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.PathVariable;import org.springframework.web.bind.annotation.RequestBody;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RequestMethod;import org.springframework.web.bind.annotation.RestController;@RestController@RequestMapping("/api/v1")public class UserServiceController { private static Map<String, User> userRepo = new HashMap<>(); static {  User ethan = new User();  ethan.setId("1");  ethan.setName("Ethan");  userRepo.put(ethan.getId(), ethan);  User xiaoming = new User();  xiaoming.setId("2");  xiaoming.setName("Xiaoming");  userRepo.put(xiaoming.getId(), xiaoming); } @RequestMapping(value = "/users/{id}", method = RequestMethod.DELETE) public ResponseEntity<Object> delete(@PathVariable("id") String id) {  userRepo.remove(id);  return new ResponseEntity<>("User is deleted successsfully", HttpStatus.OK); } @RequestMapping(value = "/users/{id}", method = RequestMethod.PUT) public ResponseEntity<Object> updateUser(@PathVariable("id") String id, @RequestBody User user) {  userRepo.remove(id);  user.setId(id);  userRepo.put(id, user);  return new ResponseEntity<>("User is updated successsfully", HttpStatus.OK); } @RequestMapping(value = "/users", method = RequestMethod.POST) public ResponseEntity<Object> createUser(@RequestBody User user) {  userRepo.put(user.getId(), user);  return new ResponseEntity<>("User is created successfully", HttpStatus.CREATED); } @GetMapping(value = "/users") public ResponseEntity<Object> getUser() {  return new ResponseEntity<>(userRepo.values(), HttpStatus.OK); }}

添加应用类 Test05HelloworldApplication.java

package com.xiaobaiai;import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;@SpringBootApplicationpublic class Test05HelloworldApplication { public static void main(String[] args) {  SpringApplication.run(Test05HelloworldApplication.class, args); }}

5.3 编译测试

编译启动,应用实际 RESTful API 有:

# 获取所有用户信息GET http://localhost:8080/api/v1/users# 新增一个用户,参数通过body传递POST http://localhost:8080/api/v1/users# 更新一个用户信息PUT http://localhost:8080/api/v1/users/{id}# 删除指定用户DELETE http://localhost:8080/api/v1/users/{id}

利用POSTMAN可以测试接口的功能运转:

Spring Boot从零入门5_五脏俱全的RESTful Web Service构建

Spring Boot从零入门5_五脏俱全的RESTful Web Service构建

5.4 代码分析

在《Spring Boot从零入门3_创建Hello World及项目剖析》我们就分析过代码,我们这里还是回顾下,我们知道 @SpringBootApplication = @Configuration + @ComponentScan + @EnableAutoConfiguration@ComponentScan扫描所有类Component,如Controller,Service,Repository。而@EnableAutoConfiguration将自动解析视图(views),视图解析器(view resolvers)等。@RestController是Spring4之后加入的注解,原来在@Controller中返回json需要@ResponseBody来配合,如果直接用@RestController替代@Controller就不需要再配置@ResponseBody,默认返回json格式。@RequestMapping用来配置url映射,现在更多的也会直接用以Http Method直接关联的映射注解来定义,比如:GetMappingPostMappingDeleteMappingPutMapping等,@RequestMapping可以映射到整个类或特定的处理方法上,通常,类级别的注解将特定的请求路径(或路径模式)映射到表单控制器上,其他方法级别的注解作用特定的HTTP请求方法。

在控制器代码里,通过@RequestMapping/api/v1映射到整个User控制器上,User控制器里具体的方法则由@RequestMapping作用到指定的HTTP请求方法上,即用户的增删查改。具体的@RequestMapping参数设置就不展开了,后面博文会专门讲述常用注解的作用和使用。

6 五脏俱全的 RESTful WEB Service 构建

6.1 工程实现

上面的简单RESTful WEB Service构建,直接通过Controller去访问和操作数据的,对于MVC结构,显然缺少了Model专门来处理数据,对业务的逻辑处理隔离度也不够,简单的WEB服务需求缺少Model也是可以的,毕竟这样设计不是很复杂,不过业务大了,我们需要尽量降低业务逻辑与上层视图的耦合度,增加模块的可重用性。下面我们来实现MVC结构。

首先我们创建一个业务操作接口 UserService.java 包括对用户的增删查改 :

package com.xiaobaiai;import java.util.Collection;public interface UserService { public abstract void createUser(User user); public abstract void updateUser(String id, User user); public abstract void deleteUser(String id); public abstract Collection<User> getUsers();}

通过@Service创建一个组件,用于在与@RestController类文件分开的不同层中编写业务逻辑,即对用户的增删查改的业务逻辑实现:

package com.xiaobaiai;import java.util.Collection;import java.util.HashMap;import java.util.Map;import org.springframework.stereotype.Service;@Servicepublic class UserServiceImpl implements UserService {    private static Map<String, User> userRepo = new HashMap<>();    static {        User ethan = new User();        ethan.setId("1");        ethan.setName("Ethan");        userRepo.put(ethan.getId(), ethan);        User xiaoming = new User();        xiaoming.setId("2");        xiaoming.setName("Xiaoming");        userRepo.put(xiaoming.getId(), xiaoming);    }        @Override    public void createUser(User user) {        // TODO Auto-generated method stub        userRepo.put(user.getId(), user);    }    @Override    public void updateUser(String id, User user) {        // TODO Auto-generated method stub        userRepo.remove(id);        user.setId(id);        userRepo.put(id, user);    }    @Override    public void deleteUser(String id) {        // TODO Auto-generated method stub        userRepo.remove(id);    }    @Override    public Collection<User> getUsers() {        // TODO Auto-generated method stub        return userRepo.values();    }}

最后在控制器类里使用@Autowired将Service接口与实现组装起来。

package com.xiaobaiai;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.http.HttpStatus;import org.springframework.http.ResponseEntity;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.PathVariable;import org.springframework.web.bind.annotation.RequestBody;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RequestMethod;import org.springframework.web.bind.annotation.RestController;@RestController@RequestMapping("/api/v1")public class UserServiceController { @Autowired UserService userService; @RequestMapping(value = "/users/{id}", method = RequestMethod.DELETE) public ResponseEntity<Object> delete(@PathVariable("id") String id) {  userService.deleteUser(id);  return new ResponseEntity<>("User is deleted successsfully", HttpStatus.OK); } @RequestMapping(value = "/users/{id}", method = RequestMethod.PUT) public ResponseEntity<Object> updateUser(@PathVariable("id") String id, @RequestBody User user) {  userService.updateUser(id, user);  return new ResponseEntity<>("User is updated successsfully", HttpStatus.OK); } @RequestMapping(value = "/users", method = RequestMethod.POST) public ResponseEntity<Object> createUser(@RequestBody User user) {  userService.createUser(user);  return new ResponseEntity<>("User is created successfully", HttpStatus.CREATED); } @GetMapping(value = "/users") public ResponseEntity<Object> getUser() {  return new ResponseEntity<>(userService.getUsers(), HttpStatus.OK); }}

6.2 代码分析

相对只有控制器的WEB服务,这里加入了@Service实现的Model层,而UserService接口的实体是通过@Autowired连接起来的,即:

@AutowiredUserService userService;

当然我们还有一种主流写法就是通过构造函数的形式,最后达到的效果是一样的:

UserService userService;;@Autowiredpublic UserServiceController(UserService userService) {    this.userService = userService;}

「如果UserService有多个实体类,这个时候@Autowired如何去绑定实体类的呢?」

这个时候需要用到@Qualifier来指定实体类的名称:

@Service// 指定UserServiceImpl名称为a@Qualifier("a")public class UserServiceImpl implements UserService { }

@Service@Qualifier("b")public class UserServiceImpl1_1 implements UserService { }

最后在控制器里自动组装的时候指定具体的实体类名称就可以了:

// 写法1@Autowired@Qualifier("a")UserService userService;// 写法2UserService userService;@Autowiredpublic UserServiceController(@Qualifier("a") UserService userService) {    this.userService = userService;}

6.3 延伸

@Resource也能够实现自动装配Bean的功能,那@Autowired@Resource有什么区别呢?

  • @Autowired与@Resource都可以用来装配bean. 都可以写在字段上,或写在setter方法上

  • @Autowired默认按类型装配,是spring支持的注解,默认情况下必须要求依赖实体类必须存在,如果要允许null值,可以设置它的required属性为false

  • 想使用名称装配可以结合@Qualifier注解进行使用

  • @Resource 是JDK1.6支持的注解,默认按照名称进行装配,名称可以通过name属性进行指定,如果没有指定name属性,当注解写在字段上时,默认取字段名,按照名称查找,如果注解写在setter方法上默认取属性名进行装配。当找不到与名称匹配的bean时才按照类型进行装配。如果name属性一旦指定,就只会按照名称进行装配

有了比较完善的控制器和Model,那么对于View层有什么专用模板引擎吗?

Thymeleaf是基于Java用于创建Web应用程序的的模板引擎。它为在Web应用程序中提供XHTML / HTML5提供了良好的支持。类似的还有Apache FreeMarkerMustacheGroovy Templates。后面博文会详解介绍。

7 总结

通过对RESTful API的介绍以及结合实例工程,我们基本了解了一个RESTful WEB ServiceSpring Boot框架下是怎么实现的。通过对工程代码的分析,让我们对@Autowired@ResourceQualifier等注解也有了实质了解。感觉现在正式步入JAVA WEB的开发。接下来,继续!

8 参考资料

文章难免疏漏,而公众号上每篇文章只能修改几个字,所以我会在我的小程序小白AI博客上也发一遍,任何的错误,我会在及时修改,欢迎随时交流。

关注我的公众号,获取最新学习分享:

Spring Boot从零入门5_五脏俱全的RESTful Web Service构建

本文分享自微信公众号 - 别打名名(biedamingming)。
如有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。

点赞
收藏
评论区
推荐文章
blmius blmius
3年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
待兔 待兔
4个月前
手写Java HashMap源码
HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22
Jacquelyn38 Jacquelyn38
3年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
Stella981 Stella981
3年前
Spring Boot从零入门1_详述
本文属于原创,转载注明出处,欢迎关注微信小程序小白AI博客和微信公众号别打名名或者网站https://xiaobaiai.net或者我的CSDNhttp://blog.csdn.net/freeape!(https://oscimg.oschina.net/oscnet/f8db20c4f3964337b76dc956a3a8
Wesley13 Wesley13
3年前
MQTT安全性设计详解
本文属于原创,转载注明出处,欢迎关注微信小程序小白AI博客微信公众号别打名名或者网站https://xiaobaiai.net或者我的CSDNhttp://blog.csdn.net/freeape!(https://oscimg.oschina.net/oscnet/ab92552a6bd64ed6a957d1854b677
Stella981 Stella981
3年前
Spring Boot从零入门6_Swagger2生成生产环境中REST API文档
本文属于原创,转载注明出处,欢迎关注微信小程序小白AI博客和微信公众号别打名名或者网站https://xiaobaiai.net或者我的CSDNhttp://blog.csdn.net/freeape!(https://oscimg.oschina.net/oscnet/cc3a3a51382349e5ab55c2a62f01
Stella981 Stella981
3年前
Spring Boot从零入门3_创建Hello World及项目剖析
本文属于原创,转载注明出处,欢迎关注微信小程序小白AI博客和微信公众号别打名名或者网站https://xiaobaiai.net或者我的CSDNhttp://blog.csdn.net/freeape!(https://oscimg.oschina.net/oscnet/89944effaec841a9b6bad0cce8cf
Stella981 Stella981
3年前
Spring Boot从零入门2_核心模块详述和开发环境搭建
本文属于原创,转载注明出处,欢迎关注微信小程序小白AI博客和微信公众号别打名名或者网站https://xiaobaiai.net或者我的CSDNhttp://blog.csdn.net/freeape!(https://oscimg.oschina.net/oscnet/824604023db348fd99425746632b
Wesley13 Wesley13
3年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
Python进阶者 Python进阶者
10个月前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这