RESTful开发风格

RESTful开发风格

1.RESTful入门

img

在传统开发模式的应用中,前端写好静态的html页面交给后端开发,后端把html渲染或重定向到前端界面。这样前端页面看到的效果都是由后端控制的,也就是后端需要控制前端的展示,这样会出现很多弊端:

  • 前后端耦合严重,前端会嵌入后端代码,导致代码混乱,可维护性差;

  • 开发出的软件响应速度慢,质量差,用户体现差;

  • 开发人员需要前后端兼顾,开发效率低下,开发周期变长;

  • 与前端开发人员之间沟通成本高,前后端开发进度相互影响,从而大大降低开发效率。

前后端分离并不只是开发模式,也是web应用的一种架构模式。在开发阶段,前后端人员约定好数据交互接口,即可并行开发与测试。前端开发完成可以独自进行mock测试,后端也可以使用postman等接口测试工具进行测试,最后可进行功能联调测试。后端项目里面看不到页面(JSP|HTML),后端给前端提供接口,前端调用后端提供的REST风格接口就行,前端专注写页面(html|jsp)和渲染(JS|CSS|各种前端框架);后端专注写代码就行。 前后端分离的核心:后台提供数据,前端负责显示。

img

img

  • 什么是 REST?

Representational State Transfer,翻译是“表现层状态转化”。可以总结为一句话:REST 是所有 Web 应用都应该遵守的架构设计指导原则

面向资源是 REST 最明显的特征,对于同一个资源的一组不同的操作。资源是服务器上一个可命名的抽象概念,资源是以名词为核心来组织的,首先关注的是名词。REST 要求,必须通过统一的接口来对资源执行各种操作。对于每个资源只能执行一组有限的操作。

  • 什么是 RESTful API?

符合 REST 设计标准的 API,即 RESTful API。REST 架构设计,遵循的各项标准和准则,就是 HTTP 协议的表现,换句话说,HTTP 协议就是属于 REST 架构的设计模式。比如,无状态,请求-响应。

  • 为什么要统一封装接口?

现在大多数项目采用前后分离的模式进行开发,统一返回方便前端进行开发和封装,以及出现时给出响应编码和信息。

img

  • RESTful最典型的特点:服务器端只返回(JSON/xml)格式的数据,通知要求返回的数据不包含任何与展现相关的内容,通常将基于RESTfull 规则开发的程序称为“前后端分离”。

  • Web服务器默认的只支持Post和Get这两种“只读”的请求方法,Http还支持其他类型的请求。

RESTful开发规范:

  • GET–>执行查询功能的URL

  • POST–>执行新增功能的URL

  • PUT–>执行修改功能的URL

  • DELETE–>执行删除请求的URL

img

2.RestController注解与路径变量

2.1 @ResponseBody

@Responsebody 注解表示该方法的返回的结果直接写入 HTTP 响应正文(ResponseBody)中,一般在异步获取数据时使用,通常是在使用 @RequestMapping 后,返回值通常解析为跳转路径,加上 @Responsebody 后返回结果不会被解析为跳转路径,而是直接写入HTTP 响应正文中。
**作用:**该注解用于将Controller的方法返回的对象,通过适当的HttpMessageConverter转换为指定格式后,写入到Response对象的body数据区。
**使用时机:**返回的数据不是html标签的页面,而是其他某种格式的数据时(如json、xml等)使用。

2.2 @RestController=@Controller + @ResponseBody

@RestController=@Controller + @ResponseBody:

  • @Controller 是一种特殊化的*@Component* 类。
  • @Controller 习惯于和@RequestMapping绑定来使用,后者是用来指定路由映射的。
  • @ResponseBody 是用来把返回对象自动序列化成HttpResponse的。
  • @ResponseBody告诉控制器返回对象会被自动序列化成JSON,并且传回HttpResponse这个对象。
  • @RequestBodyHttpRequest body映射成一个 transfer or domain object(DTO或者DO),把一个入境(inbound)的HttpRequest的body反序列化成一个Java对象。
  • @RestController从Spring 4.0以后产生的,用来将json/xml数据发送到前台页面,而不是返回视图页面。@RestController加在类上面的注解,使得类里面的每个方法都将json/xml返回数据加返回到前台页面中。@Controller加在类上面的注解,使得类里面的每个方法都返回一个视图页面。

