java api接口开发框架_3.使用Spring MVC开发RESTful API(一)

前言

使用SpringMVC 开发RESTful API主要讲解一下内容

1. 使用Spring MVC编写Restful API

2.使用Spring MVC处理其他web应用常见的需求和场景

3.Restful API开发常用辅助框架(swagger,MockMvc)

1.使用Spring MVC编写Restful API

1.1 Restful简介

1.1.1 传统接口和Restful API对比

增删查改传统和Restful API的URL对比

传统

Restful API

查询

/user/query?name=Jack

GET

/user?name=Jack

GET

详情

/user/getInfo?id=1

GET

/user/1

GET

创建

/user/create?name=Jack

POST

/user

POST

修改

/user/update?id=1&name=Jack

POST

/user/1

PUT

删除

/user/delete?id=1

GET

/user/1

DELETE

增删查改传统和Restful API的特点对比

传统

Restful API

用URL描述行为(分别带有操作动词:通过这些动词知道行为)

用URL描述资源(url上看不到行为:上面详情、修改、删除都是对id=1的用户;用户id为1的用户对系统来说是一个资源)

行为描述用url动词,http结果不管成功失败都是返回json,也许状态码都是200

用HTTP方法描述行为(用GET、POST、PUT、DELETE描述行为),使用HTTP状态码来标识不同结果

url上使用键值对传递参数较多

使用json交互数据

Restful API只是一种风格,并不是强制标准

1.1.2 Rest成熟度模型

一下模型中,把Restful成熟度分为了4级。0-3,数字越大级别越高 越来满足此模型

842a0ffdd9576a697f35bb95acc9404c.png

使用HTTP作为传输方式,不是http传输就不是restful API。

引入资源概念,每个资源都有对应url;restful API是用URL描述资源,请求接口中无动作。

使用HTTP方法进行不同操作、使用HTTP状态码表示不同结果。

超媒体控制:在资源的表达中包含了链接信息。这种规范在大部分工作中很难达到,一般满足到level2。

1.2 查询请求

编写Restful API需要编写以下内容:

编写针对Restful API测试用例(使用web浏览器地址栏是检验不了PUT、post)

使用注解声明Restful API

在Restful API中传递参数

1.2.1 编写针对Restful API测试用例

首先需要引入测试依赖;

org.springframework.boot

spring-boot-starter-test

我们有时候执行:mvn clean install时候下载不下来对应依赖时候,我们在本地依赖仓库删除所依赖,然后重新执行:mvn clean install

在test包下创建测试类:

/*

* 使用SpringRunner类运行测试用例

*/

@RunWith(SpringRunner.class)

@SpringBootTest

public class UserControllerTest {

/*

* 伪造mvc环境 伪造的环境不会真正去启动tomcat

*/ @Autowired

private WebApplicationContext wac;

private MockMvc mockMvc;

/*

* 每次执行测试用例前执行这个方法

*/

@Before

public void setup(){

/*

* 构造mvc环境

*/

mockMvc=MockMvcBuilders.webAppContextSetup(wac).build(); }

@Test

public void whenQuerySuccess() throws Exception{

//发送一个url为/user的GET请求

mockMvc.perform(MockMvcRequestBuilders.get("/user")//请求user

.param("username","Jack")//传递请求参数

.contentType(MediaType.APPLICATION\_JSON))//发送请求类型:application-json

.andExpect(MockMvcResultMatchers.status().isOk())//结果执行的期望\-返回状态码isOk

.andExpect(MockMvcResultMatchers.jsonPath("$.length()").value(3));//期望返回结果为3

}

}

1.2.2 使用注解声明Restful API

@RestController 标明此controller提供RestAPI;是@Controller 和@ResponseBody组合。

@RequestMapping及其变体。映射http请求url到java方法。

@RequestParam 映射请求参数到java方法的参数。

@PageableDefault指定分页参数默认值。

创建一个参数非必传的方法:

@RestController

public class UserController {

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

public List user(@RequestParam(name="username",required = false,defaultValue = "Linda") String username){

User user = new User();

List users = Arrays.asList(user, user, user);

return users;

}

}

