zoukankan      html  css  js  c++  java
  • SpringMVC源码学习:容器初始化+MVC初始化+请求分发处理+参数解析+返回值解析+视图解析

    一、前言

    版本:

    springMVC 5.0.2RELEASE

    JDK1.8

    前端控制器的配置:

    web.xml

        <!--配置前端控制器-->
        <servlet>
            <servlet-name>dispatcherServlet</servlet-name>
            <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
            <init-param>
                <param-name>contextConfigLocation</param-name>
                <!--加载类路径下的配置文件-->
                <param-value>classpath:springmvc.xml</param-value>
            </init-param>
            <!--服务器启动时创建对象,值越小,优先级越高,越先创建对象-->
            <load-on-startup>1</load-on-startup>
        </servlet>
    
        <servlet-mapping>
            <servlet-name>dispatcherServlet</servlet-name>
            <!--注意不是/*,而是,因为/*还会拦截*.jsp等请求-->
            <url-pattern>/</url-pattern>
        </servlet-mapping>
    

    springmvc.xml配置

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:mvc="http://www.springframework.org/schema/mvc"
           xmlns:context="http://www.springframework.org/schema/context"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xsi:schemaLocation="
            http://www.springframework.org/schema/beans
            http://www.springframework.org/schema/beans/spring-beans.xsd
            http://www.springframework.org/schema/mvc
            http://www.springframework.org/schema/mvc/spring-mvc.xsd
            http://www.springframework.org/schema/context
            http://www.springframework.org/schema/context/spring-context.xsd">
        <!-- 开启注解扫描 -->
        <context:component-scan base-package="com.smday"/>
    
        <!-- 视图解析器对象 -->
        <bean id="internalResourceViewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
            <property name="prefix" value="/WEB-INF/pages/"/>
            <property name="suffix" value=".jsp"/>
        </bean>
        <!-- 开启SpringMVC框架注解的支持 -->
        <mvc:annotation-driven/>
        <!--放行静态资源-->
        <mvc:default-servlet-handler/>
    
    </beans>
    

    二、初始化

    在这里插入图片描述

    DispatcherServlet的启动与Servlet的启动过程紧密联系,我们通过以上继承图就可以发现。

    1. 容器初始化

    Servlet中定义的init()方法就是其生命周期的初始化方法,接着往下走,GenericServlet并没有给出具体实现,在HttpServletBean中的init()方法给出了具体的实现:

    HttpServletBean.init()方法(忽略日志)

    	@Override
    	public final void init() throws ServletException {
            //根据初始化参数设置bean属性(我们设置了contextConfigLocation,故可以获取)
    		PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
    		if (!pvs.isEmpty()) {
    			try {
                    //包装DispatcherServlet
    				BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
                    //获取资源加载器,用以加载springMVC的配置文件
    				ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
                    //注册一个ResourceEditor
    				bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));
                    //该方法为空实现,可以重写,初始化BeanWrapper
    				initBeanWrapper(bw);
                    //最终将init-param读取的值spirng-mvc.xml存入contextConfigLocation中
    				bw.setPropertyValues(pvs, true);
    			}
    		}
    
    		// 让子类实现初始化
    		initServletBean();
    
    	}
    

    那就来看看FrameworfServlet.initServletBean()干了啥(基本都是日志记录,还有计时,省略了这些部分):

    	/**
    	 * Overridden method of {@link HttpServletBean}, invoked after any bean properties
    	 * have been set. Creates this servlet's WebApplicationContext.
    	 */
    	@Override
    	protected final void initServletBean() throws ServletException {
            //WebApplicationContext的初始化
            this.webApplicationContext = initWebApplicationContext();
            //也是空实现,允许子类自定义
            initFrameworkServlet();
    	}
    

    所以重头戏就在initWebApplicationContext方法上,我们可以先来看看执行后的效果:

    在这里插入图片描述

    可以看到springMVC九大组件被赋值,除此之外webApplicationContext也已被赋值。

    我们再来看看源码,看看其内部具体实现:FrameworkServlet.initWebApplicationContext()

    protected WebApplicationContext initWebApplicationContext() {
        //根容器查找
        WebApplicationContext rootContext =
            WebApplicationContextUtils.getWebApplicationContext(getServletContext());
        WebApplicationContext wac = null;
        if (this.webApplicationContext != null) {
            //在构建时注入了DispatcherServlet并且webApplicationContext已经存在->直接使用
            wac = this.webApplicationContext;
            if (wac instanceof ConfigurableWebApplicationContext) {
                ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
                if (!cwac.isActive()) {
                    //如果context还没有refresh-->进行设置父级context以及application context的id等等操作
                    if (cwac.getParent() == null) {
                        //在没有显式父级的情况下注入了context实例->将根应用程序上下文设置为父级
                        cwac.setParent(rootContext);
                    }
                    configureAndRefreshWebApplicationContext(cwac);
                }
            }
        }
        if (wac == null) {
    		//在构造时未注入任何上下文实例-->从ServletContext中查询
            wac = findWebApplicationContext();
        }
        if (wac == null) {
            // ServletContext中没有-->就创建一个被本地的
            wac = createWebApplicationContext(rootContext);
        }
        if (!this.refreshEventReceived) {
            //如果context不支持refresh或者在初始化的时候已经refresh-->就手动触发onfresh
            onRefresh(wac);
        }
        //把当前建立的上下文存入ServletContext中,使用的属性名和当前Servlet名相关
        if (this.publishContext) {
            // 将上下文发布为servlet上下文属性
            String attrName = getServletContextAttributeName();
            getServletContext().setAttribute(attrName, wac);
        }
        return wac;
    }
    

    根容器查找的方法

    WebApplicationContext rootContext =
        WebApplicationContextUtils.getWebApplicationContext(getServletContext());
    

    WebApplicationContextUtils.getWebApplicationContext

    //SpringMVC支持Spring容器与Web容易同时存在,并且Spring容器视作根容器,通常由ContextLoaderListener进行加载。
    @Nullable
    public static WebApplicationContext getWebApplicationContext(ServletContext sc) {
        //String ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE = WebApplicationContext.class.getName() + ".ROOT"
        return getWebApplicationContext(sc, WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);
    }
    
    @Nullable
    public static WebApplicationContext getWebApplicationContext(ServletContext sc, String attrName) {
    	//根据ServletName.ROOT为键查找值
        Object attr = sc.getAttribute(attrName);
        if (attr == null) {
            return null;
        return (WebApplicationContext) attr;
    }
    

    Spring容器和Web容器如果同时存在,需要使用ContextLoaderListener加载Spring的配置,且它会以key为

    WebApplicationContext.class.getName() + ".ROOT存到ServletContext中。

    容器创建的方法

    构建的时候没有任何Context实例注入,且ServletContext中也没有找到WebApplicationContext,此时就会创建一个local Context,这个方法允许显式传入父级容器作为参数。

    protected WebApplicationContext createWebApplicationContext(@Nullable ApplicationContext parent) {
        //默认:DEFAULT_CONTEXT_CLASS = XmlWebApplicationContext.class;可以在初始化参数中指定contextClass
        Class<?> contextClass = getContextClass();
        if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
            throw new ApplicationContextException(
                "Fatal initialization error in servlet with name '" + getServletName() +
                "': custom WebApplicationContext class [" + contextClass.getName() +
                "] is not of type ConfigurableWebApplicationContext");
        }
        //获取ConfigurableWebApplicationContext对象
        ConfigurableWebApplicationContext wac =
            (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
    
        wac.setEnvironment(getEnvironment());
        wac.setParent(parent);
        String configLocation = getContextConfigLocation();
        if (configLocation != null) {
            wac.setConfigLocation(configLocation);
        }
        configureAndRefreshWebApplicationContext(wac);
    
        return wac;
    }
    

    我们可以发现:在这个过程中,Web容器的IoC容器被建立,也就是XmlWebApplicationContext,,从而在web容器中建立起整个spring应用。

    configureAndRefreshWebApplicationContext(wac);

    protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) {
        //省略给ConfigurableWebApplicationContext对象设置一些值...
        //每次context refresh,都会调用initPropertySources
        ConfigurableEnvironment env = wac.getEnvironment();
        if (env instanceof ConfigurableWebEnvironment) {
            ((ConfigurableWebEnvironment) env).initPropertySources(getServletContext(), getServletConfig());
        }
        postProcessWebApplicationContext(wac);
        applyInitializers(wac);
        //初始化webApplication容器,重启
        wac.refresh();
    }
    

    加载配置文件信息

    其实也就是refresh()这个关键方法,之前了解过spring容器的初始化的过程,对这一步应该相当熟悉,还是分为三步:

    • BeanDefinition的Resource的定位,我们这定位到了classpath:springmvc.xml。

    在这里插入图片描述

    • beanDefinition的载入过程,springMVC做了一些改变,比如定义了针对mvc的命名空间解析MvcNamespaceHandler。

    • 接着是beanDefinition在IoC中的注册,也就是把beanName:beanDefinition以键值对的形式存入beandefinitionMap中。

    2. MVC的初始化

    MVC的初始化在DispatcherServlet的initStratefies方法中执行,通过方法名,我们就可以得出结论,就是在这进行了对九大组件的初始化,其实基本上都是从IoC容器中获取对象:

    	protected void initStrategies(ApplicationContext context) {
            //文件上传解析器
    		initMultipartResolver(context);
            //区域信息解析器,与国际化相关
    		initLocaleResolver(context);
            //主题解析器
    		initThemeResolver(context);
            //handler映射信息解析
    		initHandlerMappings(context);
            //handler的适配器
    		initHandlerAdapters(context);
            //handler异常解析器
    		initHandlerExceptionResolvers(context);
            //视图名转换器
    		initRequestToViewNameTranslator(context);
            //视图解析器
    		initViewResolvers(context);
            //flashMap管理器
    		initFlashMapManager(context);
    	}
    

    文件上传解析器

    private void initMultipartResolver(ApplicationContext context) {
        try {
            this.multipartResolver = context.getBean(MULTIPART_RESOLVER_BEAN_NAME, MultipartResolver.class);
        }
        catch (NoSuchBeanDefinitionException ex) {
            // 默认是没有配置multipartResolver的.
            this.multipartResolver = null;
        }
    }
    

    配置文件上传解析器也很简单,只需要在容器中注册MultipartResolver即可开启文件上传功能。

    区域信息解析器

    private void initLocaleResolver(ApplicationContext context) {
        try {
            this.localeResolver = context.getBean(LOCALE_RESOLVER_BEAN_NAME, LocaleResolver.class);
    
        }
        catch (NoSuchBeanDefinitionException ex) {
            // 使用默认策略,利用反射创建对象
            this.localeResolver = getDefaultStrategy(context, LocaleResolver.class);
        }
    }
    

    org.springframework.web.servlet.DispatcherServlet同级目录下的DispatcherServlet.properties文件中规定了几大组件初始化的默认策略。

    在这里插入图片描述

    handler映射信息解析

    handlerMappings存在的意义在于为HTTP请求找到对应的控制器Controller。

    	private void initHandlerMappings(ApplicationContext context) {
    		this.handlerMappings = null;
    		//从所有的IoC容器中导入HandlerMappings,包括其双亲上下文
    		if (this.detectAllHandlerMappings) {
    			Map<String, HandlerMapping> matchingBeans =
    					BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);
    			if (!matchingBeans.isEmpty()) {
    				this.handlerMappings = new ArrayList<>(matchingBeans.values());
    				// We keep HandlerMappings in sorted order.
    				AnnotationAwareOrderComparator.sort(this.handlerMappings);
    			}
    		}
    		else {
    			try {
                    //尝试从容器中获取
    				HandlerMapping hm = context.getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class);
                    
    				this.handlerMappings = Collections.singletonList(hm);
    			}
    			catch (NoSuchBeanDefinitionException ex) {
    				// Ignore, we'll add a default HandlerMapping later.
    			}
    		}
            //保证至少有一个handlerMapping
    		if (this.handlerMappings == null) {
    			this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
    		}
    	}
    

    接下来几个操作都差不多,就不赘述了。

    总的来说,MVC初始化的过程建立在IoC容器初始化之后,毕竟要从容器中取出这些组件对象。

    3. HandlerMapping的实现原理

    HandlerExecutionChain

    HandlerMapping在SpringMVC扮演着相当重要的角色,我们说,它可以为HTTP请求找到 对应的Controller控制器,于是,我们来好好研究一下,这里面到底藏着什么玩意。

    在这里插入图片描述

    HandlerMapping是一个接口,其中包含一个getHandler方法,能够通过该方法获得与HTTP请求对应的handlerExecutionChain,而这个handlerExecutionChain对象中持有handler和interceptorList,以及和设置拦截器相关的方法。可以判断是同通过这些配置的拦截器对handler对象提供的功能进行了一波增强。
    在这里插入图片描述

    RequestMappingHandlerMapping

    我们以其中一个HandlerMapping作为例子解析一下,我们关注一下:

    protected void initHandlerMethods() {
    	//获取所有上下文中的beanName
        String[] beanNames = (this.detectHandlerMethodsInAncestorContexts ?
                              BeanFactoryUtils.beanNamesForTypeIncludingAncestors(obtainApplicationContext(), Object.class) :
                              obtainApplicationContext().getBeanNamesForType(Object.class));
    
        for (String beanName : beanNames) {
            if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) {
                Class<?> beanType = null;
                //得到对应beanName的Class
                beanType = obtainApplicationContext().getType(beanName);
                //判断是否为控制器类
                if (beanType != null && isHandler(beanType)) {
                    //对控制器中的方法进行处理
                    detectHandlerMethods(beanName);
                }
            }
        }
        handlerMethodsInitialized(getHandlerMethods());
    }
    

    isHandler方法:判断该类是否存在@Controller注解或者@RequestMapping注解

    	@Override
    	protected boolean isHandler(Class<?> beanType) {
    		return (AnnotatedElementUtils.hasAnnotation(beanType, Controller.class) ||
    				AnnotatedElementUtils.hasAnnotation(beanType, RequestMapping.class));
    	}
    

    detectHandlerMethods方法:

    protected void detectHandlerMethods(final Object handler) {
        //获取到控制器的类型
        Class<?> handlerType = (handler instanceof String ?
                                obtainApplicationContext().getType((String) handler) : handler.getClass());
        if (handlerType != null) {
            //对类型再次进行处理,主要是针对cglib
            final Class<?> userType = ClassUtils.getUserClass(handlerType);
            //遍历方法,对注解中的信息进行处理,得到RequestMappingInfo对象,得到methods数组
            Map<Method, T> methods = MethodIntrospector.selectMethods(userType,
                                                                      (MethodIntrospector.MetadataLookup<T>) method -> {
          return getMappingForMethod(method, userType);
             });
            //遍历methods[Method,{path}]
            for (Map.Entry<Method, T> entry : methods.entrySet()) {
                //对方法的可访问性进行校验,如private,static,SpringProxy
                Method invocableMethod = AopUtils.selectInvocableMethod(entry.getKey(), userType);
                //获取最终请求路径
                T mapping = entry.getValue();
                //注册
                registerHandlerMethod(handler, invocableMethod, mapping);
            }
        }
    }
    

    mapping对象的属性:

    在这里插入图片描述

    methods对象中存储的元素:

    在这里插入图片描述

    注册方法在AbstractHandlerMethodMapping中实现:

    public void register(T mapping, Object handler, Method method) {
        this.readWriteLock.writeLock().lock();
        try {
            //处理方法的对象
            HandlerMethod handlerMethod = createHandlerMethod(handler, method);
            //判断映射的唯一性
            assertUniqueMethodMapping(handlerMethod, mapping);
    		//将mapping信息和控制器方法对应
            this.mappingLookup.put(mapping, handlerMethod);
    		//将path与处理器映射(一个方法可能可以处理多个url)
            List<String> directUrls = getDirectUrls(mapping);
            for (String url : directUrls) {
                this.urlLookup.add(url, mapping);
            }
    		//控制器名的大写英文缩写#方法名
            String name = null;
            if (getNamingStrategy() != null) {
                name = getNamingStrategy().getName(handlerMethod, mapping);
                addMappingName(name, handlerMethod);
            }
    		//跨域请求相关配置
            CorsConfiguration corsConfig = initCorsConfiguration(handler, method, mapping);
            if (corsConfig != null) {
                this.corsLookup.put(handlerMethod, corsConfig);
            }
    		//将所有配置统一注册到registry中
            this.registry.put(mapping, new MappingRegistration<>(mapping, handlerMethod, directUrls, name));
        }
        finally {
            this.readWriteLock.writeLock().unlock();
        }
    }
    

    至此,所有的Controller,以及其中标注了@RequestMapping注解的方法,都被一一解析,注册进HashMap中,于是,对应请求路径与处理方法就一一匹配,此时HandlerMapping也初始化完成。

    三、请求响应处理

    1. 请求分发

    我们需要明确的一个点是,请求过来的时候,最先执行的地方在哪,是Servlet的service方法,我们只需要看看该方法在子类中的一个实现即可:

    FrameworkServlet重写的service方法:

    	@Override
    	protected void service(HttpServletRequest request, HttpServletResponse response)
    			throws ServletException, IOException {
    		//获取请求方法
    		HttpMethod httpMethod = HttpMethod.resolve(request.getMethod());
            //拦截PATCH请求
    		if (HttpMethod.PATCH == httpMethod || httpMethod == null) {
    			processRequest(request, response);
    		}
    		else {
    			super.service(request, response);
    		}
    	}
    

    其实最后都是调用了processRequest方法,该方法中又调用了真正的doService()方法,其中细节先不探讨,我们直奔,看看DispatcherServlet的这个doService干了哪些事情(DispatcherServlet这个类确实是核心中的核心,既建立了IoC容器,又负责请求分发):

    	@Override
    	protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
    	//忽略一大串前期准备,使其能够处理view 对象
        //接着进入真正的分发
    	doDispatch(request, response);
    	}
    

    doService:

    protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
        HttpServletRequest processedRequest = request;
        HandlerExecutionChain mappedHandler = null;
        boolean multipartRequestParsed = false;
    
        WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
    
        try {
            ModelAndView mv = null;
            Exception dispatchException = null;
    
            try {
                //如果是文件上传请求,对request进行包装,如果不是就原样返回
                processedRequest = checkMultipart(request);
                //文件上传请求标识符
                multipartRequestParsed = (processedRequest != request);
    
                //为当前的request请求寻找合适的handler
                mappedHandler = getHandler(processedRequest);
                //如果没有handler可以处理该请求,就跳转到错误页面
                if (mappedHandler == null) {
                    noHandlerFound(processedRequest, response);
                    return;
                }
                //为当前的request请求寻找合适的adapter
                HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
    
                String method = request.getMethod();
                boolean isGet = "GET".equals(method);
                if (isGet || "HEAD".equals(method)) {
                    //判断是否支持getLastModified,如果不支持,返回-1
                    long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
                    if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
                        return;
                    }
                }
                //执行注册拦截器的preHandle方法
                if (!mappedHandler.applyPreHandle(processedRequest, response)) {
                    return;
                }
                // 真正处理请求的方法
                mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
    
                if (asyncManager.isConcurrentHandlingStarted()) {
                    return;
                }
    			//如果mv!=null&&mv对象没有View,则为mv对象设置一个默认的ViewName
                applyDefaultViewName(processedRequest, mv);
                //执行注册拦截器的applyPostHandle方法
                mappedHandler.applyPostHandle(processedRequest, response, mv);
            }
            //进行视图解析和渲染
            processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
        
    }
    

    需要注意的是,mappedHandler和HandlerAdapter都是从对应的集合中遍历查找,一旦找到可以执行的目标,就会停止查找,我们也可以人为定义优先级,决定他们之间的次序。

    2. 请求处理

    RequestMappingHandlerAdapter的handleInternal方法,含有真正处理请求的逻辑。

    @Override
    protected ModelAndView handleInternal(HttpServletRequest request,
                                          HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
        //定义返回值变量
        ModelAndView mav;
        //对请求进行检查 supportedMethods和requireSession
        checkRequest(request);
    
        // 看看synchronizeOnSession是否开启,默认为false
        if (this.synchronizeOnSession) {
            HttpSession session = request.getSession(false);
            //Httpsession可用
            if (session != null) {
                Object mutex = WebUtils.getSessionMutex(session);
                //加锁,所有请求串行化
                synchronized (mutex) {
                    mav = invokeHandlerMethod(request, response, handlerMethod);
                }
            }
            else {
                // 没有可用的Httpsession -> 没必要上锁
                mav = invokeHandlerMethod(request, response, handlerMethod);
            }
        }
        else {
            // 正常调用处理方法
            mav = invokeHandlerMethod(request, response, handlerMethod);
        }
        //检查响应头是否包含Cache-Control
        if (!response.containsHeader(HEADER_CACHE_CONTROL)) {
            if (getSessionAttributesHandler(handlerMethod).hasSessionAttributes()) {
                applyCacheSeconds(response, this.cacheSecondsForSessionAttributeHandlers);
            }
            else {
                prepareResponse(response);
            }
        }
    
        return mav;
    }
    

    RequestMappingHandlerAdapter的invokeHandlerMethod方法,真正返回mv。

    @Nullable
    protected ModelAndView invokeHandlerMethod(HttpServletRequest request,
                                               HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
        //对HttpServletRequest进行包装,产生ServletWebRequest处理web的request对象
        ServletWebRequest webRequest = new ServletWebRequest(request, response);
        try {
            //创建WebDataBinder对象的工厂
            WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod);
            //创建Model对象的工厂
            ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory);
    		//将handlerMethod对象进行包装,创建ServletInvocableHandlerMethod对象
            //向invocableMethod设置相关属性(最后是由invocableMethod对象调用invokeAndHandle方法
            ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);
            if (this.argumentResolvers != null) {
                invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
            }
            if (this.returnValueHandlers != null) {
                invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
            }
            invocableMethod.setDataBinderFactory(binderFactory);
            invocableMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer);
    		//创建ModelAndViewContainer对象,里面存放有向域中存入数据的map
            ModelAndViewContainer mavContainer = new ModelAndViewContainer();
            mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request));
            modelFactory.initModel(webRequest, mavContainer, invocableMethod);
            mavContainer.setIgnoreDefaultModelOnRedirect(this.ignoreDefaultModelOnRedirect);
    
            AsyncWebRequest asyncWebRequest = WebAsyncUtils.createAsyncWebRequest(request, response);
            asyncWebRequest.setTimeout(this.asyncRequestTimeout);
    
            WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
    		//省略异步处理
            //正常调用
            invocableMethod.invokeAndHandle(webRequest, mavContainer);
            if (asyncManager.isConcurrentHandlingStarted()) {
                return null;
            }
    		//获取ModelAndView对象
            return getModelAndView(mavContainer, modelFactory, webRequest);
        }
        finally {
            webRequest.requestCompleted();
        }
    }
    

    ServletInvocableHandlerMethod的invokeAndHandle方法:反射调用方法,得到返回值。

    public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer,Object... providedArgs) throws Exception {
    	//获取参数,通过反射得到返回值
        Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
        //设置响应状态
        setResponseStatus(webRequest);
    	
        if (returnValue == null) {
            if (isRequestNotModified(webRequest) || getResponseStatus() != null || mavContainer.isRequestHandled()) {
                mavContainer.setRequestHandled(true);
                return;
            }
        }
        else if (StringUtils.hasText(getResponseStatusReason())) {
            mavContainer.setRequestHandled(true);
            return;
        }
    
        mavContainer.setRequestHandled(false);
        Assert.state(this.returnValueHandlers != null, "No return value handlers");
        try {
            //处理返回值
            this.returnValueHandlers.handleReturnValue(
                returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
        }
        catch (Exception ex) {
            if (logger.isTraceEnabled()) {
                logger.trace(getReturnValueHandlingErrorMessage("Error handling return value", returnValue), ex);
            }
            throw ex;
        }
    }
    

    参数解析过程

    我们可以知道的是,传递参数时,可以传递Map,基本类型,POJO,ModelMap等参数,解析之后的结果又如何呢?我们以一个具体的例子举例比较容易分析:

        @RequestMapping("/handle03/{id}")
        public String handle03(@PathVariable("id") String sid,
                               Map<String,Object> map){
            System.out.println(sid);
            map.put("msg","你好!");
            return "success";
        }
    
    /**
     * 获取当前请求的方法参数值。
     */
    private Object[] getMethodArgumentValues(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,Object... providedArgs) throws Exception {
        //获取参数对象
        MethodParameter[] parameters = getMethodParameters();
        //创建一个同等大小的数组存储参数值
        Object[] args = new Object[parameters.length];
        for (int i = 0; i < parameters.length; i++) {
            MethodParameter parameter = parameters[i];
            parameter.initParameterNameDiscovery(this.parameterNameDiscoverer);
            args[i] = resolveProvidedArgument(parameter, providedArgs);
            if (args[i] != null) {
                continue;
            }
            if (this.argumentResolvers.supportsParameter(parameter)) {
                //参数处理器处理参数(针对不同类型的参数有不同类型的处理参数的策略)
                args[i] = this.argumentResolvers.resolveArgument(
                    parameter, mavContainer, request, this.dataBinderFactory);
                continue;
            }
            if (args[i] == null) {
                throw new IllegalStateException();
        	}
        return args;
    }
    

    resolveArgument方法:

    @Override
    @Nullable
    public final Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
    	//获取注解的信息
        NamedValueInfo namedValueInfo = getNamedValueInfo(parameter);
        //包装parameter对象
        MethodParameter nestedParameter = parameter.nestedIfOptional();
    	//获取@PathVariable指定的属性名
        Object resolvedName = resolveStringValue(namedValueInfo.name);
        //
        if (resolvedName == null) {
            throw new IllegalArgumentException(
                "Specified name must not resolve to null: [" + namedValueInfo.name + "]");
        }
    	//根据name从url中寻找并获取参数值
        Object arg = resolveName(resolvedName.toString(), nestedParameter, webRequest);
        //没有匹配
        if (arg == null) {
            //如果有default值,则根据该值查找
            if (namedValueInfo.defaultValue != null) {
                arg = resolveStringValue(namedValueInfo.defaultValue);
            }
            //如果required为false,则可以不指定name,但默认为true。
            else if (namedValueInfo.required && !nestedParameter.isOptional()) {
                handleMissingValue(namedValueInfo.name, nestedParameter, webRequest);
            }
            
            arg = handleNullValue(namedValueInfo.name, arg, nestedParameter.getNestedParameterType());
        }
        //虽然匹配,路径中传入的参数如果是“ ”,且有默认的name,则按照默认处理
        else if ("".equals(arg) && namedValueInfo.defaultValue != null) {
            arg = resolveStringValue(namedValueInfo.defaultValue);
        }
    
        if (binderFactory != null) {
            WebDataBinder binder = binderFactory.createBinder(webRequest, null, namedValueInfo.name);
                arg = binder.convertIfNecessary(arg, parameter.getParameterType(), parameter);   
        }
        handleResolvedValue(arg, namedValueInfo.name, parameter, mavContainer, webRequest);
        return arg;
    }
    

    getNameValueInfo方法:

    	private NamedValueInfo getNamedValueInfo(MethodParameter parameter) {
            //从缓存中获取
    		NamedValueInfo namedValueInfo = this.namedValueInfoCache.get(parameter);
    		if (namedValueInfo == null) {
                //创建一个namedValueInfo对象
    			namedValueInfo = createNamedValueInfo(parameter);
                //如果没有在注解中指定属性名,默认为参数名
    			namedValueInfo = updateNamedValueInfo(parameter, namedValueInfo);
                //更新缓存
    			this.namedValueInfoCache.put(parameter, namedValueInfo);
    		}
    		return namedValueInfo;
    	}
    

    createNamedValueInfo:获取@PathVariable注解的信息,封装成NamedValueInfo对象

    	@Override
    	protected NamedValueInfo createNamedValueInfo(MethodParameter parameter) {
    		PathVariable ann = parameter.getParameterAnnotation(PathVariable.class);
    		Assert.state(ann != null, "No PathVariable annotation");
    		return new PathVariableNamedValueInfo(ann);
    	}
    

    updateNamedValueInfo:

    private NamedValueInfo updateNamedValueInfo(MethodParameter parameter, NamedValueInfo info) {
        String name = info.name;
        if (info.name.isEmpty()) {
            //如果注解中没有指定name,则为参数名
            name = parameter.getParameterName();
            if (name == null) {
                throw new IllegalArgumentException(
                    "Name for argument type [" + parameter.getNestedParameterType().getName() +
                    "] not available, and parameter name information not found in class file either.");
            }
        }
        String defaultValue = (ValueConstants.DEFAULT_NONE.equals(info.defaultValue) ? null : info.defaultValue);
        return new NamedValueInfo(name, info.required, defaultValue);
    }
    

    resolveName方法:

    在这里插入图片描述

    参数解析的过程:

    • 根据方法对象,获取参数对象数组,并创建存储参数的数组。
    • 遍历参数对象数组,并根据参数解析器argumentResolver解析。
    • 如果没有参数解析器,报错。
    • 参数解析时,先尝试获取注解的信息,以@PathVariable为例。
    • 根据指定的name从url中获取参数值,如果没有指定,则默认为自己传入的参数名。

    传递页面参数

    我们可能会通过Map、Model、ModelMap等向域中存入键值对,这部分包含在请求处理中。

    我们要关注的是ModelAndViewContainer这个类,它里面默认包含着BindingAwareModelMap。

    在解析参数的时候,就已经通过MapMethodProcessor参数处理器初始化了一个BindingAwareModelMap。

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

    当然其实这里重点还是参数解析,至于数据为什么封装进map,就很简单了,无非是反射执行方法的时候,通过put将数据存入,当然最后的数据也就存在于ModelAndViewContainer中。

    返回值解析

    省略寻找返回值解析器的过程,因为返回值为视图名,所以解析器为:ViewNameMethodReturnValueHandler。

    @Override
    public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
    
        if (returnValue instanceof CharSequence) {
            //获取视图名
            String viewName = returnValue.toString();
            //向mavContainer中设置
            mavContainer.setViewName(viewName);
            //是否是isRedirectViewName
            if (isRedirectViewName(viewName)) {
                mavContainer.setRedirectModelScenario(true);
            }
        }
        else if (returnValue != null){
            // should not happen
            throw new UnsupportedOperationException("Unexpected return type: " +
                                                    returnType.getParameterType().getName() + " in method: " + returnType.getMethod());
        }
    }
    

    isRedirectViewName方法

    	protected boolean isRedirectViewName(String viewName) {
            //是否符合自定义的redirectPatterns,或者满足redirect:开头的名字
    		return (PatternMatchUtils.simpleMatch(this.redirectPatterns, viewName) || viewName.startsWith("redirect:"));
    	}
    

    最后通过getModelAndView获取mv对象,我们来详细解析一下:

    @Nullable
    private ModelAndView getModelAndView(ModelAndViewContainer mavContainer,ModelFactory modelFactory, NativeWebRequest webRequest) throws Exception {
    	//Promote model attributes listed as @SessionAttributes to the session
        modelFactory.updateModel(webRequest, mavContainer);
        //如果请求已经处理完成
        if (mavContainer.isRequestHandled()) {
            return null;
        }
        //从mavContainer中获取我们存入的数据map
        ModelMap model = mavContainer.getModel();
        //通过视图名、modelmap、和status创建一个ModelAndView对象
        ModelAndView mav = new ModelAndView(mavContainer.getViewName(), model, mavContainer.getStatus());
        if (!mavContainer.isViewReference()) {
            mav.setView((View) mavContainer.getView());
        }
        if (model instanceof RedirectAttributes) {
            Map<String, ?> flashAttributes = ((RedirectAttributes) model).getFlashAttributes();
            HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class);
            if (request != null) {
                RequestContextUtils.getOutputFlashMap(request).putAll(flashAttributes);
            }
        }
        return mav;
    }
    

    最后返回的都是ModelAndView对象,包含了逻辑名和模型对象的视图。

    返回值解析的过程相对比较简单:

    • 根据返回的参数,获取对应的返回值解析器。

    • 获取视图名,如果是需要redirect,则mavContainer.setRedirectModelScenario(true);

    • 其他情况下,直接给mvcContainer中的ViewName视图名属性设置上即可。

    • 最后将mvcContainer的model、status、viewName取出,创建mv对象返回。

    【总结】

    参数解析、返回值解析两个过程都包含大量的解决策略,其中寻找合适的解析器的过程都是先遍历初始化的解析器表,然后判断是否需要异步处理,判断是否可以处理返回值类型,如果可以的话,就使用该解析器进行解析,如果不行,就一直向下遍历,直到表中没有解析器为止。

    3. 视图解析

    private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,@Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,
     @Nullable Exception exception) throws Exception {
        boolean errorView = false;
        // 保证渲染一次,cleared作为标记
        if (mv != null && !mv.wasCleared()) {
            //渲染过程!!!
            render(mv, request, response);
            if (errorView) {
                WebUtils.clearErrorRequestAttributes(request);
            }
        }
    }
    

    DispatcherServlet的render方法

    protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {
        // Determine locale for request and apply it to the response.
        Locale locale =
            (this.localeResolver != null ? this.localeResolver.resolveLocale(request) : request.getLocale());
        response.setLocale(locale);
    
        View view;
        //获取视图名
        String viewName = mv.getViewName();
        if (viewName != null) {
            //通过视图解析器viewResolvers对视图名进行处理,创建view对象
            view = resolveViewName(viewName, mv.getModelInternal(), locale, request);
        }
        else {
            view = mv.getView();
        }
        if (mv.getStatus() != null) {
            response.setStatus(mv.getStatus().value());
        }
        view.render(mv.getModelInternal(), request, response);   
    }
    

    获取视图解析器,解析视图名:

    @Nullable
    protected View resolveViewName(String viewName, @Nullable Map<String, Object> model,
    Locale locale, HttpServletRequest request) throws Exception {
    
        //这里我们注册的是InternalResourceViewResolver
        if (this.viewResolvers != null) {
            for (ViewResolver viewResolver : this.viewResolvers) {
                View view = viewResolver.resolveViewName(viewName, locale);
                if (view != null) {
                    return view;
                }
            }
        }
        return null;
    }
    

    UrlBasedViewResolver的createView方法:

    	@Override
    	protected View createView(String viewName, Locale locale) throws Exception {
    		//如果解析器不能处理所给的view,就返回null,让下一个解析器看看能否执行
    		if (!canHandle(viewName, locale)) {
    			return null;
    		}
    		// Check for special "redirect:" prefix.
    		if (viewName.startsWith(REDIRECT_URL_PREFIX)) {
    			//判断是否需要重定向
    		}
    		// Check for special "forward:" prefix.
    		if (viewName.startsWith(FORWARD_URL_PREFIX)) {
    			//判断是否需要转发
    		}
    		//调用父类的loadView方法
    		return super.createView(viewName, locale);
    	}
    

    最后返回的视图对象:

    在这里插入图片描述

    视图解析器 viewResolver --实例化 --> view(无状态的,不会有线程安全问题)

    AbstractView的render方法

    @Override
    public void render(@Nullable Map<String, ?> model, HttpServletRequest request,
    HttpServletResponse response) throws Exception {
    	
        //获取合并后的map,有我们存入域中的map,还有PathVariable对应的键值等
        Map<String, Object> mergedModel = createMergedOutputModel(model, request, response);
        prepareResponse(request, response);
        //根据给定的model渲染内部资源,如将model设置为request的属性
        renderMergedOutputModel(mergedModel, getRequestToExpose(request), response);
    }
    

    InternalResourceView的renderMergedOutputModel

    @Override
    protected void renderMergedOutputModel(
        Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception {
    
        //将model中的值设置到request域中
        exposeModelAsRequestAttributes(model, request);
    
        // 如果有的话,给request设置helpers
        exposeHelpers(request);
    
        // 将目标地址设置到request中
        String dispatcherPath = prepareForRendering(request, response);
    
        // 获取目标资源(通常是JSP)的RequestDispatcher。
        RequestDispatcher rd = getRequestDispatcher(request, dispatcherPath);
    
        // 如果已经包含或响应已经提交,则执行包含,否则转发。
        if (useInclude(request, response)) {
            response.setContentType(getContentType());
            rd.include(request, response);
        }
        else {
            // Note: 转发的资源应该确定内容类型本身。
            rd.forward(request, response);
        }
    }
    

    exposeModelAsRequestAttributes

    protected void exposeModelAsRequestAttributes(Map<String, Object> model,HttpServletRequest request) throws Exception {
    	//遍历model
        model.forEach((modelName, modelValue) -> {
            if (modelValue != null) {
                //向request中设置值
                request.setAttribute(modelName, modelValue);
            }
            else {
                //value为null的话,移除该name
                request.removeAttribute(modelName);
            }
        });
    }
    

    视图解析器

    视图解析器(实现ViewResolver接口):将逻辑视图解析为具体的视图对象。

    在这里插入图片描述
    每个视图解析器都实现了Ordered接口,并开放order属性,order越小优先级越高。

    按照视图解析器的优先顺序对逻辑视图名进行解析,直到解析成功并返回视图对象,否则抛出异常。

    视图

    视图(实现View接口):渲染模型数据,将模型数据以某种形式展现给用户。

    最终采取的视图对象对模型数据进行渲染render,处理器并不关心,处理器关心生产模型的数据,实现解耦。

  • 相关阅读:
    <Redis开发与运维> 阅读笔记
    请求行,请求头,请求体详解
    char 与 varchar 的区别
    python字符串的常用方法。
    快速排序的代码及原理
    C#之Dictionary源码
    C#中构造函数
    U3D——单例模式的用法
    U3D学习——设置VS2019作为开发工具
    U3D学习——脚本运行周期
  • 原文地址:https://www.cnblogs.com/summerday152/p/12856338.html
Copyright © 2011-2022 走看看