SpringMVC

1.MVC是什么?

 

M是Model,V是View,C是Controller。

View很好理解,就是用户面对的视图。

Model可以理解成业务层和持久层。

Controller接收用户输入的请求数据,调用Model,返回View。

2.SpringMVC处理请求的流程

要点:

1 DispatcherServlet:称为中央调度器。在Tomcat服务器启动时,会解析web.xml文件,实例化DispatcherServlet对象。在DispatcherServlet对象的的init()方法中,根据指定的bean.xml文件,创建spring容器,创建controller,service,dao等bean对象。

2 关于子父容器的概念:DispatcherServlet对象创建时会在对象内部创建一个spring容器。如果我们有多个DispatcherServlet对象呢?理所当然会有多个spring容器,每个spring容器归属于独立的DispatcherServlet对象。但如果这样的话,多个spring容器内会存在冗余的对象。这样是对资源的浪费,能否有一种解决办法能解决冗余。答案肯定是有的,就是创建一个父容器,子容器共享父容器内的对象。利用ContextLoaderListener实现。ContextLoaderListener监听器在Tomcat启动时读取contextConfigLocation对应的路径创建一个父容器。之后我们创建的DispatcherServlet内的容器会通过setParent()方法自动设置父子关系,框架内部实现。事实上多个DispatcherServlet其实也没什么用处,一个其实够用了。如果只有一个DispatcherServlet那么也没有必要有父子容器。

3 DispatcherServlet如何工作的?DispatcherServlet在创建时需要指定他将要识别的uri,可以用通配符*。如"/*/*","/app/*"等。请求到达时,根据uri,传给DispatcherServlet,DispatcherServlet交给HandlerMapping。

4 Handler处理器(controller)的表现形式其实不只一种,我们最常用的方法就是使用@RequestMapping注解声明的handler。还可以实现Controller接口,实现HttpRequestHandler接口等。不同的表现形式处理请求的方式是不一样的,使用注解声明的handler最小单元是方法,而实现接口的形式的最小单元是类,即一个类只能表示一个处理器,类似于Servlet。正因为handler有多种表现形式,所以我们需要多中HandlerMapping实现类来处理多种handler表现形式,也应该有多种HandlerAdapter,这种关系是一对一的。

5 HandlerMapping处理映射器的作用是根据uri找到对应的handler对象,然后返回HandlerExecutionChain处理执行链对象(handler对象+对应的拦截器集合+一个index)。注解式handler使用的是RequestMappingHandlerMapping对象。springmvc在启动时框架内部就为我们创建了多种HandlerMapping实现类对象。

6 明明找到了handler对象,直接执行方法就好了,为什么还需要HandlerAdapter?还是因为Handler有多种表现形式,不同表现形式执行的方式都是不一样的,所以需要找到能执行当前表现形式的适配器,才能执行handler。springmvc在启动的时候框架内部就帮我们创建了多种HandlerAdapter实现类对象,每种表现形式都对应自己的适配器对象。注解方式创建的handler使用的是RequestMappingHandlerAdapter。

7 HandlerAdapter找到对应的适配器后,执行handler,返回ModelAndView。

8 返回mv之后判断是否出现异常,有异常通过异常处理解决。

3. @RequestMapping的使用

1.用在类上。value属性表示模块名称

2.用在方法上。value属性表示处理器映射的路径

4.注解式处理器接收请求参数

1.HttpServletRequest ,HttpServletResponse , HttpSession ,请求中所携带的请求参数是自动传入的。

2.逐个接收参数。要求:保证处理器方法的形参名和请求参数名一一对应。

3.逐个接收参数2。请求参数名和形参名不一致时可以用@RequestParam修正。

4.java对象接收参数。要求:对象的属性名和请求参数名相同的才能赋值。

5.@RequestBody。用来处理请求体中的数据,可以将请求体中的数据通过合适的HttpMessageConverter实现类转成java对象。

5.注解式处理器返回值

1.String。表示返回的是视图名,多数时候与视图解析器配合使用。

2.void。表示无返回值。

3.ModelAndView。Model和View组成。Model表示数据,往Model中加入数据等于往request作用域中加入数据,View是视图对象。

4.返回对象。

   4.1 springmvc在创建容器的时候会根据默认策略创建多个HttpMessageConverter实现类的对象。其中包含StringHttpMessageConverter(返回字符串),ByteArrayHttpMessageConverter(返回byte数组)等。

   4.2 在互联网通宵中我们经常需要使用json来进行通信,所以我们需要返回json。如何做到?1.加入jackson依赖 2.加入mvc注解驱动<mvc:annotation-driven/>。3.方法中加上@ResponseBody注解。加入注解驱动后,springmvc会在初始化容器过程中为我们创建MappingJackson2HttpMessageConverter对象,这样对象是用来将java对象转成json写到响应体中的。

   4.3 HttpMessageConverter接口定义了canRead read canWrite write等方法。