正常运行测试用例:success

创建一个多条件封装(对象)查询的方法

创建查询条件封装:

public class UserQueryCondition {

private String username;

private int age;

//getter setter省略

}

test方法中多参数传递:

@Test

public void whenQuerySuccess() throws Exception{

//发送一个url为/user的GET请求

mockMvc.perform(MockMvcRequestBuilders.get("/user")//请求user

.param("username","Jack")//传递请求参数

.param("age","18")

.contentType(MediaType.APPLICATION\_JSON))//发送请求类型:application-json

.andExpect(MockMvcResultMatchers.status().isOk())//结果执行的期望返回状态码isOk

.andExpect(MockMvcResultMatchers.jsonPath("$.length()").value(3));//期望返回结果为3

}

后端UserController

运行测试用例:

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

public List user(UserQueryCondition condition){

//使用反射工具 System.out.println(ReflectionToStringBuilder.toString(condition, ToStringStyle.MULTI_LINE_STYLE));

User user = new User();

List users = Arrays.asList(user, user, user);

return users;

}

后端输出:

2020-02-20 08:29:19.120 INFO 18416 --- [ main] o.s.t.web.servlet.TestDispatcherServlet : FrameworkServlet '': initialization completed in 37 ms

com.yxm.security.dto.UserQueryCondition@1c3d9e28[

username=Jack

age=18

]

使用@PageableDefault注解的方法

注意:PageableDefault注解是在:org.springframework.data.domain.Pageable

@RestController

public class UserController {

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

public List user(UserQueryCondition condition, @PageableDefault(page = 1,size = 10,sort = "username,asc") Pageable pageable){

//使用反射工具

System.out.println(ReflectionToStringBuilder.toString(condition, ToStringStyle.MULTI_LINE_STYLE));

System.out.println(pageable.getPageSize());

System.out.println(pageable.getPageNumber());

System.out.println(pageable.getSort());

User user = new User();

List users = Arrays.asList(user, user, user);

return users;

}

}

测试请求方法:

@Test

public void whenQuerySuccess() throws Exception{

//发送一个url为/user的GET请求

mockMvc.perform(MockMvcRequestBuilders.get("/user")//请求user

.param("username","Jack")//传递请求参数

.param("age","18")

.param("size", "15")

.param("page", "3")

.param("sort", "age,desc")

.contentType(MediaType.APPLICATION\_JSON))//发送请求类型:application-json

.andExpect(MockMvcResultMatchers.status().isOk())//结果执行的期望\-返回状态码isOk

.andExpect(MockMvcResultMatchers.jsonPath("$.length()").value(3));//期望返回结果为3

}

jsonPath详解

1.3 详情请求

讲解:

@PathVariable 映射url片段到java方法的参数

在url声明中使用正则表达式来规范url的模式

@JsonView控制json输出内容

1.3.1 @PathVariable 映射url片段到java方法的参数

测试用例:

@Test

public void whenDetailInfoSuccess() throws Exception{

//发送一个url为/user/1 的GET请求

mockMvc.perform(MockMvcRequestBuilders.get("/user/1")

.contentType(MediaType.APPLICATION\_JSON\_UTF8))

.andExpect(MockMvcResultMatchers.status().isOk())

.andExpect(MockMvcResultMatchers.jsonPath("$.username")

.value("Jack"));

}

后端Restful API接口

@RequestMapping(value \= "/user/{id}",method \= RequestMethod.GET)

public User DetailInfo(@PathVariable(name \= "id") String xxx){

User user = new User();

user.setUsername("Jack");

return user;

}

1.3.2 在url声明中使用正则表达式来规范url的模式

有时候我们需要在请求的参数中检验url中参数是否符合特定格式要求。比如:我们上面获取用户详情接口是id必须是数字。

创建测试用例,返回的应该是400错误

@Test

public void whenDetailInfoFail() throws Exception{

mockMvc.perform(MockMvcRequestBuilders.get("/user/a")

.contentType(MediaType.APPLICATION_JSON_UTF8))

.andExpect(MockMvcResultMatchers.status().is4xxClientError());

}

