1. spring mvc 应用:

image.png

1.1 MVC体系:

1.1.1 三层框架

传统的三层框架分为表现层, 业务层, 持久层。
表现层代表我们的web层, 接收客户端请求,向客户端相应结果。表现层分为展示层和控制层, 展示层就是我们说的html,jsp. 控制层就是日常工作中的controller。
业务层, 我们常说的service层, 服务业务处理。
持久层, 我们常说的dao层, 负责数据的持久化。

1.1.2 mvc模型

mvc是模型(model)-视图(view)-控制层(controller)的缩写。是创建web应用程序表现层的模式。
Model: 包含了业务模型和数据模型,数据模型[po,vo,dto…],业务模型用于处理业务。
view:通常指我们的jsp和html。
controller: 就是servlet。
spring mvc是作用于表现层的框架。

1.2 springmvc工作流程:

SpringMVC是⽬前最主流的MVC框架之⼀, servlet, struts需要实现接⼝,才能让java类成为请求控制器。而springmvc中只需要添加注解就可以让⼀个简单的Java 类成为处理请求的控制器,⽆须实现任何接⼝。同时它还⽀持RESTful编程⻛格的请求.
总之: Spring MVC和Struts2⼀样, 都是为了解决表现层问题的web框架,它们都是基于 MVC设计模
式的。⽽这些表现层框架的主要职责就是处理前端HTTP请求。本质是对servlet的封装,简化了serlvet的开发.

1.2.1 常规搭建spring mvc工程:

  1. 常规的以tomcat为服务器的web项目, 每一个servlet类都需要进行如下的配置, 然后在tomcat启动的时候解析xml, 并将类的全限定名和url放在集合中。以便去寻找对应点额servlet。在mvc框架中, 只有一个核心的servlet(DispatcherServlet). 所有的请求过来进行分发。所以这个servlet需要进行配置。

image.png
在web.xml中:
image.png

2.开发具体处理业务的handler(controller), 用RequestMapping注解标识. 此外, 这些请求控制器需要被spring管理, 所以需要在项目启动的时候需要去扫描这些类。
image.png
image.png

  1. 在业务处理完毕以后, 返回了视图(ModelAndView), 需要解析成对应的jsp文件。所以需要配置视图解析器。

image.png
综上所述, spring mvc的简易流程如下图所示:
image.png

1.2.2 详细的mvc请求处理流程:

image.png
第⼀步:⽤户发送请求⾄前端控制器DispatcherServlet
第⼆步:DispatcherServlet收到请求调⽤HandlerMapping处理器映射器
第三步:处理器映射器根据请求Url找到具体的Handler(后端控制器),⽣成处理器对象及处理器拦截
器(如果 有则⽣成)⼀并返回DispatcherServlet
第四步:DispatcherServlet调⽤HandlerAdapter处理器适配器去调⽤Handler
第五步:处理器适配器执⾏Handler
第六步:Handler执⾏完成给处理器适配器返回ModelAndView
第七步:处理器适配器向前端控制器返回 ModelAndView,ModelAndView 是SpringMVC 框架的⼀个
底层对象,包括 Model和View
第⼋步:前端控制器请求视图解析器去进⾏视图解析,根据逻辑视图名来解析真正的视图。
第九步:视图解析器向前端控制器返回View
第⼗步:前端控制器进⾏视图渲染,就是将模型数据(在 ModelAndView 对象中)填充到 request 域
第⼗⼀步:前端控制器向⽤户响应结果

1.2.3 url-pattern介绍:

拦截匹配规则的url请求, 进入springmvc框架处理,springmvc就会去找能够处理这个url的handler去执行业务逻辑.
image.png

一般我们在做项目的时候, 都会配置成/ 去拦截web请求和静态文件。但是直接访问静态文件, 会报错(400,找不到匹配的处理请求的适配器)。
image.pngimage.png
解决方案:
image.png

1.2.4 请求参数绑定:

原生的servlet:从request中获取, 并进行类型转换

String data = request.getParameter(“data”);
Integer d = new Integer(data);

MVC简单参数:

直接在handler方法的形参中声明即可。框架会取出参数值并绑定到对应的参数上。其中对于不一致的参数,使用
@RequestParam注解适配
image.png
image.png
PS: 参数类型推荐使⽤包装数据类型,因为基础数据类型不可以为null,对于布尔类型的参数,请求的参数值为true或false。或者1或0

