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架构。
参考: