JAVA服务接口风格_基于springboot创建RESTful风格接口

本文介绍了如何基于SpringBoot创建遵循RESTful风格的API。内容包括查询请求的实现,使用注解声明RESTful API,单元测试,使用JsonView控制JSON输出,处理创建请求,日期参数处理,请求参数的校验以及更新和删除操作。此外,还讨论了Spring Boot的错误处理机制和自定义异常处理。
摘要由CSDN通过智能技术生成

基于springboot创建RESTful风格接口

RESTful API风格

AAffA0nNPuCLAAAAAElFTkSuQmCC

restfulAPI.png

特点:URL描述资源

使用HTTP方法描述行为。使用HTTP状态码来表示不同的结果

使用json交互数据

RESTful只是一种风格,并不是强制的标准

AAffA0nNPuCLAAAAAElFTkSuQmCC

REST成熟度模型.png

一、查询请求

1.编写单元测试@RunWith(SpringRunner.class)@SpringBootTestpublic class UserControllerTest {

@Autowired

private WebApplicationContext wac;

private MockMvc mockMvc;

@Before

public void setup() {

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

}

//查询

@Test

public void whenQuerySuccess() throws Exception {

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

.param("username", "jojo")

.param("age", "18")

.param("ageTo", "60")

.param("xxx", "yyy")//              .param("size", "15")//              .param("sort", "age,desc")

.contentType(MediaType.APPLICATION_JSON_UTF8))

.andExpect(status().isOk())

.andExpect(jsonPath("$.length()").value(3))

.andReturn().getResponse().getContentAsString();//将服务器返回的json字符串当成变量返回

System.out.println(result);//[{"username":null},{"username":null},{"username":null}]

}

}

2.使用注解声明RestfulAPI

常用注解@RestController 标明此Controller提供RestAPI@RequestMapping及其变体。映射http请求url到java方法@RequestParam 映射请求参数到java方法的参数@PageableDefault 指定分页参数默认值@PathVariable 映射url片段到java方法的参数

在url声明中使用正则表达式@JsonView控制json输出内容

查询请求:@RestControllerpublic class UserController {

@RequestMapping(value="/user",method=RequestMethod.GET)    public List query(@RequestParam(name="username",required=false,defaultValue="tom") String username){

System.out.println(username);

List users = new ArrayList<>();

users.add(new User());

users.add(new User());

users.add(new User());        return users;

}

}

①当前端传递的参数和后台自己定义的参数不一致时,可以使用name属性来标记:(@RequestParam(name="username",required=false,defaultValue="hcx") String nickname

②前端不传参数时,使用默认值 defaultValue="hcx"

③当查询参数很多时,可以使用对象接收

④使用Pageable作为参数接收,前台可以传递分页相关参数

pageSize,pageNumber,sort;

也可以使用@PageableDefault指定默认的参数值。@PageableDefault(page=2,size=17,sort="username,asc")//查询第二页,查询17条,按照用户名升序排列

3.jsonPath表达式书写

AAffA0nNPuCLAAAAAElFTkSuQmCC

jsonpath表达式.png

AAffA0nNPuCLAAAAAElFTkSuQmCC

打包发布maven项目.png

二、编写用户详情服务

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

在url声明中使用正则表达式

@JsonView控制json输出内容

单元测试:@RunWith(SpringRunner.class)@SpringBootTestpublic class UserControllerTest {

@Autowired

private WebApplicationContext wac;

private MockMvc mockMvc;

@Before

public void setup() {

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

}

//获取用户详情

@Test

public void whenGetInfoSuccess() throws Exception {

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

.contentType(MediaType.APPLICATION_JSON_UTF8))

.andExpect(status().isOk())

.andExpect(jsonPath("$.username").value("tom"))

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

System.out.println(result); //{"username":"tom","password":null}

}

后台代码:@RestController@RequestMapping("/user")//在类上声明了/user,在方法中就可以省略了public class UserController {    @RequestMapping(value="/user/{id}",method=RequestMethod.GET)    public User getInfo(@PathVariable String id) {

User user = new User();

user.setUsername("tom");            return user;

}

