技术开发 频道

基于SpringMVC的RESTful API设计

  【IT168 技术】REST(Representational State Transfer)描述了一种软件架构风格,相比SOAP和RPC更加简单明了,目前互联公司越来越流行提供RESTful形式的API供第三方调用。REST通过与标准的HTTP方法进行映射,能够完整地表述系统资源的各种形为。SpringMVC从3.0的开始增加了RESTful功能,因其快速简单、与Spring框架无缝集成等优点,被广大Java开发者奉为RESTful首选。本文基于SpringMVC3.2.5和maven讲解如何通过SpringMVC构造企业级应用基础的RESTful API,对开发人员快速开发企业级项目集成有所帮助。

  1. 基于Maven创建SpringMVC工程

  Maven是一个项目管理工具,它包含了一个项目对象模型 (Project Object Model),使用Maven能够快速完成Spring MVC工程的创建,而不必去逐个下载Spring MVC的依赖jar包。

  Maven创建Spring MVC非常简单,主要包括以下几步:

  1)创建工程

mvn archetype:generate
-DgroupId=com.ibm.cdl
-DartifactId=spirngmvc-restful
-DarchetypeArtifactId=maven-archetype-webapp
-DinteractiveMode=false

  2)修改POM文件,内容如下:

  略

  3)基本框架已经生成,后续根据实际需求编辑RESTful API

  2. Token认证机制

  RESTful提供的API是无状态的,下一次调用请求与当前的调用请求是完全无关,但这就无法保证系统资源的安全性。Token机制就是用来解决无状态和安全性之间的矛盾。其实现机理主机如下:

  1)用户发送用户名和密码(加密密码),验证请求通过

  2)服务器通过SSO服务判断用户名和密码是否正确

  3)验证通过,返回Token及过期时间

  4)后边所有请求在这段时间内携带这一Token即可使用其他API

@Controller
@RequestMapping("/admin/v1/")
public class AuthenticateAPI {
    
    @Autowired
    private UserService userService;

        /**
         * 用户授权验证
         * @param param
         * @return
         */
        @RequestMapping(value="/tokens",method=RequestMethod.POST)
        @ResponseBody
        public String authenticate(@RequestBody String param){
            try {
                User user  = JSON.parseObject(param, User.class);
                Token token = userService.login(user);
                if(token == null){
                    throw new CutomizedException(HttpStatus.UNAUTHORIZED, "User authenticate failed.");
                }
                TokenResponseEntity tokenResponseEntity = new TokenResponseEntity();
                tokenResponseEntity.setEntity(token);
                tokenResponseEntity.setHttpStatus(HttpStatus.OK);
                tokenResponseEntity.setMessage("User authenticate successful.");
                return JSON.toJSONStringWithDateFormat(tokenResponseEntity, "yyyy-MM-dd HH:mm:ss", SerializerFeature.WriteDateUseDateFormat);
            }catch(JSONException e){
                throw new CutomizedException(HttpStatus.BAD_REQUEST, "Bad request body.");
            }catch (Exception e) {
                throw new CutomizedException(HttpStatus.INTERNAL_SERVER_ERROR, "Server internal error");
            }
            
        }
    
}

  3. Token验证

  考虑到安全性,除了Token认证的API外,其他所有的请求都需要经过Token验证,SpringMVC提供的@ModelAttribute("tokenIdValidateResult")注解能够预先对所有方法进Token有效性的验证,一旦Token过期或者Token验证失败,则不允许调用方进行相关操作,具体实现过程如下:

    /**
     * All Method need to validate token id
     * @param headers
     * @return
     */
    @ModelAttribute("tokenIdValidateResult")
    public TokenValidateResult validateTokenId(@RequestHeader HttpHeaders headers){
        String tokenId=headers.getFirst("X-Auth-Token");
        Token searchToken = new Token();
        searchToken.setTokenId(tokenId);
        if(StringUtils.isEmpty(tokenId)){
            return TokenValidateResult.TOKEN_EMPTY;
        }
        
        Token token=tokenService.findOneByExample(searchToken);
        //Token not exists
        if(token==null){
            return TokenValidateResult.TOKEN_NOT_EXIST;
        }
        
        //Token 过期
        if(token.getExpireTime().before(new Date())){
            return TokenValidateResult.TOKEN_EXPIRED;
        }
        
        //Success
        return TokenValidateResult.SUCCESS;
    }

  4. 创建资源

  创建资源采用HTTP中的POST请求来实现。

  创建Resource API示例:

