05-SpringBoot工程中MVC应用实践

简介

背景分析

在大型软件系统设计时,业务一般会相对复杂,假如所有业务实现的代码都纠缠在一起,会出现逻辑不清晰、可读性差,维护困难,改动一处就牵一发而动全身等问题。为了更好解决这个问题就有了我们现在常说的分层架构设计。

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&name=aa
//@RequestParam 注解描述方法参数时,默认必须为id参数传值(不传就会有400异常)
@RequestMapping("/param01")
public String doParam01(@RequestParam("id") String id,@RequestParam("name") String name){
 return "request params id is "+id+";name is "+name;
}
//访问方式: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 doParam06(@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设计的理念(对MVC思想进行落地,简化传统请求响应的处理流程)
  • Spring MVC中核心组件及其作用。(XxxFilter,DispatcherServlet,RequestMapping,HandlerInteceptor,Handler,ViewResover,…)
  • 基于Spring MVC实现请求处理的过程。

FAQ分析

  • 什么是MVC(分层架构设计思想,一种编程套路)
  • MVC具体指的是哪些单词的缩写(Model,View,Controller)
  • 举个生活中MVC的例子(全聚德饭店:菜单-view,服务员-controller,厨师-model)
  • Spring Web模块是如何基于MVC的设计思想做具体实现的?
  • @RestController的作用是什么?(@Controller+@ResponseBody)
  • @ResponseBody注解的作用是什么?(描述类或方法,假如类的所有方法都需要@ResponseBody,则直接将注解写到类上)
  • @RequestMapping的作用是什么,注解内部都有什么属性?
  • @RestController描述的类中,其方法返回值是如何转换为json的。(默认使用jackson类库中的API进行实现)
  • @RequestParam注解的作用?(定义请求参数的一些规则)
  • @PathVariable注解的作用?(告诉springmvc 参数的值来自请求url中{}占位符内部的值)
  • @RequestBody注解的作用?(告诉spring mvc将请求中的json串转换为pojo或map对象)
  • @RestControllerAdvice注解的作用?(描述全局异常处理类)
  • 你如何理解Spring MVC中的拦截器?如何编写?如何配置。
  • 文件上传使用什么参数对象接收文件内容?(MultipartFile)
  • @WebFilter的作用什么?(描述java中的过滤器,并定义名字,过滤规则等)
  • @ServletComponentScan?(告诉spring扫描启动类所在包或子包中的servlet和filter对象)
  • 后端请求是如何测试的?(http client)

BUG分析

  • 404 (资源没有找到)
  • 400 (请求参数与服务端参数不匹配)
  • 405 (请求方式与服务端不匹配)
  • 500 (服务端处理请求的过程中出现了问题)
  • 5
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值