canRead 和 canWrite 的判断逻辑是什么呢?

客户端 Request Header 中设置好 Content-Type(传入的数据格式)和Accept(接收的数据格式),根据配置好的MessageConverter来判断是否 canRead 或 canWrite,然后决定 response.body 的 Content-Type 的第一要素是对应的request.headers.Accept 属性的值,又叫做 MediaType。如果服务端支持这个 Accept,那么应该按照这个 Accept 来确定返回response.body 对应的格式,同时把 response.headers.Content-Type 设置成自己支持的符合那个 Accept 的 MediaType

   4.4 返回对象给响应体时,遍历springmvc容器中的全部HttpMessageConverter实现类,调用他们的canWrite方法,判断能不能操作这种数据。如果返回true就用这个类的write方法将java对象以对应的形式输出到响应体中。请求体中的参数转为java对象的过程。

将各种常用 HttpMessageConverter 支持的MediaType 和 JavaType 以及对应关系总结在此处:

类名支持的JavaType支持的MediaType
ByteArrayHttpMessageConverterbyte[]application/octet-stream, */*
StringHttpMessageConverterStringtext/plain, */*
MappingJackson2HttpMessageConverterObjectapplication/json, application/*+json
AllEncompassingFormHttpMessageConverterMap<K, List<?>>application/x-www-form-urlencoded, multipart/form-data
SourceHttpMessageConverterSourceapplication/xml, text/xml, application/*+xml

6.控制器的异常处理

1.模块内局部异常处理。在处理器类中定义某方法,使用@ExceptionHandler注解(value=异常类.class)表示,当处理器类中的任意处理器抛出对应异常就会执行异常处理方法。

2.全局异常处理。定义一个异常处理类,用@ControllerAdvice标识,类中所有方法都用@ExceptionHandler注解标识。

7.拦截器interceptor

1.如何定义拦截器?

1.1实现HandlerInterceptor接口。接口中定义了三个方法,preHandler,postHandler,afterCompletion。

preHandler表示在handler执行前执行,通常用来做一些预处理,如认证,安全等,preHandler返回值是Boolean类型的,只有返回true时才会执行handler方法,返回false当前请求就被截断了。

postHandler表示在handler执行之后执行,可以对handler处理的ModelAndView做二次处理。

afterCompletion只有在preHandler方法返回true才会执行。

1.2声明拦截器,配置拦截路径

2.多个拦截器的执行顺序?

按照拦截器定义的顺序 preHandler1->preHandler2...->handler...->postHandler2->postHandler1...->afterCompletion2->afterCompletion1。

preHandler返回false之后后面的操作直接中断。

afterCompletion根据preHandler的返回结果来判断是否执行的,返回true就执行,false就不执行。

8.java配置springmvc

定义一个类实现WebMvcConfigurer接口用@Configuration标识,重写里面的方法即可。

9.DispatcherServlet底层是一个Servlet,继承了FrameworkServlet。

FrameworkServlet继承HttpServletBean。 HttpServletBean继承HttpServlet。HttpServlet继承GenericServlet。GenericServlet继承Servlet。

9.DispatcherServlet初始化的过程 

1.DispatcherServlet是一个Servlet,符合Servlet生命周期。创建DispatcherServlet时首先调用init方法初始化。HttpServletBean对init方法进行了重写,DispatcherServlet执行的init其实是继承而来的。在HttpServletBean的init中继续调用FrameworkServlet的initServletBean方法。在initServletBean中调用initWebApplication方法初始化容器。在initWebApplication中调用createWebApplicationContext通过反射创建容器,然后onFresh刷新容器,调用initStrategies进行组件的初始化,最后将容器放入全局作用域。

 

 

 

 

 

 

因为DIspatcherServlet继承了上面全部方法,所以在DispatcherServlet初始化时会执行一遍这些方法

10.DispatcherServlet调用服务的过程

Servlet处理服务的方法是service方法。当然DispatcherServlet也不例外。调用service方法处理请求,然后调用doService方法,最终调用doDispatch方法处理请求。

DispatcherServlet的doDispatcher原码。看注释!

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 mv = null;
                Object dispatchException = null;

                try {
                    processedRequest = this.checkMultipart(request);
                    multipartRequestParsed = processedRequest != request;

                //调用HandlerMapping找到对应的handler对象封装成执行链对象并返回
                    mappedHandler = this.getHandler(processedRequest);

                    if (mappedHandler == null) {
                        this.noHandlerFound(processedRequest, response);
                        return;
                    }

                //获取可执行该handler的HandlerAdapter实现类对象
                    HandlerAdapter ha = this.getHandlerAdapter(mappedHandler.getHandler());
                    String method = request.getMethod();
                    boolean isGet = "GET".equals(method);
                    if (isGet || "HEAD".equals(method)) {
                        long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
                        if ((new ServletWebRequest(request, response)).checkNotModified(lastModified) && isGet) {
                            return;
                        }
                    }

                 //执行拦截器的preHandler方法
                    if (!mappedHandler.applyPreHandle(processedRequest, response)) {
                        return;
                    }
                    
                 //真正执行handler方法。这一步其实做了非常多工作。
                 //这一步其实做了非常多工作。HandlerAdaptr帮我们得到方法形参,执行handler方 //法体内容,处理返回值等等。
                    mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
                    if (asyncManager.isConcurrentHandlingStarted()) {
                        return;
                    }

                    this.applyDefaultViewName(processedRequest, mv);

                  //执行postHandler方法
                    mappedHandler.applyPostHandle(processedRequest, response, mv);
                } catch (Exception var20) {
                    dispatchException = var20;
                } catch (Throwable var21) {
                    dispatchException = new NestedServletException("Handler dispatch failed", var21);
                }
 //最后是对视图的处理和判断mv中是否有异常,有就进行异常处理和afterCompletion方法的执行。
                this.processDispatchResult(processedRequest, response, mappedHandler, mv, (Exception)dispatchException);
            } catch (Exception var22) {
                this.triggerAfterCompletion(processedRequest, response, mappedHandler, var22);
            } catch (Throwable var23) {
                this.triggerAfterCompletion(processedRequest, response, mappedHandler, new NestedServletException("Handler processing failed", var23));
            }

        } finally {
            if (asyncManager.isConcurrentHandlingStarted()) {
                if (mappedHandler != null) {
                    mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
                }
            } else if (multipartRequestParsed) {
                this.cleanupMultipart(processedRequest);
            }

        }
    }

11.几个重要方法的细节

//调用HandlerMapping找到对应的handler对象封装成执行链对象并返回
mappedHandler = this.getHandler(processedRequest);

每个HandlerMapping在所有handler对象创建完成后都将自己管理的handler存入自己身上,RequestMappingHandlerMapping利用map存储用@RequestMapping注解声明的handler,key为请求方式/uri,value就是handler。遍历系统中所有handlerMapping找到对应handler,并且找到拦截该handler的所有interceptor拦截器,将handler,拦截器们,和一个拦截器索引值为-1,封装成handlerExecutionChain对象返回出去。

 //获取可执行该handler的HandlerAdapter实现类对象
HandlerAdapter ha = this.getHandlerAdapter(mappedHandler.getHandler());

遍历系统中的所有HandlerAdapter实现类,挨个判断能不能支持support找到的handler。若能就返回这个HandlerAdapter实现类。以@RequestMapping声明的handler被RequestMappingHandlerAdapter处理。

 //执行拦截器的preHandler方法
 if (!mappedHandler.applyPreHandle(processedRequest, response)) {
                        return;
 }

执行拦截器的preHandler方法。正向执行拦截该handler的所有拦截器的preHandler方法,遍历他们,过程中修改拦截器索引。为true索引就加一。若在过程中有一个返回false,直接根据拦截器索引倒叙执行拦截器们的afterCompletion方法,然后直接返回。

  //真正执行handler方法。这一步其实做了非常多工作。HandlerAdaptr帮我们得到方法形参,执行handler方法体内容,处理返回值等等。
 mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

执行处理器方法首先要解决如何得到参数的问题。springmvc在创建完容器之后会执行一系列初始化策略里面就包括了创建一些参数解析器HandlerMethodArgumentResolver实现类,创建一些返回值处理器HandlerMethodReturnValueHandler实现类等等springmvc运行时需要的组件。参数解析器HandlerMethodArgumentResolver实现类就是帮我们解决handler参数绑定的问题的。为什么会有多个参数解析器实现类呢?因为我们spring团队为了让我们可以写多种形式的参数。所以可以得到一个结论,有多少个参数解析器实现类,就能写多少种形式的参数。处理参数的过程大概是这样的:先获取handler的所有形参,挨个处理每个形参。每个形参都遍历系统中所有的参数解析器,判断支不支持这种形式的参数(判断依据:判断是不是某种类型的参数,判断参数上有没有对应注解)。如果支持则将参数(handler方法一些信息和参数信息)与能处理这种参数的参数解析器放入缓存中。然后就是处理参数了,根据参数从缓存中拿到支持该参数的参数解析器,解析参数。缓存的作用是:下次请求过来,缓存过的参数能直接知道什么参数解析器能处理这个参数。

解析完参数就能真正执行方法体的内容了。是通过反射调用handler方法的。

执行完方法就要处理返回值了。springmvc如何帮我们处理返回值呢?那就是利用返回值处理器HandlerMethodReturnValueHandler实现类。那么我们就可以知道,有多少种返回值处理器,我们就能写多少种被springmvc识别的返回形式 处理返回值:先遍历系统的中所有返回值处理器,判断能不能支持这种返回值形式(判断依据:判断返回值类型,判断返回值上有没有某注解,判断类上有没有某注解等等)。如果支持就用该返回值处理器的返回逻辑处理返回值,封装成ModelAndView返回出去。

*******************************************************************************************************

在前后端分离项目中我们经常需要将对象以application/json格式返回到客户端。我们只需要在返回值上加入@ResponseBody注解就能轻松返回json对象。那么springmvc究竟是如何做到的呢?让来我们深究一下把!

我们直接来到处理返回值的过程,遍历系统中的所有返回值处理器,看谁能支持 例如@ResponseBody Person返回Person对象。找来找去最终找到RequestResponseBodyMethodProcessor类的对象支持处理这种返回值,他的判断依据是返回值或类上有没有@ResponseBody注解。很明显有,所有他支持。接着我们利用RequestResponseBodyMethodProcessor处理返回值。关键的处理过程如下:

1.确定将对象转为什么格式的数据

   -1内容协商,获得客户端期望的MediaTypes媒体类型(MediaType可以理解成application/json,application/xml, text/plain,image/png等等)

     --1springmvc默认的内容协商策略是请求头策略。其实就是当前请求请求头中的accept的属性。

     --2可以看出这种默认的内容协商策略对浏览器来说是不公平的,因为浏览器访问时accept字段都是默认的,不能指定accept,而用postman访问可以指定accept。所以springmvc也有解决这种问题的方法,就是在内容协商中加入参数策略,只需在配置文件中加入简单配置就能实现。何为参数策略,就是在访问时加一个format参数表示期望得到的返回值格式。springmvc为我们创建的参数策略只支持application/json和application/xml两种格式。当我们需要其他格式时需要自己创建参数策略对象,注册参数策略需要我们创建新的内容协商策略,会覆盖springmvc为我们创建的内容协商策略,所以我们还要在我们自己的内容协商策略中手动加入其他策略,如请求头策略。这样之后springmvc的路径参数策略就能识别我们自定义的format,变成客户端期望的MediaType。如浏览器输入uri?format=application/jijisu,期望得到application/jijisu格式数据。当然这只是第一步,内容协商。如果要做到能处理这种格式数据还需要我们添加额外的HttpMessageConverter消息转换器实现类,在getSupportedMediaTypes中返回application/jijisu,重写canWrite,wirte方法。

   -2找到服务端能将这种返回值转成的MediaTypes媒体类型。遍历系统中的所有HttpMessageConverter的canWrite?判断谁能写这种返回值类型。能写就获得他支持的所有MediaTypes(getSupportedMediaTypes)。将全部消息转换器能支持的媒体类型集合在一起。

   -3两个数组进行比较匹配,找出同类项。得到能用的MediaTypes数组

   -4对能用的MediaTypes数组进行排序,选取优先级最高的媒体类型作为这次返回的最终类型。

2.将对象转成确定的格式。再次遍历系统中的所以HttpMessageConverter实现类,根据返回值和最终MediaType判断canWrite?找到后根据各HttpMessageConverter实现类自己的写write逻辑写就完了。

 //执行postHandler方法
mappedHandler.applyPostHandle(processedRequest, response, mv);

根据拦截器索引的值,逆序执行拦截当前handler的拦截器的postHandler方法。

12.springmvc处理服务的最终流程

1.用户请求到达,DispatcherServlet进行捕获

2.根据uri发给HandlerMapping处理,没找到就报404。

3.找到了的话HandlerMapping就把handler和拦截器集合和索引封装起来给DispatcherServlet返回一个HandlerExceuctionChain。

4.DispatcherServlet调用HandlerAdapter找到合适的,能处理这个handler的HandlerAdapter实现类。

5.得到这个HandlerAdapter实现类之后,开始执行拦截器preHandler方法。(正向)

6.HandlerMethodArgumentResolver实现类们处理参数赋值赋值到handler方法的形参上,接着执行真正handler方法。接着HandlerMethodReturnValueHandler对返回值进行处理。

7.返回一个ModelAndView对象给DispatcherServlet。

8.执行postHandler方法(反向)

9.渲染视图。判断ModelAndView中是否有异常,处理异常。执行afterCompletion方法(反向)。

10返回视图,返回数据。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值