简介
背景分析
在大型软件系统设计时,业务一般会相对复杂,假如所有业务实现的代码都纠缠在一起,会出现逻辑不清晰、可读性差,维护困难,改动一处就牵一发而动全身等问题。为了更好解决这个问题就有了我们现在常说的分层架构设计。
MVC 是什么
MVC是一种软件架构分层设计思想,基于MVC架构将我们的应用软件进行分层设计和实现,例如可以分为视图层(View),控制层(Controller),模型层(Model),通过这样的分层设计让我们程序具备更好的灵活性和可扩展性.因为这样可以将一个复杂应用程序进行简化,实现各司其职,各尽所能.比较适合一个大型应用的开发.(生活中的MVC-思考饭店中服务人员的角色)
Spring MVC 概述
Spring MVC是MVC设计思想在Spring框架中的一种实现,基于这样的设计思想,Spring框架设计了一些相关对象,最后将这些对象组装在一起,构建了一个专门用于处理Web请求的模块,称之为spring web 模块,这个模块底层基于MVC设计思想,封装了对Servlet的技术的应用,简化了程序员对请求和响应过程中数据的处理,其简易架构分析如图所示:
其中,核心组件分析:
- DispatcherServlet :前端控制器, 请求处理入口(你去银行营业厅有一个大堂经理负责问你需求一样,你去公司有前台接待一样)。
- HandlerMapping:映射器对象, 管理url与controller的映射关系(就类型于基于身份证号能够查到对应人的信息一样)。
- Interceptors:拦截器,实现请求响应的共性处理(类似与你进大楼要看你的健康码,你上地铁要进行安检一样)。
- Controller:后端控制器-handler, 负责处理请求的控制逻辑(银行用于处理各种请求的业务的窗口)。
- ViewResolver:视图解析器,解析对应的视图(类似别人给了你一个地址,你知道这个地址再哪,有人帮你指路)。
备注:先鸟瞰其全貌,然后基于实践逐步进行渗透、学习每个组件的应用原理。
快速入门实践
业务描述
客户端向服务端发送一个请求,服务端响应一个字符串到客户端,页面上呈现hello spring web mvc。
创建项目
第一步:创建maven项目模块(假如已有则无需创建),名字为spr-mvc 其初始pom.xml文件内容如下:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>spr-boot</artifactId>
<groupId>com.spring</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>spr-mvc</artifactId>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
</project>
添加项目依赖
打开pom.xml文件并添加Spring Web依赖(提供了spring mvc依赖支持),代码如下:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
其中,这个依赖添加时,会关联下载一个tomcat依赖(这个tomcat为一个嵌入式tomcat服务器)
创建项目配置文件
在resources目录创建项目配置文件application.properties并配置服务启动端口为80(默认为8080)
server.port=80 #以application.properties为例
创建启动类对象
package com.spring;
@SpringBootApplication
public class SpringMvcApplication {
public static void main(String[] args) {
SpringApplication.run(SpringMvcApplication.class, args);
}
}
创建请求处理器对象
创建一个客户端请求处理器对象,并将此对象交给spring管理,代码如下:
package com.spring.controller;
@RestController
public class HelloController {
@RequestMapping("/doSayHello")
public String doSayHello(){
return "hello spring web mvc";
}
}
其中:
- @RestController注解描述的类为spring web模块的请求处理类型,此类型的对象会交给spring管理。
- @RequestMapping注解描述的方法为请求处理方法,此注解内容定义的字符串为访问此方法需要的请求url。
启动服务并访问测试
打开浏览器,在浏览器地址栏输入http://localhost/doSayHello,检测日志输出。
请求参数处理实践
准备工作
创建一个请求参数处理器,例如:
package com.spring.mvc.controller;
@RestController
public class ParameterController {
}
直接量方式
在ParameterController中添加方法,基于直接量方式接受客户端请求参数,例如:
//访问方式:http://localhost/param01?id=10
//@RequestParam 注解描述方法参数时,默认必须为id参数传值
@RequestMapping("/param01")
public String doParam01(@RequestParam String id){
return "request params id is "+id;
}
//访问方式:http://localhost/param/10/jack
//{id}{name}表示占位符,用于接收请求url中/rest/后面的内容,注意顺序
//@PathVariable描述方法参数时,表示接收与参数名相同的/param/{id}/{name}表达式中{id} {name}的值
@RequestMapping("/param02/{id}/{name}")
public String doParam02(@PathVariable("id") Integer id, @PathVariable("name") String name){
return "request params id is"+id+" ; name is "+name;
}
POJO方式
第一步:定义pojo对象,用于接受客户端请求参数,例如:
package com.spring.mvc.pojo;
public class ParameterWrapper {
private Integer id;
private String name;
//自己添加set/get/toString方法
}
第二步:定义请求处理方法
//访问方式:http://localhost/param03?id=10&name=jack
//系统底层在使用pojo对象接收请求参数时,底层会通过反射构建参数对象,
//并通过反射调用参数名对应的pojo对象set方法将值封装到pojo对象。
@RequestMapping("/param03")
public String doParam03(ParameterWrapper parameterWrapper ){
return "request params id is "+parameterWrapper;
}
//访问方式:基于ajax、postman、httpclient等发送post请求,参数类型为application/json,传递数据为json格式:{"id":10,"name":"jack"}
//@RequestBody描述pojo对象时表示参数的值类来自自一个json格式数据
@RequestMapping("/param04")
public String doParam04(@RequestBody ParameterWrapper parameterWrapper ){
return "request params id is "+parameterWrapper;
}
Map对象方式
有时候为了简单,我们不想使用pojo对象接收请求参数,还可以使用map对象来接收,又该如何实现呢?
定义Controller方法,基于map对象接收请求参数,例如:
//访问方式:http://localhost/param05?id=10&name=jack
//注意:当使用map对象接收请求参数时,必须使用@RequestParam注解对参数进行修饰,
@RequestMapping("/param05")
public String doParam05(@RequestParam Map<String,Object> param){
return "request params "+param.toString();
}
//访问方式:http://localhost/param06
//注意:当使用map对象接收请求参数时,必须实用@RequestParam注解对参数进行修饰,
//Content-Type: application/json
//{"id":100,"title":"springmvc"}
public String doParam05(@RequestBody Map<String,Object> param){
return "request params "+param.toString();
}
响应数据处理
准备工作
定义请求处理器,重点演示响应数据的封装,例如:
package com.spring.mvc.controller;
@RestController
public class ResponseController {
}
响应数据封装
实际项目中我们通常会定义一个pojo对象,封装响应数据(状态吗,消息,业务数据),例如:
public class JsonResult {
/**状态码*/
private Integer state=1;//1 表示OK,0表示Error
/**状态码信息*/
private String message="ok";
/**封装正确的查询结果*/
private Object data;
public JsonResult(){}
public JsonResult(String message){
this.message=message;
}
public JsonResult(Integer state,String message){
this(message);
this.state=state;
}
public JsonResult(Object data){//new JsonResult(list)
this.data=data;
}
//当出现异常时,可以通过此构造方法对异常信息进行封装
public JsonResult(Throwable exception){//new JsonResult(exception);
this(0,exception.getMessage());
}
public Integer getState() {
return state;
}
public void setState(Integer state) {
this.state = state;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public Object getData() {
return data;
}
public void setData(Object data) {
this.data = data;
}
}
请求处理方法定义
案例1:在ResponseController中添加请求处理方法,将响应对象封装为pojo对象,例如
//访问方式 http://localhost/resp/pojo
//将响应数据封装为pojo对象,然后spring底层会将这个对象转换为json格式字符串
@RequestMapping("/resp01)
public JsonResult doResp01(){
Map<String,Object> map=new HashMap<>();
map.put("id",101);
map.put("title","spring mvc");
JsonResult rs=new JsonResult();
rs.setState(1);
rs.setMessage("OK");
rs.setData(map);
return rs;
}
案例2:直接基于response对象响应数据
//访问方式:http://localhost/resp02
@RequestMapping("/resp02")
public void doResp02(HttpServletResponse response)throws Exception{
Map<String,Object> map=new HashMap<>();
map.put("id",101);
map.put("title","hello spring web mvc");
//将map中的数据转换为json格式字符串
ObjectMapper om=new ObjectMapper();
String jsonStr=om.writeValueAsString(map);
System.out.println("jsonStr="+jsonStr);
//将字符串响应到客户端
//设置响应数据的编码
response.setCharacterEncoding("utf-8");
//告诉客户端,要向它响应的数据类型为application/json,编码为utf-8.请以这种编码进行数据呈现
response.setContentType("application/json;charset=utf-8");
PrintWriter pw=response.getWriter();
pw.println(jsonStr);
pw.flush();
}
响应异常统一处理
Spring框架中web模块定义了一套全局异常处理规范,基于这个套规范处理Controller中抛出的异常,例如:
package com.spring.mvc.advice;
/** @RestControllerAdvice 注解描述的类为全局异常处理类,启动时会交给spring管理*/
@RestControllerAdvice
public class GlobalExceptionHandler {
private static final Logger log=LoggerFactory.getLogger(GlobalExceptionHandler.class);
/**@ExceptionHandler注解描述的方法为异常处理方法,注解中定义的异常类型为方法可以处理的异常类型.*/
@ExceptionHandler(RuntimeException.class)
public JsonResult doHandleRuntimeException(RuntimeException e){
e.printStackTrace();
log.error("exception msg is {}",e.getMessage());
return new JsonResult(e);
}
//可以定义多个异常处理方法
}
其中,全局异常处理过程,如图所示:
拦截器技术应用
概述
Spring Web中的拦截器(Interceptor)基于回调机制,可以在目标方法执行之前,先进行业务检测,满足条件则放行,不满足条件则进行拦截,拦截器原理分析如下图所示:
拦截器定义
基于Spring MVC模块提供的HandlerInterceptor接口规范定义拦截器,对特定请求进行拦截,例如检测请求访问时间,关键代码如下,
package com.spring.mvc.interceptor;
public class TimeAccessInterceptor implements HandlerInterceptor {
/**
* preHandle在控制层目标方法执行之前执行
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response,Object handler) throws Exception {
//testRequestInfo(request,handler);
LocalTime now=LocalTime.now();//JDK8中的时间对象
int hour=now.getHour();//获取当前时间对应小时
//System.out.println("hour="+hour);
log.info("hour {}",hour);
if(hour<=6||hour>=22)
throw new RuntimeException("请在6~10点进行访问");
return true;
}
}
拦截器配置
定义配置类,实现对拦截器的注册,关键代码如下。
package com.spring.mvc.config;
@Configuration
public class SpringWebConfig implements WebMvcConfigurer{//web.xml
//配置spring mvc 拦截器
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new TimeAccessInterceptor())
.addPathPatterns("/param/*");
}
}
访问测试
启动服务,对于url中path以/param/开头的进行访问测试,检测请求是否被拦截处理了。
文件上传案例实践
配置文件
spring:
resources:
static-locations: file:E:/file,classpath:static
Controller对象设计
package com.spring.mvc.controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import java.io.File;
import java.io.IOException;
import java.util.UUID;
@RestController
public class UploadController {
private String dirPath = "E:/file";
@RequestMapping("/upload")
public String upload(MultipartFile picFile) throws IOException {
//得到原始文件夹名
String fileName = picFile.getOriginalFilename();
String suffix = fileName.substring(fileName.lastIndexOf("."));
fileName = UUID.randomUUID()+suffix;
File dirFile = new File(dirPath);
if (!dirFile.exists()){
dirFile.mkdirs();
}
String filePath = dirPath+"/"+fileName;
picFile.transferTo(new File(filePath));
return "/"+fileName;
}
@RequestMapping("/remove")
public void remove(String name){
String filePath = dirPath+name;
new File(filePath).delete();//删除文件
}
}
访问测试分析
在resources目录下创建test目录,并在目录中创建upload-rest-api.http文件
POST http://localhost/upload
Content-Type: multipart/form-data; boundary=WebAppBoundary
--WebAppBoundary
Content-Disposition: form-data; name="picFile"; filename="git-theory.png"
< E:\git-theory.png
--WebAppBoundary--
总结(Summary)
重难点分析
- MVC设计思想的理解
- Spring Web模块中MVC设计的设计
- Spring MVC中核心组件及其作用。
- 基于Spring MVC实现请求处理的过程。
FAQ分析
- 什么是MVC(分层设计思想,一种编程套路)
- MVC具体指的是哪些单词的缩写(Model,View,Controller)
- 举个生活中MVC的例子(全聚德饭店:菜单-view,服务员-controller,厨师-model)
- Spring Web模块是如何基于MVC设计思想做具体实现的?
- @RestController的作用是什么?
- @ResponseBody注解的作用是什么?
- @RequestMapping的作用是什么,注解内部都有什么属性?
- @RestController描述的类中,其方法返回值是如何转换为json的。
BUG分析
- 404 (资源没有找到)
- 400 (请求参数与服务端参数不匹配)
- 405 (请求方式与服务端不匹配)
- 500 (服务端处理请求的过程中出现了问题)
本文由 liyunfei 创作,采用 知识共享署名4.0
国际许可协议进行许可
本站文章除注明转载/出处外,均为本站原创或翻译,转载前请务必署名
最后编辑时间为: Oct 4,2022