java restapi_「Java」 - SpringBoot & RestAPI

RESTful是目前非常流行的一种互联网软件架构。REST(Representational State Transfer,表述性状态转移)一词是由Roy Thomas Fielding在他2000年博士论文中提出的,定义了他对互联网软件的架构原则,如果一个架构符合REST原则,则称它为RESTful架构。

一、REST接口简介

REST通过URL定位资源(Resource),用HTTP的请求方式表示操作。看URL知道要什么(URL表示资源)

看HTTP Method知道干什么(通过动作完成资源操作)

看statusCode知道结果怎么样

资源可以自描述(URI),REST是一种风格,而不是一种具体实现或协议,HTTP是目前RSET最广泛应用的实例。

A、Resource

RESTful架构一个核心概念是「资源」(Resource)。从RESTful的角度看,网络里的任何东西都是资源,它可以是一段文本、一张图片、一首歌曲、一种服务等,每个资源都对应一个特定的 URI(统一资源定位符),并用它进行标示,访问这个URI就可以获得这个资源。代表具体实体的信息

存储在服务端

通过URI(Uniorm Resource Identifier)指定

http://httpd.apache.org/docs-project/#report

|----------------URL----------------|--URN--|

|--------------------URI--------------------|

B、HTTP Method

互联网中,客户端和服务端之间的互动传递的就只是资源的表述,上网的过程,就是调用资源的URI,获取它不同表现形式的过程。

客户端可以使用HTTP的几个基本操作,包括GET(获取)、POST(创建)、PUT(更新)与 DELETE(删除),使得服务端上的资源发生状态转化(State Transfer),也就是所谓的表述性状态转移。GET -> Read

POST -> Create

PUT -> Update(变更后的完整资源)

PATCH -> Update(部分更新)

DELETC -> Delete

有些客户端只能使用GET和POST,POST可以用于模拟PUT、PATCH、DELETE。客户端发出的HTTP请求,要加上X-HTTP-Method-Override属性,告诉服务器应该使用哪一个动词,覆盖POST方法。

C、REST设计原则无状态(客户端请求包含完整信息,保证信息唯一性、完整性;服务器不用额外保存状态,使得分布式、高并发容易处理和维护);

支持缓存(减少数据传输,降低网络开销);

URI不包含动词,动词应该包含在HTTP协议中。

二、Spring Boot对RESTful的支持

A、常用注解

Spring Boot全面支持开发RESTful程序,通过不同的注解来支持前端的请求,除了经常使用的注解外,Spring Boot还提了一些组合注解。这些注解来帮助简化常用的HTTP方法的映射,并更好地表达被注解方法的语义。@GetMapping,处理Get请求

@PostMapping,处理Post请求

@PutMapping,用于更新资源

@DeleteMapping,处理删除请求

@PatchMapping,用于更新部分资源

其实这些组合注解是@RequestMapping的精简版本。

@GetMapping(value="/xxx")

等价于

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

@PostMapping(value="/xxx")

等价于

@RequestMapping(value = "/xxx", method = RequestMethod.POST)

@PutMapping(value="/xxx")

等价于

@RequestMapping(value = "/xxx", method = RequestMethod.PUT)

@DeleteMapping(value="/xxx")

等价于

@RequestMapping(value = "/xxx", method = RequestMethod.DELETE)

@PatchMapping(value="/xxx")

等价于

@RequestMapping(value = "/xxx", method = RequestMethod.PATCH)

B、RESTful API

1、entity类Message

@Getter

@Setter

public class Message

{

private Long id;

private String text;

private String summary;

private Calendar created = Calendar.getInstance();

}

2、模拟Dao

使用ConcurrentHashMap模拟存储Message对象,进行增删改查,AtomicLong做为自增主键使用。

ConcurrentHashMap是Java中高性能并发的Map接口,AtomicLong作用是对长整形进行原子操作,可以在高并场景下获取到唯一的Long值。

public interface MessageRepository

{

List findAll();

Message save(Message message);

Message update(Message message);

Message updateText(Message message);

Message findMessage(Long id);

void deleteMessage(Long id);

}

@Service("messageRepository")

public class MessageRepositoryImpl implements MessageRepository

{

private static AtomicLong counter = new AtomicLong();

private final ConcurrentMap messages = new ConcurrentHashMap<>();

}

查询所有用户,是将 Map 中的信息全部返回。

@Override

public List findAll()

{

List messages = new ArrayList(this.messages.values());

return messages;

}

保存消息,需要判断是否存在ID,如果没有,可以使用AtomicLong获取一个。

@Override

public Message save(Message message)

{

Long id = message.getId();

if (id == null)

{

id = counter.incrementAndGet();

message.setId(id);

}

this.messages.put(id, message);

return message;

}

更新时直接覆盖对应的Key。

@Override

public Message update(Message message)

{

this.messages.put(message.getId(), message);

return message;

}

更新text字段。

@Override

public Message updateText(Message message)

{

Message msg = this.messages.get(message.getId());

msg.setText(message.getText());

this.messages.put(msg.getId(), msg);

return msg;

}

根据ID查找和删除消息。

@Override

public Message findMessage(Long id)

{

return this.messages.get(id);

}

@Override

public void deleteMessage(Long id)

{

this.messages.remove(id);

}

3、Controller

将封装的MessageRepository注入到Controller中,调用对应的增删改查方法。

@RestController

@RequestMapping("/")

public class MessageController

