我在前面的文章中介绍了Spring MVC最核心的组件DispatcherServlet,DispatcherServlet把Servlet容器(如Tomcat)中的请求和Spring中的组件联系到一起,是SpringWeb应用的枢纽。但是我们在日常开发中往往不需要详细知道枢纽的作用,我们只需要处理枢纽分发给我们的请求。Spring中处理请求业务逻辑最常见的组件是Controller,本文会对Spring的Controller及相关组件做详细介绍。

Controller的定义

Controller是Spring中的一个特殊组件,这个组件会被Spring识别为可以接受并处理网页请求的组件。Spring中提供了基于注解的Controller定义方式:@Controller和@RestController注解。基于注解的Controller定义不需要继承或者实现接口,用户可以自由的定义接口签名。以下为Spring Controller定义的示例。

  1. @Controller
  2. public class HelloController {
  3. @GetMapping("/hello")
  4. public String handle(Model model) {
  5. model.addAttribute("message", "Hello World!");
  6. return "index";
  7. }
  8. }

@Controller注解继承了Spring的@Component注解,会把对应的类声明为Spring对应的Bean,并且可以被Web组件管理。@RestController注解是@Controller和@ResponseBody的组合,@ResponseBody表示函数的返回不需要渲染为View,应该直接作为Response的内容写回客户端。

映射关系RequestMapping

路径的定义

定义好一个Controller之后,我们需要将不同路径的请求映射到不同的Controller方法之上,Spring同样提供了基于注解的映射方式:@RequestMapping。通常情况下,用户可以在Controller类和方法上面添加@RequestMapping注解,Spring容器会识别注解并将满足路径条件的请求分配到对应的方法进行处理。在下面的示例中,"GET /persons/xxx"会调用getPerson方法处理。

  1. @RestController
  2. @RequestMapping("/persons")
  3. class PersonController {
  4. @GetMapping("/{id}")
  5. public Person getPerson(@PathVariable Long id) {
  6. // ...
  7. }
  8. @PostMapping
  9. @ResponseStatus(HttpStatus.CREATED)
  10. public void add(@RequestBody Person person) {
  11. // ...
  12. }
  13. }

路径的匹配

Spring支持两种路径匹配方式,二者之间可以很好的兼容,Spring默认使用PathPattern进行路径的匹配。

  1. PathPattern:使用预解析的方法匹配路径。专门为Web路径匹配而设计,可以支持复杂的表达式,执行效率很高。
  2. AntPathMatcher:Spring中用于类路径、文件系统和其它资源的解决方案,效率比较低。

PathPattern基本可以向下兼容AntPathMatcher的逻辑,并且支持路径变量和"**"多段路径匹配,以下列出几种PathPattern的示例:

