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错误代码有
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