2.3 @ControllerAdvice
@ControllerAdvice註解一個特殊的Bean元件,顧名思義它負責所有控制器共享的功能,如異常處理(配合@ExceptionHandler)、資料繫結(配合@InitBinder)等。@ControllerAdvice透過屬性指定起效的控制器範圍:
透過註解限制:
@ControllerAdvice(annotations = RestController.class) public class someControllerAdvice{}
透過包名指定:
@ControllerAdvice("top.wisely.learningspringmvc.controller")public class someControllerAdvice{}
指定控制器:
@ControllerAdvice(assignableTypes = {PeopleController.class, DemoController.class})public class someControllerAdvice{}
2.3.1 異常處理
我們使用@ExceptionHandler組合@ControllerAdvice處理所有控制器的異常,當然@ExceptionHandler也可以註解在@Controller(包含@RestController)內,異常處理只對當前控制器有效。
我們先自定義個異常:
@Getter@Setter@AllArgsConstructor@NoArgsConstructorpublic class PersonNameNotFoundException extends RuntimeException{ private String name;}
我們定義異常處理類:
@ControllerAdvicepublic class ExceptionHandlerAdvice { @ExceptionHandler(PersonNameNotFoundException.class) // public ResponseEntity<String> customExceptionHandler(PersonNameNotFoundException exception){ return ResponseEntity.status(HttpStatus.NOT_FOUND) .body(exception.getName() + "沒有找到!"); } }
我們在控制器中,手動丟擲異常:
@GetMapping("/exceptions")public void exceptions(String name) { throw new PersonNameNotFoundException(name);}
異常被ExceptionHandlerAdvice的exceptionHandler方法處理。
2.3.2 初始化資料繫結使用@InitBinder註解組合@ControllerAdvice實現初始化資料繫結。所謂出具繫結資料繫結,即在web請求在進入控制器方法處理之前,對web請求引數(不包含請求體@RequestBody,它使用下節的RequestBodyAdvice處理)進行預先的初始化處理,這個處理是透過WebDataBinder物件來做的。這些請求引數包括來自於@RequestParam、@PathVariable、ServletRequest、ServletReponse等,也就是這些引數都可以在@InitBinder註解的方法中進行先處理,該方法沒有返回值。如我們將引數1-wyf-35轉成一個Person物件。@InitBinder也可以用在@Controller內,只對使用的控制器有效。
我們先定義一個屬性編輯器將接收到的格式為id-name-age轉換成物件:
public class PersonEditor extends PropertyEditorSupport { @Override public void setAsText(String text) throws IllegalArgumentException { String[] personStr = text.split("-"); // 將1-wyf-35分割成字串陣列 Long id = Long.valueOf(personStr[0]); String name = personStr[1]; Integer age = Integer.valueOf(personStr[2]); setValue(new Person(id, name, age)); //利用字串陣列建立Person物件 }}
我們使用@InitBinder來註冊這個屬性編輯器:
@ControllerAdvice@Slf4jpublic class InitBinderAdvice { @InitBinder public void registerPersonEditor(WebDataBinder binder, @RequestBody String person){ log.info("在InitBinder中為字串:" + person); binder.registerCustomEditor(Person.class, new PersonEditor()); }}
未經WebDataBinder註冊PersonEditor轉換前,從請求引數裡拿到的只是字串person,此處的字串引數只是為了大家加深理解需要,我們開發時不需要這個引數。
我們用控制器來驗證:
@GetMapping("/propertyEditor")public Person propertyEditor(@RequestParam Person person){//支援的引數型別@RequetParam log.info("經過InitBinder註冊的PropertyEditor轉換後為物件:" + person); return person; return person;}
控制檯顯示:
我們正確地將字串1-wyf-35轉成了Person物件。
2.3.3 自定義Validator我們在“引數校驗”一節演示了預設的校驗方式,若有特殊的校驗需求,也可以透過實org.springframework.validation.Validator介面來完全控制校驗行為:
public class PersonValidator implements Validator { @Override public boolean supports(Class<?> clazz) { return Person.class.isAssignableFrom(clazz); //只支援Person類 } @Override public void validate(Object target, Errors errors) { Person person = (Person) target; validateId(person, errors); //校驗id validateName(person, errors); //校驗name validateAge(person, errors); //校驗age } private void validateId(Person person, Errors errors){ ValidationUtils.rejectIfEmpty(errors,"id", "person.code", "id不能為空-自定義"); } private void validateName(Person person, Errors errors){ int nameLength = person.getName().length(); if (nameLength < 3 || nameLength > 5) errors.rejectValue("name", "person.name", "name在3到5個字元之間-自定義"); } private void validateAge(Person person, Errors errors){ if (person.getAge() < 18) errors.rejectValue("age", "person.age", "age不能低於18歲-自定義"); }}
在InitBinderAdvice類中的@InitBinder方法中註冊:
@ControllerAdvicepublic class InitBinderAdvice { @InitBinder public void setPersonValidator(WebDataBinder binder){ binder.setValidator(new PersonValidator()); }}
我們還是使用前面的校驗控制器方法請求: