SpringMVC

SpringMVC是一个基于三层处理架构的表现层处理框架。三层架构是指:表现层、业务层、数据访问层。表现层对应于业务中的视图、数据和servlet。MVC旨在对视图和servlet解耦,使控制器的逻辑与view分离,降低开发难度。

1. Spring容器启动

通常如果我们希望通过注解的方式来进行Spring MVC开发,会在***-servlet.xml中加入<mvc:annotation-driven/>标签来告诉Spring我们的目的。
该标签被org.springframework.web.servlet.config.AnnotationDrivenBeanDefinitionParser解析,向Spring容器中注册了以下8个Bean实例。

注册的3类8个实例

  • 1.1 RequestMappingHandlerMapping对象创建

RequestMappingHandlerMapping实现了InitializingBean接口,在Spring创建RequestMappingHandlerMapping对象实例之后,调用初始化方法时,会调用其afterPropertiesSet()方法。

protected void invokeInitMethods(String beanName, final Object bean, RootBeanDefinition mbd)
            throws Throwable {

        boolean isInitializingBean = (bean instanceof InitializingBean);
        if (isInitializingBean && 
                (mbd == null || !mbd.isExternallyManagedInitMethod("afterPropertiesSet"))) {
            if (System.getSecurityManager() != null) {
              //....
            }                
            else {//调用InitializingBean的afterPropertiesSet方法。
                ((InitializingBean) bean).afterPropertiesSet();
            }
        }
        //调用自定义初始化方法。。。省略,不关心
    }

afterPropertiesSet()中直接调用initHandlerMethods()方法,去遍历beanDefinitionName中注册的bean, 判断是否被@Controller注解,如果是,则检查被标注为@RequestMapping的方法,创建RequestMappingInfo对象并将RequestMappingInfo:处理器方法(controller中的对应方法)注册到HandlerMapping。同时将url:RequestMappingInfo注册到HandlerMapping。 当获取到请求url时,通过url找到mapping,然后通过mapping找到method。

public void afterPropertiesSet() {
        initHandlerMethods();
    }

    //Scan beans in the ApplicationContext, detect and register handler methods.
    protected void initHandlerMethods() {
        //扫描所有注册的Bean
        String[] beanNames = (this.detectHandlerMethodsInAncestorContexts ?
           BeanFactoryUtils.beanNamesForTypeIncludingAncestors(getApplicationContext(), 
                Object.class) : getApplicationContext().getBeanNamesForType(Object.class));
        //遍历这些Bean,依次判断是否是处理器,并检测其HandlerMethod
        for (String beanName : beanNames) {
            if (isHandler(getApplicationContext().getType(beanName))){
                detectHandlerMethods(beanName);
            }
        }
        //这个方法是个空实现,不管他
        handlerMethodsInitialized(getHandlerMethods());
    }

2. 请求处理流程

DispatcherServlet接收到请求doDispatch开始说起。

请求处理流程 一图胜千言
    protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
        // ... 省略
        try {
            try {
                ModelAndView mv = null;
                Object dispatchException = null;

                try {
                    processedRequest = this.checkMultipart(request);
                    multipartRequestParsed = processedRequest != request;
                    // 获取拦截器链和handler
                    mappedHandler = this.getHandler(processedRequest);
                    // ... 省略诸多代码 
                    // 遍历interceptors, 调用interceptor.postHandle()                  
                    if (!mappedHandler.applyPreHandle(processedRequest, response)) {
                        return;
                    }
                    // 由于需要支持众多第三方的handler,如:servlet, controller, HttpRequestHandler
                    // 不能去修改人家的源码,要实现灵活的支持就可以考虑使用适配器模式
                    mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
                    // ... 省略诸多代码
                    // 遍历interceptors, 调用interceptor.postHandle()
                    mappedHandler.applyPostHandle(processedRequest, response, mv);
                } catch (Exception var20) {
                    dispatchException = var20;
                } catch (Throwable var21) {
                    dispatchException = new NestedServletException("Handler dispatch failed", var21);
                }
                // 捕获异常并处理
                this.processDispatchResult(processedRequest, response, mappedHandler, mv, (Exception)dispatchException);
            } catch (Exception var22) {
                // 遍历interceptors, 调用interceptor.afterCompletion()
                this.triggerAfterCompletion(processedRequest, response, mappedHandler, var22);
            } catch (Throwable var23) {
                this.triggerAfterCompletion(processedRequest, response, mappedHandler, new NestedServletException("Handler processing failed", var23));
            }

        } finally {
           // ...
        }
    }

3. 拦截器 原理与实现

  • 3.1 拦截器的实现

实现HandlerInterceptor接口

public class AuthInterceptor implements HandlerInterceptor {
    //...
    @Override
    public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o) throws Exception {
    }

    @Override
    public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) throws Exception {
    }

    @Override
    public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception {
    }
}
  • 3.2 注册拦截器

@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Autowired
    private TimeInterceptor timeInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(timeInterceptor).excludePattern("pattern");
    }
}
  • 3.3 拦截器是如何生效的

  1. 向容器中创建RequestMappingHandlerMapping对象时,通过WebMvcConfigurationSupport::requestMappingHandlerMapping()方法实现,对mapping注册拦截器,然后获取WebMvcConfigurer对象,执行其addInterceptors()方法,将拦截器注册。
  2. DispatcherServlet.doDispatch()方法中getHandler()会根据request对象从HandlerMapping获取拦截器链和HanderMethod。
  3. dispatcherServlet对象的applyPreHandle()等方法调用拦截器的pre/postHandle等方法。
    @Bean
    public RequestMappingHandlerMapping requestMappingHandlerMapping() {
        RequestMappingHandlerMapping mapping = this.createRequestMappingHandlerMapping();
        //...
        mapping.setInterceptors(this.getInterceptors());
        // ...
    }

4. 异常处理机制

  • 4.1 定义异常处理方式

  1. @ExceptionHandler在特定Controller上处理(not recommend)
  2. 实现HandlerExceptionResolver接口完成全局异常处理
  3. @ControllerAdvice+@ExceptionHandler全局异常处理(recommend)
    这三种方式实现起来都比较简单,就不举例了。
  • 4.2 解析异常处理advice

第一张图提到SpringMVC向容器注入ExceptionHandlerExceptionResolver,这个类同样实现了InitializingBean接口,在afterpropertiesSet()方法中,initExceptionhandlerAdviceCache()时,从容器中获取被标注为ControllerAdvice的bean,创建ExceptionHandlerMethodResolver并缓存起来。

    public void afterPropertiesSet() {

        //从容器中获取被标注为ControllerAdvice的bean,创建ExceptionHandlerMethodResolver并缓存
        this.initExceptionHandlerAdviceCache();
        List handlers;
        if (this.argumentResolvers == null) {
            handlers = this.getDefaultArgumentResolvers();
            this.argumentResolvers = (new HandlerMethodArgumentResolverComposite()).addResolvers(handlers);
        }

        if (this.returnValueHandlers == null) {
            handlers = this.getDefaultReturnValueHandlers();
            this.returnValueHandlers = (new HandlerMethodReturnValueHandlerComposite()).addHandlers(handlers);
        }

    }
    private void initExceptionHandlerAdviceCache() {
        if (this.getApplicationContext() != null) {
            if (this.logger.isDebugEnabled()) {
                this.logger.debug("Looking for exception mappings: " + this.getApplicationContext());
            }

            List<ControllerAdviceBean> adviceBeans = ControllerAdviceBean.findAnnotatedBeans(this.getApplicationContext());
            AnnotationAwareOrderComparator.sort(adviceBeans);
            Iterator var2 = adviceBeans.iterator();

            while(var2.hasNext()) {
                ControllerAdviceBean adviceBean = (ControllerAdviceBean)var2.next();
                ExceptionHandlerMethodResolver resolver = new ExceptionHandlerMethodResolver(adviceBean.getBeanType());
                if (resolver.hasExceptionMappings()) {
                    this.exceptionHandlerAdviceCache.put(adviceBean, resolver);
                }

                if (ResponseBodyAdvice.class.isAssignableFrom(adviceBean.getBeanType())) {
                    this.responseBodyAdvice.add(adviceBean);
                }
            }

        }
    }
  • 4.3 异常处理流程

