简介:
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的工作原理,核心组件,以及日常编码中常用注解及使用方式,如何传参,拦截器等应用,更多使用技巧还需要大家在日常工作中去磨炼,共同进步。