2.3 路径变量

路径变量:存放在URL中可变的数值。

img

img

  • 如果URL为/POST/arcticle/1就是创建id值为1的文章,在id的位置上是可变的,这种变量就是路径变量,restful中经常使用路径变量;
  • 只需要在@GetMapping(“/request”)注解中加上{},括号中放入路径变量就能实现,如:@PostMapping(“/request/{rid}”);
  • 这样POST请求发送到URI上的时候就能通过rid获取数值,只要匹配到rid的数值就能自动注入到requestId中;
  • @PathVariable注解是获取url中的参数,@RequestParam 和 @PathVariable 都是 springMVC的注解,都用于接收请求中的参数,@RequestParam 是从request里面直接拿取值,而 @PathVariable 则是从一个URI模板里面来填充,也就是RESTful风格,RESTful风格的请求中,使方法中的参数与路径变量相匹配,要使用@PathVariable注解。

2.4 简单请求与非简单请求

img

img

非简单请求发送过程:

  • 首先,发送预检请求,如果服务器可以处理预检请求并返回数据,则浏览器会将“实际请求”发送给服务器。
  • 如果服务器不能处理预检请求,则浏览器不会再次发送“实际请求”这样就可以减轻服务器的压力。

注意:简单请求与非简单请求二者的数据结构几乎一致,只是数据的内容不同,非简单请求需要使用表单内容过滤器,对put,delete额外处理,springMVC需要对非简单请求做过滤器配置,在web.xml文件中添加过滤器配置:

<!--内容过滤器对非简单请求进行支持-->
<filter>
    <filter-name>formContentFilter</filter-name>
    <filter-class>org.springframework.web.filter.FormContentFilter</filter-class>
</filter>
<!--内容过滤器对所有请求地址进行过滤-->
<filter-mapping>
    <filter-name>formContentFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

2.5 JSON序列化

1.pom.xml添加Jackson依赖

<!--添加jackson相关依赖-->
<!--jackson 核心包-->
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-core</artifactId>
    <version>2.13.2</version>
</dependency>
<!--jackson数据绑定 实现jackson与目标对象的交互-->
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.13.2</version>
</dependency>
<!--jackson注解依赖 简化开发-->
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-annotations</artifactId>
    <version>2.13.2</version>
</dependency>

jackson与fastjson有什么区别?

Jackson和Fastjson都是Json库,Jackson在国外比较流行,Fastjson在国内用到比较多。Fastjson是由阿里巴巴开源的Json解析库,主要是速度快,为了提高速度在规范兼容性方面做了一些妥协。而Jackson历史悠久,可扩展性强。

使用jackson,spring是则么知道用它来进行序列化的?

在Spring中使用@ResponseBody和@RequestBody注解是会默认自动使用jackson处理json的序列化和反序列化。

注意:Jackson 2.9版本之前有系统漏洞,所以使用时一定要使用2.9以后的版本。

示例代码:

1.RestfulController:对象序列化与集合对象序列化

package com.imooc.restful.controller;

import com.imooc.restful.entity.Person;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;

@RestController
@RequestMapping("/restful")
public class RestfulController {
    @GetMapping("/request")
    //@ResponseBody
    public String doGetRequest(){
        return "{\"message\":\"返回查询结果\"}";
    }

    // POST /article/1
    // POST /restful/request/100
    @PostMapping("/request/{rid}")
    //@ResponseBody
    public String doPostRequest(@PathVariable("rid") Integer requestId, Person person){
        System.out.println(person.getName() + ":" + person.getAge());
        return "{\"message\":\"数据新建成功\",\"id\":" + requestId + "}";
    }

    @PutMapping("/request")
    //@ResponseBody
    public String doPutRequest(Person person){
        System.out.println(person.getName() + ":" + person.getAge());
        return "{\"message\":\"数据更新成功\"}";
    }

