三、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)。

posted @ 2020-06-29 16:27  词汇族  阅读(207)  评论(0编辑  收藏  举报