RESTFul API

什么是REST

Representational; State Transfer:表述性状态转移

一种风格、约束、设计理念。

RESTFul API : 基于REST的API设计理念

  • 通常来说,使用JSON描述数据(服务器返回的结果)
  • 无状态
  • 基于资源,增删改查都只是对于资源状态的改变
  • 使用HTTP动词来操作资源 (get/post/put/delete)。参数传递简单用get;参数传递复杂用post,比如提交表单
  • url的设计:/getmovie/:mid 不建议
      • GET:/movie/:mid 建议
        注意, 这些uri没有使用任何动词或操作。重要的是不要在uri中包含任何动词。uri都应该是名词。

RESTFul API 的最佳实践

HTTP动词(幂等性、资源安全性)

  • POST:创建
  • PUT:更新
  • GET:查询
  • DELETE:删除

状态码: 404(在网站开发里:当前请求的页面没有找到;在RESTFul API里:资源没有找到)、400(参数错误)、200(查询操作get请求执行成功)、201(post创建资源成功)、202(一般来说指的是put更新成功;在http请求本身的状态码解释当中指的是请求已经发送,但是服务器暂时没有处理)、401(未授权)、403(当前资源被禁止)、500(服务器的未知错误)
(每个http请求都会自带一个状态码)

错误码: 自定义的错误ID号

统一描述错误: 错误码、错误信息、当前URL

  • 在RESTFul API中使用Token令牌来授权和验证身份
  • 版本控制(api一定要有版本号)
  • 测试与生产环境分开:api.xxx.com/ dev.api.xxx.com
  • URL语义要明确,最好可以“望文知意”
  • 最好是有一份比较标准的文档

学习RESTFul API 的最佳方式

学习RESTFul API 的最佳方式:模仿
可以参照: 豆瓣开放API(足够简单,适合才入门RESTFul API 设计的人群去模仿与参考)、 GitHub 开发者API(如果要做一套非常标准的RESTFul API)

RESTFul API 的合理使用:(切勿盲目照搬标准REST)

如何在应用程序设计过程中应用REST原则

设计REST服务的步骤:

  1. 识别对象模型
  2. 创建模型的uri
  3. 确定表示
  4. 指定HTTP方法
  5. 更多的行动

识别对象模型

设计基于REST api的应用程序的第一步是——识别将作为资源显示的对象

对于基于网络的应用程序,对象建模要简单得多。可能有很多东西,比如设备、托管实体、路由器、调制解调器等。为了简单起见,我们只考虑两种资源,即。

  • 设备
  • 配置

这里配置可能是设备的子资源。一个设备可以有许多配置选项。
注意, 上面模型中的两个对象/资源都有一个唯一的标识符,即整数id

创建模型的uri

现在,当对象模型准备好后,就可以决定资源uri了。在这个步骤中,当设计资源uri时——关注资源和它们的子资源之间的关系。这些资源uri是api的端点。
在我们的应用程序中,设备是顶级资源。而配置是设备下的一个子资源。

/devices
/devices/{id}

/configurations
/configurations/{id}

/devices/{id}/configurations
/devices/{id}/configurations/{id}
  • url的设计:/getmovie/:mid 不建议
      • GET:/movie/:mid 建议

注意, 这些uri没有使用任何动词或操作。重要的是不要在uri中包含任何动词。uri都应该是名词。

确定资源表示形式

  • 资源uri都是名词。
  • uri通常有两种形式——资源集合和单一资源。
  • 收集有两种形式:一次收集和二次收集。辅助收集仅是来自主收集的子收集。
  • 每个资源/集合包含至少一个链接,即到它自己。
  • 集合只包含关于资源的最重要的信息。
  • 要获得关于资源的完整信息,我们只需要通过其特定的资源URI进行访问。
  • 表示可以有额外的链接(例如单个设备中的方法)。这里的方法表示POST方法。我们还可以以全新的方式拥有更多属性或表单链接。
  • 我们还没有讨论这些资源的运作。

指定HTTP方法

所以我们的资源uri及其表示现在是固定的。让我们决定应用程序中所有可能的操作,并将这些操作映射到资源uri。