    @DeleteMapping("/request")
    //@ResponseBody
    public String doDeleteRequest(){
        return "{\"message\":\"数据删除成功\"}";
    }
	//1.对象序列化
    @GetMapping("/person")
    public Person findByPersonId(Integer id){
        Person p = new Person();
        if(id==1){
            p.setName("lily");
            p.setAge(23);
        }else if(id==2){
            p.setName("smith");
            p.setAge(22);
        }
        return p;
    }
	//2.集合对象序列化
    @GetMapping("/persons")
    public List<Person> findPersons(){
        List list = new ArrayList();
        Person p1 = new Person();
        p1.setName("lily");
        p1.setAge(23);
        p1.setBirthday(new Date());
	
        Person p2 = new Person();
        p2.setName("smith");
        p2.setAge(22);
        p2.setBirthday(new Date());
        list.add(p1);
        list.add(p2);
        return list;
    }
}

2.Person实体类:

package com.imooc.restful.entity;

import com.fasterxml.jackson.annotation.JsonFormat;
import org.springframework.format.annotation.DateTimeFormat;

import java.util.Date;

public class Person {
    private String name;
    private Integer age;
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss" , timezone = "GMT+8")
    private Date birthday;

    public String getName() {
        return name;
    }

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

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    public Date getBirthday() {
        return birthday;
    }

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

img

img

3.client.html:客户端使用传来的JSON数据

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>RESTful实验室</title>
    <script src="jquery-3.3.1.min.js"></script>
    <script>
        $(function(){
            $("#btnGet").click(function () {
                $.ajax({
                    url : "/restful/request",
                    type : "get" ,
                    dataType : "json" ,
                    success : function(json){
                        $("#message").text(json.message);
                    }
                })
            });
        })

        $(function(){
            $("#btnPost").click(function () {
                $.ajax({
                    url : "/restful/request/100",
                    type : "post" ,
                    data : "name=lily&age=23",
                    dataType : "json" ,
                    success : function(json){
                        $("#message").text(json.message+":"+json.id);
                    }
                })
            });
        })

        $(function(){
            $("#btnPut").click(function () {
                $.ajax({
                    url : "/restful/request",
                    type : "put" ,
                    data : "name=lily&age=23",
                    dataType : "json" ,
                    success : function(json){
                        $("#message").text(json.message);
                    }
                })
            });
        })

        $(function(){
            $("#btnDelete").click(function () {
                $.ajax({
                    url : "/restful/request",
                    type : "delete" ,
                    dataType : "json" ,
                    success : function(json){
                        $("#message").text(json.message);
                    }
                })
            });
        })

        $(function () {
            $("#btnPersons").click(function () {
                $.ajax({
                    url : "http://localhost/restful/persons",
                    type : "get",
                    dataType : "json",
                    success : function(json){
                        console.info(json);
                        for(var i = 0 ; i < json.length;i++){
                            var p = json[i];
                            $("#divPersons").append("<h2>" + p.name + "-" + p.age + "-" + p.birthday+ "</h2>")
                        }
                    }
                })
            })
        })
    </script>
</head>
<body>
    <input type="button" id="btnGet" value="发送Get请求">
    <input type="button" id="btnPost" value="发送Post请求">
    <input type="button" id="btnPut" value="发送Put请求">
    <input type="button" id="btnDelete" value="发送Delete请求">
    <h1 id="message"></h1>
    <hr/>
    <input type="button" id="btnPersons" value="查询所有人员">
    <div id="divPersons"></div>
</body>
</html>

4.结果示例:查询所有人员

img

5.JSON在时间处理上不是很好:JSON直接对当前时间进行输出是一长串数字,该数字是1970年到当前时间的毫秒数。Jackson对这种情况有相应的注解来进行输出,json默认处理时间按照个格林尼治时间计算,中国需要+8个时区, @JsonFormat(pattern = “yyyy-MM-dd HH:mm:ss” , timezone = “GMT+8”)。

JSON直接对当前时间进行输出是一长串数字:

img

输出准确时间:

img

3.跨域问题

3.1 浏览器同源策略

img

img

img

3.2 Spring MVC跨域访问:

img

img

示例代码:将相同的项目拷贝,修改另一个的端口号,通过8080端口访问80端口的url,在80端口中增加@CrossOrigin

package com.imooc.restful.controller;

import com.imooc.restful.entity.Person;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;

@RestController
@RequestMapping("/restful")
@CrossOrigin(origins = {"http://localhost:8080","http://www.imooc.com"})
@CrossOrigin(origins = "*",maxAge = 3600)
public class RestfulController {
    @GetMapping("/request")
    //@ResponseBody
    public String doGetRequest(){
        return "{\"message\":\"返回查询结果\"}";
    }