运行测试用例(后端代码如上):

服务端返回了200

客户端报错了,这和我们Restful API违背

8910f84461f996982940ed193daa9534.png

@RequestMapping(value = "/user/{id:\\d+}",method = RequestMethod.GET)

public User DetailInfo(@PathVariable(name = "id") String xxx){

User user = new User();

user.setUsername("Jack");

return user;

}

此时运行测试用例:whenDetailInfoFail测试用例通过,服务端返回4xx

1.3.3 @JsonView控制json输出内容

1.3.3.1 @JsonView注解使用场景

我们在列表查询时候为了安全考虑不显示用户的密码,但是在用户详情查询(可能具备密码校验)需要返回password密码字段。

控制返回同一个对象时候,在不同条件下返回不同的视图对象。

1.3.3.2 @JsonView注解使用步骤

使用接口声明多个视图

在值对象的GET方法上指定视图

在controller方法上指定视图

import com.fasterxml.jackson.annotation.JsonView;

public class User {

/*

*使用接口声明多个视图

*/

public interface UserSimpleView{};

public interface UserDetailView extends UserSimpleView{};

private String username;

private String password;

//在值对象的GET方法上指定视图

@JsonView(UserSimpleView.class)

public String getUsername() {

return username;

}

@JsonView(UserDetailView.class)

public String getPassword() {

return password;

}

public void setUsername(String username) {

this.username = username;

}

public void setPassword(String password) {

this.password = password;

}

}

controller处理:

@RestController

public class UserController {

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

@JsonView(User.UserSimpleView.class)

public List user(UserQueryCondition condition, @PageableDefault(page = 1,size = 10,sort = "username,asc") Pageable pageable){

//使用反射工具

System.out.println(ReflectionToStringBuilder.toString(condition, ToStringStyle.MULTI_LINE_STYLE));

System.out.println(pageable.getPageSize());

System.out.println(pageable.getPageNumber());

System.out.println(pageable.getSort());

User user = new User();

List users = Arrays.asList(user, user, user);

return users;

}

@RequestMapping(value = "/user/{id:\\d+}",method = RequestMethod.GET)

@JsonView(User.UserDetailView.class)

public User DetailInfo(@PathVariable(name = "id") String xxx){

User user = new User();

user.setUsername("Jack");

return user;

}

}

测试用例中打印出结果详情:

@Test

public void whenQuerySuccess() throws Exception{

//发送一个url为/user的GET请求

String result = mockMvc.perform(MockMvcRequestBuilders.get("/user")//请求user

.param("username","Jack")//传递请求参数

.param("age","18")

.param("size", "15")

.param("page", "3")

.param("sort", "age,desc")

.contentType(MediaType.APPLICATION_JSON))//发送请求类型:application-json

.andExpect(MockMvcResultMatchers.status().isOk())//结果执行的期望返回状态码isOk

.andExpect(MockMvcResultMatchers.jsonPath("$.length()").value(3))//期望返回结果为3

.andReturn().getResponse().getContentAsString();

System.out.println("whenQuerySuccess:"+result);

//结果输出:whenQuerySuccess:[{"username":null},{"username":null},{"username":null}]

}

@Test

public void whenDetailInfoSuccess() throws Exception{

//发送一个url为/user/1 的GET请求

String result = mockMvc.perform(MockMvcRequestBuilders.get("/user/1")

.contentType(MediaType.APPLICATION_JSON_UTF8))

.andExpect(MockMvcResultMatchers.status().isOk())

.andExpect(MockMvcResultMatchers.jsonPath("$.username")

.value("Jack"))

.andReturn().getResponse().getContentAsString();

System.out.println("whenDetailInfoSuccess:"+result);

//输出结果:whenDetailInfoSuccess:{"username":"Jack","password":null}

}

1.4 创建请求

讲解:

@RequestBody 映射请求体到java方法的参数

日期类型参数处理

@Valid注解和BingingResult验证请求参数的合法性并处理校验结果

