SpringMVC

SpringMVC

一、概述

1、三层架构

层级作用
表现层(web)接收请求,返回响应。
服务层(service)业务逻辑的实现。
数据层(dao)数据存储相关。

2、MVC

MVC,即 Model 模型、View 视图,及 Controller 控制器。

在这里插入图片描述

MVC是软件工程中的一种软件架构模式,它是一种分离业务逻辑显示界面的设计方法。

  • Model 模型:承载数据,并对用户提交的请求进行计算(处理)的模块。分为两类:
    1. 数据承载 Bean:专门用于承载业务数据的,实体类,如User
    2. 业务处理 Bean:专门用于处理业务逻辑的,Service 或 Dao 对象
  • View 视图:为用户提供使用界面,与用户直接进行交互。
  • Controller 控制器:将用户请求转发给相应的 Model 处理,并根据 Model 的处理结果给用户返回响应。

目的:高内聚,低耦合。

3、MVC与三层架构

在这里插入图片描述

MVC 架构程序的工作流程:

  1. 用户通过 View 页面向服务端提出请求,可以是表单请求、超链接请求、AJAX 请求等。
  2. 服务端 Controller 控制器接收到请求后对请求进行解析,找到相应的 Model 对用户请求进行处理。
  3. Model 处理后,将处理结果再交给 Controller。
  4. Controller 接到处理结果后,找到要响应的 View 页面,页面经渲染(数据填充)后,再发送给客户端。

4、SpringMVC

MVC 是一种设计模式,Spring MVC 是一款很优秀的 MVC 框架。

Spring MVC 可以帮助我们进行更简洁的 Web 层的开发,并且它天生与 Spring 框架集成。Spring MVC 下我们一般把后端项目分为 Service 层(处理业务)、Dao 层(数据库操作)、Entity 层(实体类)、Controller 层(控制层,返回数据给前台页面)。

在这里插入图片描述

  • SpringMVC主要负责 View 和 Controller 的交互
    • 请求路径的映射规则
    • 请求参数的接收
    • 返回参数的处理
    • 视图的渲染

二、SpringMVC 原理

在Web阶段,处理用户请求,我们需要编写相关的Servlet,并且要进行配置。

在 SpringMVC 框架中,提供了一个统一的 DispatcherServlet 来 接收 和 处理 用户请求。

1、SpringMVC 的 组件

  • 前端控制器:DispatcherServlet

    核心组件,负责接收请求、分发,并给予客户端响应。

  • 处理器映射器:HandlerMapping

    根据 请求URL 匹配查找能处理的 Handler ,并会将请求涉及到的拦截器和 Handler 一起封装。

  • 处理器适配器:HandlerAdapter

    根据 HandlerMapping 找到的 Handler ,适配执行对应的 Handler

  • 请求处理器:Handler

    处理来自用户的请求,执行业务逻辑并返回相应的数据或视图。(就是Controller控制器)

  • 视图解析器:ViewResolver

    根据 Handler 返回的逻辑视图 / 视图,解析并渲染真正的视图,并传递给 DispatcherServlet 响应客户端。

  • 拦截器:HandlerInterceptor

    在请求到达 Controller 之前和响应发送给客户端之前进行一些操作,例如记录日志、用户认证等。

2、组件初始化

项目启动,初始化DispatcherServlet 时,会通过 initStrategies 方法初始化各个组件

// org.springframework.web.servlet.DispatcherServlet

/**
 * 初始化各个组件
 */ 
protected void initStrategies(ApplicationContext context) {
    this.initMultipartResolver(context);
    this.initLocaleResolver(context);
    this.initThemeResolver(context);
    // 初始化`处理器映射器`,定义了 `请求路径` 和 `方法` 之间的关系
    this.initHandlerMappings(context);
    // 初始化`处理器适配器`,制定 `请求参数` 和 `返回参数` 的解析规则。
    this.initHandlerAdapters(context);
    // 初始化异常处理器
    this.initHandlerExceptionResolvers(context);
    this.initRequestToViewNameTranslator(context);
    // 初始化`视图解析器`,指定 `前缀` 和 `后缀` 和其他一些配置(文件、文件类型)
    this.initViewResolvers(context);
    this.initFlashMapManager(context);
}

3、核心方法 doService

DispatcherServlet 的核心方法为 doService()doService() 中的核心逻辑由 doDispatch() 实现

// org.springframework.web.servlet.DispatcherServlet

protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
    // ...
    try {
        this.doDispatch(request, response);
    } finally {
        // ...
    }
}

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
    // 初始化
    HttpServletRequest processedRequest = request;
    HandlerExecutionChain mappedHandler = null;
    boolean multipartRequestParsed = false;
    WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);

    try {
        try {
            // 最终目标:构建ModelAndView
            ModelAndView mv = null;
            Exception dispatchException = null;

            try {
                // 检测是否是文件上传内容
                processedRequest = this.checkMultipart(request);
                multipartRequestParsed = processedRequest != request;

                // 处理器映射器
                // 通过 HandlerMapping 找到 HandlerExecutionChain 返回
                // HandlerExecutionChain 封装了 handler 和 interceptors
                mappedHandler = this.getHandler(processedRequest);
                if (mappedHandler == null) {
                    this.noHandlerFound(processedRequest, response);
                    return;
                }

                // 处理器解析器
                HandlerAdapter ha = this.getHandlerAdapter(mappedHandler.getHandler());
                String method = request.getMethod();
                boolean isGet = "GET".equals(method);
                if (isGet || "HEAD".equals(method)) {
                    // ...
                }

                if (!mappedHandler.applyPreHandle(processedRequest, response)) {
                    return;
                }

                // 构建ModelAndView
                mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
                if (asyncManager.isConcurrentHandlingStarted()) {
                    return;
                }

                this.applyDefaultViewName(processedRequest, mv);
                mappedHandler.applyPostHandle(processedRequest, response, mv);
            } catch (Exception var20) {
                dispatchException = var20;
            } catch (Throwable var21) {
                dispatchException = new NestedServletException("Handler dispatch failed", var21);
            }

            // 解析View,返回最终内容
            this.processDispatchResult(processedRequest, response, mappedHandler, mv, 
                                       (Exception)dispatchException);
        } catch (Exception var22) {
            // ...
        }
    } finally {
        // ...
    }
}

private void processDispatchResult(...) throws Exception {
    // ...
    if (mv != null && !mv.wasCleared()) {
        // 渲染
        this.render(mv, request, response);
        if (errorView) {
            WebUtils.clearErrorRequestAttributes(request);
        }
    } // ...
}

protected void render(...) throws Exception {
    // ...
    View view;	// 具体的View
    if (viewName != null) {
        // 根据viewName获取
        view = this.resolveViewName(viewName, mv.getModelInternal(), locale, request);
    } else {
        view = mv.getView();
    }
    try {
        // ...

        // 将Model传给view渲染
        view.render(mv.getModelInternal(), request, response);
    } // ...
}

// org.springframework.web.servlet.view.AbstractView

public void render(@Nullable Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) throws Exception {
    // ...
    // 渲染输出结果
    this.renderMergedOutputModel(mergedModel, this.getRequestToExpose(request), response);
}

protected void exposeModelAsRequestAttributes(Map<String, Object> model, HttpServletRequest request) throws Exception {
    model.forEach((name, value) -> {
        if (value != null) {
            // 将对象封装到request域中
            request.setAttribute(name, value);
        } else {
            request.removeAttribute(name);
        }

    });
}
// org.springframework.web.servlet.view.InternalResourceView

