1. 何为MVC?
**MVC ** 是软件工程中的一种软件架构模式,把软件系统分为三个基本部分:模型(Model)、视图(View)和控制器(Controller)。
- 视图(View) 在 JavaEE 应用程序中,视图(View)可以由 JSP(Java Server Page)担任。在现在前后端分离的模式下,View 已经由前端所取代。
- 控制器(Controller) JavaEE 应用中,Controller 可能是一个 Servlet 。在 Spring MVC 中担任控制器角色的是 DispatcherServlet。
- 模型(Model) Model 则是由一个实体 Bean 来实现,主要对应数据层。
2. springmvc工作原理
Spring MVC 最核心的思想在于 DispatcherServlet
。在现在的开发模式中,我们主要使用的也是 Spring MVC 的这一核心功能。
Spring MVC 工作原理图如下:
浏览器发起一个请求(如:http://localhost:8080/hello), 会经历如下步骤:
- DispatcherServlet 接收到请求
- 通过 HandlerMapping 找到对应的 handler
- 然后通过 HandlerAdapter 调用 Controller 进行后续业务逻辑处理(3-4)
- 处理完业务逻辑后,Controller 将视图名返回给 HandlerAdapter
- DispatcherServlet 选择合适的 ViewResolver 生成 View 对象
- 最后 View 渲染并返回响应数据
3. 核心组件
springmvc核心组件分为3个,他们分别是Handler、HandlerMapping、HanderAdapter
3.1 Handler
Handler 是用来做具体事情的,对应的是 Controller 里面的方法,所有有 @RequestMapping 标注的方法都可以看做为一个 Handler。
3.2 HandlerMapping
HandlerMapping 是用来找到 Handler 的,是请求路径与 Handler 的映射关系。
3.3 HanderAdapter
HandlerAdapter 从名字看,可以知道它是一个适配器。它是用来跟具体的 Handler 配合使用的。可以简单理解为各种电子产品与电源适配器(充电器)的关系。
DispatcherServlet 最核心的方法就是 doDispatch ,doDispatch 主要做了四件事:
- 根据 request 找到 Handler
- 根据 Handler 找到对应的 HanderAdapter
- 用 HanderAdapter 处理 Handler
- 处理经过以上步骤的结果
4. springmvc常用注解
注解 | 作用域 | 说明 |
---|---|---|
@Controller | 类 | Controller标识 |
@RequestMapping | 类/方法 | URL映射 |
@ResponseBody | 类/方法 | 以Json方式返回 |
@RequestParam | 参数 | 按名字接收参数 |
@RequestBody | 参数 | 接收Json参数 |
@PathVariable | 参数 | 接收URL中的参数 |
@RestController | 类 | 组合注解:@Controller + @ResponseBody |
@GetMapping | 方法 | 组合注解:@RequestMapping(method = RequestMethod.GET) |
@PostMapping | 方法 | 组合注解:@RequestMapping(method = RequestMethod.POST) |
@PutMapping | 方法 | 组合注解:@RequestMapping(method = RequestMethod.PUT) |
@PatchMapping | 方法 | 组合注解:@RequestMapping(method = RequestMethod.PATCH) |
@DeleteMapping | 方法 | 组合注解:@RequestMapping(method = RequestMethod.DELETE) |
从上表我们可以发现组合注解就是具有多个功能的注解,由多个注解或者一个注解 + 一个特定的属性值组成的注解,相当于对注解的一种封装。
例如@RestController 不仅可以标识一个 Controller ,还能让被标识的 Controller 中的所有方法都返回 JSON 格式的数据;@GetMapping 不仅可以映射一个请求路径,还让该路径只响应 GET 请求,对于其他的请求方式不响应。
5 如何优雅传递参数
Spring MVC 的主要工作就是接收外部的请求,然后根据请求去调用相应的服务,最后将处理结果返回。外部发来的请求会以各种形式带着各式各样的参数,以达到不同的目的。Spring MVC共 有四种接收参数的方式:
- 无注解方式
- @RequestParam 方式
- @PathVariable 方式
- @RequestBody 方式
接下来,我们分别给出示例:
首先,我们需要准备一个接收入参的实体类:
public class User {
private String name;
private int age;
// 此处省略set get方法
// ......
}
5.1 无注解形式
@RestController
public class ParamController {
@GetMapping("/noannotation")
public User noAnnotation( User user) {
return user;
}
}
请求示例:
http://localhost:8080/noannotation?name=无注解方式&age=18
5.2 @RequestParam方式
@RequestParam 注解有四个属性:
属性 | 类型 | 说明 |
---|---|---|
name | String | 参数名称 |
value | String | name 属性的别名 |
required | boolean | 指定是否为必传参数(为 true 时不传会报错) |
defaultValue | String | 参数默认值 |
@GetMapping("/requestparam")
public User RequestParam(@RequestParam String name, @RequestParam int age) {
User user = new User();
user.setName(name);
user.setAge(age);
return user;
}
请求示例:
http://localhost:8080/requestparam?name=@RequestParam方式&age=4
5.3 @PathVariable方式
@PathVariable 注解有三个属性:
属性 | 类型 | 说明 |
---|---|---|
name | String | 参数名称 |
value | String | name 属性的别名 |
required | boolean | 指定是否为必传参数(为 true 时不传会报错) |
@GetMapping("/pathvariable/{name}/{age}")
public User PathVariable(@PathVariable String name,@PathVariable int age) {
User user = new User();
user.setName(name);
user.setAge(age);
return user;
}
请求示例:
http://localhost:8080/pathvariable/@PathVariable方式/2
5.4 @RequestBody方式
@RequestBody 只有一个属性:
属性 | 类型 | 说明 |
---|---|---|
required | boolean | 指定是否为必传参数(为 true 时不传会报错) |
@PostMapping("/requestbody")
public User RequestBody(@RequestBody User user) {
return user;
}
请求示例(请自行使用接口调试工具测试,如postMan):
url: http://localhost:8080/requestbody
method: post
body: {"name":"@RequestBody方式","age":12}
6. 拦截器
6.1 简介
书接上回,在这一趴我们一起来学习一下 Spring MVC 中的拦截器。拦截器在我们日常开发当中有着很重要的地位,很多重要的功能需要借助拦截器帮我们完成。我们通常会使用拦截器帮我们完成以下功能:
- 登录认证
- 权限验证
- 记录日志
- 性能监控
- …
6.2 自定义拦截器
接下来我们学习如何写一个拦截器。Spring MVC 中所有的拦截器都实现/继承自 HandlerInterceptor
接口。我们想要写一个自定义拦截器的话,需要实现/继承 HandlerInterceptor
或其子接口/实现类。下图是 Spring MVC 中拦截器的类图(还有几个类是 HandlerInterceptorAdapter
的子类,这里没有列出):
HandlerInterceptor
接口的源码如下:
public interface HandlerInterceptor {
// 处理器执行前被调用
default boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
return true;
}
// 处理器执行后,视图渲染前被调用
default void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
@Nullable ModelAndView modelAndView) throws Exception {
}
// 视图渲染完成后背调用
default void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler,
@Nullable Exception ex) throws Exception {
}
}
下面我们自定义一个最简单、纯净的拦截器,也就是直接实现 HandlerInterceptor
接口。
新建一个类 LogInterceptor
并实现 HandlerInterceptor
接口:
并在三个方法中分别添加一条日志打印的代码
@Slf4j
@Component
public class LogInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
log.info("preHandle");
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
log.info("postHandle");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
log.info("afterCompletion");
}
}
新建一个类 WebConfigurer
并实现 WebMvcConfigurer
接口,用于注册我们自定义的拦截器:
@Configuration
public class WebConfigurer implements WebMvcConfigurer {
@Autowired
private LogInterceptor logInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(logInterceptor);
}
}
在 HelloController
的 hello
方法中添加一条日志打印代码:
@Slf4j
@RestController
public class HelloController {
@GetMapping("/hello")
public String hello(@RequestParam(required = false) @ApiParam("名字") String name) {
if (name == null || "".equals(name)) {
name = "Spring Boot";
}
log.info("hello");
return "Hello "+name;
}
}
OK,接下来启动工程,并访问以下hello
方法,控制台会看到如下的输出:
com.imooc.springboot.LogInterceptor : preHandle
com.imooc.springboot.HelloController : hello
com.imooc.springboot.LogInterceptor : postHandle
com.imooc.springboot.LogInterceptor : afterCompletion
如果一切正常,将出现如上结果,这代表我们的自定义拦截器成功了!
3.3 拦截器执行流程
从控制台的日志输出,我们可以大概看出拦截器的执行流程。下面我们来更加深入的学习一下拦截器的整个执行流程:
- 执行 preHandle 方法,该方法会返回一个布尔值。如果为 false ,则结束本次请求:如果为 true 则继续。
- 执行处理器逻辑,也就是我们的 Controller 。
- 执行 postHandle 方法。
- 执行视图解析和视图渲染 (我们直接返回了 JSON 对象,所以没有视图处理)。
- 执行 afterCompletion 方法。
我们可以在 DispatcherServlet
的 doDispatch
方法的源码中进一步验证这个执行逻辑:
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
try {
try {
// 返回 HandlerExecutionChain 其中包含了拦截器队列
mappedHandler = getHandler(processedRequest);
//调用拦截器 PreHandle 方法,若返回 false 将直接 return
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
// 处理 Controller
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
// 调用拦截器的 PostHandle 方法
mappedHandler.applyPostHandle(processedRequest, response, mv);
}
// 调用拦截器的 afterCompletion 方法
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
}
}
7 总结
本文主要从日常中对springmvc高频使用点做了简单的介绍,包括springmvc的工作原理,核心组件,以及日常编码中常用注解及使用方式,如何传参,拦截器等应用,更多使用技巧还需要大家在日常工作中去磨炼,共同进步。