@Controller
@RequestMapping(value="/resources/v1")
public class ResourceAPI {
    
    @RequestMapping(method=RequestMethod.POST)
    @ResponseBody
    public ResourceResponseEntity createResource(@ModelAttribute("tokenIdValidateResult")TokenValidateResult tokenIdValidateResult,@RequestHeader HttpHeaders headers,@JsonParam ResourceReponseEntity resourceResponseEntity ) {
        if(tokenIdValidateResult == TokenValidateResult.SUCCESS){
            try {
                String tokenId=headers.getFirst("X-Auth-Token");
                Token searchToken = new Token();
                searchToken.setTokenId(tokenId);
                //TODO:创建Resource
                return resourceResponseEntity;
            } catch (Exception e) {
                throw new CutomizedException(e);
            }
        }else{
            //异常处理交给异常处理框架
            throw new CutomizedException(HttpStatus.UNAUTHORIZED,"Token validte failed,ErrorCode:"+tokenIdValidateResult);
        }
      }
}

  5. 更新资源属性

  更新资源采用HTTP中的PUT请求来实现。具体实现如下:

@Controller
@RequestMapping(value="/resources/v1")
public class ResourceAPI {
    
    @RequestMapping(value="/{id}",method=RequestMethod.PUT)
    @ResponseBody
    public ResourceResponseEntity createResource(@ModelAttribute("tokenIdValidateResult")TokenValidateResult tokenIdValidateResult,
            @PathVariable Integer id,
            @RequestHeader HttpHeaders headers,
            @JsonParam Resource resource) {
        if(tokenIdValidateResult == TokenValidateResult.SUCCESS){
            try {
                String tokenId=headers.getFirst("X-Auth-Token");
                Token searchToken = new Token();
                searchToken.setTokenId(tokenId);
                //TODO:更新Resource
                return resourceResposeEntity;
            } catch (Exception e) {
                throw new CutomizedException(e);
            }
        }else{
            //异常处理交给异常处理框架
            throw new CutomizedException(HttpStatus.UNAUTHORIZED,"Token validte failed,ErrorCode:"+tokenIdValidateResult);
        }
    }

}

  6. 删除资源

  删除资源采用HTTP中的DELETE请求来实现。具体实现如下:

@Controller
@RequestMapping(value="/resources/v1")
public class ResourceAPI {
    
    @RequestMapping(value="/{id}",method=RequestMethod.DELETE)
    @ResponseBody
    public ResourceResponseEntity createResource(@ModelAttribute("tokenIdValidateResult")TokenValidateResult tokenIdValidateResult,
            @PathVariable Integer id,
            @RequestHeader HttpHeaders headers) {
        if(tokenIdValidateResult == TokenValidateResult.SUCCESS){
            try {
                String tokenId=headers.getFirst("X-Auth-Token");
                Token searchToken = new Token();
                searchToken.setTokenId(tokenId);
                //TODO:删除Resource
                return resourceResposeEntity;
            } catch (Exception e) {
                throw new CutomizedException(e);
            }
        }else{
            //异常处理交给异常处理框架
            throw new CutomizedException(HttpStatus.UNAUTHORIZED,"Token validte failed,ErrorCode:"+tokenIdValidateResult);
        }
    }

}

  7. 获取资源列表及单个资源信息

  获取资源信息采用HTTP协议中的GET请求实现,具体实现如下:

@Controller
@RequestMapping(value="/resources/v1")
public class ResourceAPI {
    
