1
Spring Restful Service
Calvin Xiao edited this page 2013-11-14 19:10:43 -08:00
This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

##1. Overview JAX-RS协议的参考实现Jersey无论服务端还是客户端都是不错的,SpringSide之前也一直用它。 从RC4版后为了减少技术的使用,才用SpringMVC + RestTemplate进行替代。

##2. JAX-RS JAX-RS毕竟是个规范,在某些场合使用也非常合适,详见[Jersey](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 详见SpringMVC的相关章节

##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来做这个事情。