protected void renderMergedOutputModel(Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception {
    // 将模型对象设置为请求属性
    this.exposeModelAsRequestAttributes(model, request);
    // Expose helpers as request attributes, if any.
    this.exposeHelpers(request);
    // Determine the path for the request dispatcher.
    String dispatcherPath = this.prepareForRendering(request, response);
    // 获取请求转发对象
    RequestDispatcher rd = this.getRequestDispatcher(request, dispatcherPath);
    // ...
    // 请求转发
    rd.forward(request, response);
    
    // ...
}

4、请求流程

在这里插入图片描述

  1. 客户端(浏览器)发送请求,被 前端控制器 DispatcherServlet 拦截。

  2. DispatcherServlet 调用 处理器映射器 HandlerMapping

    HandlerMapping 根据 request 匹配 HandlerExecutionChain(封装了 handlerinterceptors

  3. DispatcherServlet 根据 handler 找到对应的 处理器适配器 HandlerAdapter

  4. HandlerAdapter 解析参数,调用对应的 handler(即Controller控制器)构建 ModelAndView

  5. ModelAndView 返回给 DispatcherServlet

  6. 视图解析器 ViewResolver解析 ModelAndView(数据对象 + 逻辑View) 后返回具体的 View 。

  7. 根据 Model 渲染 View

  8. 将渲染过后的 View 返回给请求者(浏览器)

三、SpringMVC 使用

1、RESTful风格

强调 HTTP 应当以 资源 为中心,并且规范了 资源URL 的风格。
- 路径规范:路径一般都是资源名称,尽量不出现动词
- 请求规范:请求方式必须遵循规定,要使用GET、POST、PUT、DELETE、PATCH等,具有不同含义。
Restful风格请求方式描述SpringMVC注解支持
/customerPOST新增用户@PostMapping
/customerGET获取用户列表@GetMapping
/customer/1GET获取id=1的用户信息@GetMapping
/customer/1DELETE删除id=1的用户信息@DeleteMapping
/customer/1PUT修改id=1的用户信息(整体@PutMapping
/customer/1PATCH修改id=1的用户信息(部分@PatchMapping

总结:面向资源的,带有动作表达的一种规则和标准(底层会将请求方式拼接到路径中)。

2、Controller 控制器

注解说明
@Controller添加在类上,标记一个类是控制器,用于 处理请求 和 返回响应。
@ResponseBody添加在方法上,将方法返回值写入 HTTP 响应体中。(一般用于返回 JSON 数据)
@RestController添加在类上,相当于 @Controller + @ResponseBody
  • 前后端不分离,ModelAndView 应该是最最常见的返回值类型。
  • 前后端分离后,基本都是以 JSON 数据为主了。

因此,前后端分离之后,一般都是使用 @RestController 注解加在 Controller 类上

3、请求映射

@RequestMapping 用于映射 请求路径 到 Controller 方法上,可以配置 HTTP 请求类型、请求参数、请求头等。

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Mapping
public @interface RequestMapping {
    String name() default "";

    @AliasFor("path")
    String[] value() default {};

    // 请求路径
    @AliasFor("value")
    String[] path() default {};

    // 指定请求的 HTTP 方法(GET、POST、PUT、DELETE等)
    RequestMethod[] method() default {};

    // 指定请求必须包含的参数
    String[] params() default {};

    // 指定请求中必须包含某些指定的header值,才能够让该方法处理请求
    String[] headers() default {};
    
	// 指定处理请求的提交内容类型(为指定类型才能够让该方法处理请求)
    // 示例:consumes = "application/json"
    String[] consumes() default {};

    // 指定返回的内容类型,必须是request请求头(Accept)中所包含的类型
    // 示例:produces = "application/json,charset=utf-8"
    String[] produces() default {};
}

以下注解是 @RequestMapping 的扩展,可以看做指定了 method 属性的 @RequestMapping

注解等价于适用场景
@GetMapping@RequestMapping(method = RequestMethod.GET)获取资源
@PostMapping@RequestMapping(method = RequestMethod.POST)新增内容
@DeleteMapping@RequestMapping(method = RequestMethod.DELETE)删除内容
@PutMapping@RequestMapping(method = RequestMethod.PUT)修改整体内容
@PatchMapping@RequestMapping(method = RequestMethod.PATCH)修改部分内容

4、参数绑定

@RequestBody:将 请求体 中的 JSON数据 转化为 方法参数。(Json 的 Key 与 Java对象 的 Field 对应)

@PostMapping("/hello")
public String hello(@RequestBody User user) {
    return "Hello, " + user.getName();
}

@PathVariable:用于从 URL 路径中提取参数。(占位符 {xxx}

@GetMapping("/hello/{name}")
public String hello(@PathVariable("name") String name) {
    return "Hello, " + name;
}

@RequestParam:用于从请求中获取参数(可以是 URL参数、表单数据、文件等)。

@GetMapping("/hello")
public String hello(@RequestParam("name") String name) {
    return "Hello, " + name;
}
/**
 * 文件上传
 */
@PostMapping("/upload")
public String handleFileUpload(@RequestParam("file") MultipartFile file) {
    //...
    return "File uploaded successfully";
}

四、HTTP请求示例

注解请求类型使用场景
@RequestBodyPOST接收 Json对象,对象的属性名要与jsonkey保持一致
@PathVariableGET 或 POST接收 URL中的参数值,可以接收多个
@RequestParamGET 或 POST接收 文件/表单数据

1、GET - URL键值对参数

请求格式: GET /user?name=jack&age=18 HTTP/1.1
/**
 * 用字段接收
 */
@GetMapping("/user")
public String getUserInfo(@RequestParam("name") String name, @RequestParam("age") Integer age) {
    return "姓名:" + name + ",年龄:" + age;
}
/**
 * 用对象接收
 */
@GetMapping("/user")
public String getUserInfo(User user) {
    return "姓名:" + user.getName() + ",年龄:" + user.getAge();
}

注意:用 对象 接收 前端数据 时,@RequestParam注解省略不写(写了会报错)

  • 编译工具会根据对象的set,get方法自动生成@RequestParam
  • 有几个属性就会生成几个@RequestParam,其中required属性默认为false

2、GET - URL拼接参数

请求格式: GET /user/jack/18 HTTP/1.1
@GetMapping("/user/{name}/{age}")
public String getUserInfo(@PathVariable("name") String name, @PathVariable("age") Integer age) {
    return "姓名:" + name + ",年龄:" + age;
}

3、POST - URL拼接参数

POST请求也支持URL拼接参数

请求格式: POST /user/jack/18 HTTP/1.1
@PostMapping("/user/{name}/{age}")
public String getUserInfo(@PathVariable("name") String name, @PathVariable("age") Integer age) {
    return "姓名:" + name + ",年龄:" + age;
}

4、POST - raw

处理 JSON 数据

{
    "name": "John",
    "age": 30
}
@PostMapping("/processJson")
public ResponseEntity<String> processJson(@RequestBody MyModel myModel) {
    // myModel 对象自动填充来自请求体的 JSON 数据
    return ResponseEntity.ok("Processed " + myModel.getName());
}

处理 纯文本 数据

123
@PostMapping("/processText")
public ResponseEntity<String> processText(@RequestBody String text) {
    return ResponseEntity.ok("Received: " + text);
}

5、POST - x-www-form-urlencoded

处理 表单 数据

name=John&age=30
@PostMapping("/processForm")
public ResponseEntity<String> processForm(@RequestParam String name, @RequestParam int age) {
    return ResponseEntity.ok("Name: " + name + ", Age: " + age);
}
@PostMapping("/processForm")
public ResponseEntity<String> processForm(User user) {
    return ResponseEntity.ok("Name: " + user.getName() + ", Age: " + user.getAge());
}

6、POST - multipart/form-data

这个请求体是由多部分组成的,每个部分包含不同的内容(比如 文本字段上传文件)。

----------------------------------------------------7de1a433602ac
Content-Disposition: form-data; name="remark"
123
----------------------------------------------------7de1a433602ac
Content-Disposition: form-data; name="file"; filename="photo.jpg"
Content-Type: image/jpeg
(binary data of the file)
----------------------------------------------------7de1a433602ac--
@PostMapping("/uploadFile")
public ResponseEntity<String> uploadFile(
    @RequestParam("remark") String remark,
    @RequestParam("file") MultipartFile file) {
    // ...
    return ResponseEntity.ok("File uploaded successfully: " + file.getOriginalFilename());
}

Spring 默认通过 CommonsMultipartResolverStandardServletMultipartResolver 处理文件上传。

可以通过配置 Spring 的 multipart 解析器来启用文件上传功能。

spring:
  servlet:
    multipart:
      # 启用文件上传支持(默认为 true)
      enabled: true
      # 单个文件的最大大小限制(默认为 1MB)
      max-file-size: 2MB
      # 请求的最大大小(包括所有文件和表单数据,默认为 1MB)
      max-request-size: 2MB

7、POST - binary

处理 原始的二进制数据

Content-Type: application/octet-stream
(binary data)
@PostMapping("/uploadBinary")
public ResponseEntity<String> uploadBinary(@RequestBody byte[] data) {
    // 处理二进制数据(例如存储文件等)
    return ResponseEntity.ok("Binary data received: " + data.length + " bytes");
}

Spring 不需要额外的配置来处理二进制数据,可以通过 @RequestBody 来确保接收原始的二进制数据。

五、异常统一处理

1、原理

// org.springframework.web.servlet.DispatcherServlet

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
    // ...
    try {
        // ...
    } catch (Exception var20) {
        dispatchException = var20;
    } catch (Throwable var21) {
        dispatchException = new NestedServletException("Handler dispatch failed", var21);
    }

    // doDispatch中的异常,都会被 catch 到 processDispatchResult 进行统一处理
    this.processDispatchResult(processedRequest, response, mappedHandler, mv, (Exception)dispatchException);
}

private void processDispatchResult(..., @Nullable Exception exception) throws Exception {
    boolean errorView = false;
    // 优先对异常进行统一处理
    if (exception != null) {
        if (exception instanceof ModelAndViewDefiningException) {
            this.logger.debug("ModelAndViewDefiningException encountered", exception);
            mv = ((ModelAndViewDefiningException)exception).getModelAndView();
        } else {
            Object handler = mappedHandler != null ? mappedHandler.getHandler() : null;
            // 非 ModelAndViewDefiningException 都会进到这里处理
            mv = this.processHandlerException(request, response, handler, exception);
            errorView = mv != null;
        }
    }
    // ...
}

@Nullable
protected ModelAndView processHandlerException(..., Exception ex) throws Exception {
    request.removeAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);
    ModelAndView exMv = null;
    if (this.handlerExceptionResolvers != null) {
        Iterator var6 = this.handlerExceptionResolvers.iterator();

        while(var6.hasNext()) {
            // 最终,异常都由 HandlerExceptionResolver 处理
            HandlerExceptionResolver resolver = (HandlerExceptionResolver)var6.next();
            exMv = resolver.resolveException(request, response, handler, ex);
            if (exMv != null) {
                break;
            }
        }
    }
    // ...
}
public interface HandlerExceptionResolver {
    @Nullable
    ModelAndView resolveException(HttpServletRequest var1, HttpServletResponse var2, 
                                  @Nullable Object var3, Exception var4);
}

2、异常统一处理

1)继承方式处理(了解)

既然异常最终都会由 HandlerExceptionResolver 处理,只要自定义异常解析类继承它就可以了

@Component
public class ProjectExceptionResolver implements HandlerExceptionResolver {

    @Override
    public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response,Object handler, Exception ex) {
        // 自定义的异常
        ProjectException projectException = null;
        // 如果是自定义的异常,强制类型转换
        if (ex instanceof ProjectException){
            projectException = (ProjectException) ex;
        } else {
            // 如果不是,给出提示
            projectException = new ProjectException("未知异常", "-1");
        }
        // 如果发生异常,跳转到error页面并显示页面
        ModelAndView modelAndView = new ModelAndView("error");
        modelAndView.addObject("message", projectException.getMessage());
        modelAndView.addObject("code", projectException.getCode());
        return modelAndView;
    }
}

2)注解方式处理(推荐)