当希望对传递进来的参数作一些限制时,可以使用正则表达式://测试提交错误信息@Testpublic void whenGetInfoFail() throws Exception {

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

.contentType(MediaType.APPLICATION_JSON_UTF8))

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

}

后台代码:@RequestMapping(value="/user/{id:\\d+}",method=RequestMethod.GET)//如果希望对传递进来的参数作一些限制,使用正则表达式@JsonView(User.UserDetailView.class)public User getInfo(@PathVariable String id) {

User user = new User();

user.setUsername("tom");    return user;

}

使用@JsonView控制json输出内容

1.场景:在以上两个方法中,查询集合和查询用户详细信息时,期望查询用户集合时不返回密码给前端,而在查询单个用户信息时才返回。

2.使用步骤:

①使用接口来声明多个视图

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

③在Controller方法上指定视图

在user实体中操作:package com.hcx.web.dto;import java.util.Date;import javax.validation.constraints.Past;import org.hibernate.validator.constraints.NotBlank;import com.fasterxml.jackson.annotation.JsonView;import com.hcx.validator.MyConstraint;public class User {

public interface UserSimpleView{};    //有了该继承关系,在显示detail视图的时候同时会把simple视图的所有字段也显示出来

public interface UserDetailView extends UserSimpleView{};

@MyConstraint(message="这是一个测试")    private String username;

@NotBlank(message = "密码不能为空")    private String password;

private String id;

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

@JsonView(UserSimpleView.class)    public Date getBirthday() {        return birthday;

}    public void setBirthday(Date birthday) {        this.birthday = birthday;

}    @JsonView(UserSimpleView.class)    public String getId() {        return id;

}    public void setId(String id) {        this.id = id;

}    @JsonView(UserSimpleView.class) //在简单视图上展示该字段

public String getUsername() {        return username;

}    public void setUsername(String username) {        this.username = username;

}    @JsonView(UserDetailView.class)    public String getPassword() {        return password;

}    public void setPassword(String password) {        this.password = password;

}

}

在具体的方法中操作Controller:@GetMapping@JsonView(User.UserSimpleView.class)public List query(@RequestParam(name="username",required=false,defaultValue="tom") String username){

System.out.println(username);

List users = new ArrayList<>();

users.add(new User());

users.add(new User());

users.add(new User());    return users;

}@RequestMapping(value="/user/{id:\\d+}",method=RequestMethod.GET)//如果希望对传递进来的参数作一些限制,就需要使用正则表达式@JsonView(User.UserDetailView.class)public User getInfo(@PathVariable String id) {

User user = new User();

user.setUsername("tom");    return user;

}

单元测试://查询@Testpublic void whenQuerySuccess() throws Exception {

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

.param("username", "jojo")

.param("age", "18")

.param("ageTo", "60")

.param("xxx", "yyy")           //.param("size", "15")

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

.contentType(MediaType.APPLICATION_JSON_UTF8))

.andExpect(status().isOk())

.andExpect(jsonPath("$.length()").value(3))

.andReturn().getResponse().getContentAsString();//将服务器返回的json字符串当成变量返回

System.out.println(result);//[{"username":null},{"username":null},{"username":null}]}//获取用户详情@Testpublic void whenGetInfoSuccess() throws Exception {

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

.contentType(MediaType.APPLICATION_JSON_UTF8))

.andExpect(status().isOk())

.andExpect(jsonPath("$.username").value("tom"))

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

System.out.println(result); //{"username":"tom","password":null}

}

代码重构:

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

替换成:

@GetMapping("/user")

2.在每个url中都重复声明了/user,此时就可以提到类中声明@RestController@RequestMapping("/user")//在类上声明了/user,在方法中就可以省略了public class UserController {

@GetMapping

@JsonView(User.UserSimpleView.class)    public List query(@RequestParam(name="username",required=false,defaultValue="tom") String username){

System.out.println(username);

List users = new ArrayList<>();

users.add(new User());

users.add(new User());

users.add(new User());        return users;

}

@GetMapping("/{id:\\d+}")    @JsonView(User.UserDetailView.class)    public User getInfo(@PathVariable String id) {

User user = new User();

user.setUsername("tom");        return user;

}

}

三、处理创建请求

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

单元测试:@Test

