Spring MVC的工作原理-从M(Model)-C(Controller)-V(View)

一、Spring MVC概念

MVC即是模型(Model)、视图(View)、控制器(Controller)的简称。其中M负责对应用数据进行封装,并向外提供应用功能的接口;V负责通过向控制器发送请求,得到响应结果,并向用户展示处理好的的应用数据;C这是用于定义应用的功能,接收用户的动作,并选择相应的视图。Spring MVC就相当于Spring的一个处理模型、视图和控制器的框架,下面我们来分析Spring MVC是如何处理请求,封装数据,再以特定的视图展示给用户的。

二、使用ContextConfigLocation在Web容器中使用IoC容器建立上下文

首先看基本的配置:

<servlet>
        <servlet-name>mvc-dispatcher</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <!-- spring mvc的配置文件 -->
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>/WEB-INF/applicationContext.xml</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>mvc-dispatcher</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>

先讲讲两个很重要的类,DispatcherServlet这个是Spring MVC的关键类,起着分发请求的作用,ContextConfigLocation是一个用于完成IoC容器在Web环境中的启动工作。通过ContextConfigLocation在Web环境中建立起一个上下文环境,然后把DispatcherServlet作为Spring MVC处理Web请求的转发器,从而完成相应前端http请求的准备工作。
在ContextConfigLocation可以看到两个核心的方法:

 //在Web环境中初始化上下文
 public void contextInitialized(ServletContextEvent event) {
        this.initWebApplicationContext(event.getServletContext());
    }
//销毁上下文
    public void contextDestroyed(ServletContextEvent event) {
        this.closeWebApplicationContext(event.getServletContext());
        ContextCleanupListener.cleanupAttributes(event.getServletContext());
    }

其中ContextConfigLocation启动的上下文为根上下文,同时还会创建一个用于保存DispatcherServlet所需要的MVC对象的子上下文,从而构成一个完成的上下文体系。这个上下文体系的建立是交给ContextConfigLocation的基类ContextLoader完成:

//创建子上下文
 this.context = this.createWebApplicationContext(servletContext);
//加载并创建根上下文
 ApplicationContext parent = this.loadParentContext(servletContext);

在ContextLoader中,完成了两个IoC容器(应用上下文)建立的基本过程,一个是在Web容器中建立起的双亲IoC容器,另一个是生成相应的WebApplicationContext并将其初始化。

三、DispatcherServlet的启动和初始化

DispatcherServlet作为一个Servlet,自然启动和初始化的过程与init()方法密切相关,下面示其基类HttpServletBean中定义的init()方法部分代码:

//获取Servlet的基本配置,同时对bean的属性进行设置
PropertyValues pvs = new HttpServletBean.ServletConfigPropertyValues(this.getServletConfig(), this.requiredProperties);
        if (!pvs.isEmpty()) {
            try {
                BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
                ResourceLoader resourceLoader = new ServletContextResourceLoader(this.getServletContext());
                bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, this.getEnvironment()));
                this.initBeanWrapper(bw);
                bw.setPropertyValues(pvs, true);
            } catch (BeansException var4) {
                if (this.logger.isErrorEnabled()) {
                    this.logger.error("Failed to set bean properties on servlet '" + this.getServletName() + "'", var4);
                }

                throw var4;
            }
        }
//对bean进行初始化
 this.initServletBean();

接下来,对前文中所提到的DispatcherServlet所持有的子上下文进行初始化,根上下文是与整个的Web应用相对应着的一个上下文,而子上下文则是和一个Servlet相对应的上下文。同时,由于IoC容器中获取bean时,会先去其双亲上下文中getBean(),因此在根上下文中所定义的bean会在子下文中得到共享。至此完成了整个MVC的上下文建立过程。

四、HandlerMapping的实现原理(M与C的设计原理)

在前文的初始化过程中,在上下文环境中已定义的HandlerMapping都已被加载了,这些都被有序的放在在一个List中,其中存储这Http请求对应的映射数据。

 private List<HandlerMapping> handlerMappings;
 this.handlerMappings = new ArrayList(matchingBeans.values());
 //排序处理
 AnnotationAwareOrderComparator.sort(this.handlerMappings);

以SimpleUrlHandlerMapping为例,在SimpleUrlHandlerMapping中维持着一个LinkedHashMap去持有URL请求和控制器的映射关系,因此Spring MVC可以通过一个Http请求去找到对应的Controller。

 private final Map<String, Object> urlMap = new LinkedHashMap();

在HandlerMapping中定义了一个getHandler()方法,用于获取与http请求对应的HandlerExecutionChain:

HandlerExecutionChain getHandler(HttpServletRequest var1) throws Exception;

其中HandlerExecutionChain持有一个Interceptor链和一个handler对象,而handler对象即是http请求所对应着的Controller,通过拦截器链上的拦截器可以对handler对象的功能提供增强处理,类似于AOP中拦截器链的功能:

public class HandlerExecutionChain {
    private static final Log logger = LogFactory.getLog(HandlerExecutionChain.class);
    //Controller对象
    private final Object handler;
    //拦截器链
    private HandlerInterceptor[] interceptors;
    private List<HandlerInterceptor> interceptorList;
    private int interceptorIndex;
    //将配置好的Handler和拦截器链转化为HandlerExecutionChain的内部属性
    public HandlerExecutionChain(Object handler, HandlerInterceptor... interceptors) {
        this.interceptorIndex = -1;
        if (handler instanceof HandlerExecutionChain) {
            HandlerExecutionChain originalChain = (HandlerExecutionChain)handler;
            this.handler = originalChain.getHandler();
            this.interceptorList = new ArrayList();
            CollectionUtils.mergeArrayIntoCollection(originalChain.getInterceptors(), this.interceptorList);
            CollectionUtils.mergeArrayIntoCollection(interceptors, this.interceptorList);
        } else {
            this.handler = handler;
            this.interceptors = interceptors;
        }

    }

下面来分析是如何将http请求与handler对象注册在handlerMap当中,以SimpleUrlHandlerMapping为例:

protected void registerHandlers(Map<String, Object> urlMap) throws BeansException {
        String url;
        Object handler;
        if (urlMap.isEmpty()) {
            this.logger.warn("Neither 'urlMap' nor 'mappings' set on SimpleUrlHandlerMapping");
        } else {
        //对urlMap中的bean进行解析,并调用基类的registerHandler完成注册
            for(Iterator var2 = urlMap.entrySet().iterator(); var2.hasNext(); this.registerHandler(url, handler)) {
                Entry<String, Object> entry = (Entry)var2.next();
                url = (String)entry.getKey();
                handler = entry.getValue();
                if (!url.startsWith("/")) {
                    url = "/" + url;
                }

                if (handler instanceof String) {
                    handler = ((String)handler).trim();
                }
            }
        }

    }

基类AbstractUrlHandlerMapping中的registerHandler():

//持有映射关系
 private final Map<String, Object> handlerMap = new LinkedHashMap();

 Object mappedHandler = this.handlerMap.get(urlPath);
        if (mappedHandler != null) {
            if (mappedHandler != resolvedHandler) {
                throw new IllegalStateException("Cannot map " + this.getHandlerDescription(handler) + " to URL path [" + urlPath + "]: There is already " + this.getHandlerDescription(mappedHandler) + " mapped.");
            }
        }
        //处理URL是"/"的映射,并把映射的Controller设置到rootHandler中 
        else if (urlPath.equals("/")) {
            if (this.logger.isInfoEnabled()) {
                this.logger.info("Root mapping to " + this.getHandlerDescription(handler));
            }

            this.setRootHandler(resolvedHandler);
        } 
        //处理是"/*"的映射,并把响应的Controller设置到DefaultHandler中
        else if (urlPath.equals("/*")) {
            if (this.logger.isInfoEnabled()) {
                this.logger.info("Default mapping to " + this.getHandlerDescription(handler));
            }

            this.setDefaultHandler(resolvedHandler);
        } 
        //处理正常的映射
        else {
            this.handlerMap.put(urlPath, resolvedHandler);
            if (this.logger.isInfoEnabled()) {
                this.logger.info("Mapped URL path [" + urlPath + "] onto " + this.getHandlerDescription(handler));
            }
        }

至此通过Spring MVC中通过上文的handlerMap持有http请求与Controller的映射关系,每当有请求到来,都可以找到相应的handler了。而对于请求的处理,是在doService方法中完成的:

 protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
        ...
        Map<String, Object> attributesSnapshot = null;
        ...
        //对http请求进行处理,设置上下文,解析器...
        request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.getWebApplicationContext());
        request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver);
        request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver);
        request.setAttribute(THEME_SOURCE_ATTRIBUTE, this.getThemeSource());
        FlashMap inputFlashMap = this.flashMapManager.retrieveAndUpdate(request, response);
        if (inputFlashMap != null) {
            request.setAttribute(INPUT_FLASH_MAP_ATTRIBUTE, Collections.unmodifiableMap(inputFlashMap));
        }

        request.setAttribute(OUTPUT_FLASH_MAP_ATTRIBUTE, new FlashMap());
        request.setAttribute(FLASH_MAP_MANAGER_ATTRIBUTE, this.flashMapManager);

        try {
            //正式处理和分发请求的入口
            this.doDispatch(request, response);
        } 
        ...
    }

