SpringMVC

SpringMVC

一、概述

1、三层架构

在这里插入图片描述

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 原理

1、SpringMVC 的 核心组件

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

  • 在SpringMVC框架中,提供了一个统一的Servlet(前端控制器:DispatcherServlet)来接收用户请求

  • 前端控制器:DispatcherServlet

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

  • 处理器映射器:HandlerMapping

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

  • 处理器适配器:HandlerAdapter

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

  • 请求处理器:Handler

    处理实际请求的处理器。(就是Controller控制器)

  • 视图解析器:ViewResolver

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

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、请求映射

@RequestMapping 是 Spring MVC 中用于映射请求路径和处理方法的注解

@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) 修改部分内容

这些标签也可以根据请求方式不同,来限定url的映射关系

2、参数绑定

/**
 * 请求参数绑定:将请求中的参数绑定到方法的参数上。
 */
@RequestMapping("/hello")
public String hello(@RequestParam("name") String name) {
   
    return "Hello, " + name;
}
/**
 * 路径变量绑定:将请求路径中的变量(占位符)绑定到方法的参数上。
 */
@RequestMapping("/hello/{name}")
public String hello(@PathVariable("name") String name) {
   
    return "Hello, " + name;
}
/**
 * 请求体绑定: 将请求体中的数据绑定到方法的参数上。
 */
@RequestMapping(value = "/hello", method = RequestMethod.POST)
public String hello(@RequestBody User user) {
   
    return "Hello, " + user.getName();
}

/**
 * 文件上传
 */
@PostMapping("/upload")
public String handleFileUpload(@RequestParam("file") MultipartFile file) {
   
    //...
    return "File uploaded successfully";
}
/**
 * 表单数据绑定: 将 HTML 表单中的数据绑定到方法的参数上。
 */
@RequestMapping(value = "/hello", method = RequestMethod.POST)
public String hello(@ModelAttribute User user) {
   
    return "Hello, " + user.getName();
}

3、返回值类型

  • 前后端不分离,ModelAndView 应该是最最常见的返回值类型。
  • 前后端分离后,基本都是以 JSON 数据为主了。
注解 说明
@RestController 添加在类上,将当前类交给Spring容器管理,并且声明所有方法返回类型为json格式
@ResponseBody 添加在方法上,表示此方法的返回类型为json格式

@RestController 相当于 @Controller + @ResponseBody

四、异常统一处理

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();
    }
}

相关推荐

  1. <span style='color:red;'>SpringMVC</span>

    SpringMVC

    2024-02-21 10:26:03      42 阅读
  2. <span style='color:red;'>SpringMVC</span>

    SpringMVC

    2024-02-21 10:26:03      34 阅读
  3. <span style='color:red;'>SpringMVC</span>

    SpringMVC

    2024-02-21 10:26:03      35 阅读
  4. <span style='color:red;'>SpringMVC</span>

    SpringMVC

    2024-02-21 10:26:03      35 阅读
  5. <span style='color:red;'>springMVC</span>

    springMVC

    2024-02-21 10:26:03      27 阅读
  6. <span style='color:red;'>SpringMVC</span>

    SpringMVC

    2024-02-21 10:26:03      19 阅读
  7. <span style='color:red;'>SpringMVC</span>

    SpringMVC

    2024-02-21 10:26:03      28 阅读
  8. <span style='color:red;'>SpringMVC</span>

    SpringMVC

    2024-02-21 10:26:03      17 阅读
  9. SpringMVC

    2024-02-21 10:26:03       24 阅读
  10. <span style='color:red;'>springMVC</span>

    springMVC

    2024-02-21 10:26:03      18 阅读

最近更新

  1. TCP协议是安全的吗?

    2024-02-21 10:26:03       18 阅读
  2. 阿里云服务器执行yum,一直下载docker-ce-stable失败

    2024-02-21 10:26:03       19 阅读
  3. 【Python教程】压缩PDF文件大小

    2024-02-21 10:26:03       18 阅读
  4. 通过文章id递归查询所有评论(xml)

    2024-02-21 10:26:03       20 阅读

热门阅读

  1. 认识K8S

    认识K8S

    2024-02-21 10:26:03      26 阅读
  2. python创建word文档

    2024-02-21 10:26:03       31 阅读
  3. LeetCode23.合并K个升序链表

    2024-02-21 10:26:03       23 阅读
  4. Python在生物信息学中的应用:序列化Python对象

    2024-02-21 10:26:03       29 阅读
  5. 导出docker MySQL中的数据库写一个shell 脚本

    2024-02-21 10:26:03       24 阅读
  6. Python内置函数07——copy

    2024-02-21 10:26:03       31 阅读
  7. 企业内部数据如何进行数据分析

    2024-02-21 10:26:03       34 阅读
  8. MySQL学习Day19——索引的数据结构

    2024-02-21 10:26:03       31 阅读