MVC复杂参数(POJO): 进行json交互

从前端到后端: 前端发送ajax请求, 后端接收pojo参数, 使用@RequestBody注解
从后端前端: 后台直接返回pojo对象,前端直接接收为json对象或者字符串,使⽤注解@ResponseBody
image.png
PS: 当然对于复杂参数,也可以进行表单提交, 对于后端来说, 使用POJO接收, 但是需要前后端参数定义要一致。

1.2.5 REST介绍:

常规的url请求:http:localhost:8080/query?id=1. 对于rest风格来说: http:localhost:8080/user/1(在url中定义了操作类型, 参数具体锁定了是哪个数据)
rest认为,互联网中所有东西都是资源, 既然是资源, 就可以唯一标识, 上面代表了用户id是1的信息。通过请求方式来(get 查,post 增,put 改,delete 删)
rest风格带来的直观上的体验: 传参方式的变化, 参数可以在uri中。

2. springmvc 高级使用:

2.1 Interceptor介绍:

2.1.1 拦截器, 过滤器的区别以及使用:

过滤器:过滤器配置比较简单,直接实现Filter接口就可以. 可以通过@WebFilter注解实现对特定的URL进行拦截。

  1. @Order(1)
  2. @Component
  3. @WebFilter(filterName = "firstFilter", urlPatterns = "/*")
  4. public class FirstFilter implements Filter {
  5. // 容器每一次请求都会调用, FilterChain来调用下一个Filter
  6. @Override
  7. public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
  8. System.out.println("Filter 处理中...");
  9. chain.doFilter(request, response);
  10. }
  11. // 在容器启动的时候调用, 在整个生命周期只会调用一次。
  12. @Override
  13. public void init(FilterConfig filterConfig) throws ServletException {
  14. System.out.println("Filter 前置");
  15. }
  16. // 容器销毁的时候调用, 一般进行资源关闭。生命周期只会调用一次。
  17. @Override
  18. public void destroy() {
  19. System.out.println("Filter 后置");
  20. }
  21. }

拦截器: 链式调用, 拦截器配置是实现HandlerInterceptor来实现. 并将自定义的拦截器处理类进行注册, 并设定需要拦截或者排除的url。

@Component
public class FirstInterceptor implements HandlerInterceptor {
        // 请求处理之前会调用, 如果返回false则当前请求结束。
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("interceptor pre");
        return true;
    }

        // 会在controller方法调用结束之后, DispatchServlet返回渲染视图之前调用。
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("interceptor post");
    }
        // 在请求结束之后 DispatchServlet返回渲染视图之后 调用
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("interceptor after");
    }
}

@Configuration
public class MyMvcConfig implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new FirstInterceptor()).addPathPatterns("/**");
    }
}
对于mvc项目, 使用xml配置:
<mvc:interceptors>
    <mvc:interceptor> 
      <mvc:mapping path="/**"/>
        <bean class="com.lagou.edu.interceptor.MyIntercepter02"/>
    </mvc:interceptor>
</mvc:interceptors>

区别1: 实现原理不一致

filter是基于回调函数执行的, 在每一个过滤器执行完业务逻辑之后,都会调用FilterChain类, 而FilterChain中也会显示的调用下一个过滤器。如图所示:
image.png具体代码逻辑如下:
image.png
image.png
image.png

区别2: 触发实际不同

Filter接口是在Servlet规范中定义的, 也就是说要依赖于Tomcat容器, 所以它只能在web程序中使用, 而Interceptor是一个spring组件, 不依赖tomcat, 运用范围广。
Filter是请求进入容器, 但是还未达到servlet之前进行预处理, 在servlet处理完之后请求才结束。
Interceptor是请求进入servlet之后, 未进入请求处理器(controller之前)进行预处理, Controller 中渲染了对应的视图之后请求结束。
image.png
image.png
image.png

区别3: 拦截返回不一致

Filter会拦截所有资源的访问(servlet, js/css). 拦截器只会拦截控制器方法.

区别4: 控制执行顺序不一致:

拦截器特殊地方, 先声明的拦截器 preHandle() 方法先执行,而postHandle()方法反而会后执行。
image.png
image.png

2.2 异常处理机制:

