Spring Restful Service

优质
小牛编辑
134浏览
2023-12-01

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

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