例如,我们的网络应用程序的用户可以执行浏览、创建、更新或从网络中删除设备,以及创建/部署/删除设备配置。让我们把这些操作分配给各自的资源。

使用JAX-RS创建REST api

jax - rs规范

JAX-RS提供了可移植的api,用于开发、公开和访问按照REST架构风格原则设计和实现的Web应用程序。
JAX-RS侧重于将Java注释应用于普通Java对象。JAX-RS具有将特定的URI模式和HTTP操作绑定到Java类的特定方法的注释。它还有注释,可以帮助您处理输入/输出参数。

jax - rs注释

  • @Path(‘resourcePath’)
    @Path注释用于匹配URI路径,它是相对于基URI的。它可以在资源类或方法上指定。
  • @POST
    带@POST注释的方法将在匹配的资源路径上处理HTTP POST请求。
  • @PUT
    带@PUT注释的方法将在匹配的资源路径上处理HTTP PUT请求。
  • @GET
    带注释的@GET方法将在匹配的资源路径上处理HTTP GET请求。
  • @DELETE
    带@DELETE注释的方法将在匹配的资源路径上处理HTTP DELETE请求。
  • @PathParam(“parameterName”)
    @PathParam用于将URL中的值(资源标识符)注入方法参数中。
  • @Produces
    @Produces注释定义了由带注释的资源方法交付的MIME类型。它既可以在类级别定义,也可以在方法级别定义。
    如果在类级别定义,资源类中的所有方法都将返回相同的MIME类型,如果没有在任何方法中重写的话。
  • @Consumes
    @Consumes注释定义了带注释的资源方法使用的MIME类型。
  • @Context
    为了构建HATEOAS链接,JAX-RS 2.0提供了可以通过@Context注释获得的UriInfo类。

默认情况下,如果没有显式实现,JAX-RS运行时将自动支持HEAD和OPTIONS方法。
对于HEAD,运行时将调用实现的GET方法(如果存在),并忽略响应实体(如果设置)。
OPTIONS方法可以在“Allow”头中返回一组受支持的资源方法。

使用JAX-RS创建REST api

创建资源表示形式

在JAX-RS中,资源表示是用JAXB注解注解的POJO类,例如@XmlRootElement、@XmlAttribute和@XmlElement等。
在这个例子中,我们展示了两种表示。让我们为它们创建java类。

配置收集资源

package net.restfulapi.app.rest.domain;

import java.util.List;

import javax.ws.rs.core.Link;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;

@XmlRootElement(name = "configurations")
@XmlAccessorType(XmlAccessType.FIELD)
public class Configurations
{
	@XmlAttribute
	private Integer size;

	@XmlJavaTypeAdapter(Link.JaxbAdapter.class)
	@XmlElement
	private Link link;

	@XmlElement
	private List<Configuration> configurations;

	public Integer getSize() {
		return size;
	}

	public void setSize(Integer size) {
	this.size = size;
	}

	public Link getLink() {
		return link;
	}

	public void setLink(Link link) {
		this.link = link;
	}

	public List<Configuration> getConfigurations() {
		return configurations;
	}

	public void setConfigurations
		(List<Configuration> configurations) {
		this.configurations = configurations;
	}
}

独特的配置资源

package net.restfulapi.app.rest.domain;

import javax.ws.rs.core.Link;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;

import net.restfulapi.app.rest.domain.common.Status;

@XmlRootElement(name="configuration")
@XmlAccessorType(XmlAccessType.FIELD)
public class Configuration
{
	@XmlAttribute
	private Integer id;

	@XmlJavaTypeAdapter(Link.JaxbAdapter.class)
	@XmlElement
	private Link link;

	@XmlElement
	private String content;

	@XmlElement
	private Status status;

	public Link getLink() {
		return link;
	}

	public void setLink(Link link) {
		this.link = link;
	}

	public Integer getId() {
		return id;
	}

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

	public String getContent() {
		return content;
	}

	public void setContent(String content) {
		this.content = content;
	}

	public Status getStatus() {
		return status;
	}

	public void setStatus(Status status) {
		this.status = status;
	}
}

消息资源[在不需要资源表示时通知客户端]

package net.restfulapi.app.rest.domain.common;

