乱七八糟的SpringMVC笔记

1、requestMapping注解的方法怎么转变成handlerMethod

  • 入口
    WebMvcAutoConfiguration里面有个内部配置类WebMvcAutoConfigurationAdapter它会Import一个EnableWebMvcConfiguration,它会@Bean一个RequestMappingHandlerMapping对象,同时这边设置了很多属性,包括拦截器,mapping顺序,CorsConfiguration等等;关键点在HandlerMapping对象的父类实现了InitializingBean,HandlerMapping对象又重写了afterPropertiesSet这个方法,但是最后还是调用了父类的afterPropertiesSet,这个方法里面就是处理HandlerMethods方法逻辑
  • HandlerMethod如何初始化的
    1. 获取applicationContext里面所有的Bean对象,过滤出不是scopedTarget.开头的bean
    2. 然后判断这个bean的类型是否有注解Controller或者RequestMapping注解
    3. 获取这个bean的中所有标@RequestMapping注解的方法,可以扩展customCondition,然后为每个方法创建RequestMappingInfo对象,这个Info对象主要将requestMapping注解里面的属性进行解析出来封装,一个方法创建完成后,继续处理如果类上有@requestMapping对象,则要继续同样封装成为RequestMappingInfo对象,然后跟方法的组合成一个新的RequestMappingInfo对象,同时接着处理PathPrefix逻辑;最后封装为一个method映射mapingInfo的map类型
    4. 接着为每个方法创建HandlerMethod对象,这边会处理方法上面的ResponseStatus注解,将里面的属性赋值给HandlerMethod,然后将mapingInfo与HandlerMethod建立关系存入mappingLookup(Map<mapingInfo, HandlerMethod>),如果mappingLookup里面已经有了mapping对应的HandlerMethod就会报错;
    5. 接着如果请求路径没有包含* ? { } 这些,将会为url建立mapping集合放入urlLookup(MultiValueMap<String, mapingInfo>),即相同路径的mappingInfo对象放在一起
    6. 如果requestMappingInfo有name即@requestMapping注解有设置name的,如果么有,则将当前handlerMethod的类名大写字母都提取出来+#+方法名称,建立nameLookup( Map<String, List<HandlerMethod》),这里面放着是相同name的handlerMethod集合
    7. 接着处理@CrossOrigin注解,包括类上和方法上的,如果有,则会初始化CorsConfiguration对象,然后放入corsLookup(Map<HandlerMethod, CorsConfiguration>)
    8. 最后将mapping,handlerMethod,requestMapping的请求路径,requestMapping的name,这个四个封装成MappingRegistration对象,放入registry(Map<mapping, MappingRegistration>)
    9. 这就完成了所有方法的初始化了,这边留了一个扩展点,可以覆盖handlerMethodsInitialized进行额外业务逻辑处理mappingLookup

2、@ControllerAdvice@ExceptionHandler怎么被收集起来和生效的

  • 入口
    WebMvcAutoConfiguration里面有个内部配置类WebMvcAutoConfigurationAdapter它会Import一个EnableWebMvcConfiguration,在它的父类WebMvcConfigurationSupport里面会@Bean一个HandlerExceptionResolverComposite对象;
    先检查如果没有通过configurers配置HandlerExceptionResolver,那么就会加入默认的三种异常解析器:
    ExceptionHandlerExceptionResolver同时会直接调用它的afterPropertiesSet方法, ResponseStatusExceptionResolver,
    DefaultHandlerExceptionResolver
    接下去提供通过configurers去extendHandlerExceptionResolvers,最后将这些解析器设置到HandlerExceptionResolverComposite对象里面,它的顺序值是0;
  • ControllerAdvice注解类的收集
    1. 收集ControllerAdviceBeans
      在类ExceptionHandlerExceptionResolver的afterPropertiesSet方法里面,获取容器内所有bean对象过滤出有标识了ControllerAdvice注解的类,然后封装成ControllerAdviceBean,里面会解析出注解里面所有属性封装为HandlerTypePredicate成员对象赋值给beanTypePredicate;
      然后将这些adviceBean进行排序,根据注解order
    2. ExceptionHandlerMethodResolver的封装
      循环这些adviceBean获取类中标注@ExceptionHandler注解的方法集合,会解析出注解里面的异常类型,如果注解里面没写,则解析方法参数,必须要有一个异常类型参数,否则报错;
      然后建立一个异常类型与method的对应关系mappedMethods(Map<Class<? extends Throwable>, Method>),如果存在相同异常类型则报错;
      如果收集到异常则建立adviceBean和resolver关系,放入exceptionHandlerAdviceCache(Map<ControllerAdviceBean, ExceptionHandlerMethodResolver>)
      如果这个类有实现ResponseBodyAdvice接口,则adviceBean会添加到responseBodyAdvice
      在这里插入图片描述