    // POST /article/1
    // POST /restful/request/100
    @PostMapping("/request/{rid}")
    //@ResponseBody
    public String doPostRequest(@PathVariable("rid") Integer requestId, Person person){
        System.out.println(person.getName() + ":" + person.getAge());
        return "{\"message\":\"数据新建成功\",\"id\":" + requestId + "}";
    }

    @PutMapping("/request")
    //@ResponseBody
    public String doPutRequest(Person person){
        System.out.println(person.getName() + ":" + person.getAge());
        return "{\"message\":\"数据更新成功\"}";
    }

    @DeleteMapping("/request")
    //@ResponseBody
    public String doDeleteRequest(){
        return "{\"message\":\"数据删除成功\"}";
    }

    @GetMapping("/person")
    public Person findByPersonId(Integer id){
        Person p = new Person();
        if(id==1){
            p.setName("lily");
            p.setAge(23);
        }else if(id==2){
            p.setName("smith");
            p.setAge(22);
        }
        return p;
    }

    @GetMapping("/persons")
    public List<Person> findPersons(){
        List list = new ArrayList();
        Person p1 = new Person();
        p1.setName("lily");
        p1.setAge(23);
        p1.setBirthday(new Date());

        Person p2 = new Person();
        p2.setName("smith");
        p2.setAge(22);
        p2.setBirthday(new Date());
        list.add(p1);
        list.add(p2);
        return list;
    }
}

注意:@CrossOrigin(origins = “*”,maxAge = 3600)中maxAge属性设置后,浏览器会对“预检请求”的结果进行缓存,缓存有效期就是maxAge的标签值。有效期内,可以直接向服务器发送“非简单请求”。预检缓存的处理结果是缓存在浏览器中的,在服务器端是没有任何状态,也不缓存任何数据的。

3.3 CORS全局配置

Spring MVC全局跨域配置,使得项目配置中的URL都支持跨域访问。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:mv="http://www.springframework.org/schema/mvc"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
            http://www.springframework.org/schema/beans/spring-beans.xsd
            http://www.springframework.org/schema/context
            http://www.springframework.org/schema/context/spring-context.xsd
            http://www.springframework.org/schema/mvc
            http://www.springframework.org/schema/mvc/spring-mvc.xsd">
    <context:component-scan base-package="com.imooc.restful"/>
    <mvc:annotation-driven>
        <mvc:message-converters>
            <bean class="org.springframework.http.converter.StringHttpMessageConverter">
                <property name="supportedMediaTypes">
                    <list>
                        <!-- response.setContentType("text/html;charset=utf-8") -->
                        <value>text/html;charset=utf-8</value>
                        <value>application/json;charset=utf-8</value>
                    </list>
                </property>
            </bean>
        </mvc:message-converters>
    </mvc:annotation-driven>
    <mvc:default-servlet-handler/>
    <mvc:cors>
        <mvc:mapping path="/restful/**"
                     allowed-origins="http://localhost:8080,http://www.imooc.com"
                     max-age="3600"/>
    </mvc:cors>
</beans>

注解跨域和全局跨域配置在什么场景下使用呢?

  • 如果当前应用是专用于webAPI 也就是只对外提供web数据服务时,此时就需要使用mvc:cors标签进行全局配置;
  • 如果只是某些Controller需要对外暴露服务,就推荐使用注解;
  • 如果即配置了全局跨域设置,又配置了注解跨域设置,则以注解跨域规定范围为准;
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值