Spring Restful Service
1. Overview
JAX-RS协议的参考实现 Jersey 无论服务端还是客户端都是不错的,SpringSide之前也一直用它。 从RC4版后为了减少技术的使用,才用SpringMVC + RestTemplate进行替代。
2. JAX-RS
JAX-RS毕竟是个规范,在某些场合使用也非常合适,详见 Jersey ,之前的使用心得. 在Showcase中,顺道用CXF作为JAX-RS框架进行了演示,见AccountJaxRsService.java 和 applicationContext-jaxrs-server.xml.
3. ServerSide: Spring MVC
Spring MVC虽然没有实现JAX-RS协议,但整体风格与annotation和JAX-RS都已经非常接近了。
3.1 Example
在Quickstart中演示了一个典型的CRUD的Restful Service,演示了各种返回各种状态码(404), 返回新创建对象的URL的方式。
@RequestMapping(value = "/{id}", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
@ResponseBody
public ResponseEntity<?> get(@PathVariable("id") Long id) {
Task task = taskService.getTask(id);
if (task == null) {
return new ResponseEntity(HttpStatus.NOT_FOUND);
}
return new ResponseEntity(task, HttpStatus.OK);
}
3.2 Content negotiation, 根据url后缀返回JSON与XML两种风格
<mvc:annotation-driven content-negotiation-manager="contentNegotiationManager">
<bean id="contentNegotiationManager" class="org.springframework.web.accept.ContentNegotiationManagerFactoryBean">
<property name="mediaTypes" >
<value>
json=application/json
xml=application/xml
</value>
</property>
</bean>
@RequestMapping(value = "/{id}", method = RequestMethod.GET)
@ResponseBody
public UserDTO getUser(@PathVariable("id") Long id) {
}
Content negotiation的最大好处是,可以定义函数只返回Java对象,然后Spring MVC会根据URL后缀自动判定Content-Type及渲染成相应的View,如/api/v1/user/${id},测试用例见showcase中的UserRestFt。 当然,现在既要返回JSON,又要返回XML的接口越来越少了,多数只专心于JSON。
3.3 异常控制
详见SpringMVC的相关章节,为了避免扰乱别人,Rest专门有自己的Exception。
3.4 MeidaType
4. ClientSide: Spring RestTemplate
RestTemplate胜在够简单,详见Spring官方手册,通过getForObject()/getForEntity(), postForLocation()/postForObject()等方法快速实现访问URL并将结果转换为Object。 在quickstart的functional test 中TaskRestFT.java中,完整演示了其使用。
4.1 转换类型为List<?>的返回值
如果服务端返回的是一个JSON格式编码的集合时,最快的转换方法是定义一个List的子类,然后作为Class参数.
private static class TaskList extends ArrayList<Task> {
};
TaskList tasks = restTemplate.getForObject(resoureUrl, TaskList.class);
4.2 处理Header
如果要在Request/Response中处理Header,则比 Jersey 要复杂一点,需要用到比较原始的exchange()方法,见showcase中的UserRestFT.java.
HttpHeaders requestHeaders = new HttpHeaders();
requestHeaders.set(com.google.common.net.HttpHeaders.AUTHORIZATION, Servlets.encodeHttpBasic("admin", "admin"));
HttpEntity<?> requestEntity = new HttpEntity(requestHeaders);
HttpEntity<UserDTO> response = restTemplate.exchange(resoureUrl + "/{id}.xml", HttpMethod.GET, requestEntity,UserDTO.class, 1L);
如果有很多函数都有相同的Header处理请求, 又不想用exchange()这么冗长的写法,可以使用ClientHttpRequestInterceptor, 同样在showcase中进行了演示, 下面的代码与上面的例子的效果是一样的。
ClientHttpRequestInterceptor interceptor = new HttpBasicInterceptor("admin", "admin");
restTemplate.setInterceptors(Lists.newArrayList(interceptor));
restTemplate.getForObject(resoureUrl + "/{id}.json", UserDTO.class, 1L);
4.3 底层Http Connection管理及超时控制
RestTemplate 默认使用SimpleClientHttpRequestFactory, 基于JDK自带的HttpURL Connection. 也可以设置基于Apache HttpClient4.0,并使用了多线程安全的Connection Pool的HttpComponentsClientHttpRequestFactory.(此时注意在退出时要调用其destroy()方法) 无论哪种RequestFactory,都可以调用setConnectTimeout()/setReadTimeout()方法设置超时,以毫秒为单位,0是无超时控制,不设的话则是系统默认值。
同样在showcase的UserRestFt中进行了演示。
5. XML与JSON格式的转换
无论Server还是client,Spring已自带了一堆Converter:
- MappingJackson2HttpMessageConverter,负责用Jackson2.0转换JSON格式数据
- Jaxb2RootElementHttpMessageConverter, 负责用Jaxb2RootElementHttpMessageConverter转换XML格式数据,注意DTO必须用@XmlRootElement标注才会被转换,另外如果DTO之间循环依赖,会抛出Spring MVC会抛出406的返回码,需要在它的writeToResult()方法上设置断点来调试,才能看到真正原因。
如果自定义Converters,在server-side:
<mvc:annotation-driven>
<mvc:message-converters>
<bean class="com.google.protobuf.spring.http.ProtobufHttpMessageConverter"/>
</mvc:message-converters>
</mvc:annotation-driven>
在client-side,调用restTemplate的setMessageConverters()函数:
restTemplate.setMessageConverters(Arrays.asList(new MappingJackson2HttpMessageConverter())
6. 安全认证
Restful API一般不搞Login/Logout+SessionId的认证方式,而是无状态的每次附带认证信息。 Showcase中演示了Http Basic在Http Header中明文传密码,然后利用Shiro的http basic集成。 当然,也可以自己简单的写个Filter来做这个事情。