[导读]在API 中处理错误的另外一种方法就是抛出异常。
在API 中处理错误的另外一种方法就是抛出异常。使用Spring MVC,有两种方法来匹配异常:
1. 在类级别使用@ExceptionHandler,类似于我们在第4 章中,在上传控制器上处理IOException 的方式;
2. 使用@ControllerAdvice,捕获所有控制器或控制器的一个子集所抛出的全局异常。
这两个方案能够帮助你做一些面向业务的决策,并且在应用中定义一组实践。
要将这些处理器与HTTP 状态码进行关联,我们可以将response 注入到带有注解的方法中并使用HttpServletResponse.sendError()方法,或者为这些方法添加@ResponseStatus注解。
我们将会定义自己的异常,即EntityNotFoundException。当要操作的用户实体不存在的时候,业务repository 将会抛出该异常。这会有助于完善API 代码。
如下是异常的代码,我们可以将它放到一个名为error 的新包中:package masterSpringMvc.error;
public class EntityNotFoundException extends Exception {
public EntityNotFoundException(String message) {
super(message);
}
public EntityNotFoundException(String message, Throwable cause) {
super(message, cause);
}
}
现在,我们的repository 就可以在各种地方抛出异常了,同时,代码中也区分了保存用
户和更新用户的操作:package masterSpringMvc.user;
import masterSpringMvc.error.EntityNotFoundException;
import org.springframework.stereotype.Repository;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
@Repository
public class UserRepository {
private final Map
User > userMap = new ConcurrentHashMap ();
public User update(String email, User user) throws EntityNotFoundException {
if (!exists(email)) {
throw new EntityNotFoundException("User " + email + "cannot be found");
}
user.setEmail(email);
return userMap.put(email, user);
}
public User save(User user) {
return userMap.put(user.getEmail(), user);
}
public User findOne(String email) throws EntityNotFoundException {
if (!exists(email)) {
throw new EntityNotFoundException("User " + email + "cannot be found");
}
return userMap.get(email);
}
public List findAll() {
return new ArrayList (userMap.values());
}
public void delete(String email) throws EntityNotFoundException {
if (!exists(email)) {
throw new EntityNotFoundException("User " + email + "cannot be found");
}
userMap.remove(email);
}
public boolean exists(String email) {
return userMap.containsKey(email);
}
}
控制器变得更简单了,因为它不用处理404 状态了,现在我们在控制器方法中抛出EntityNotFound 异常:package masterSpringMvc.user.api;
import masterSpringMvc.error.EntityNotFoundException;
import masterSpringMvc.user.User;
import masterSpringMvc.user.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation. * ;
import java.util.List;
@RestController@RequestMapping("/api")
public class UserApiController {
private UserRepository userRepository;
@Autowired
public UserApiController(UserRepository userRepository) {
this.userRepository = userRepository;
}
@RequestMapping(value = "/users", method = RequestMethod.GET)
public List findAll() {
return userRepository.findAll();
}
@RequestMapping(value = "/users", method = RequestMethod.POST)
public ResponseEntity createUser(@RequestBody User user) {
HttpStatus status = HttpStatus.OK;
if (!userRepository.exists(user.getEmail())) {
status = HttpStatus.CREATED;
}
User saved = userRepository.save(user);
return new ResponseEntity (saved, status);
}
@RequestMapping(value = "/user/{email}", method = RequestMethod.PUT)
public ResponseEntity updateUser(@PathVariable String email, @RequestBody User user) throws EntityNotFoundException {
User saved = userRepository.update(email, user);
return new ResponseEntity (saved, HttpStatus.CREATED);
}
@RequestMapping(value = "/user/{email}", method = RequestMethod.DELETE)
public ResponseEntity deleteUser(@PathVariable String email) throws EntityNotFoundException {
userRepository.delete(email);
return new ResponseEntity (HttpStatus.OK);
}
}
如果我们不处理这个异常的话,Spring 默认将会抛出500 错误。为了处理这个异常,我们需要在error 包中创建一个很小的类,与EntityNotFoundException 类位于同一个包中,名称是EntityNotFoundMapper,它将会负责处理异常:package masterSpringMvc.error;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
@ControllerAdvice
public class EntityNotFoundMapper {
@ExceptionHandler(EntityNotFoundException.class)
@ResponseStatus(value = HttpStatus.NOT_FOUND, reason = "Entitycould not be found")
public void handleNotFound() {}
}
通过为bean 添加@ControllerAdvice 注解,我们能够为一组控制器添加额外的行为。这些控制器通知可以用来处理异常,也可以通过@ModelAttribute 来声明模型属性,或通过@InitBinder 声明校验策略。
借助刚刚编写的代码,我们就能处理控制器抛出的所有EntityNotFoundException 异常,并将其与404 状态进行关联。我们可以抽象这种理念并确保应用的所有控制器按照一致的方式来进行处理。
在当前的级别下,我们还没有在API 处理中处理超链接。我建议你去了解一下Spring HATEOAS 和Spring Data REST,它们提供了非常优雅的方案,能够让你的资源更容易被发现。