    @RequestMapping(method=RequestMethod.GET)
    @ResponseBody
    public ResourceResponseEntity createResource(@ModelAttribute("tokenIdValidateResult")TokenValidateResult tokenIdValidateResult,@RequestHeader HttpHeaders headers) {
        if(tokenIdValidateResult == TokenValidateResult.SUCCESS){
            try {
                String tokenId=headers.getFirst("X-Auth-Token");
                Token searchToken = new Token();
                searchToken.setTokenId(tokenId);
                //TODO:删除Resource列表
                return resourceResposeEntity;
            } catch (Exception e) {
                throw new CutomizedException(e);
            }
        }else{
            //异常处理交给异常处理框架
            throw new CutomizedException(HttpStatus.UNAUTHORIZED,"Token validte failed,ErrorCode:"+tokenIdValidateResult);
        }
    }
    
    @RequestMapping(value="/{id}",method=RequestMethod.GET)
    @ResponseBody
    public ResourceResponseEntity createResource(@ModelAttribute("tokenIdValidateResult")TokenValidateResult tokenIdValidateResult,
            @PathVariable Integer id,
            @RequestHeader HttpHeaders headers) {
        if(tokenIdValidateResult == TokenValidateResult.SUCCESS){
            try {
                String tokenId=headers.getFirst("X-Auth-Token");
                Token searchToken = new Token();
                searchToken.setTokenId(tokenId);
                //TODO:获取单个资源信息
                return resourceResposeEntity;
            } catch (Exception e) {
                throw new CutomizedException(e);
            }
        }else{
            //异常处理交给异常处理框架
            throw new CutomizedException(HttpStatus.UNAUTHORIZED,"Token validte failed,ErrorCode:"+tokenIdValidateResult);
        }
    }

}

  8. 异常处理

  API在调用过程中总会出现各种各样的异常情况,Spring MVC提供了@ControllerAdvice以及ResponseEntityExceptionHandler类,专门用来自定义输出错误格式。只要在发生异常的地方将异常消息直接抛出,这样所有的异常都统一由自定义的异常处理类来处理,具体如下:

@ControllerAdvice
public class RestExceptionHandler extends ResponseEntityExceptionHandler  {    
    @ExceptionHandler(value = { VMWareException.class })
    public final ResponseEntity<?> handleVMWareException(VMWareException ex, WebRequest request) {
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.parseMediaType(MediaTypes.TEXT_PLAIN_UTF_8));
        return handleExceptionInternal(ex, ex.getMessage(), headers, ex.status, request);
    }    
    @ExceptionHandler(value = { PowerException.class })
    public final ResponseEntity<?> handlePowerException(PowerException ex, WebRequest request) {
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.parseMediaType(MediaTypes.TEXT_PLAIN_UTF_8));
        return handleExceptionInternal(ex, ex.getMessage(), headers, ex.status, request);
    }    
    @ExceptionHandler(value = { UspException.class })
    public final ResponseEntity<?> handleUspException(UspException ex, WebRequest request) {
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.parseMediaType(MediaTypes.TEXT_PLAIN_UTF_8));
        return handleExceptionInternal(ex, ex.getMessage(), headers, ex.status, request);
    }    
    @ExceptionHandler(value = { KVMException.class })
    public final ResponseEntity<?> handleKVMException(KVMException ex, WebRequest request) {
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.parseMediaType(MediaTypes.TEXT_PLAIN_UTF_8));
        return handleExceptionInternal(ex, ex.getMessage(), headers, ex.status, request);
    }
}

  这里的异常都封装了具体的HTTPStatus代码,这样第三方就能够清楚地知道调用时的出错类型,常见的HTTP错误代码有

基于SpringMVC的RESTful API设计

  9. 总结

  本文主要讲解了如何通过Spring MVC封装RESTful API,包括Token的认证机制,封装资源的CRUD操作,异常处理等,对开发人员快速开发企业级项目集成有所帮助。

参考资料
1. http://www.ruanyifeng.com/blog/2011/09/restful.html
2. http://www.ruanyifeng.com/blog/2011/09/restful.html
3. http://jinnianshilongnian.iteye.com/blog/1866350
作者简介
俞超:
软件开发工程师
任职于某大型IT外资企业,主要从事J2EE开发、架构设计工作。

1
相关文章