在这里插入图片描述

  • 生效

    1. 入口
      在DispatcherServlet执行到拦截器调用了postHandle方法,在这之前任何的异常抛出,且异常类型不是ModelAndViewDefiningException,那么会由handlerExceptionResolvers进行处理异常
      循环handlerExceptionResolvers解析器,找到一个能处理异常的HandlerExceptionResolver;
      这个解析集合就是IOC内实现了HandlerExceptionResolver接口的类集合。这边发现有两个DefaultErrorAttributes(这个主要将异常保存到request),HandlerExceptionResolverComposite

    2. HandlerExceptionResolverComposite
      这个类在WebMvcConfigurationSupport里面进行@Bean了,然后取出它里面解析器HandlerExceptionResolver,就是上面提到的三个默认的;
      在这里插入图片描述

    3. 异常处理

      • ExceptionHandlerExceptionResolver
               从exceptionHandlerCache进行获取当前handlerMethod所在的类对应的ExceptionHandlerMethodResolver,如果没有则使用handlerMethod的handlerType直接构造出ExceptionHandlerMethodResolver对象(这个实例化的会将当前类标有@ExceptionHandler注解异常类型全部收集起来跟method对应)进行缓存到exceptionHandlerCache中,这是先检查本地Controller是否写了异常处理,比如BasicErrorController类中就写了一个@ExceptionHandler(HttpMediaTypeNotAcceptableException.class);
               然后使用这个ExceptionHandlerMethodResolver对象找出异常类型对应的method,如果当前类封装的resolver对象里面没有找到,则往exceptionHandlerAdviceCache里面找,这个在上面1的步骤中缓存了;那advice匹配逻辑如下:
               将ControllerAdviceBean与当前的方法所在的类进行匹配,怎么匹配呢?ControllerAdviceBean注解里面有很多值(basePackages,assignableTypes,annotations)已经封装成了beanTypePredicate成员变量,如果注解里面啥都没有写,则是符合;否则按照三个注解值进行相应匹配;匹配成功就获取到了ExceptionHandlerMethodResolver,此时也是将异常类型进行查找是否有对应的method,然后封装成ServletInvocableHandlerMethod对象,后面就是反射调用了;
               从异常处理的源码中发现@ExceptionHandler注解方法可以设置request,response,handleMethod等参数
        在这里插入图片描述

      • ResponseStatusExceptionResolver
        这个解析器是在上一个解析器没找到对应时,它就出场了,这个处理异常类型上面有写注解ResponseStatus的情况

      • DefaultHandlerExceptionResolver
        这个就是上面两个都么有处理到,这个类里面会处理一些默认的异常,比如NoHandlerFoundException,AsyncRequestTimeoutException,ConversionNotSupportedException等等