使用注解@ExceptionHandler 就可以捕获某种类型的异常, 写一个全局的异常处理器(用@ControllerAdvice标识) 可以捕获所有的controller对象handler方法抛出的异常。所以一般是两者结合起来使用。

/**
 * @ControllerAdvice 可以捕获所有的controller对象handler方法抛出的异常
 */
@ControllerAdvice
@Slf4j
public class SgrGlobalExceptionHandler {

    /**
     * @ExceptionHandler 捕获某一种特定的异常
     * @ResponseBody 以字符串的形式返回
     */
    @ExceptionHandler(CodedException.class)
    @ResponseBody
    public Result<String> unknownException(CodedException exception) {
        log.warn("CodedException", exception);
        return Result.fail(ResultEnum.SYSTEM_ERROR.getCode(), ResultEnum.SYSTEM_ERROR.getMsg());
    }


    /**
     * 可以直接以response的形式返回回去
     */
    @ExceptionHandler(CrmException.class)
    public void capException(CrmException exception, HttpServletResponse response) {
        try {
            response.getWriter().write("---->" + exception.getMessage());
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 返回到固定的页面 error
     *
     * @param exception
     * @return
     */
    @ExceptionHandler(Exception.class)
    public ModelAndView codedException(Exception exception) {
        ModelAndView modelAndView = new ModelAndView();
        modelAndView.setViewName("error");
        modelAndView.addObject("msg", exception.getMessage());
        return modelAndView;
    }
}

3. 手撕mvc:

3.1 mvc大致执行流程:

第一步: tomcat加载web.xml, 读取相关的servlet(在mvc中只有一个唯一的DispatchServlet) , 这里我们写一个自己的servlet。
image.png
image.png
第二步: 对于mvc框架来说, @Controller @RequestMapping注解是必不可少的。被@Controller标识的类会被spring容器管理, 被@RequestMapping标识的方法会被认定为是一个个处理具体业务的handler。我们需要定义自己的这些注解。
image.png
image.png
image.png
image.png
第三步: 在mvc容器中, 被@Controller, @Service标识的类都需要被容器管理, 被@AutoWired标识的字段, 都需要进行依赖注入维护。
image.png
image.png
image.png
第四步: 在mvc中的一个核心组件(处理器映射器), 实质就是建立url与handler的映射关系。
image.png
第五步, 上述一些都是初始化的操作, 真实的请求会调到dispatchservlet中, 并在里面进行分发。(原理其实就是从request中拿URI,继而找到相应的处理方法, 利用反射的机制进行调用)
image.png

4. springmvc 源码分析:

4.1 DispatcherServlet的继承结构:

image.png

4.2 源码分析mvc请求处理的大致流程:

所有的http请求都是进入到DispatcherServlet的dispatch方法:
spring mvc - 图37
正如mvc的流程图中所示, 请求在进到dispatch方法中的时候, 首先去找处理器映射器, 寻找对应的处理器。
处理器映射器会返回能够处理当前请求的执⾏链 HandlerExecutionChain(Handler+拦截器)。并在之后寻找Handler适配器。并执行拦截器前置方法。
spring mvc - 图38
spring mvc - 图39
实际处理器处理请求,返回结果视图对象
spring mvc - 图40
在handler返回结果后, 接着会执行拦截器的postHandler方法。
spring mvc - 图41
调⽤processDispatchResult()⽅法完成视图渲染跳转,并在最后调拦截器的afterCompletion
spring mvc - 图42
spring mvc - 图43

4.3 handle⽅法剖析:

进行适配器调用:
spring mvc - 图44
对参数进行处理, 将request参数转为handler的参数形式。并反射对目标方法进行调用。
spring mvc - 图45
spring mvc - 图46
spring mvc - 图47

4.4 九大组件初始化:

/* MultipartResolver used by this servlet. /
// 多部件解析器
private MultipartResolver multipartResolver;
// 区域化 国际化解析器
private LocaleResolver localeResolver;
// 主题解析器
private ThemeResolver themeResolver;
// 处理器映射器组件
private List handlerMappings;
// 处理器适配器组件
@Nullableprivate List handlerAdapters;
// 异常解析器组件
private List handlerExceptionResolvers;
// 默认视图名转换器组件
private RequestToViewNameTranslator viewNameTranslator;
// flash属性管理组件
private FlashMapManager flashMapManager;
// 视图解析器
private List viewResolvers;
九⼤组件都是定义了接⼝,接⼝其实就是定义了该组件的规范. 可扩展。
image.png

4.4.1 处理器映射器初始化:

spring mvc - 图49
如果按照类型和按照固定id从ioc容器中找不到对应组件,则会按照默认策略进⾏注册初始化,默认策略在DispatcherServlet.properties⽂件中配置
spring mvc - 图50
spring mvc - 图51

5. 补充:

5.1 RequestMapping的注册以及查找过程

5.1.1 注册:

在4.4中提到, HandlerMapping是处理器映射器. 它是一个接口, 有很多的实现类(RequestMappingHandlerMapping, BeanNameUrlHandlerMapping
�,RouterFunctionMapping…)。这些实现类都会被容器实例化. 以常用的RequestMappingHandlerMapping举例, 其继承的抽象类AbstractHandlerMethodMapping有一个成员变量MappingRegistry, 该成员变量存放RequestMappingInfo和HandlerMethod的映射关系。
image.png
image.png
步骤1: RequestMappingHandlerMapping实现了InitializingBean接口. spring初始化bean的时候,如果bean实现了InitializingBean接口,会自动调用afterPropertiesSet方法。
image.png
下述方法有一个判断, 该bean是一个处理器(有Controller或者RequestMapping注解)
image.png
根据Handler,Method,RequestMappingInfo将映射关系注册到map中
image.png

5.1.2 发现:

在4.2中介绍了mvc请求处理的大致流程, 其中关键的一步是查找处理器映射器集合, 寻找对应的处理器映射器.
image.png
重点流程如下:
AbstractHandlerMapping#getHandler
AbstractHandlerMethodMapping#getHandlerInternal
AbstractHandlerMethodMapping#lookupHandlerMethod

@Nullable
protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {
    // 匹配结果列表
    List<Match> matches = new ArrayList<>();
    // 直接匹配
    List<T> directPathMatches = this.mappingRegistry.getMappingsByUrl(lookupPath);
    // 如果有匹配的,就添加进匹配列表中
    if (directPathMatches != null) {
        addMatchingMappings(directPathMatches, matches, request);
    }
    // 还没有匹配,就遍历所有的处理方法去找
    if (matches.isEmpty()) {
        // No choice but to go through all mappings...
        addMatchingMappings(this.mappingRegistry.getMappings().keySet(), matches, request);
    }

    // 匹配结果不为空
    if (!matches.isEmpty()) {
        // 匹配结果比较器,直接使用匹配信息进行比较
        Comparator<Match> comparator = new MatchComparator(getMappingComparator(request));
        // 对多个匹配结果进行排序
        matches.sort(comparator);
        if (logger.isTraceEnabled()) {
            logger.trace("Found " + matches.size() + " matching mapping(s) for [" + lookupPath + "] : " + matches);
        }
        // 最佳匹配
        Match bestMatch = matches.get(0);
        if (matches.size() > 1) {
            if (CorsUtils.isPreFlightRequest(request)) {
                return PREFLIGHT_AMBIGUOUS_MATCH;
            }
            Match secondBestMatch = matches.get(1);
            // 第一个元素和第二个元素进行比较
            if (comparator.compare(bestMatch, secondBestMatch) == 0) {
                // 结果为0,至少有2个最佳匹配
                Method m1 = bestMatch.handlerMethod.getMethod();
                Method m2 = secondBestMatch.handlerMethod.getMethod();
                // 多个最佳匹配,抛出异常
                throw new IllegalStateException("Ambiguous handler methods mapped for HTTP path '" +
                        request.getRequestURL() + "': {" + m1 + ", " + m2 + "}");
            }
        }
        request.setAttribute(BEST_MATCHING_HANDLER_ATTRIBUTE, bestMatch.handlerMethod);
        handleMatch(bestMatch.mapping, lookupPath, request);
        // 返回最佳匹配对应的处理器方法
        return bestMatch.handlerMethod;
    }
    else {
        // 没有匹配结果,执行无处理器匹配方法,可执行一些特殊逻辑
        return handleNoMatch(this.mappingRegistry.getMappings().keySet(), lookupPath, request);
    }
}

最终实现逻辑就是, 在MappingRegistry对象中根据请求获取相应的HandlerMethod对象.