SpringBoot工程中MVC应用实践
in JavaDevelop with 0 comment

SpringBoot工程中MVC应用实践

in JavaDevelop with 0 comment

简介

背景分析

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

MVC 是什么

MVC是一种软件架构分层设计思想,基于MVC架构将我们的应用软件进行分层设计和实现,例如可以分为视图层(View),控制层(Controller),模型层(Model),通过这样的分层设计让我们程序具备更好的灵活性和可扩展性.因为这样可以将一个复杂应用程序进行简化,实现各司其职,各尽所能.比较适合一个大型应用的开发.(生活中的MVC-思考饭店中服务人员的角色)

Spring MVC 概述

Spring MVC是MVC设计思想在Spring框架中的一种实现,基于这样的设计思想,Spring框架设计了一些相关对象,最后将这些对象组装在一起,构建了一个专门用于处理Web请求的模块,称之为spring web 模块,这个模块底层基于MVC设计思想,封装了对Servlet的技术的应用,简化了程序员对请求和响应过程中数据的处理,其简易架构分析如图所示:

在这里插入图片描述

其中,核心组件分析:

备注:先鸟瞰其全貌,然后基于实践逐步进行渗透、学习每个组件的应用原理。

快速入门实践

业务描述

客户端向服务端发送一个请求,服务端响应一个字符串到客户端,页面上呈现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";
   }
}

其中:

启动服务并访问测试

打开浏览器,在浏览器地址栏输入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)

重难点分析

FAQ分析

BUG分析