import javax.xml.bind.annotation.XmlRootElement;

@XmlRootElement(name = "message")
public class Message {

	public Message() {
		super();
	}

	public Message(String content) {
		super();
		this.content = content;
	}

	private String content;

	public String getContent() {
		return content;
	}

	public void setContent(String content) {
		this.content = content;
	}
}

此外,我们还使用ConfigurationDB类模拟了数据库功能。它公开了用于配置资源集合和单个配置资源中的CRUD操作的静态实用程序方法。

package net.restfulapi.app.dao;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;

import net.restfulapi.app.rest.domain.Configuration;
import net.restfulapi.app.rest.domain.common.Status;

public class ConfigurationDB 
{
	private static Map<Integer, Configuration> configurationDB 
		= new ConcurrentHashMap<Integer, Configuration>();
	private static AtomicInteger idCounter = new AtomicInteger();

	public static Integer createConfiguration(String content, Status status){
		Configuration c = new Configuration();
		c.setId(idCounter.incrementAndGet());
		c.setContent(content);
		c.setStatus(status);
		configurationDB.put(c.getId(), c);
		return c.getId();
	}

	public static Configuration getConfiguration(Integer id){
		return configurationDB.get(id);
	}

	public static List<Configuration> getAllConfigurations(){
		return new ArrayList<Configuration>(configurationDB.values());
	}

	public static Configuration removeConfiguration(Integer id){
		return configurationDB.remove(id);
	}

	public static Configuration updateConfiguration(Integer id, Configuration c){
		return configurationDB.put(id, c);
	}
}

创建其他资源

JAX-RS注释,让我们将它们应用到REST资源,并将HTTP方法映射到REST资源上的操作。
我在每个方法上面添加了自我解释的代码注释来解释它。

package net.restfulapi.app.rest.service;

import java.util.List;

import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.Link;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriBuilder;
import javax.ws.rs.core.UriInfo;

import net.restfulapi.app.dao.ConfigurationDB;
import net.restfulapi.app.rest.domain.Configuration;
import net.restfulapi.app.rest.domain.Configurations;
import net.restfulapi.app.rest.domain.common.Message;
import net.restfulapi.app.rest.domain.common.Status;

/**
* This REST resource has common path "/configurations" and
* represents configurations collection resources 
* as well as individual collection resources.
*
* Default MIME type for this resource is "application/XML"
* 这个REST资源有公共路径“/configurations”和
*表示配置收集资源
*以及个人收集资源。
*
这个资源的默认MIME类型是"application/XML"
* */
@Path("/configurations")
@Produces("application/xml")
public class ConfigurationResource
{
	/**
	* Initialize the application with these two default configurations
	* 用这两个默认配置初始化应用程序
	* */
	static {
		ConfigurationDB.createConfiguration("Some Content", Status.ACTIVE);
		ConfigurationDB.createConfiguration("Some More Content", Status.INACTIVE);
	}
	
	/**
	* Use uriInfo to get current context path and to build HATEOAS links
	* 使用uriInfo获取当前上下文路径并构建HATEOAS链接
	* */
	@Context
	UriInfo uriInfo;

	/**
	* Get configurations collection resource mapped at path "HTTP GET /configurations"
	* 获取映射到路径“HTTP Get /configurations”的配置收集资源
	* */
	@GET
	public Configurations getConfigurations() {

		List<Configuration> list = ConfigurationDB.getAllConfigurations();

		Configurations configurations = new Configurations();
		configurations.setConfigurations(list);
		configurations.setSize(list.size());

		//Set link for primary collection
		Link link = Link.fromUri(uriInfo.getPath()).rel("uri").build();
		configurations.setLink(link);

		//Set links in configuration items
		for(Configuration c: list){
		Link lnk = Link.fromUri(uriInfo.getPath() + "/" + c.getId()).rel("self").build();
		c.setLink(lnk);
		}
		return configurations;
	}