写任何方法时候我们以测试用例为入口,先写测试用例,再写对应方法实现。

1.user

public class User {

public interface UserSimpleView{};

public interface UserDetailView extends UserSimpleView{};

private String id;

private String username;

@NotBlank

private String password;

private Date birthday;

@JsonView(UserSimpleView.class)

public Date getBirthday() {

return birthday;

}

@JsonView(UserSimpleView.class)

public String getId() {

return id;

}

//在值对象的GET方法上指定视图

@JsonView(UserSimpleView.class)

public String getUsername() {

return username;

}

@JsonView(UserDetailView.class)

public String getPassword() {

return password;

}

}

1.Test

@Test

public void whenCreateSuccess() throws Exception{

long time = new Date().getTime();

String content = "{\"username\\":\"Jack\",\"password\":null,\"birthday\":"+time+"}";

String resut = mockMvc.perform(MockMvcRequestBuilders.post("/user")

.contentType(MediaType.APPLICATION_JSON_UTF8)

.content(content)).andExpect(MockMvcResultMatchers.status().isOk())

.andExpect(MockMvcResultMatchers.jsonPath("$.id").value("1"))//返回的json对象属性id值为1

.andReturn().getResponse().getContentAsString();

System.out.println("Test:"+resut);

//Test:{"id":"1","username":"Jack","password":null,"birthday":1582190517178}

}

3. create接口

/**

* 接口日期常见处理:

* 1.通过日期格式化转换,比如: yyyy-MM-dd HH:mm:ss

* 但是当我们多个终端:web、app、第三方同时调用这个接口时候:并且在每个终端显示的格式可能不一样

* app端显示时分秒、web端显示年月日

* 2.所有参数传递时候:不用指定格式时间传递,那传什么呢?传时间戳

* 时间戳是一个精确到毫秒的值,前端拿到时间戳之后决定怎样展示。

* @param user

* @return

*/

@PostMapping

public User create(@Valid @RequestBody User user, BindingResult errors){

/**

* 参数校验:最常用方式:

* 1.自己写代码校验:自己写 非常繁琐 可能 有时候有代码重复修改

* 2.java发展到现在,其实常见的都有现有框架组件去解决的:在对象属性上添加注解:@Valid会根据参数对象属性注解进行校验;

*

*上面注解添加@Valid在我们进行参数校验时候,如果没有过的话直接打回来返回4xx错误码(Restful API就是按照code);有时候参数没有校验通过的时候,我们也是需要进入方法体做一些处理的

* 此时用到注解:BingingResult

*

* BingingResult类需要跟@Valid配合的

*/

if(errors.hasErrors()){

errors.getAllErrors().stream().forEach(error->System.out.println(error.getDefaultMessage()));

//may not be empty

}

//使用反射工具

System.out.println("create:"+ReflectionToStringBuilder.toString(user, ToStringStyle.MULTI_LINE_STYLE));

//create:com.yxm.security.dto.User@6ffdbeef[

// id=

// username=Jack

// password=

// birthday=Thu Feb 20 17:21:57 CST 2020

//]

user.setId("1");

return user;

}

1.5 修改和删除请求

用户修改和删除接口;主要涉及到:

常见验证注解

自定义错误处理消息

自定义校验注解

上面讲到,@Valid会根据修饰参数对象的属性上的注解校验规则来校验,并将校验后的结果封装到:BindingResult errors中去,作为方法参数传进来。

1.51.常见验证注解(Hibernate Validator)

常见验证注解主要指代:Hibernate Validator。

其注解可以参考:https://blog.csdn.net/danielz...

我们在开发修改和删除的Restful API接口引入注解校验只是,与新增类似,我们先I写测试用例,然后运行后再写代码(原则:测试用例也是代码,我们需要先保证测试用例代码先跑起来,说明测试用例代码没错才能起到真正校验能力),报405说明请求method不支持。

书写测试用例:

@Test