{

@Autowired

private MessageRepository messageRepository;

@GetMapping(value = "messages")

public List list()

{

List messages = this.messageRepository.findAll();

return messages;

}

@PostMapping(value = "message")

public Message create(Message message)

{

message = this.messageRepository.save(message);

return message;

}

@PutMapping(value = "message")

public Message modify(Message message)

{

Message messageResult = this.messageRepository.update(message);

return messageResult;

}

@PatchMapping(value = "/message/text")

public Message patch(Message message)

{

Message messageResult = this.messageRepository.updateText(message);

return messageResult;

}

@GetMapping(value = "message/{id}")

public Message get(@PathVariable Long id)

{

Message message = this.messageRepository.findMessage(id);

return message;

}

@DeleteMapping(value = "message/{id}")

public void delete(@PathVariable("id") Long id)

{

this.messageRepository.deleteMessage(id);

}

}

4、测试

关于Spring Boot web的测试,在之前已经进行总结,这里就不赘述。

@Slf4j

public class MessageControllerTest extends WebRestfulApplicationTests

{

@Autowired

private WebApplicationContext applicationContext;

private MockMvc mockMvc;

private void saveMessages()

{

for (int i = 1; i < 10; i++)

{

final MultiValueMap params = new LinkedMultiValueMap<>();

params.add("text", "text" + i);

params.add("summary", "summary" + i);

try

{

MvcResult mvcResult = mockMvc.perform(MockMvcRequestBuilders.post("/message").params(params)).andReturn();

}

catch (Exception e)

{

e.printStackTrace();

}

}

}

@Before

public void setup()

{

this.mockMvc = MockMvcBuilders.webAppContextSetup(this.applicationContext).build();

saveMessages();

}

@Test

public void saveMessage() throws Exception

{

final MultiValueMap params = new LinkedMultiValueMap<>();

params.add("text", "text");

params.add("summary", "summary");

String mvcResult = mockMvc.perform(MockMvcRequestBuilders.post("/message").params(params)).andReturn().getResponse().getContentAsString();

log.info("{}", mvcResult);

}

@Test

public void getAllMessages() throws Exception

{

String mvcResult = mockMvc.perform(MockMvcRequestBuilders.get("/messages")).andReturn().getResponse().getContentAsString();

log.info("{}", mvcResult);

}

@Test

public void getMessage() throws Exception

{

String mvcResult = mockMvc.perform(MockMvcRequestBuilders.get("/message/6")).andReturn().getResponse().getContentAsString();

log.info("{}", mvcResult);

}

@Test

public void modifyMessage() throws Exception

{

final MultiValueMap params = new LinkedMultiValueMap<>();

params.add("id", "6");

params.add("text", "text");

params.add("summary", "summary");

String mvcResult = mockMvc.perform(MockMvcRequestBuilders.put("/message").params(params)).andReturn().getResponse().getContentAsString();

log.info("{}", mvcResult);

}

@Test

public void patchMessage() throws Exception

{

final MultiValueMap params = new LinkedMultiValueMap<>();

params.add("id", "6");

params.add("text", "text");

String mvcResult = mockMvc.perform(MockMvcRequestBuilders.patch("/message/text").params(params)).andReturn().getResponse().getContentAsString();

log.info("{}", mvcResult);

}

@Test

public void deleteMessage() throws Exception

{

mockMvc.perform(MockMvcRequestBuilders.delete("/message/6")).andReturn();

String mvcResult = mockMvc.perform(MockMvcRequestBuilders.get("/messages")).andReturn().getResponse().getContentAsString();

log.info("{}", mvcResult);

}

}

C、总结

RESTful是一种非常优雅的设计,相同URL请求方式不同后端处理逻辑不同,利用RESTful风格很容易设计出更优雅和直观的API交互接口。同时Spring Boot对RESTful的支持也做了大量的优化,方便在Spring Boot体系内使用RESTful架构。

参考:

(1)项目简介 这个demo很简单,是一个记账小工程。用户可以注册、修改密码,可以记账、查找记账记录等。 (2)接口介绍 用户操作相关: post /users 用户注册 post /users/login 用户登录(这里我把login当成一个名词) put /users/pwd?userId=xxx&sign=xxx 用户修改密码 delete /users?uerId=xxx&sign=xxx 删除用户 记账记录操作相关: post /records?userId=xxx&sign=xxx 增加一条记账记录 get /records/:id?userId=xxx&sign=xxx 查询一条记账记录详情 put /records/:id?userId=xxx&sign=xxx 修改一条记账记录详情 get /records?查询参数&userId=xxx&sign=xxx 分页查询记账记录 delete /records/:id?userId=xxx&sign=xxx 删除一条记账记录 其中url中带sign参数的表示该接口需要鉴权,sign必须是url中最后一个参数。具体的鉴权方法是:用户登录后,服务器生成返回一个token,然后客户端要注意保存这个token,需要鉴权的接口加上sign签名,sign=MD5(url+token),这样可以避免直接传token从而泄露了token。这里我觉得接口最好还带一个时间戳参数timestamp,然后可以在服务端比较时间差,从而避免重放攻击。而且这样还有一个好处,就是如果有人截获了我们的请求,他想伪造我们的请求则不得不改时间戳参数(因为我们在服务器端会比较时间),这样一来sign势必会改变,他是无法得知这个sign的。如果我们没有加时间戳参数的话,那么,他截获了请求url,再重发这个请求势必又是一次合法的请求。我在这里为了简单一些,就不加时间戳了,因为这在开发测试阶段实在是太麻烦了。 (3)关于redis和数据库的说明 服务端在用户登录后,生成token,并将token保存到redis中。后面在接口鉴权的时候会取出token计算签名MD5(除sign外的url+token),进行比对。 这个demo搭建了一个redis主从复制,具体可以参考:http://download.csdn.net/detail/zhutulang/9585010 数据库使用mysql,脚本在 src/main/resources/accounting.sql
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值