public void whenCreateSuccess() throws Exception {

Date date = new Date();

System.out.println(date.getTime());//1524741370816

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

String result = mockMvc.perform(post("/user").contentType(MediaType.APPLICATION_JSON_UTF8)

.content(content))

.andExpect(status().isOk())

.andExpect(jsonPath("$.id").value("1"))

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

System.out.println(result);//{"username":"tom","password":null,"id":"1","birthday":1524741229875}

}

Controller:要使用@RequestBody才可以接收前端传递过来的参数@PostMappingpublic User create(@RequestBody User user) {

System.out.println(user.getId()); //null

System.out.println(user.getUsername()); //tom

System.out.println(user.getPassword());//null

user.setId("1");    return user;

}

2.日期类型参数的处理

对于日期的处理应该交给前端或app端,所以统一使用时间戳

前端或app端拿到时间戳,由他们自己决定转换成什么格式,而不是由后端转好直接给前端。

前端传递给后台直接传时间戳:@Test

public void whenCreateSuccess() throws Exception {

Date date = new Date();

System.out.println(date.getTime());//1524741370816

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

String result = mockMvc.perform(post("/user").contentType(MediaType.APPLICATION_JSON_UTF8)

.content(content))

.andExpect(status().isOk())

.andExpect(jsonPath("$.id").value("1"))

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

System.out.println(result);//{"username":"tom","password":null,"id":"1","birthday":1524741229875}(后台返回的时间戳)

}

Controller:@PostMappingpublic User create(@RequestBody User user) {

System.out.println(user.getId()); //null

System.out.println(user.getUsername()); //tom

System.out.println(user.getPassword());//null

System.out.println(user.getBirthday());//Thu Apr 26 19:13:49 CST 2018(Date类型)

user.setId("1");    return user;

}

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

1.hibernate.validator中的常用验证注解:

AAffA0nNPuCLAAAAAElFTkSuQmCC

注解及其含义.png

①在实体中添加相应验证注解:@NotBlankprivate String password;

②后台接收参数时加@Valid注解@PostMappingpublic User create(@Valid @RequestBody User user) {

System.out.println(user.getId()); //null

System.out.println(user.getUsername()); //tom

System.out.println(user.getPassword());//null

System.out.println(user.getBirthday());//Thu Apr 26 19:13:49 CST 2018

user.setId("1");    return user;

}

2.BindingResult:带着错误信息进入方法体@PostMapping

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

if(errors.hasErrors()) {        //有错误返回true

errors.getAllErrors().stream().forEach(error -> System.out.println(error.getDefaultMessage()));        //may not be empty

}

System.out.println(user.getId()); //null

System.out.println(user.getUsername()); //tom

System.out.println(user.getPassword());//null

System.out.println(user.getBirthday());//Thu Apr 26 19:13:49 CST 2018

user.setId("1");    return user;

}

四、处理用户信息修改

1.自定义消息@Test

public void whenUpdateSuccess() throws Exception {    //一年之后的时间

Date date = new Date(LocalDateTime.now().plusYears(1).atZone(ZoneId.systemDefault()).toInstant().toEpochMilli());

System.out.println(date.getTime());//1524741370816

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

String result = mockMvc.perform(put("/user/1").contentType(MediaType.APPLICATION_JSON_UTF8)

.content(content))

.andExpect(status().isOk())

.andExpect(jsonPath("$.id").value("1"))

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

System.out.println(result);//{"username":"tom","password":null,"id":"1","birthday":1524741229875}

}

Controller:@PutMapping("/{id:\\d+}")public User update(@Valid @RequestBody User user,BindingResult errors) {

/*if(errors.hasErrors()) {

//有错误返回true

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

//may not be empty

}*/

if(errors.hasErrors()) {

errors.getAllErrors().stream().forEach(error -> {            //FieldError fieldError = (FieldError)error;

//String message = fieldError.getField()+" "+error.getDefaultMessage();

System.out.println(error.getDefaultMessage());

//密码不能为空

//生日必须是过去的时间

//birthday must be in the past

//password may not be empty

}

);

}

System.out.println(user.getId()); //null

System.out.println(user.getUsername()); //tom

System.out.println(user.getPassword());//null

System.out.println(user.getBirthday());//Thu Apr 26 19:13:49 CST 2018

user.setId("1");    return user;

}