public void whenUpdateSuccess() throws Exception{

//与创建区别:创建时候是post请求,我们修改时候用put请求,创建时候是没有id的但是修改时候需要根据id去修改,所以有id

Date date = new Date(LocalDateTime.now().plusYears(1).atZone(ZoneId.systemDefault()).toInstant().toEpochMilli());//定义未来时间作为生日过去时间的校验

System.out.println(date);

String content = "{\"id\":\"1\",\"username\":\"Jack\",\"password\":null,\"birthday\":"+date.getTime()+"}";

String resut = mockMvc.perform(MockMvcRequestBuilders.put("/user/1")//针对id为1的用户进行修改

.contentType(MediaType.APPLICATION_JSON_UTF8)

.content(content)).andExpect(MockMvcResultMatchers.status().isOk())

.andExpect(MockMvcResultMatchers.jsonPath("$.id").value("1"))//返回的json对象属性id值为1

.andReturn().getResponse().getContentAsString();

System.out.println("Test:"+resut);

//Test:{"id":"1","username":"Jack","password":null,"birthday":1582190517178}

}

书写controller接口并运行测试用例后输出:

f5c415c7193225447d0c7bd8e8f817b0.png

上面输出的参数校验结果,一:是英文的;二是:不便于我们封装。

我们可以在对象属性的校验注解里面的message属性添加我们的校验结果。

public class User {

/*

* 使用接口声明多个视图

*/

public interface UserSimpleView{};

public interface UserDetailView extends UserSimpleView{};

private String id;

private String username;

@NotBlank(message = "密码不能为空")

private String password;

@Past(message = "生日必须是过去的时间")

private Date birthday;//声明生日是过去时间

}

在很多情况下,默认的Hibernate Validator提供的注解并不能满足我们的需求,不能满足我们的业务逻辑,有时候我们的业务逻辑是有复杂数据的:比如这个订单在数据库中存在与否,是不是重复的?这些并不是简单判断传过来的值就可以了,还要做一些其他东西,所以我们需要自个去写一些校验的逻辑。自个去写的逻辑怎样用注解去标识呢?

定义注解:MyConstraint

@Target({ElementType.METHOD,ElementType.FIELD})

@Retention(RetentionPolicy.RUNTIME)

@Constraint(validatedBy = MyConstraintValidator.class)//定义当前注解用什么类去校验;把校验的逻辑写到某个类里

public @interface MyConstraint {

/**

* 参考Hibernate Validator相关注解我们知道 校验类的相关注解需要添加基本的三个属性

*/

String message() default "{org.hibernate.validator.constraints.NotBlank.message}";

Class>[] groups() default {};

Class extends Payload>[] payload() default {};

}

定义注解逻辑检验处理类:

public class MyConstraintValidator implements ConstraintValidator {

/**

* 注意:

* 1.ConstraintValidator A-指代注解 T-指代注解A所修饰属性的类型;我们定义成Object

* 2.我们可以把spring的bean通过Autowire注解引入到此类里面的成员变量

* 3.在MyConstraintValidator类上不用添加@Component注解,因为实现ConstraintValidator的类会自动被spring管理

* @param myConstraint

*/

/* @Autowired

private HelloService helloService;*/

@Override

public void initialize(MyConstraint myConstraint) {

System.out.println("my ConstraintValidator init");

}

@Override

public boolean isValid(Object o, ConstraintValidatorContext context) {

System.out.println(o);

return false;

}

}

3.在user实体上使用:

@MyConstraint(message = "这是一个测试")

private String username;

4.我们运行测试用例(由于我们的注解校验类的isValid是一个返回false的方法,所以会始终输出message):

生日必须是过去的时间

这是一个测试

密码不能为空

删除接口:

Test

public void whenDeleteSuccess() throws Exception{

mockMvc.perform(MockMvcRequestBuilders.delete("/user/1")//针对id为1的用户进行修改

.contentType(MediaType.APPLICATION_JSON_UTF8))

.andExpect(MockMvcResultMatchers.status().isOk());

}

后端接口:

@DeleteMapping("/{id:\\d+}")

public void delete(@PathVariable String id) {

System.out.println("update:" + id);

}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值