理论
Representational state transfer
Representational state transfer (REST ) or RESTful web services are a way of providing interoperability between computer systems on the Internet . REST-compliant Web services allow requesting systems to access and manipulate textual representations of Web resources using a uniform and predefined set of stateless operations. Other forms of Web services exist, which expose their own arbitrary sets of operations such as WSDL and SOAP .[1]
特性
性能
伸缩性
简化统一接口
组件的可修改性
通讯的可视性
组件移植性
可靠性
约束
客户端计算能力薄弱,服务端计算能力较强
无状态
缓存
分层系统
统一接口
自描述消息
类型
GET
PUT
POST
DELETE
PATCH
幂等 :
初始状态:0
修改状态:1 * N
最终状态:1
非幂等 :
初始状态:1
修改状态:1+1=2
N次修改:1+N=N+1
最终状态:N+1
幂等/非幂等 依赖于服务端实现,这种方式是一种契约。
服务端核心接口
定义相关
@Controller
RestController
映射相关
@RequestMapping
@PathVariable
方法相关
代码示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 package com.bai.restonwebwebmvc.domain;public class Person { private long id; private String name; public String getName () { return name; } public void setName (String name) { this .name = name; } public long getId () { return id; } public void setId (long id) { this .id = id; } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 package com.bai.restonwebwebmvc.controller;import com.bai.restonwebwebmvc.domain.Person;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.PathVariable;import org.springframework.web.bind.annotation.RequestParam;import org.springframework.web.bind.annotation.RestController;@RestController public class PersonRestController { @GetMapping ("/person/{id}" ) public Person person (@PathVariable Long id, @RequestParam(required = false ) String name ) { Person person=new Person(); person.setId(id); person.setName(name); return person; } }
启动程序(xxxApplication)
如果IDE用的是IntelliJ IDEA ,可以通过Tools->Test RESTful Web Service
进行访问http://localhost:8080/person/1?name=2bai
。
返回:{"id":1,"name":"2bai"}
自描述消息
Accept:
text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,/ ;q=0.8
第一优先顺序
text/html
application/xhtml+xml
application/xml
第二优先顺序
q=0.9
按照xml格式返回
1 2 3 4 5 6 <dependency > <groupId > com.fasterxml.jackson.dataformat</groupId > <artifactId > jackson-dataformat-xml</artifactId > </dependency >
问题及解答 Q:为什么第一次是JSON,后来添加了XML依赖后,又变成了XML
A:Spring Boot应用默认没有增加XML处理器实现,所以最后采用轮询的方式去逐一尝试是否可读(canWrite(POJO)),如果返回true,说明可以序列化改POJO对象,那么Jackson2恰好能处理,那么Jackson输出了。可以查阅源码org.springframework.web.servlet.mvc.method.annotation.ResponseBodyEmitterReturnValueHandler#sendInternal
。
Q:当Accept请求头未被指定时,为什么还是JSON
A:依赖于messageConverters的插入顺序。
Q:converters中的顺序可以修改吗?
A:可以。参考下面实现的扩展之调整顺序。
源码导读 @EnableWebMvc
org.springframework.web.servlet.config.annotation.EnableWebMvc
1 2 3 4 5 6 @Retention (RetentionPolicy.RUNTIME)@Target ({ElementType.TYPE})@Documented @Import ({DelegatingWebMvcConfiguration.class})public @interface EnableWebMvc {}
org.springframework.web.servlet.config.annotation.DelegatingWebMvcConfiguration
1 2 3 4 @Configuration public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport { }
org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 protected final void addDefaultHttpMessageConverters (List<HttpMessageConverter<?>> messageConverters) { StringHttpMessageConverter stringConverter = new StringHttpMessageConverter(); stringConverter.setWriteAcceptCharset(false ); messageConverters.add(new ByteArrayHttpMessageConverter()); messageConverters.add(stringConverter); messageConverters.add(new ResourceHttpMessageConverter()); messageConverters.add(new ResourceRegionHttpMessageConverter()); messageConverters.add(new SourceHttpMessageConverter()); messageConverters.add(new AllEncompassingFormHttpMessageConverter()); if (romePresent) { messageConverters.add(new AtomFeedHttpMessageConverter()); messageConverters.add(new RssChannelHttpMessageConverter()); } Jackson2ObjectMapperBuilder builder; if (jackson2XmlPresent) { builder = Jackson2ObjectMapperBuilder.xml(); if (this .applicationContext != null ) { builder.applicationContext(this .applicationContext); } messageConverters.add(new MappingJackson2XmlHttpMessageConverter(builder.build())); } else if (jaxb2Present) { messageConverters.add(new Jaxb2RootElementHttpMessageConverter()); } if (jackson2Present) { builder = Jackson2ObjectMapperBuilder.json(); if (this .applicationContext != null ) { builder.applicationContext(this .applicationContext); } messageConverters.add(new MappingJackson2HttpMessageConverter(builder.build())); } else if (gsonPresent) { messageConverters.add(new GsonHttpMessageConverter()); } else if (jsonbPresent) { messageConverters.add(new JsonbHttpMessageConverter()); } if (jackson2SmilePresent) { builder = Jackson2ObjectMapperBuilder.smile(); if (this .applicationContext != null ) { builder.applicationContext(this .applicationContext); } messageConverters.add(new MappingJackson2SmileHttpMessageConverter(builder.build())); } if (jackson2CborPresent) { builder = Jackson2ObjectMapperBuilder.cbor(); if (this .applicationContext != null ) { builder.applicationContext(this .applicationContext); } messageConverters.add(new MappingJackson2CborHttpMessageConverter(builder.build())); } }
org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 protected final List<HttpMessageConverter<?>> getMessageConverters() { if (this .messageConverters == null ) { this .messageConverters = new ArrayList<>(); configureMessageConverters(this .messageConverters); if (this .messageConverters.isEmpty()) { addDefaultHttpMessageConverters(this .messageConverters); } extendMessageConverters(this .messageConverters); } return this .messageConverters; }
org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 @Bean public RequestMappingHandlerAdapter requestMappingHandlerAdapter () { RequestMappingHandlerAdapter adapter = createRequestMappingHandlerAdapter(); adapter.setContentNegotiationManager(mvcContentNegotiationManager()); adapter.setMessageConverters(getMessageConverters()); adapter.setWebBindingInitializer(getConfigurableWebBindingInitializer()); adapter.setCustomArgumentResolvers(getArgumentResolvers()); adapter.setCustomReturnValueHandlers(getReturnValueHandlers()); if (jackson2Present) { adapter.setRequestBodyAdvice(Collections.singletonList(new JsonViewRequestBodyAdvice())); adapter.setResponseBodyAdvice(Collections.singletonList(new JsonViewResponseBodyAdvice())); } AsyncSupportConfigurer configurer = new AsyncSupportConfigurer(); configureAsyncSupport(configurer); if (configurer.getTaskExecutor() != null ) { adapter.setTaskExecutor(configurer.getTaskExecutor()); } if (configurer.getTimeout() != null ) { adapter.setAsyncRequestTimeout(configurer.getTimeout()); } adapter.setCallableInterceptors(configurer.getCallableInterceptors()); adapter.setDeferredResultInterceptors(configurer.getDeferredResultInterceptors()); return adapter; }
org.springframework.http.converter.json.MappingJackson2HttpMessageConverter#MappingJackson2HttpMessageConverter
1 2 3 public MappingJackson2HttpMessageConverter (ObjectMapper objectMapper) { super (objectMapper, new MediaType[]{MediaType.APPLICATION_JSON, new MediaType("application" , "*+json" )}); }
扩展自描述消息 org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport#getMessageConverters
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 protected final List<HttpMessageConverter<?>> getMessageConverters() { if (this .messageConverters == null ) { this .messageConverters = new ArrayList<>(); configureMessageConverters(this .messageConverters); if (this .messageConverters.isEmpty()) { addDefaultHttpMessageConverters(this .messageConverters); } extendMessageConverters(this .messageConverters); } return this .messageConverters; }
一次失败的试验
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 package com.bai.restonwebwebmvc.config;import org.springframework.beans.factory.annotation.Configurable;import org.springframework.context.annotation.Configuration;import org.springframework.http.converter.HttpMessageConverter;import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;import java.util.List;@Configuration public class WebMvcConfig implements WebMvcConfigurer { @Override public void configureMessageConverters (List<HttpMessageConverter<?>> converters) { converters.add(new MappingJackson2HttpMessageConverter()); System.err.println("converters:" +converters); } }
但是,经debug发现,org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport#getMessageConverters
方法内,判断this.messageConverters.isEmpty()
为true,还是加载了默认配置,这可能是个bug。其实扩展已经加载成功了,可能在某一地方又清空了。
这次是成功的 添加扩展
把默认实现的方法configureMessageConverters
换为extendMessageConverters
debug发现,在加载默认配置后,自定义配置已经加载成功。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 package com.bai.restonwebwebmvc.config;import org.springframework.context.annotation.Configuration;import org.springframework.http.converter.HttpMessageConverter;import org.springframework.http.converter.xml.MappingJackson2XmlHttpMessageConverter;import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;import java.util.List;@Configuration public class WebMvcConfig implements WebMvcConfigurer { private Object object; @Override public void extendMessageConverters (List<HttpMessageConverter<?>> converters) { converters.add(new MappingJackson2XmlHttpMessageConverter()); } }
调整顺序 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 package com.bai.restonwebwebmvc.config;import org.springframework.context.annotation.Configuration;import org.springframework.http.converter.HttpMessageConverter;import org.springframework.http.converter.xml.MappingJackson2XmlHttpMessageConverter;import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;import java.util.List;@Configuration public class WebMvcConfig implements WebMvcConfigurer { private Object object; @Override public void extendMessageConverters (List<HttpMessageConverter<?>> converters) { converters.set(0 ,new MappingJackson2XmlHttpMessageConverter()); } }
代码示例-完整版
POJO类—Person
扩展处理类—PropertiesPersonHttpMessageConverter
配置类—WebMvcConfig
Controller—PersonRestController
反序列化:JSON -> MappingJackson2HttpMessageConverter把文本转化为Person对象
序列化 Person对象 -> PropertiesPersonHttpMessageConverter#write
->Properties文本
如果请求中accept中和produces不一样,controller是不能执行的,会返回HTTP 状态码406
并不一定需要POJO,这么做是简化操作
JSON -> 反序列化 -> Map -> Properties
增加了操作的复杂度
Person
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 package com.bai.restonwebwebmvc.domain;public class Person { private long id; private String name; public String getName () { return name; } public void setName (String name) { this .name = name; } public long getId () { return id; } public void setId (long id) { this .id = id; } }
PropertiesPersonHttpMessageConverter
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 package com.bai.restonwebwebmvc.http.message;import com.bai.restonwebwebmvc.domain.Person;import org.springframework.http.HttpInputMessage;import org.springframework.http.HttpOutputMessage;import org.springframework.http.MediaType;import org.springframework.http.converter.AbstractHttpMessageConverter;import org.springframework.http.converter.HttpMessageNotReadableException;import org.springframework.http.converter.HttpMessageNotWritableException;import java.io.*;import java.nio.charset.Charset;import java.util.Properties;public class PropertiesPersonHttpMessageConverter extends AbstractHttpMessageConverter <Person > { public PropertiesPersonHttpMessageConverter () { super (MediaType.valueOf("application/properties+person" )); setDefaultCharset(Charset.forName("UTF-8" )); } @Override protected boolean supports (Class<?> clazz) { return clazz.isAssignableFrom(Person.class); } @Override protected Person readInternal (Class<? extends Person> clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException { InputStream inputStream=inputMessage.getBody(); Properties properties=new Properties(); properties.load(new InputStreamReader(inputStream,getDefaultCharset())); Person person=new Person(); person.setId(Long.valueOf(properties.getProperty("person.id" ))); person.setName(properties.getProperty("person.name" )); return person; } @Override protected void writeInternal (Person person, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException { OutputStream outputStream=outputMessage.getBody(); Properties properties=new Properties(); properties.setProperty("person.id" ,String.valueOf(person.getId())); properties.setProperty("person.name" ,person.getName()); properties.store(new OutputStreamWriter(outputStream,getDefaultCharset()),"Written By Application Server" ); } }
WebMvcConfig
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 package com.bai.restonwebwebmvc.config;import com.bai.restonwebwebmvc.http.message.PropertiesPersonHttpMessageConverter;import org.springframework.context.annotation.Configuration;import org.springframework.http.converter.HttpMessageConverter;import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;import java.util.List;@Configuration public class WebMvcConfig implements WebMvcConfigurer { @Override public void extendMessageConverters (List<HttpMessageConverter<?>> converters) { converters.add(new PropertiesPersonHttpMessageConverter()); } }
PersonRestController
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 package com.bai.restonwebwebmvc.controller;import com.bai.restonwebwebmvc.domain.Person;import org.springframework.http.MediaType;import org.springframework.web.bind.annotation.*;@RestController public class PersonRestController { @GetMapping ("/person/{id}" ) public Person person (@PathVariable Long id, @RequestParam(required = false ) String name ) { Person person=new Person(); person.setId(id); person.setName(name); return person; } @PostMapping (value="/person/json/to/properties" , consumes = MediaType.APPLICATION_JSON_UTF8_VALUE, produces = "application/properties+person" ) public Person personJsonToProperties (@RequestBody Person person) { return person; } @PostMapping (value="/person/properties/to/json" , consumes = "application/properties+person" , produces = MediaType.APPLICATION_JSON_UTF8_VALUE ) public Person personPropertiesToJson (@RequestBody Person person) { return person; } }
其它
HttpMessageConverter执行逻辑
读操作
尝试是否能读取,canRead方法去尝试,如果返回true,下一步执行read
写操作
尝试是否能写入,canWrite方法去尝试,如果返回true,下一步执行write