路径示例 说明
/resources/ima?e.png 路径中有一个字符是可变的,如/resources/image.png
/resources/*.png 路径中多个字符是可变的,如/resources/test.png
/resources/** 路径中多段可变,如/resources/test/path/xxx
/projects/{project}/versions 匹配一段路径,并且把路径中的值提取出来,如/projects/MyApp/versions
/projects/{project:[a-z]+}/versions 匹配一段符合正则表达式路径,并且把路径中的值提取出来,如/projects/myapp/versions

路径中匹配到的变量可以使用@PathVariable获取,Path变量可以是方法或者类级别的,匹配到的变量会自动进行类型转换,如果转换失败则会抛出异常。

  1. @GetMapping("/owners/{ownerId}/pets/{petId}")
  2. public Pet findPet(@PathVariable Long ownerId, @PathVariable Long petId) {
  3. // ...
  4. }
  5. @Controller
  6. @RequestMapping("/owners/{ownerId}")
  7. public class OwnerController {
  8. @GetMapping("/pets/{petId}")
  9. public Pet findPet(@PathVariable Long ownerId, @PathVariable Long petId) {
  10. // ...
  11. }
  12. }

路径冲突

当一次请求匹配到多个Pattern,那么就需要选出最接近的Pattern路径。Spring为Pattern和AntPathMatcher提供了选择最接近的路径策略,二者之间逻辑相近,此处只介绍PathPattern。对于PathPattern,Spring提供了PathPattern.SPECIFICITY_COMPARATOR用于对比路径之间的优先级,对比的规则如下:

  1. null的pattern具有最低优先级。
  2. 包含通配符的pattern的具有最低优先级(如/**)。
  3. 如果两个pattern都包含通配符,长度比较长的有更高的优先级。
  4. 包含越少匹配符号和越少路径变量的pattern有越高的优先级。
  5. 路径越长的优先级越高。

Spring 5.3之后不再支持.*后缀匹配,默认情况下“/person”就会匹配到所有的 “/person.*”

接受和返回参数的类型

RequestMapping还可以指定接口接受什么类型的参数以及返回什么类型的参数,这通常会在请求头的Content-Type中指定:

  1. @PostMapping(path = "/pets", consumes = "application/json")
  2. public void addPet(@RequestBody Pet pet) {
  3. // ...
  4. }
  5. @GetMapping(path = "/pets/{petId}", produces = "application/json")
  6. @ResponseBody
  7. public Pet getPet(@PathVariable String petId) {
  8. // ...
  9. }

根据参数或Header选择

RequestMapping还支持按照请求的参数或者Header判断是否处理请求。

  • 如只接受参数myParam的值为myValue的情况,可以通过如下方式指定:

    1. @GetMapping(path = "/pets/{petId}", params = "myParam=myValue")
    2. public void findPet(@PathVariable String petId) {
    3. // ...
    4. }
  • 如只接受请求头中myParam的值为myValue的情况,可以通过如下方式指定:

    1. @GetMapping(path = "/pets", headers = "myHeader=myValue")
    2. public void findPet(@PathVariable String petId) {
    3. // ...
    4. }

编程式注册RequestMapping

我们前面的教程中讲的都是怎么通过@RequestMapping进行路径的映射,使用这种方式会自动把路径映射为添加了注解的方法。这种方式虽然使用很方便,但是灵活性方面有一些欠缺,如果我想要根据Bean的配置信息动态映射路径之间的关系时,注解的方式就无法做到这种需求。Spring提供了一种动态注册RequestMapping的方法,注册示例如下所示:

  1. @Configuration
  2. public class MyConfig {
  3. // 从容器中获取维护映射关系的RequestMappingHandlerMapping和自定义组件UserHandler
  4. @Autowired
  5. public void setHandlerMapping(RequestMappingHandlerMapping mapping, UserHandler handler)
  6. throws NoSuchMethodException {
  7. // 生成路径匹配信息
  8. RequestMappingInfo info = RequestMappingInfo
  9. .paths("/user/{id}").methods(RequestMethod.GET).build();
  10. // 获取需要映射的方法
  11. Method method = UserHandler.class.getMethod("getUser", Long.class);
  12. // 注册路径和方法之间的映射信息
  13. mapping.registerMapping(info, handler, method);
  14. }
  15. }

处理方法

通过RequestMapping映射通常可以把依次请求映射到某个方法,这个方法就是处理方法(Handler Methods)。处理方法的参数和返回值可以使用很多请求中的信息(如@RequestParam, @RequestHeader)等,这些参数支持使用Optional进行封装。

方法参数 说明
WebRequest, NativeWebRequest 包含了请求参数、请求和Session信息,主要用于Spring框架内部解析参数等操作
javax.servlet.ServletRequest, javax.servlet.ServletResponse Servlet的请求和参数信息
javax.servlet.http.HttpSession 请求的Session信息
javax.servlet.http.PushBuilder 服务器推送是HTTP/2协议中的新特性之一,旨在通过将服务器端的资源推送到浏览器的缓存中来预测客户端的资源需求,以便当客户端发送网页请求并接收来自服务器的响应时,它需要的资源已经在缓存中。这是一项提高网页加载速度的性能增强的功能。在Servlet 4.0中,服务器推送功能是通过PushBuilder实例公开的,此实例是从HttpServletRequest实例中获取的。
java.security.Principal 当前用户的登录信息
HttpMethod 请求的方式,如GET,POST等
java.util.Locale 请求中的国际化信息
java.util.TimeZone + java.time.ZoneId 请求的时区信息
java.io.InputStream, java.io.Reader 用于获取请求原始Body的输入流
java.io.OutputStream, java.io.Writer 用于写回响应的输出流
@PathVariable 路径变量,如"/pets/{petId}"中的petId
@MatrixVariable 用分号分割的参数,如GET /pets/42;q=11;r=22
@RequestParam 获取请求中的参数,包含multipart类型的文件
@RequestHeader 请求头信息
@CookieValue 请求中的Cookie信息
@RequestBody 把请求的Body,会使用HttpMessageConverter转为指定的类型的数据。
HttpEntity<B> 类似于@RequestBody
@RequestPart 用于获取multipart/form-data中的数据
java.util.Map, org.springframework.ui.Model, org.springframework.ui.ModelMap 获取用于渲染HTML视图的参数
@ModelAttribute 用于获取模型中的属性
Errors, BindingResult 获取参数校验结果信息
SessionStatus + class-level @SessionAttributes Session信息
UriComponentsBuilder 获取匹配过程中的参数信息
@SessionAttribute 获取一个Session属性
@RequestAttribute 获取请求中的属性

处理方法也可以支持很多类型的返回值,不同类型的返回有不同的意义。

返回参数 说明
@ResponseBody @RestController就包含了这个注解,这个注解表示使用HttpMessageConverter把返回值写入Response,不会进行视图解析
HttpEntity<B>, ResponseEntity<B> 和@ResponseBody类似,返回值直接写入Response
HttpHeaders 只返回Header不返回body
String 按照返回值去查找View,并解析为模型
View 返回一个视图
java.util.Map, org.springframework.ui.Model 用于渲染视图的模型,View由RequestToViewNameTranslator决定
@ModelAttribute 用于渲染视图的模型,View由RequestToViewNameTranslator决定
ModelAndView 返回一个可用的模型视图
void 通常表示没有返回Body
DeferredResult<V> 异步返回结果,后文详细介绍
Callable<V> 异步返回结果,后文详细介绍
ListenableFuture<V>, java.util.concurrent.CompletionStage<V>, java.util.concurrent.CompletableFuture<V> 类似于DeferredResult,异步返回调用结果
ResponseBodyEmitter, SseEmitter 异步的把HttpMessageConverter转换后的Body写入Response
StreamingResponseBody 把返回异步写入Response
Reactive types — Reactor, RxJava, or others through ReactiveAdapterRegistry Flux场景下的异步返回

类型转换

网络请求的参数往往是String类型的,而映射到后端时需要转为处理方法需要的数据类型(如@RequestParam, @RequestHeader, @PathVariable, @MatrixVariable 和 @CookieValue)。这种情况下Spring会获取容器内的类型转换服务和属性编辑器进行转换,用户也可以向WebDataBinder中注入自己需要的转换服务。

Matrix参数

Matrix参数其实时RFC3986中关于Url编码的一些规范,Matrix参数之间用分号分割,Matrix参数的多个值之间用逗号分割,例如/cars;color=red,green;year=2012,多个值之间也允许用分号分割,如color=red;color=green;color=blue

如果一个URL需要包含Matrix参数,那么包含Matrix参数应该是一个路径变量,否则Matrix参数会对路径匹配造成影响:

  1. // GET /pets/42;q=11;r=22
  2. // 最后一段路径必须为路径变量{petId},否则会造成路径匹配失败
  3. @GetMapping("/pets/{petId}")
  4. public void findPet(@PathVariable String petId, @MatrixVariable int q) {
  5. // petId == 42
  6. // q == 11
  7. }

不仅仅URL最后一段可以加Matrix参数,URL的任意一段都可以家Matrix参数,如下所示:

  1. // GET /owners/42;q=11/pets/21;q=22
  2. @GetMapping("/owners/{ownerId}/pets/{petId}")
  3. public void findPet(
  4. @MatrixVariable(name="q", pathVar="ownerId") int q1,
  5. @MatrixVariable(name="q", pathVar="petId") int q2) {
  6. // q1 == 11
  7. // q2 == 22
  8. }

Matrix参数允许设置默认值,用户没有传该参数的时候使用这个默认值:

  1. // GET /pets/42
  2. @GetMapping("/pets/{petId}")
  3. public void findPet(@MatrixVariable(required=false, defaultValue="1") int q) {
  4. // q == 1
  5. }

如果路径中包含很多Matrix参数,一个一个接收可能比较麻烦,我们可以通过MultiValueMap用集合的形式去接收:

  1. // GET /owners/42;q=11;r=12/pets/21;q=22;s=23
  2. @GetMapping("/owners/{ownerId}/pets/{petId}")
  3. public void findPet(
  4. @MatrixVariable MultiValueMap<String, String> matrixVars,
  5. @MatrixVariable(pathVar="petId") MultiValueMap<String, String> petMatrixVars) {
  6. // matrixVars: ["q" : [11,22], "r" : 12, "s" : 23]
  7. // petMatrixVars: ["q" : 22, "s" : 23]
  8. }

如果你需要在程序中使用Matrix参数,需要的配置UrlPathHelperremoveSemicolonContent=false

@RequestParam

@RequestParam用于把请求中的参数(查询参数或者表单参数)绑定到对应的方法参数上,默认情况下不允许请求参数中不包含指定的参数,不过用户可以指定required=false去允许设置请求参数到对应的方法参数。如果方法的参数类型不为String类型,Spring会自动进行类型转换。当@RequestParam注解的参数类型为Map<String, String>并且@RequestParam没有指定参数名称的时候,Spring会把所有的参数注入到Map中。

  1. @Controller
  2. @RequestMapping("/pets")
  3. public class EditPetForm {
  4. // ...
  5. @GetMapping
  6. public String setupForm(@RequestParam("petId") int petId, Model model) {
  7. Pet pet = this.clinic.loadPet(petId);
  8. model.addAttribute("pet", pet);
  9. return "petForm";
  10. }
  11. // ...
  12. }

@RequestHeader

一次Http请求往往会包含请求头和Body两部分,我们可以通过@RequestHeader把请求头和处理方法的参数进行绑定,@RequestHeader同样支持Map,假设一次请求有如下的头:

  1. Host localhost:8080
  2. Accept text/html,application/xhtml+xml,application/xml;q=0.9
  3. Accept-Language fr,en-gb;q=0.7,en;q=0.3
  4. Accept-Encoding gzip,deflate
  5. Accept-Charset ISO-8859-1,utf-8;q=0.7,*;q=0.7
  6. Keep-Alive 300

如果我们需要在方法中获取Accept-Encoding和Keep-Alive标签,我们可以通过如下代码获取:

  1. @GetMapping("/demo")
  2. public void handle(
  3. @RequestHeader("Accept-Encoding") String encoding,
  4. @RequestHeader("Keep-Alive") long keepAlive) {
  5. //...
  6. }

@CookieValue

如果我们需要获取一次请求中的cookie信息,我们可以通过@CookieValue获取,获取方法如下所示:

  1. @GetMapping("/demo")
  2. public void handle(@CookieValue("JSESSIONID") String cookie) {
  3. //...
  4. }

@ModelAttribute

@ModelAttribute可以把请求中的参数映射为对象,然后传递给对应的方法。

  1. @PostMapping("/owners/{ownerId}/pets/{petId}/edit")
  2. public String processSubmit(@ModelAttribute Pet pet) {
  3. // method logic...
  4. }

上面的例子中,请求参数可以来自pet可以来自以下几种途径:

  1. 在请求预处理的过程中添加的@ModelAttribute属性中的pet;
  2. 从HttpSession中的@SessionAttributes属性中查找pet;
  3. 从请求参数或者pathVariable中查找pet属性;
  4. 使用默认的构造函数初始化数据。
  1. @PutMapping("/accounts/{account}")
  2. public String save(@ModelAttribute("account") Account account) {
  3. // ...
  4. }
  5. @PostMapping("/owners/{ownerId}/pets/{petId}/edit")
  6. public String processSubmit(@ModelAttribute("pet") Pet pet, BindingResult result) {
  7. if (result.hasErrors()) {
  8. return "petForm";
  9. }
  10. // ...
  11. }
  12. @ModelAttribute
  13. public AccountForm setUpForm() {
  14. return new AccountForm();
  15. }
  16. @ModelAttribute
  17. public Account findAccount(@PathVariable String accountId) {
  18. return accountRepository.findOne(accountId);
  19. }
  20. @PostMapping("update")
  21. public String update(@Valid AccountForm form, BindingResult result,
  22. @ModelAttribute(binding=false) Account account) {
  23. // ...
  24. }

@SessionAttributes 和 @SessionAttribute

@SessionAttributes用于在多个请求之间共享Session数据,该注解只能加载类之上。在第一次请求的时候,会把Session数据放入SessionAttributes中,Session结束的时候清除数据。

  1. @Controller
  2. @SessionAttributes("pet") // 把数据放入Session中
  3. public class EditPetForm {
  4. // 从Session中查询数据
  5. @PostMapping("/pets/{id}")
  6. public String handle(Pet pet, BindingResult errors, SessionStatus status) {
  7. if (errors.hasErrors) {
  8. // ...
  9. }
  10. // 清空Session中的数据.
  11. status.setComplete();
  12. // ...
  13. }
  14. }
  15. }

如果Session的属性不由Controller管理,而是其它组件管理(如Filter管理),我们就可以使用@SessionAttribute去把Session中的数据和处理方法中的参数进行绑定。

  1. @RequestMapping("/")
  2. public String handle(@SessionAttribute User user) {
  3. // ...
  4. }

@RequestAttribute

@RequestAttribute和@SessionAttributes类似,一个是请求级别的,一个是Session级别的,此处不做详细介绍。

  1. @GetMapping("/")
  2. public String handle(@RequestAttribute Client client) {
  3. // ...
  4. }

Multipart参数

我们在前面的文章中说过,DispatcherServlet中会包含MultipartResolver组件,如果一次请求的数据为multipart/form-data类型,DispatcherServlet会把上传的文件解析为MultipartFile格式的文件。Servlet3中也支持使用 javax.servlet.http.Part代替MultipartFile接收文件,上传多个文件的时候可以使用列表或者Map获取参数。

  1. @Controller
  2. public class FileUploadController {
  3. @PostMapping("/form")
  4. public String handleFormUpload(@RequestParam("name") String name,
  5. @RequestParam("file") MultipartFile file) {
  6. if (!file.isEmpty()) {
  7. byte[] bytes = file.getBytes();
  8. // store the bytes somewhere
  9. return "redirect:uploadSuccess";
  10. }
  11. return "redirect:uploadFailure";
  12. }
  13. }

Multipart也可以把需要接收的文件封装为对象进行接收。

  1. class MyForm {
  2. private String name;
  3. private MultipartFile file;
  4. // ...
  5. }
  6. @Controller
  7. public class FileUploadController {
  8. @PostMapping("/form")
  9. public String handleFormUpload(MyForm form, BindingResult errors) {
  10. if (!form.getFile().isEmpty()) {
  11. byte[] bytes = form.getFile().getBytes();
  12. // store the bytes somewhere
  13. return "redirect:uploadSuccess";
  14. }
  15. return "redirect:uploadFailure";
  16. }
  17. }

除了通过浏览器上传文件,我们还可以通过RestFul方式以Json的格式上传文件:

  1. POST /someUrl
  2. Content-Type: multipart/mixed
  3. --edt7Tfrdusa7r3lNQc79vXuhIIMlatb7PQg7Vp
  4. Content-Disposition: form-data; name="meta-data"
  5. Content-Type: application/json; charset=UTF-8
  6. Content-Transfer-Encoding: 8bit
  7. {
  8. "name": "value"
  9. }
  10. --edt7Tfrdusa7r3lNQc79vXuhIIMlatb7PQg7Vp
  11. Content-Disposition: form-data; name="file-data"; filename="file.properties"
  12. Content-Type: text/xml
  13. Content-Transfer-Encoding: 8bit
  14. ... File Data ...
  1. @PostMapping("/")
  2. public String handle(@RequestPart("meta-data") MetaData metadata,
  3. @RequestPart("file-data") MultipartFile file) {
  4. // ...
  5. }

@RequestBody和HttpEntity

@RequestBody应该是日常开发中使用最多的参数之一了,我们可以通过@RequestBody把请求中的Body和处理方法中的参数对象进行绑定,Spring会调用HttpMessageConverter服务把请求中的数据反序列化为处理方法中的参数对象。@RequestBody还可以和@Validated注解组合进行使用,如果校验失败会抛出异常或者交给用户处理校验异常信息。

  1. @PostMapping("/accounts")
  2. public void handle(@Valid @RequestBody Account account, BindingResult result) {
  3. // ...
  4. }

HttpEntity和@RequestBody的原理类似,不过会把请求体封装到HttpEntity中。

  1. @PostMapping("/accounts")
  2. public void handle(HttpEntity<Account> entity) {
  3. // ...
  4. }

@ResponseBody和ResponseEntity

@ResponseBody表示会把返回值通过HttpMessageConverter直接序列化为String写入Response,我们平时使用比较多的@RestController就是由@ResponseBody和@Controller组成。

  1. @GetMapping("/accounts/{id}")
  2. @ResponseBody
  3. public Account handle() {
  4. // ...
  5. }

ResponseEntity和@ResponseBody,不过返回的基础上会包含状态码和返回头等信息。

  1. @GetMapping("/something")
  2. public ResponseEntity<String> handle() {
  3. String body = ... ;
  4. String etag = ... ;
  5. return ResponseEntity.ok().eTag(etag).build(body);
  6. }

JSON Views

Spring内置了对JacksonJSON的支持,并且支持Jackson的Json序列化视图,在使用@ResponseBody和ResponseEntity返会数据时,可以按照@JsonView来指定Json序列化时需要显示的字段。

  1. @RestController
  2. public class UserController {
  3. @GetMapping("/user")
  4. @JsonView(User.WithoutPasswordView.class)
  5. public User getUser() {
  6. return new User("eric", "7!jd#h23");
  7. }
  8. }
  9. public class User {
  10. public interface WithoutPasswordView {};
  11. public interface WithPasswordView extends WithoutPasswordView {};
  12. private String username;
  13. private String password;
  14. public User() {
  15. }
  16. public User(String username, String password) {
  17. this.username = username;
  18. this.password = password;
  19. }
  20. @JsonView(WithoutPasswordView.class)
  21. public String getUsername() {
  22. return this.username;
  23. }
  24. @JsonView(WithPasswordView.class)
  25. public String getPassword() {
  26. return this.password;
  27. }
  28. }

我们也可以通过编程的方式实现对象的不同视图的序列化,使用方法如下所示:

  1. @RestController
  2. public class UserController {
  3. @GetMapping("/user")
  4. public MappingJacksonValue getUser() {
  5. User user = new User("eric", "7!jd#h23");
  6. MappingJacksonValue value = new MappingJacksonValue(user);
  7. value.setSerializationView(User.WithoutPasswordView.class);
  8. return value;
  9. }
  10. }

对于基于View的解决方案,我们可以在Model中添加对应的对象以及Json序列化视图,使用的示例如下所示:

  1. @Controller
  2. public class UserController extends AbstractController {
  3. @GetMapping("/user")
  4. public String getUser(Model model) {
  5. model.addAttribute("user", new User("eric", "7!jd#h23"));
  6. model.addAttribute(JsonView.class.getName(), User.WithoutPasswordView.class);
  7. return "userView";
  8. }
  9. }

Model对象

Spring中的model对象负责在控制器和展现数据的视图之间传递数据。Spring提供了@ModelAttribute去获取和写入Model对象的属性,@ModelAttribute有多种使用方式:

  1. 在处理方法的入参上添加@ModelAttribute,可以获取WebDataBinder中已经有的Model中的属性值。
  2. 在类上(如Controller)添加@ModelAttribute注解,则会为所有的请求初始化模型。
  3. 在处理方法的返回值上添加@ModelAttribute,表示返回值会作为模型的属性。
  1. @ModelAttribute
  2. public void populateModel(@RequestParam String number, Model model) {
  3. model.addAttribute(accountRepository.findAccount(number));
  4. // add more ...
  5. }
  6. @ModelAttribute
  7. public Account addAccount(@RequestParam String number) {
  8. return accountRepository.findAccount(number);
  9. }

DataBinder

前面我们讲了很多如何把请求参数和处理方法入参进行绑定的注解或者类型,并且知道请求参数需要经过类型转换才能转为对应类型的数据。然而注解只是一个标记,并不会实际执行参数绑定和类型转换操作,Spring中必定有一个组件进行参数绑定和类型转换,这个组件就是WebDataBinder。WebDataBinder有一下作用:

  1. 将请求中的参数和处理方法参数进行绑定;
  2. 把请求中Spring类型的数据转为处理方法的参数类型;
  3. 对渲染表单的数据进行格式化。

Spring给用户提供了修改WebDataBinder的接口,用户可以在Controller中定义被@InitBinder注解的方法,在方法中修改WebDataBinder的定义:

  1. @Controller
  2. public class FormController {
  3. @InitBinder
  4. public void initBinder(WebDataBinder binder) {
  5. SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
  6. dateFormat.setLenient(false);
  7. binder.registerCustomEditor(Date.class, new CustomDateEditor(dateFormat, false));
  8. }
  9. // ...
  10. }

异常处理

在关于DispatcherServlet相关的章节中,我们知道了DispatcherServlet包含了异常解析组件,当异常发生的时候会对异常进行解析。日常开发中使用比较多的异常处理组件是ExceptionHandlerExceptionResolver,用于在遇到异常时,使用带有@ExceptionHandler注解的方法处理对应的异常,该方法可以定义中Controller或者ControllerAdvice中。

  1. @Controller
  2. public class SimpleController {
  3. // ...
  4. @ExceptionHandler
  5. public ResponseEntity<String> handle(IOException ex) {
  6. // ...
  7. }
  8. @ExceptionHandler({FileSystemException.class, RemoteException.class})
  9. public ResponseEntity<String> handle(Exception ex) {
  10. // ...
  11. }
  12. }

如果我们需要定义很多@ExceptionHandler,我们可以选择在@ControllerAdvice中定义,而不是在每个Controller中定义。

如果一个异常匹配到多个@ExceptionHandler,Spring会尝试使用距离异常继承体系最近的@ExceptionHandler去处理这个异常。

Controller Advice

如果我们需要定义全局的@InitBinder或者@ExceptionHandler,那我们就不应该在Controller中定义这些方法。 Spring提供了@ControllerAdvice用于添加全局配置:

  1. // Target all Controllers annotated with @RestController
  2. @ControllerAdvice(annotations = RestController.class)
  3. public class ExampleAdvice1 {}
  4. // Target all Controllers within specific packages
  5. @ControllerAdvice("org.example.controllers")
  6. public class ExampleAdvice2 {}
  7. // Target all Controllers assignable to specific classes
  8. @ControllerAdvice(assignableTypes = {ControllerInterface.class, AbstractController.class})
  9. public class ExampleAdvice3 {}

我是御狐神,欢迎大家关注我的微信公众号:wzm2zsd

本文最先发布至微信公众号,版权所有,禁止转载!

SpringMVC 解析(三) Controller 注解的更多相关文章

  1. spring mvc:内部资源视图解析器2(注解实现)@Controller/@RequestMapping

    spring mvc:内部资源视图解析器2(注解实现)  @Controller/@RequestMapping 访问地址: http://localhost:8080/guga2/hello/goo ...

  2. SpringMVC自动扫描@Controller注解的bean

    若要对@Controller注解标注的bean进行自动扫描,必须将<context:component-scan base-package="包路径.controller"/ ...

  3. 【Java EE 学习 83 下】【SpringMVC】【使用注解替代已过时的API】【SpringMVC、Hibernate整合】

    一.SpringMVC中注解的使用 1.为什么要使用注解 之前曾经提到过的三种控制器在spring3.0中都已经被明确标记为过时了,spring3.0推荐使用注解的方式替代三种控制器,实际上使用注解的 ...

  4. springMVC学习笔记(二)-----注解和非注解入门小程序

    最近一直在做一个电商的项目,周末加班,忙的都没有时间更新博客了.终于在上周五上线了,可以轻松几天了.闲话不扯淡了,继续谈谈springMvc的学习. 现在,用到SpringMvc的大部分使用全注解配置 ...

  5. SpringMVC框架搭建 基于注解

    本文将以一个很简单的案例实现 Springmvc框架的基于注解搭建,一下全为个人总结 ,如有错请大家指教!!!!!!!!! 第一步:创建一个动态web工程(在创建时 记得选上自动生成 web.xml ...

  6. SpringMVC解析3-DispatcherServlet组件初始化

    在spring中,ContextLoaderListener只是辅助功能,用于创建WebApplicationContext类型实例,而真正的逻辑实现其实是在DispatcherServlet中进行的 ...

  7. springmvc入门基础之注解和参数传递

    一.SpringMVC注解入门 1. 创建web项目2. 在springmvc的配置文件中指定注解驱动,配置扫描器 <!-- mvc的注解驱动 --> <mvc:annotation ...

  8. SpringMvc入门三----控制器

    在传统的Spring MVC开发方法中,必须在Bean配置文件中为每个控制器类配置实例和请求映射和让每个控制器类去实现或者扩展特定于框架的接口或者基类,不够灵活. 如果Spring MVC可以自动侦测 ...

  9. springMVC整理02--常用注解的使用

    1.使用@RequestMapping  映射请求 1.1在类和方法上添加@RequestMappingSpringMVC 使用@RequestMapping 为控制器指定可以处理哪些 URL 请求. ...

随机推荐

  1. Selenium_截图(16)

    selenium截图有两种方式 截取全屏 get_screenshot_as_file(filename):将截图转化成文件保存到本地,filename为保存的文件路径 get_screenshot_ ...

  2. python中类对象、实例对象、类属性、实例属性、类方法、实例方法、静态方法

    类对象.类属性与实例对象.实例属性的区别 在Python中一切皆是对象,类是一个特殊的对象即类对象,描述类的属性称为类属性.类属性在内存中只有一份,在__init__外部定义. 通过类创建的对象称为实 ...

  3. Linux系统使用SSH登录之前如何显示横幅消息

    OpenSSH有一个名为Banner的内置选项.在允许身份验证之前,将指定文件的内容发送给远程用户.如果Banner选项设置为none,那么在ssh登录时就不会显示任何Banner消息.默认情况下,不 ...

  4. 使用 淘宝 接口,根据公网ip 获取地理信息

    1.  源码,点击查看 1 import java.io.BufferedReader; 2 import java.io.IOException; 3 import java.io.InputStr ...

  5. 安装Apache-storm-0.9.1-incubating图解教程

    注:图片如果损坏,点击文章链接:https://www.toutiao.com/i6596214331988247054/ 安装步骤 (1) 安装Zookeeper集群,可以参考前一篇文章,本文已安装 ...

  6. vue render中如何正确配置img的路径

    第一种:适用于静态路径 attrs: { src: require('../common/images/logo.png'), title: 'img' } 第二种:适用于动态路径 domProps: ...

  7. 《手把手教你》系列技巧篇(五十六)-java+ selenium自动化测试-下载文件-上篇(详细教程)

    1.简介 前边几篇文章讲解完如何上传文件,既然有上传,那么就可能会有下载文件.因此宏哥就接着讲解和分享一下:自动化测试下载文件.可能有的小伙伴或者童鞋们会觉得这不是很简单吗,还用你介绍和讲解啊,不说就 ...

  8. 使用.NET 6开发TodoList应用(27)——实现API的Swagger文档化

    系列导航及源代码 使用.NET 6开发TodoList应用文章索引 需求 在日常开发中,我们需要给前端提供文档化的API接口定义,甚至需要模拟架设一个fake服务用来调试接口字段.或者对于后端开发人员 ...

  9. Docker 与 K8S学习笔记(十 二)容器间数据共享

    数据共享是volume的关键特性,今天我们来看一下通过volume实现容器与host.容器与容器之间共享数据. 一.容器与host共享数据 在上一篇中介绍到的bind mount和docker man ...

  10. 为什么JavaWeb要分层

    首先bai让我们坐着时光机回到n年前的web开发.那个时候最早du都是静态的html页面,zhi后来有了数据库,有了所谓dao的动态页面,然后程序猿在编码的时候,会把所有的代码都写在页面上,包括数据库 ...