3、SpringMVC同步请求执行流程

  • springboot自动装配做了好多事情,如图
    在这里插入图片描述
    在这里插入图片描述

  • DispatcherServlet
    在首次发起请求的时候,会执行initStrategies方法,这里面进行很多赋值处理,比如
    multipartResolver:获取MultipartResolver接口子类赋值
    handlerMappings :获取HandlerMapping接口子类进行排序后赋值
    handlerAdapters:获取HandlerAdapter接口子类进行排序后赋值
    handlerExceptionResolvers:获取HandlerExceptionResolver接口子类进行排序后赋值
    viewResolvers:获取ViewResolver接口子类进行排序后赋值
    在这里插入图片描述

  • 请求执行流程 doDispatch

    1. 如果是文件上传请求,则使用multipartResolver进行解析处理request
    2. 从handlerMapping里面获取HandlerExecutionChain对象,其实就是获取handlerMethod
      1. 循环handlerMappings集合,第一个就是RequestMappingHandlerMapping;
      2. 解析出请求路径lookupPath,然后通过urlLookup找到匹配的mappingInfo集合,循环这个集合为当前的request匹配到合适的mappingInfo;怎么匹配------->就是mappingInfo里面有methodsCondition,paramsCondition,patternsCondition就是注解里面属性进行依次匹配request,匹配成功后构造新的RequestMappingInfo,最后封装成为Match(mappingInfo, HandlerMethod )对象,handlerMethod是通过mappingInfo去mappingLookup里面寻找获取到的;
                针对于restful风格的路径并不存在urlLookup里面,所以当urlLookup里面找不到的时候,会将所有的requestMappingInfo进行全部匹配一遍,根据Ant风格进行匹配路径;
      3. 如果匹配到多个,要进行比较mappingInfo,如果比较后还是多个直接报错;
      4. 选到一个合适的mappingInfo后,如果lookupPath是restful风格的,会将路径和值直接解析成map的形式然后存到request
      5. 直接new一个HandlerExecutionChain(Object HandlerMethod, HandlerInterceptor… interceptors),然后获取到adaptedInterceptors拦截器,循环这些拦截器集合里面有没有是MappedInterceptor类型的,如果有要进行lookupPath匹配,符合则加入到chain;如果没有MappedInterceptor类型的拦截器,则直接加入chain
      6. 判断corsLookup里面是否有当前HandlerMethod的跨域配置,如果有,则需要在chain里面 加入CorsInterceptor拦截器或者CorsUtils.isPreFlightRequest(request)为true,则封装新的HandlerExecutionChain对象返回
      7. 如果最终没有发现能够处理handler,如果配置spring.mvc.throw-exception-if-no-handler-found=true,那么就会抛出异常(比如让404抛出异常,然后项目自定义捕获处理),如果没有配置,那么会设置response.sendError(HttpServletResponse.SC_NOT_FOUND);然后此时后续会再次发起/error请求
      8. 总结:解析lookupPath,然后找到HandlerMethod,检查是否有跨域配置,最后和拦截器一起封装为chain对象;如果没找到HandlerMethod就会抛出异常或者sendError
    3. 根据handlerMethod获取HandlerAdapter
      1. 循环handlerAdapters集合,adapter.supports(handler),就能获取到adapter
      2. RequestMappingHandlerAdapter直接判断handler是否是handlerMethod,supportsInternal方法没人重写直接是true
        (handler instanceof HandlerMethod && supportsInternal((HandlerMethod) handler))
    4. 执行chain里面的拦截器preHandle方法,如果返回false,接着执行afterCompletion;然后就结束流程了
    5. adapter处理实际业务方法获取mv
      1. 这边分synchronizeOnSession情况处理,正常是么有同步的
      2. 获取到handlerMethod所在类中标有注解InitBinder的方法,缓存起来,然后为这些方法封装为InvocableHandlerMethod,然后封装到ServletRequestDataBinderFactory对象里面
      3. 获取到handlerMethod所在类中标有注解ModelAttribute的方法,缓存起来,然后为这些方法封装为InvocableHandlerMethod,然后封装到ModelFactory(attrMethods, binderFactory, sessionAttrHandler)
      4. 为当前的handlerMethod创建ServletInvocableHandlerMethod对象,设置argumentResolvers,returnValueHandlers,binderFactory
      5. 创建ModelAndViewContainer对象,这边会调用ModelAttribute方法,处理下sessionAttributes的东西,略过了
      6. 创建WebAsyncManager处理异步请求,callableInterceptors,deferredResultInterceptors,略过
      7. 执行ServletInvocableHandlerMethod#invokeAndHandle方法,先解析请求参数,然后通过反射调用实际方法,参数使用resolvers去解析,这里面很深,跳过跳过…
      8. 处理方法上@ResponseStatus注解,将里面的code,reason设置到response里面,这个reason如果有值就response.sendError设置到这里面;不会去处理返回值了
      9. 接着用使用returnValueHandlers去解析返回值,这里面先判断这些returnHandler里面是否有AsyncHandlerMethodReturnValueHandler,然后检查isAsyncReturnValue,如果满足isAsyncValue && !(handler instanceof AsyncHandlerMethodReturnValueHandler),则不进行解析了;
        如果不是,那依次判断returnHandler的supportsReturnType方法,比如返回JSON,就是判断方法或者类上是否有ResponseBody注解,这样就获取到了具体的返回值处理器了HandlerMethodReturnValueHandler。然后就到对应的returnHandler的handleReturnValue去解析返回值,这里面也很深,跳过跳过
      10. 接着处理sessionAttributes的事情,跳过跳过
      11. 此时如果mavContainer.isRequestHandled()是false,会接着处理ModelAndView,如果true,这个就返回null;当responseStatus有reason这个就会被设置成true,或者么有返回值但是方法上有responseStatus也是True
      12. 最后处理response的cacheControl
    6. 获取到mv后,如果没有视图名称,会进行设置默认的ViewName
    7. 处理拦截器的postHandle方法了,如果在这之前抛出异常,这个拦截器调用就无法触发了
    8. 处理异常,看上面controllerAdvice那边有写;
    9. 渲染试图,如果有viewName,先从需要从viewResolvers解析出View,解析不出来就报错,如果没有viewName,这直接获取view,如果没有,则报错;获取到veiw后进行view.render了;最后 处理拦截器的afterCompletion
    10. 发布ServletRequestHandledEvent事件
      总结:从handlerMapping里面根据urlLookup获取到handlerMethod和拦截器,使用handleMethod找到对应的adapter,执行拦截器preHandler,开始使用adapter处理业务逻辑方法得到mav,然后执行拦截器postHandler,如果有抛出异常则处理异常,否则就开始渲视图,最后执行拦截器的afterCompletion;

    4、@ResponseStatus中的reason值引出/error请求

  • ResponseStatus常规用法

    • 写在了标有 @RequestMapping注解方法上时
            这种注解ResponseStatus如果没写reason,只是写了code,那么会改变了http请求的状态码,如果同时写了reason,那么页面就报错了,同时显示reason和code。
            原理: 在ServletInvocableHandlerMethod#invokeAndHandle执行完业务方法获取返回值后,会开始处理ResponseStatus,如果handlerMethod方法上标注了@ResponseStatus注解,写了reason触发response.sendError(status.value(), reason)同时后面就不会处理返回值和视图了,到了tomcat源码中会去找这个code对应的errorPage,如果没找到则获取code=0的errorPage,然后继续发起 errorPage.getLocation()地址的请求。

      这个0的errorpage是在IOC#onfresh过程中,TomcatServletWebServerFactory#configureContext中加入的

      如果没写reason触发response.setStatus(status.value());

    • 第二种写在标有注解@ExceptionHandler的方法上
      这种本质跟第一种最后都是一样的逻辑处理,因为你会发现在异常处理过程中也是封装为了ServletInvocableHandlerMethod对象去调用的,如果下图所示,handlerAdapter最终也是封装为ServletInvocableHandlerMethod执行的
      在这里插入图片描述

