Spring MVC框架的作用
-
创建Spring Boot项目自动添加基础依赖项中没有SpringMVC框架依赖,把基础依赖项spring-boot-starter换成spring-boot-starter-web依赖项,该依赖项中包含SpringMVC依赖项
Spring MVC框架的基础依赖项是spring-webmvc
。
Spring MVC框架主要解决了接收请求、响应结果及相关的问题。
关于控制器
控制器(Controller)是用于接收请求、响应结果的。
在类上添加@Controller
即可将此类标记为“控制器类”。
默认情况下,Spring MVC框架处理响应时,并不是“响应正文”的,各方法的返回值表示“视图组件的名称”,也就是说,每次处理完请求后,Spring MVC框架还应该处理一个“视图”,最终,将“视图”响应到客户端去,这不是前后端分离的做法。(视图组件暂时可以理解为html页面)
目前,主流的开发模式是前后端分离的,更加适用于多种不同客户端软件的项目中,在这种模式下,当服务器端每次处理完请求后,只需要将结果(数据)响应到客户端,由客户端去决定如何识别、使用所响应的数据。
在处理请求的方法上添加@ResponseBody
注解,即可使得此方法是“响应正文”的,即方法的返回值会直接响应到客户端去,就符合前后端分离的模式。
也可以将@ResponseBody
添加在控制器类上,则当前类中所有处理请求的方法都是“响应正文”的!
在开发实践中,通常选择在类上添加@RestController
注解,此注解使用了@Controller
和@ResponseBody
作为其元注解,同时具有这2个注解的效果,其源代码:
@Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Controller @ResponseBody public @interface RestController { @AliasFor( annotation = Controller.class ) String value() default ""; }
关于@RequestMapping
注解
在Spring MVC框架中,提供了@RequestMapping
,主要用于配置请求路径与处理请求的方法的映射关系。
还可以在控制器类上添加@RequestMapping
注解,当配置了某个路径值后,此路径值将作为当前控制器类中的每个处理请求的URL的前缀部分,例如,在类上的配置值是/album
,在方法上的配置值是/add-new
,则完整的URL就是/album/add-new
。
提示:如果在控制器类和方法上都使用@RequestMapping
配置了URL,框架会自动处理必要的和多余的/
,例如:
在类上使用@RequestMapping 的配置值 | 在方法上使用@RequestMapping 的配置值 |
---|---|
/album | /add-new |
/album | add-new |
album | /add-new |
album | add-new |
/album/ | /add-new |
/album/ | add-new |
album/ | /add-new |
album/ | add-new |
以上配置的组合都是等效的!通常,推荐使用以上第1种或第4种。
在@RequestMapping
注解的源代码中有:
String[] value() default {};
以上代码表示:
-
value()
:表示此注解可以配置名为value
的属性 -
String[]
:表示此属性的值是String[]
类型的 -
default {}
:表示此属性的默认值是空数组
所以,在使用时,可以配置为:
@RequestMapping(value = {"a", "b"})
在Java语言中,如果需要配置的注解参数的属性名是value
,且只配置这1个属性时,可以不必显式声明属性名称,即:
@RequestMapping(value = {"a", "b"}) @RequestMapping({"a", "b"})
以上2种写法是完全等效的!
在Java语言中,如果需要配置的注解属性的值是某种数组,但是,需要使用的数组值中只有1个元素时,可以不必使用大括号将其框住,即:
@RequestMapping({"a"}) @RequestMapping("a")
以上2种写法是完全等效的!
在@RequestMapping
的源代码有:
@AliasFor("path") // 关注 String[] value() default {};
以上@AliasFor
表示“等效于”,所以,在源代码中还有:
@AliasFor("value") String[] path() default {};
另外,在@RequestMapping
的源代码中有:
RequestMethod[] method() default {};
以上属性用于“限制客户端提交请求时的请求方式”,如果没有配置此属性,则所有的请求方式都是允许的,如果进行限制,则只允许特定的请求方式,如果使用了不匹配的请求方式,将响应405
,例如,配置为:
@RequestMapping(value = "/hello", method = RequestMethod.POST)
当提交GET类型的请求时,服务器端将响应:
Whitelabel Error Page This application has no explicit mapping for /error, so you are seeing this as a fallback. Thu Feb 23 11:35:10 CST 2023 There was an unexpected error (type=Method Not Allowed, status=405).
并且,如果存在2个配置,只要请求方式不同,URL允许相同!例如:
@RequestMapping(value = "/hello", method = RequestMethod.POST) public String hello1() { // ... } @RequestMapping(value = "/hello", method = RequestMethod.GET) public String hello2() { // ... }
在Spring MVC框架中,提供了一些基于@RequestMapping
的其它注解,例如:
-
@GetMapping
-
@PostMapping
-
@PutMapping
-
@DeleteMapping
-
@PatchMapping
开发实践小结
-
在控制器类上推荐使用
@RestController
-
在控制器类上推荐使用
@RequestMapping
配置URL前缀 -
各处理请求的方法上推荐使用
@GetMapping
等限制了请求方法的注解来配置URL
统一处理异常
Spring MVC框架提供了统一处理异常的机制,可以实现“对于每种异常,只需要编写1次处理此异常的代码即可,无论在处理哪个请求的过程中出现此异常,都会通过这同一段代码进行处理”。
关于统一处理异常的方法:
-
注解:
@ExceptionHandler
-
返回值类型:参考处理请求的方法
-
方法名:参考处理请求的方法
-
参数列表:必须包含异常类型的参数,表示需要被处理的异常,并且,只能按需添加
HttpServletRequest
、HttpServletResponse
等少量特定类型的参数,不可以随意添加其它参数,当方法有多个参数时,各参数可以不区分先后顺序
例如,在AlbumController
中添加:
@ExceptionHandler public JsonResult handleServiceException(ServiceException e) { return JsonResult.fail(ServiceCode.ERROR, "出错啦~~~!"); }
则当前类中任何处理请求的方法不再需要处理ServiceException
,如果执行过程中出现了ServiceException
,都会由以上方法进行处理!
需要注意:当把处理异常的方法定义在控制器类中时,仅作用于当前控制器类中所有处理请求的方法,无法作用于其它控制器类中处理请求的方法!
应该自定义类,在类上添加@ControllerAdvice
注解,则此类中特定的方法(例如添加了@ExceptionHandler
注解的方法)将作用于Spring MVC框架处理每个请求的过程中,例如:
@ControllerAdvice public class GlobalExceptionHandler { @ExceptionHandler @ResponseBody public JsonResult handleServiceException(ServiceException e) { return JsonResult.fail(ServiceCode.ERROR, "出错啦~~~!"); } }
另外,也可以使用@RestControllerAdvice
来取代@ControllerAdvice
与@ResponseBody
,例如:
@RestControllerAdvice // 取代@ControllerAdvice与@ResponseBody public class GlobalExceptionHandler { @ExceptionHandler public JsonResult handleServiceException(ServiceException e) { return JsonResult.fail(ServiceCode.ERROR, "出错啦~~~!"); } }
在处理异常的类中,关于处理异常的方法:
-
允许同时存在若干个处理异常的方法,但各方法处理的异常必须不同
-
多个处理不同异常的方法,所处理的异常允许存在父子级关系,在实际处理时,优先按照最匹配的类型的方法进行处理
-
强烈建议添加处理
Throwable
类型异常的方法,避免向客户端响应500错误
-
在此方法中,应该记录异常的类型和关键信息,并且,执行到此方法时,应该及时添加新的、处理对应异常的方法
-
另外,关于异常信息的描述,应该是“谁抛出,谁描述”的原则,所以,应该在ServiceException
类中添加带String message
的构造方法,并且通过父级类别的构造方法将参数用于对detailMessage
属性(定义在Throwable
类中)赋值,后续,可以通过getMessage()
方法获取此值,例如:
public class ServiceException extends RuntimeException { public ServiceException(String message) { super(message); } }
然后,在业务实现过程中,当需要抛出异常时,封装描述文本,例如:
// 判断查询结果是否为null if (album == null) { // 是:数据不存在,抛出异常 String message = "删除相册失败,尝试删除的相册数据不存在!"; log.warn(message); throw new ServiceException(message); }
后续,在全局异常处理器中,通过异常对象可以获取所封装的描述文本,例如:
@ExceptionHandler public JsonResult handleServiceException(ServiceException e) { return JsonResult.fail(ServiceCode.ERROR, e.getMessage()); }
相关注解汇总
springMVC
@Value
@SpringBootTest
@Test
@Autowired
@Configuration
@Controller
@RestController
@ResponseBody
@Repository
@RequestBody
@AliasFor
@RequestMapping
-
@GetMapping
-
@PostMapping
-
@PutMapping
-
@DeleteMapping
-
@PatchMapping
@ExceptionHandler
@ControllerAdvice
@RestControllerAdvice
枚举
在异常处理时需要向前端返回业务状态代码,状态代码可以用枚举类来实现
public enum ServiceCode { OK(20000), ERROR_BAD_REQUEST(40000), ERROR_NOT_FOUND(40400), ERROR_CONFLICT(40900), ERROR_UNKNOWN(99999); private Integer value; ServiceCode(Integer value) { this.value = value; } public Integer getValue() { return value; } }
-
声明枚举时必须使用 enum 关键字,然后定义枚举的名称、可访问性、基础类型和成员等。
-
枚举是一个被命名的整型常数的集合,用于声明一组带标识符的常数。枚举在曰常生活中很常见,例如一个人的性别只能是“男”或者“女”,一周的星期只能是 7 天中的一个等。类似这种当一个变量有几种固定可能的取值时,就可以将它定义为枚举类型。枚举的结果是可以穷举的
-
在 JDK 1.5 之前没有枚举类型,那时候一般用接口常量来替代。而使用 Java 枚举类型 enum 可以更贴近地表示这种常量。
-
任意两个枚举成员不能具有相同的名称,且它的常数值必须在该枚举的基础类型的范围之内,多个枚举成员之间使用逗号分隔。
-
Java 中的每一个枚举都继承自 java.lang.Enum 类。当定义一个枚举类型时,每一个枚举类型成员都可以看作是 Enum 类的实例,这些枚举成员默认都被 final、public, static 修饰,当使用枚举类型成员时,直接使用枚举名称调用成员即可。所有枚举实例都可以调用 Enum 类的方法。
-
每一个枚举都是一个对象,在外部调用时,用类名点出来,像类的一个属性一样,