使用 @ControllerAdvice + @ExceptionHandler 注解,为所有的 Controller 添加异常处理。

// @ControllerAdvice 为所有 Controller 添加增强效果处理
@ControllerAdvice
public class BaseController {

    // 异常处理方法上添加注解 @ExceptionHandler,可以指定处理的异常类型
    @ExceptionHandler(Exception.class)
    @ResponseBody
    public BaseResponse exceptionHandler(Exception e) {
        errorLogger(e);
        return new BaseResponse(500, "系统出小差了");
    }
    
    @ExceptionHandler(RedisException.class)
    @ResponseBody
    public BaseResponse exceptionHandler(RedisException e) {
        errorLogger(e);
        return new BaseResponse(ResponseCodeEnum.INTERNAL_SERVER_ERROR, e.getMessage());
    }
    
    // ...

}

@ControllerAdvice 注解为所有 Controller 添加增强效果处理(相当于给所有Controller加了try

  • Controller 中的方法抛出异常的时候,由被@ExceptionHandler 注解修饰的方法进行处理。

六、SpringMVC 拦截器

SpringMVC拦截器用于对请求信息进行拦截处理的机制,是切面思想的一个实现。

1、执行流程

在这里插入图片描述

【执行时机】

  1. Controller方法执行之前(权限的认证)
  2. Controller方法执行之后(敏感信息的处理)
  3. 视图方法执行完成之后(资源的回收)

2、拦截器实现

1)自定义拦截器

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接口,重写方法即可。(default方法,按需手动重写)

@Slf4j
public class UserInfoInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
        try {
            String staffId = request.getHeader("STAFF_ID");
            String tenantId = request.getHeader("TENANT_ID");
            UserInfo userInfo = new UserInfo(staffId, tenantId);
            UserHelper.setUserInfo(userInfo);
        } catch (Exception e) {
            log.error("get userInfo error", e);
        }
        return true;
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, 
                                Object handler, Exception ex) throws Exception {
        UserHelper.clear();
    }

}
public class UserHelper {

    private static TransmittableThreadLocal<UserInfo> USER_INFO = new TransmittableThreadLocal<>();

    public static UserInfo getUserInfo() {
        return USER_INFO.get();
    }

    public static void setUserInfo(UserInfo userinfo) {
        USER_INFO.set(userinfo);
    }
    
    public static void clear() {
        USER_INFO.remove();
    }
    
}

2)注册自定义拦截器

继承WebMvcConfigurer 接口,重写addInterceptors方法,注册拦截器

@Configuration
public class WebConfig extends WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        // 添加拦截器实现类
        registry.addInterceptor(new UserInfoInterceptor())
            	// 配置拦截路径
                .addPathPatterns("/**")
            	// 配置放过路径
                .excludePathPatterns();
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

scj1022

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值