在这里插入图片描述

- 
  • 场景
    如果在@ResponseStatus中写了reason值,那么在执行的时候,如果没有引入ErrorPage配置,使用默认的,那么会再次发起一个/error请求
  • 流程
    1. springboot暗中搞了些动作
      springboot里面有个后置处理器ErrorPageRegistrarBeanPostProcessor,它在postProcessBeforeInitialization方法执行的时候会将容器内ErrorPageRegistrar接口的所有的子类执行一遍这个接口registerErrorPages方法,将当前bean传入,当前bean必须是ErrorPageRegistry类型,即每个实现了ErrorPageRegistrar接口子类一个机会接触到ErrorPageRegistry类,它可以添加errorPages
      重点:TomcatServletWebServerFactory实现了这个ErrorPageRegistry接口,所以添加的errorPages就全部积累到了tomcat里面了,在tomcat在发现错误的时候,会去寻找对应code的errorPage
      Springboot框架在tomcat初始化的过程中会将这些errorPage统统加入到errorPageSupport,方便tomcat执行过程出现错误时执行对应的errorPage
      在这里插入图片描述在这里插入图片描述
      在这里插入图片描述
    2. 解析responseStatus注解
      在mvc执行完业务方法后invokeAndHandle,会解析handerMethod里面的responseStatus,处理里面的reason,这个一设置就在response里面标记了errorState为1即ErrorReportRequired
      在这里插入图片描述
    3. RequestDispatcher.forward
      执行完业务逻辑流程后,在StandardHostValve类的invoke方法,会进行判断response.isErrorReportRequired(),然后获取 RequestDispatcher rd = servletContext.getRequestDispatcher(errorPage.getLocation()); 执行/error/请求
      在这里插入图片描述
  1. /error请求的处理
    在spring-boot-autoconfigure jar包里面有个类BasicErrorController,这类里面处理了/error请求,一个modelView,一个是RsponseEntity的;
    从源码分析可得在mvc请求过程中如果一个lookupPath找到两个requestMappingInfo的时候,要进行比较,结果一比较MediaType.TEXT_HTML_VALUE它的处理排在了前面,所以此时你页面会出现一个很经典的Whitelabel Error Page,就是它搞出来的;
    这个排序我目前还不知道改变,所以舍弃这个类被扫描,它是在ErrorMvcAutoConfiguration进行@bean进入的,在上面有个注解ConditionalOnMissingBean,就是如果没有出现ErrorController子类,那么就用这个BasicErrorController,
    1. 啥都不动,在classpath的建立一个文件夹,目录/static/error/,这个目录下可以放置对应code的html那么那个Whitelable就不会出现了

    2. 实现接口ErrorController,自定义处理/error/请求在这里插入图片描述

    3. 添加对应code的errorPage,这样就没机会执行这个默认的为0的errorpage了在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

