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还是clientSpring已自带了一堆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来做这个事情。