doDispatch()方法中,当HandlerMethod抛出异常时,异常被catch,然后作为参数传入processDispatchResult()方法

                try {
                    processedRequest = this.checkMultipart(request);
                    multipartRequestParsed = processedRequest != request;
                    // 获取拦截器链和handler
                    mappedHandler = this.getHandler(processedRequest);
                    // ... 省略诸多代码 
                    // 遍历interceptors, 调用interceptor.postHandle()                  
                    if (!mappedHandler.applyPreHandle(processedRequest, response)) {
                        return;
                    }

                    mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
                    // ... 省略诸多代码
                    // 遍历interceptors, 调用interceptor.postHandle()
                    mappedHandler.applyPostHandle(processedRequest, response, mv);
                } catch (Exception var20) {
                    dispatchException = var20;
                } catch (Throwable var21) {
                    dispatchException = new NestedServletException("Handler dispatch failed", var21);
                }
                // 捕获异常并处理
                this.processDispatchResult(processedRequest, response, mappedHandler, mv, (Exception)dispatchException);

processDispatchResult()方法通过processHandlerException完成异常处理,逻辑是:遍历handlerExceptionResolvers,依次处理。

    private void processDispatchResult(HttpServletRequest request, HttpServletResponse response, HandlerExecutionChain mappedHandler, ModelAndView mv, Exception exception) throws Exception {
        boolean errorView = false;
        if (exception != null) {
            if (exception instanceof ModelAndViewDefiningException) {
                mv = ((ModelAndViewDefiningException)exception).getModelAndView();
            } else {
                Object handler = mappedHandler != null ? mappedHandler.getHandler() : null;
                mv = this.processHandlerException(request, response, handler, exception);
                errorView = mv != null;
            }
        }
    }
    protected ModelAndView processHandlerException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        ModelAndView exMv = null;
        Iterator var6 = this.handlerExceptionResolvers.iterator();

        while(var6.hasNext()) {
            HandlerExceptionResolver handlerExceptionResolver = (HandlerExceptionResolver)var6.next();
            exMv = handlerExceptionResolver.resolveException(request, response, handler, ex);
            if (exMv != null) {
                break;
            }
        }
    // ...
    }

每个resolver的解决思路是:判断当前resolver是否适用于该handler和request,适用则进行解析。

    public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
        if (this.shouldApplyTo(request, handler)) {
            if (this.logger.isDebugEnabled()) {
                this.logger.debug("Resolving exception from handler [" + handler + "]: " + ex);
            }

            this.prepareResponse(ex, response);
            ModelAndView result = this.doResolveException(request, response, handler, ex);
            if (result != null) {
                this.logException(ex, request);
            }

            return result;
        } else {
            return null;
        }
    }

doResolveException()根据当前handler对象和exception对象去exceptionHandlerCache中找到对应的ExceptionHandlerMethodResolver,拿到要被调用的Method对象,然后完成该方法的调用,返回modelAndView.

    protected ModelAndView doResolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
        if (handler != null) {
            Method handlerMethod = this.findBestExceptionHandlerMethod(handler, ex);
            if (handlerMethod != null) {
                ServletWebRequest webRequest = new ServletWebRequest(request, response);

                try {
                    Object[] args = this.resolveHandlerArguments(handlerMethod, handler, webRequest, ex);
                    if (this.logger.isDebugEnabled()) {
                        this.logger.debug("Invoking request handler method: " + handlerMethod);
                    }

                    Object retVal = this.doInvokeMethod(handlerMethod, handler, args);
                    return this.getModelAndView(handlerMethod, retVal, webRequest);
                } catch (Exception var9) {
                    this.logger.error("Invoking request method resulted in exception : " + handlerMethod, var9);
                }
            }
        }

        return null;
    }

整体流程简单来说就是:
1.在启动过程中扫描ExceptionHandlerExceptionResolver接口的实现和被@ControllerAdvice注解的Bean,创建ExceptioResolver对象。
2.捕获到controller中某方法的异常,遍历所有的ExceptioResolver,根据出现异常的方法handler和exception从异常处理Bean的缓存中找到相对应的methodResolver,解析出与(handler和exception)相对应的异常处理方法,并调用该方法对象完成异常处理。
参考:
SpringMVC解读系列

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,884评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,755评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,369评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,799评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,910评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,096评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,159评论 3 411
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,917评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,360评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,673评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,814评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,509评论 4 334
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,156评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,882评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,123评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,641评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,728评论 2 351

推荐阅读更多精彩内容