实体:public class User {

public interface UserSimpleView{};    //有了该继承关系,在显示detail视图的时候同时会把simple视图的所有字段也显示出来

public interface UserDetailView extends UserSimpleView{};

@MyConstraint(message="这是一个测试")    private String username;

@NotBlank(message = "密码不能为空")    private String password;

private String id;

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

@JsonView(UserSimpleView.class)    public Date getBirthday() {        return birthday;

}    public void setBirthday(Date birthday) {        this.birthday = birthday;

}    @JsonView(UserSimpleView.class)    public String getId() {        return id;

}    public void setId(String id) {        this.id = id;

}    @JsonView(UserSimpleView.class) //在简单视图上展示该字段

public String getUsername() {        return username;

}    public void setUsername(String username) {        this.username = username;

}    @JsonView(UserDetailView.class)    public String getPassword() {        return password;

}    public void setPassword(String password) {        this.password = password;

}

}

2.自定义校验注解

创建一个注解MyConstraint:@Target({ElementType.METHOD,ElementType.FIELD})//可以标注在方法和字段上@Retention(RetentionPolicy.RUNTIME)//运行时注解@Constraint(validatedBy = MyConstraintValidator.class)//validatedBy :当前的注解需要使用什么类去校验,即校验逻辑public @interface MyConstraint {

String message();//校验不通过要发送的信息

Class>[] groups() default { };

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

}

校验类:MyConstraintValidator:public class MyConstraintValidator implements ConstraintValidator {    /*ConstraintValidator

参数一:验证的注解

参数二:验证的类型

ConstraintValidator 当前注解只能放在String类型字段上才会起作用

*/

@Autowired

private HelloService helloService;

@Override

public void initialize(MyConstraint constraintAnnotation) {

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

}    @Override

public boolean isValid(Object value, ConstraintValidatorContext context) {

helloService.greeting("tom");

System.out.println(value);        return false;//false:校验失败;true:校验成功

}

}

五、处理删除

单元测试:@Testpublic void whenDeleteSuccess() throws Exception {

mockMvc.perform(delete("/user/1")

.contentType(MediaType.APPLICATION_JSON_UTF8))

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

}

Controller:@DeleteMapping("/{id:\\d+}")public void delete(@PathVariable String id) {    System.out.println(id);

}

六、RESTful API错误处理

1.Spring Boot中默认的错误处理机制

Spring Boot中默认的错误处理机制,

对于浏览器是响应一个html错误页面,

对于app是返回错误状态码和一段json字符串

2.自定义异常处理

①针对浏览器发出的请求

在src/mian/resources文件夹下创建文件夹error编写错误页面

AAffA0nNPuCLAAAAAElFTkSuQmCC

错误页面.png

对应的错误状态码就会去到对应的页面

只会对浏览器发出的请求有作用,对app发出的请求,错误返回仍然是错误码和json字符串

②针对客户端app发出的请求

自定义异常:package com.hcx.exception;public class UserNotExistException extends RuntimeException{

private static final long serialVersionUID = -6112780192479692859L;

private String id;

public String getId() {        return id;

}    public void setId(String id) {        this.id = id;

}    public UserNotExistException(String id) {        super("user not exist");        this.id = id;

}

}

在Controller中抛自己定义的异常//发生异常时,抛自己自定义的异常@GetMapping("/{id:\\d+}")@JsonView(User.UserDetailView.class)public User getInfo1(@PathVariable String id) {    throw new UserNotExistException(id);

}

默认情况下,springboot不读取id的信息

抛出异常时,进入该方法进行处理:

ControllerExceptionHandler:@ControllerAdvice //只负责处理异常public class ControllerExceptionHandler {

@ExceptionHandler(UserNotExistException.class)    @ResponseBody

@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)    public Map handleUserNotExistException(UserNotExistException ex){

Map result = new HashMap<>();        //把需要的信息放到异常中

result.put("id", ex.getId());

result.put("message", ex.getMessage());        return result;

}

}

作者:JS_HCX

原文链接

來源:简书

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值