	/**
	* Get individual configuration resource mapped at path "HTTP GET /configurations/{id}"
	* 获取映射到路径“HTTP Get /configurations/{id}”的单个配置资源
	* */
	@GET
	@Path("/{id}")
	public Response getConfigurationById(@PathParam("id") Integer id){
		Configuration config = ConfigurationDB.getConfiguration(id);

		if(config == null) {
			return Response.status(javax.ws.rs.core.Response.Status.NOT_FOUND)
					.build();
		}

		if(config != null){
			UriBuilder builder = UriBuilder.fromResource(ConfigurationResource.class)
						.path(ConfigurationResource.class, "getConfigurationById");
			Link link = Link.fromUri(builder.build(id))
					.rel("self")
					.build();
			config.setLink(link);
		}

		return Response.status(javax.ws.rs.core.Response.Status.OK)
				.entity(config)
				.build();
	}

	/**
	* Create NEW configuration resource in configurations collection resource
	* 在配置收集资源中创建新的配置资源
	* */
	@POST
	@Consumes("application/xml")
	public Response createConfiguration(Configuration config){
		if(config.getContent() == null) {
			return Response.status(javax.ws.rs.core.Response.Status.BAD_REQUEST)
					.entity(new Message("Config content not found"))
					.build();
		}

		Integer id = ConfigurationDB.createConfiguration(config.getContent(), config.getStatus());
		Link lnk = Link.fromUri(uriInfo.getPath() + "/" + id).rel("self")
					.build();
		return Response.status(javax.ws.rs.core.Response.Status.CREATED)
				.location(lnk.getUri())
				.build();
	}

	/**
	* Modify EXISTING configuration resource by it’s "id" at path "/configurations/{id}"
	* 通过路径/configurations/{id}的“id”修改现有的配置资源
	* */
	@PUT
	@Path("/{id}")
	@Consumes("application/xml")
	public Response updateConfiguration(@PathParam("id") Integer id, Configuration config){

		Configuration origConfig = ConfigurationDB.getConfiguration(id);
		if(origConfig == null) {
			return Response.status(javax.ws.rs.core.Response.Status.NOT_FOUND)
					.build();
		}

		if(config.getContent() == null) {
			return Response.status(javax.ws.rs.core.Response.Status.BAD_REQUEST)
					.entity(new Message("Config content not found"))
					.build();
		}

		ConfigurationDB.updateConfiguration(id, config);
		return Response.status(javax.ws.rs.core.Response.Status.OK)
					.entity(new Message("Config Updated Successfully"))
					.build();
	}

	/**
	* Delete configuration resource by it’s "id" at path "/configurations/{id}"
	* 通过路径/configurations/{id}的id删除配置资源
	* */
	@DELETE
	@Path("/{id}")
	public Response deleteConfiguration(@PathParam("id") Integer id){

		Configuration origConfig = ConfigurationDB.getConfiguration(id);
		if(origConfig == null) {
			return Response.status(javax.ws.rs.core.Response.Status.NOT_FOUND).build();
		}

		ConfigurationDB.removeConfiguration(id);
		return Response.status(javax.ws.rs.core.Response.Status.OK).build();
	}
}

在运行时注册资源

要将JAX-RS REST资源注册到服务器的运行时,我们需要扩展javax.ws.rs.core.Application类,并将其放在应用程序的类路径中。

package net.restfulapi.app.rest;

import java.util.HashSet;
import java.util.Set;

import javax.ws.rs.ApplicationPath;
import javax.ws.rs.core.Application;

import net.restfulapi.app.rest.service.ConfigurationResource;

@ApplicationPath("/network-management")
public class NetworkApplication extends Application {

	private Set<Object> singletons = new HashSet<Object>();
	private Set<Class<?>> empty = new HashSet<Class<?>>();

	public NetworkApplication() {
		singletons.add(new ConfigurationResource());
	}

	@Override
	public Set<Class<?>> getClasses() {
		return empty;
	}

	@Override
	public Set<Object> getSingletons() {
		return singletons;
	}
}

这里@ApplicationPath注释将这个类标识为servlet 3.0容器中自动扫描进程的REST应用程序。它有助于使web.xml文件几乎为空——完全不需要特定于REST的配置。

<web-app version="3.0" xmlns="http://java.sun.com/xml/ns/javaee"
		xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
		xsi:schemaLocation="http://java.sun.com/xml/ns/javaee 
		http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd">
	<display-name>Archetype Created Web Application</display-name>
</web-app>
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值