zoukankan      html  css  js  c++  java
  • 三、SpringMVC终结篇(补充中...)

    一、基本流程与总览

    《Spring in Action》上给了一张 Spring MVC 最最基本大致处理流程图
    在这里插入图片描述

    解释:

    ① DispatcherServlet 是 SpringMVC 中的核心控制器,负责接收 Request 并将 Request 转发给对应的处理组件

    ② HanlerMapping 是 SpringMVC 中 完 成 url 到 Controller 映 射 的 组 件 。DispatcherServlet 接 收 Request, 然 后 从HandlerMapping 查 找 处 理 Request 的Controller

    ③ Controller 处理 Request,并返回 ModelAndView 对象,Controller 是 SpringMVC中负责处理 Request 的组件,ModelAndView 是封装结果视图的组件

    ④、⑤、⑥视图解析器解析 ModelAndView 对象并返回对应的视图给客户端

    根据 Spring MVC 工作机制,从三个部分来分析 Spring MVC 的源代码。
    一,初始化。包括ApplicationContext 初始化时用 Map 保存所有 url 和 Controller 类的对应关系;
    二,查找。根据请求 url 找到对应的 Controller,并从 Controller 中找到处理请求的方法;
    三,处理返回。Request 参数绑定到方法的形参,执行方法处理请求,并返回结果视图。

    附上全文源码神图一张

    在这里插入图片描述

    二、初始化

    打开DispatcherServlet类继承图

    img

    DispatcherServlet继承自HttpServlet,它的本质就是一个Servlet,这就是为什么需要在web.xml通过url-mapping为DispatcherServlet配置映射请求的原因。

    Servlet初始化必然是看init方法,在父类HttpServletBean中

    父类HttpServletBean的init方法

    	public final void init() throws ServletException {
    		// Set bean properties from init parameters.
    		PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
    		if (!pvs.isEmpty()) {
    			try {
    				BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
    				ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
    				bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));
    				initBeanWrapper(bw);
    				bw.setPropertyValues(pvs, true);
    			}
    			catch (BeansException ex) {
    				//....
    			}
    		}
    		// Let subclasses do whatever initialization they like.
    		initServletBean();
    	}
    

    创建了ServletConfigPropertyValues,负责取到web.xml中contextConfigLocation,并addPropertyValue(),在PropertyValues可以看到取到的值

    还用this创建了BeanWrapper,一个实体包装类,简单地说,BeanWrapper提供分析和操作JavaBean的方案,如值的set/get方法、描述的set/get方法以及属性的可读可写性

    ResourceLoader读取到servletContext和classLoader,servletContext装载了我们刚才的dispatcher-servlet.xml,classLoader找到我们的字节码文件并追踪到我们的jar包路径,还有很多属性不一一介绍

    最后关键是initServletBean,在FrameworkServlet中

    FrameworkServlet的initServletBean

    核心代码:

    		if (wac == null) {
    			// No context instance is defined for this servlet -> create a local one
    			wac = createWebApplicationContext(rootContext);
    		}
    
    		if (!this.refreshEventReceived) {
    			// Either the context is not a ConfigurableApplicationContext with refresh
    			// support or the context injected at construction time had already been
    			// refreshed -> trigger initial onRefresh manually here.
    			onRefresh(wac);
    		}
    

    主要就是获取初始化 IOC 容器,最终会调用 refresh()方法 ,refresh流程就不说了,然后会调用onRefresh,在DisptcherServlet 中实现了

    DisptcherServlet的onRefresh

    	@Override
    	protected void onRefresh(ApplicationContext context) {
    		initStrategies(context);
    	}
    
    	//初始化策略
    	protected void initStrategies(ApplicationContext context) {
            //多文件上传的组件
            initMultipartResolver(context);
            //初始化本地语言环境
            initLocaleResolver(context);
            //初始化模板处理器
            initThemeResolver(context);
            //handlerMapping
            initHandlerMappings(context);
            //初始化参数适配器
            initHandlerAdapters(context);
            //初始化异常拦截器
            initHandlerExceptionResolvers(context);
            //初始化视图预处理器
            initRequestToViewNameTranslator(context);
            //初始化视图转换器
            initViewResolvers(context);
            //FlashMap 管理器
            initFlashMapManager(context);
    	}
    

    到这一步就完成了Spring MVC的九大组件(九大组件最后附上)的初始化,其中就包含了url和Controller的 关 系 建 立

    Url与Controller关系映射RequestMappingHandlerMapping

    目前主流的三种mapping 如下:

    1.SimpleUrlHandlerMapping:基于手动配置 url 与control 映射

    2.BeanNameUrlHandlerMapping: 基于ioc name 中已 "/" 开头的Bean时行 注册至映射.

    3.RequestMappingHandlerMapping:基于@RequestMapping注解配置对应映射

    现在基本都是用的@RequestMapping,对应处理类是RequestMappingHandlerMapping

    在这里插入图片描述

    1.父类实现了InitializingBean接口,导致创建RequestMappingHandlerMapping到容器回调初始化方法afterPropertiesSet(populateBean之后有个initializeBean)

    2.afterPropertiesSet中调用了initHandlerMethods解析,这个就是解析url与controller映射

    3.initHandlerMethods=》processCandidateBean=》detectHandlerMethods (调用链),逻辑依次

    3.1基本获取容器所有Bean,

    3.2筛选是否isHandler(根据是否有Controller和RequestMapping注解),

    3.3注册到MappingRegistry(运用读写锁,加到mappingLookup、urlLookup等等,都是map,第一个路径与method关系,第二个单路径(可以正则))

    this.urlLookup.add(url, mapping);//kv:字符串、RequestMappingInfo数组(更详细的请求信息包括get post各种)

    this.mappingLookup.put(mapping, handlerMethod);//kv: RequestMappingInfo、处理方法handlerMethod

    三、查找处理方法handlerMethod

    DispatcherServlet是个Servlet 核心处理方法即是doService() 开始,主要逻辑是doDispatch

    /**
         * //处理实际调度处理器
         * //处理程序将通过按顺序的servlet的处理器映射器获得。
         * //处理器适配器将通过查询servlet的安装的处理器适配器来获得
         * //找到支持处理程序类的第一个。
         * //所有HTTP方法都由此方法处理。 这取决于处理器适配器或处理程序
         * //自己决定哪些方法是可以接受的。
         * @param  request//请求当前HTTP请求
         * @param response//响应当前的HTTP响应
         * @throws Exception //任何类型的处理失败的例外
         */
        protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
                 HttpServletRequest processedRequest = request;  //processedRequest是经过checkMultipart方法处理过的request请求
            HandlerExecutionChain mappedHandler = null;
            boolean multipartRequestParsed = false;
    
            WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
    
            try {
                ModelAndView mv = null;
                Exception dispatchException = null;
    
                try {          
                    //1、文件上传解析,如果请求类型是multipart将通过MultipartResolver进行文件上传解析;  
                    processedRequest = checkMultipart(request);
                    multipartRequestParsed = (processedRequest != request);
              //2.通过HandlerMapping,将请求映射到处理器(返回一个HandlerExecutionChain,它包括一个处理器、多个HandlerInterceptor拦截器);
                    // 确定当前请求的处理程序。
                    mappedHandler = getHandler(processedRequest);      //解析第一个方法
                    if (mappedHandler == null || mappedHandler.getHandler() == null) {
                        noHandlerFound(processedRequest, response);
                        return;
                    }
              //3、通过HandlerAdapter支持多种类型的处理器(HandlerExecutionChain中的处理器);  
                    // 确定当前请求的处理程序适配器。
                    HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());    //解析第二个方法
    
                    // 如果处理程序支持,则处理最后修改的头文件。
                    String method = request.getMethod();    //得到当前的http方法。  
                    boolean isGet = "GET".equals(method);
                    if (isGet || "HEAD".equals(method)) {    //处理http的head方法。这种方法应该很少用  
                        long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
                        if (logger.isDebugEnabled()) {
                            logger.debug("Last-Modified value for [" + getRequestUri(request) + "] is: " + lastModified);
                        }
                        if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
                            return;
                        }
                    }
              //4.1调用HandlerExecutionChain的interceptor  
                    if (!mappedHandler.applyPreHandle(processedRequest, response)) {
                        return;
                    }
              //4.2执行解析handler中的args,调用(invoke) controller的方法。得到视图  
                    // Actually invoke the handler.  实际上调用处理程序。
                    mv = ha.handle(processedRequest, response, mappedHandler.getHandler());    //解析第三个方法
    
                    if (asyncManager.isConcurrentHandlingStarted()) {
                        return;
                    }
              //4.3调用HandlerExecutionChain的interceptor  
                    applyDefaultViewName(processedRequest, mv);
                    mappedHandler.applyPostHandle(processedRequest, response, mv);
                }
                catch (Exception ex) {
                    dispatchException = ex;
                }  
                //5.解析视图、处理异常  
                processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
            }
            catch (Error err) {
                //......
            }
        }
    

    其中getHandler(processedRequest)方法实际上就是从 HandlerMapping 中找到 request url 和Controller 的对应关系。也就是 Map<url,Controller>,之前初始化保存的映射关系,根据request url找到对应controller的方法

    四、handle开始处理

    dispatch后面就是找到处理器对应正确的适配器、执行拦截器pre方法,再就是真正执行,通过适配器的handle方法,内部反射获取处理器方法上的注解和参数,解析方法和参数上的注解,然后反射调用方法获取 ModelAndView 结果视图 。

    最后一步:

     mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
    

    实际上拿到的适配器是RequestMappingHandlerAdapter ,打开handle方法源码:

    public final ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler){
       return handleInternal(request, response, (HandlerMethod) handler);
    }
    

    核心处理逻辑在适配器的handleInternal=》invokeHandlerMethod=》invokeAndHandle=》invokeForRequest方法中:

    public Object invokeForRequest(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
          Object... providedArgs) throws Exception {
    
       Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
       if (logger.isTraceEnabled()) {
          logger.trace("Arguments: " + Arrays.toString(args));
       }
       return doInvoke(args);
    }
    

    首先,完成 Request 中的参数和方法参数上数据的绑定。Spring MVC 中提供两种 Request 参数到方法中参数的绑
    定方式:
    1、通过注解进行绑定,@RequestParam。
    2、通过参数名称进行绑定。
    使用注解进行绑定,我们只要在方法参数前面声明@RequestParam("name"),就可以将 request 中参数 name 的值绑定到方法的该参数上。使用参数名称进行绑定的前提是必须要获取方法中参数的名称,Java 反射只提供了获取方法的参数的类型,并没有提供获取参数名称的方法。SpringMVC 解决这个问题的方法是用 asm 框架读取字节码文件,来获取方法的参数名称。asm 框架是一个字节码操作框架 .

    最后,运用反射把绑定好的参数,传进去调用Controller的方法了doInvoke(args)。

  • 相关阅读:
    Django模板语言之组合搜索
    爬虫
    模版语言 实现瀑布流页面
    JQuery实现瀑布流页面
    HTML中 .clearfix:after的使用
    Java8新特性——StreamAPI(一)
    Java for循环和foreach循环的性能比较
    使用java8的lambda将list转为map(转)
    取得当天的零点
    【mysql】Date和String的互相转换(DATE_FORMAT & STR_TO_DATE)
  • 原文地址:https://www.cnblogs.com/chz-blogs/p/13208625.html
Copyright © 2011-2022 走看看