5、拦截器怎么加入到requestMapping里面

  • 前置
    springboot拦截器的定义就声明一个拦截器,然后加入到配置类里面,这个配置类实现了一个WebMvcConfigurer接口,凡是实现了这个接口的子类都会加入到configurers(DelegatingWebMvcConfiguration类)里面,最后放入delegates这里面了
    在这里插入图片描述
    在这里插入图片描述
    • delegates的套路执行
      delegates这是所有WebMvcConfigurer接口子类集合,WebMvcConfigurer接口里面定义很多方法,
      然后都是传入一个对应的处理类型,比如InterceptorRegistry,CorsRegistry,HandlerMethodArgumentResolver等等,这样子要在这些传入进来的类型上做扩展功能,就是循环一遍WebMvcConfigurer子类,然后把传入的类型传给这些WebMvcConfigurer子类处理一遍它们自己的处理逻辑,比如添加拦截器,添加方法解析器
      在这里插入图片描述
      • requestMapping拦截器加入
        在RequestMappingHandlerMapping对象创建的时候,会设置拦截器,前提创建了一个InterceptorRegistry,然后放入到了configurers里面,这样自己就会把程序定义的拦截器给加入进去了,如果拦截器有路径匹配,它就会封装成为MappedInterceptor
        在这里插入图片描述

6、Filter怎么加入

  • 过滤器属于servlet框架层面的,所以这玩意是在tomcat启动过程中进行设置的

  • Springboot中有一个类FilterRegistrationBean,从类名定义以及内容来看,这里面可以放置一个filter

  • 所以项目要自定义Filter,只要生成此对象实例,往里面设置filter,然后同时设置相关属性即可在这里插入图片描述

  • 原理

    • springboot的tomcat在启动过程中会触发ServletContainerInitializer接口子集类的onStartup方法
    • 所以先获取这些子类,从源码可以发现 servlet,filter,Listener三大组件齐全在这里插入图片描述
    • 所以FilterRegistrationBean实例都会被收集到,然后逐一将他们里面的filter添加到StandardContext中
    • 所以dispatcherServlet 这个类在springboot中,从上图源码可以看到肯定由ServletRegistrationBean子类负责,所以你会很顺畅的看到一个类DispatcherServletRegistrationBean,他在@Bean过程中就放入了DispatcherServlet
  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值