下面示doDispatch()方法:

 protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
                    processedRequest = this.checkMultipart(request);
                    multipartRequestParsed = processedRequest != request;
                    //从http请求中获取相应的handler
                    mappedHandler = this.getHandler(processedRequest);
                    if (mappedHandler == null || mappedHandler.getHandler() == null) {
                        this.noHandlerFound(processedRequest, response);
                        return;
                    }
                    //选择适应的HandlerAdapter 
                    HandlerAdapter ha = this.getHandlerAdapter(mappedHandler.getHandler());
                    //处理handler并将处理结果封装到ModelAndView对象中
                    mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

至此,M生成完成,下面看看C如何生成:

protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
        //遍历Map中的HandlerMapping 
        Iterator var2 = this.handlerMappings.iterator();
        HandlerExecutionChain handler;
        do {
            if (!var2.hasNext()) {
                return null;
            }

            HandlerMapping hm = (HandlerMapping)var2.next();
            if (this.logger.isTraceEnabled()) {
                this.logger.trace("Testing handler map [" + hm + "] in DispatcherServlet with name '" + this.getServletName() + "'");
            }
            //根据http请求从HandlerMapping之中取得相对应的Handler即Controller对象,并将其和拦截器链一起封装到HandlerExecutionChain中
            handler = hm.getHandler(request);
        } while(handler == null);

        return handler;
    }

到此C也实现完成了。

五、视图呈现的实现原理(V的设计原理)

对于视图的呈现和处理,依旧是从doDispatch()为入口,具体的实现又是在render()方法之中完成,如下:

protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {
        Locale locale = this.localeResolver.resolveLocale(request);
        response.setLocale(locale);
        View view;
        //根据ModelAndView中设置的视图名称进行解析,得到对应的视图对象
        //ModelAndView中的仅仅包含的View对象的名字,View还未解析过
        if (mv.isReference()) {
            view = this.resolveViewName(mv.getViewName(), mv.getModelInternal(), locale, request);
            if (view == null) {
                throw new ServletException("Could not resolve view with name '" + mv.getViewName() + "' in servlet with name '" + this.getServletName() + "'");
            }
        } else {
            //已经解析过,直接取出View对象
            view = mv.getView();
            if (view == null) {
                throw new ServletException("ModelAndView [" + mv + "] neither contains a view name nor a View object in servlet with name '" + this.getServletName() + "'");
            }
        }

        if (this.logger.isDebugEnabled()) {
            this.logger.debug("Rendering view [" + view + "] in DispatcherServlet with name '" + this.getServletName() + "'");
        }

        try {
            if (mv.getStatus() != null) {
                response.setStatus(mv.getStatus().value());
            }

            //调用view的render()方法对数据进行呈现,并通过HttpResponse对象把视图呈现给客户端
            view.render(mv.getModelInternal(), request, response);
        } catch (Exception var7) {
            if (this.logger.isDebugEnabled()) {
                this.logger.debug("Error rendering view [" + view + "] in DispatcherServlet with name '" + this.getServletName() + "'", var7);
            }

            throw var7;
        }
    }

那么又是如何结合解析和生成View对象的,如下:

protected View resolveViewName(String viewName, Map<String, Object> model, Locale locale, HttpServletRequest request) throws Exception {
        //遍历已有的视图解析器
        Iterator var5 = this.viewResolvers.iterator();
        View view;
        do {
            if (!var5.hasNext()) {
                return null;
            }

            ViewResolver viewResolver = (ViewResolver)var5.next();
            //选择对应的视图解析器进行解析
            view = viewResolver.resolveViewName(viewName, locale);
        } while(view == null);

        return view;
    }

以BeanNameViewResolver为例,直接从已有上下文中通过名称的对应关系将作为View对象的Bean取出:

public View resolveViewName(String viewName, Locale locale) throws BeansException {
        //获取上下文
        ApplicationContext context = this.getApplicationContext();
        if (!context.containsBean(viewName)) {
            if (this.logger.isDebugEnabled()) {
                this.logger.debug("No matching bean found for view name '" + viewName + "'");
            }

            return null;
        } else if (!context.isTypeMatch(viewName, View.class)) {
            if (this.logger.isDebugEnabled()) {
                this.logger.debug("Found matching bean for view name '" + viewName + "' - to be ignored since it does not implement View");
            }

            return null;
        } else {
        //从上下文中根据配置的Bean名称取出相应的Bean对象,并转化为View对象
            return (View)context.getBean(viewName, View.class);
        }
    }

至此,View对象生成以及通过render()方法对数据进行呈现,并通过HttpResponse对象把视图呈现给客户端完成。

六、总结

本文分析了Spring MVC中三个模块的实现原理,以及如何处理请求,找到相应的Handler,最后将视图呈现给客户端。以后将详细总结Spring MVC具体的请求流程

参考文献:《Spring技术内幕》计文柯

猜你喜欢

转载自blog.csdn.net/jackFXX/article/details/81812907