美文网首页
springmvc网上看各种文章和自己粗略读读源码解析

springmvc网上看各种文章和自己粗略读读源码解析

作者: 炫迈哥 | 来源:发表于2017-11-02 12:50 被阅读0次

1. 二话不说先上图

  • 该图是以为大咖在他的文章里发的,我只是转载,学习。
image.png

2.HttpServletBean

  • HttpServletBean主要负责init-param里面的属性注入

  • springmvc利用spring的BeanWrapper功能,自动设置了initParam的所有值到DispathServlet中去。先上代码,配注释

public final void init() throws ServletException {
        if (logger.isDebugEnabled()) {
            logger.debug("Initializing servlet '" + getServletName() + "'");
        }

        // Set bean properties from init parameters.
        try {
                        // 从servletconfig中读取所有的initParam,并转换成spring的PropertyValues
                        // PropertyValues中包含了PropertyValue数组,PropertyValue是一个name,value对
                        // ServletConfigPropertyValues是HttpServletBean的一个内部类,稍后帖源码
            PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
                        // 创建BeanWrapper,BeanWrapper是一个对象的包裹器,提供对这个对象的属性信息的修改访问等功能
                        // 需要特别注意这里的this(this很经典),HttpServletBean是抽象类,这里的this其实就是DispatchServlet实例。所以BeanWrapper包裹的就是DispatchServlet
            BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
            ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
            bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader));
            initBeanWrapper(bw);
                        // 设置所有init-param值到DispatchServlet中
            bw.setPropertyValues(pvs, true);
        }
        catch (BeansException ex) {
            logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex);
            throw ex;
        }

        // Let subclasses do whatever initialization they like.
        initServletBean();

        if (logger.isDebugEnabled()) {
            logger.debug("Servlet '" + getServletName() + "' configured successfully");
        }
    }
  • 可以看看ServletConfigPropertyValues
private static class ServletConfigPropertyValues extends MutablePropertyValues {
                // requiredProperties其实是子类定义的哪些init-param是必须的
        public ServletConfigPropertyValues(ServletConfig config, Set requiredProperties)
            throws ServletException {
            // 用一个hashset来记录所有必须存在的init-param,遍历真正的param时,找到一个就从这里remove掉一个,最后遍历完了,还有=没有remove完,则会报错(DispatchServlet没有定义requiredProperties)
            Set missingProps = (requiredProperties != null && !requiredProperties.isEmpty()) ?
                    new HashSet(requiredProperties) : null;
                        // 从servlet中读取所有的init-param值
            Enumeration en = config.getInitParameterNames();
            while (en.hasMoreElements()) {
                String property = (String) en.nextElement();
                Object value = config.getInitParameter(property);
                                //  调用父类的addPropertyValue方法添加值
                addPropertyValue(new PropertyValue(property, value));
                if (missingProps != null) {
                    missingProps.remove(property);
                }
            }

            // Fail if we are still missing properties.
            if (missingProps != null && missingProps.size() > 0) {
                throw new ServletException(
                    "Initialization from ServletConfig for servlet '" + config.getServletName() +
                    "' failed; the following required properties were missing: " +
                    StringUtils.collectionToDelimitedString(missingProps, ", "));
            }
        }
    }
  • 再看看BeanWrapper到底是干啥的

可以举个例子,加入我们有这样一个bean:

public class Test {
    private String a;

    private String b;
    
    // 嵌套
    private Test c;

     /*
        getter,setters
      */

}

我们可以用BeanWrapper包装它,然后直接操作其属性:

    Test tb = new Test(); 
    BeanWrapper bw = new BeanWrapperImpl(tb); 
    bw.isReadableProperty("a");//判断a属性是否可读 
    bw.setPropertyValue("a", "tom"); //设置name属性的值为tom 
    bw.getPropertyValue("b")//取得属性 

    // 设置可以属性嵌套
    bw.setPropertyValue("c.a", "tom"); 

    // bw 同样支持数组和集合,map属性设置 (这两个值只是举例,Test中无此属性)
    bw.getPropertyValue("array[0].name"); 
    bw.getPropertyValue("map[key4][0].name"); 
  • 综合,HttpServletBean就是提供了init-param的自动注入功能,注入完成后调用子类的initServletBean实现真正的web spring上下文初始化

3.可爱强大的FrameworkServlet,真正的web层spring初始化工作执行者

  • FrameworkServlet就是用来初始化web的controller, Interceptor, handerMapping, hanlderAdapter这些web层的组件的。(关键:它就是根据init-param里面配置的spring-servlet.xml进行web层spring初始化的)

  • 看看源码,主要看initServletBean,它复写了HttpServletBean。

protected final void initServletBean() throws ServletException, BeansException {
            // 省略
        try {
                        // 可以看到这里调用initWebApplicationContext进行了spring上下文的初始化
            this.webApplicationContext = initWebApplicationContext();
            initFrameworkServlet();
        }
               // 省略
    }
  • 下面我们进入initWebApplicationContext
protected WebApplicationContext initWebApplicationContext() throws BeansException {
                // 先看看是否已经存在上下文了
        WebApplicationContext wac = findWebApplicationContext();
        if (wac == null) {
                        // 查找根上下文,即在web.xml中配置的org.springframework.web.context.ContextLoaderListener;
            WebApplicationContext parent =
                    WebApplicationContextUtils.getWebApplicationContext(getServletContext());
                        // 创建web层的spring上下文,并以根上下文为父上下文
            wac = createWebApplicationContext(parent);
        }

        if (!this.refreshEventReceived) {
            // Apparently not a ConfigurableApplicationContext with refresh support:
            // triggering initial onRefresh manually here.
            onRefresh(wac);
        }

        if (this.publishContext) {
            // Publish the context as a servlet context attribute.
            String attrName = getServletContextAttributeName();
                        // 将上下文放进servlet的属性中去
            getServletContext().setAttribute(attrName, wac);
            if (this.logger.isDebugEnabled()) {
                this.logger.debug("Published WebApplicationContext of servlet '" + getServletName() +
                        "' as ServletContext attribute with name [" + attrName + "]");
            }
        }

        return wac;
    }
  • 进入createWebApplicationContext
protected WebApplicationContext createWebApplicationContext(WebApplicationContext parent)
            throws BeansException {
                // 省略非关键代码

        ConfigurableWebApplicationContext wac =
                (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(getContextClass());
                // 设置父上下文
        wac.setParent(parent);
        wac.setServletContext(getServletContext());
        wac.setServletConfig(getServletConfig());
        wac.setNamespace(getNamespace());
                // 设置xxx-servlet.xml的位置
        wac.setConfigLocation(getContextConfigLocation());
        wac.addApplicationListener(new SourceFilteringListener(wac, this));

        postProcessWebApplicationContext(wac);
                // 进入spring初始化逻辑
        wac.refresh();

        return wac;
    }

  • 这里需要理清楚第一件事,父子上下文是个什么鬼?spring的applicationcontext可以有父子关系的,子上下文内的组件可以使用访问父上下文的组件(如:你在controller中可以直接@autowired你在service层定义的bean),而父上下文是不能访问子上下文的组件的,springmvc对web层和业务层的spring容器进行了隔离。(原因?个人觉得是防止污染吧,其实就用一个上下文也没啥问题呀,这里给个传送门,stackoverflow上的人的回答:https://stackoverflow.com/questions/18682486/why-does-spring-mvc-need-at-least-two-contexts)

  • 父子上下文不是在一段代码里连续执行的,他们是怎么能关联起来的?

哈哈,秘密很简单啊,就是利用ServletConfig嘛,root上下文初始化完成后先放进ServletConfig的属性里,web的上下文初始化时去读取就行啦,我们看代码验证下吧

先看看root上下文的初始化,查看ContextLoaderListener源码,

@Override
    public void contextInitialized(ServletContextEvent event) {
                // 调用父类
        initWebApplicationContext(event.getServletContext());
    }

再看看initWebApplicationContext方法:

public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
                // 可以看到如果初始化完了是放在servletContext的ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE属性里面的
        if (servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null) {
                        // 已经初始化过了会直接报错哦
            throw new IllegalStateException(
                    "Cannot initialize context because there is already a root application context present - " +
                    "check whether you have multiple ContextLoader* definitions in your web.xml!");
        }
                // log只用一次,所以不做成类变量了,直接写在方法里。哈哈,可以的
        Log logger = LogFactory.getLog(ContextLoader.class);
        servletContext.log("Initializing Spring root WebApplicationContext");
        if (logger.isInfoEnabled()) {
            logger.info("Root WebApplicationContext: initialization started");
        }
        long startTime = System.currentTimeMillis();

        try {
            // Store context in local instance variable, to guarantee that
            // it is available on ServletContext shutdown.
            if (this.context == null) {
                                // spring上下文初始化
                this.context = createWebApplicationContext(servletContext);
            }
            if (this.context instanceof ConfigurableWebApplicationContext) {
                ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
                if (!cwac.isActive()) {
                    // The context has not yet been refreshed -> provide services such as
                    // setting the parent context, setting the application context id, etc
                    if (cwac.getParent() == null) {
                        // The context instance was injected without an explicit parent ->
                        // determine parent for root web application context, if any.
                        ApplicationContext parent = loadParentContext(servletContext);
                        cwac.setParent(parent);
                    }
                    configureAndRefreshWebApplicationContext(cwac, servletContext);
                }
            }
// 终于找到你,把rootcontext放到servletContext属性里面去咯
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);

            ClassLoader ccl = Thread.currentThread().getContextClassLoader();
            if (ccl == ContextLoader.class.getClassLoader()) {
                currentContext = this.context;
            }
            else if (ccl != null) {
                currentContextPerThread.put(ccl, this.context);
            }

            if (logger.isDebugEnabled()) {
                logger.debug("Published root WebApplicationContext as ServletContext attribute with name [" +
                        WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE + "]");
            }
            if (logger.isInfoEnabled()) {
                long elapsedTime = System.currentTimeMillis() - startTime;
                logger.info("Root WebApplicationContext: initialization completed in " + elapsedTime + " ms");
            }

            return this.context;
        }
        catch (RuntimeException ex) {
            logger.error("Context initialization failed", ex);
            servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex);
            throw ex;
        }
        catch (Error err) {
            logger.error("Context initialization failed", err);
            servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, err);
            throw err;
        }
    }

再回头看看我们的webcontext的初始化入口:

protected WebApplicationContext initWebApplicationContext() throws BeansException {
        WebApplicationContext wac = findWebApplicationContext();
        if (wac == null) {org.springframework.web.context.ContextLoaderListener;
                        // 我们看看它是如何找到根上下文的
            WebApplicationContext parent =
                    WebApplicationContextUtils.getWebApplicationContext(getServletContext());
            wac = createWebApplicationContext(parent);
        }
      // 省略。。。
  }

进入这个方法:

public static WebApplicationContext getWebApplicationContext(ServletContext sc) {  
        // 哈哈哈,就是你servlet的ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE属性
        return getWebApplicationContext(sc, WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);  
    }  
  • 综合,FrameworkServlet就是对xxx-servlet.xml这个配置文件进行spring初始化的,所有的controller等东东都已经被spring托管了

4.真正的mvc核心分发器:DispatchServlet

  • FrameworkServlet把所有的玩意儿都纳入了spring的怀抱,那么DispatchServlet就是真正的这些玩意儿的使用者,它从spring中读取所有的这些玩意,并存在自己的属性里,用它们去处理每一个http请求。

  • 先看他的初始化

/*
   就是从sping里读取它所欲想要的所有bean,读取不到自己也有定义默认bean。
常见读取方式:根据beaname读取,根据class读取等等
*/
protected void initStrategies(ApplicationContext context) {
        initMultipartResolver(context);
        initLocaleResolver(context);
        initThemeResolver(context);
        initHandlerMappings(context);
        initHandlerAdapters(context);
        initHandlerExceptionResolvers(context);
        initRequestToViewNameTranslator(context);
        initViewResolvers(context);
    }

随便找个看看,例如initHandlerMappings方法,源码的注释已经很清楚了,先加载,加载不到直接反射创建默认bean:

private void initHandlerMappings(ApplicationContext context) {
        this.handlerMappings = null;

        if (this.detectAllHandlerMappings) {
            // Find all HandlerMappings in the ApplicationContext,
            // including ancestor contexts.
            Map matchingBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors(
                    context, HandlerMapping.class, true, false);
            if (!matchingBeans.isEmpty()) {
                this.handlerMappings = new ArrayList(matchingBeans.values());
                // We keep HandlerMappings in sorted order.
                Collections.sort(this.handlerMappings, new OrderComparator());
            }
        }
        else {
            try {
                Object 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.
            }
        }

        // Ensure we have at least one HandlerMapping, by registering
        // a default HandlerMapping if no other mappings are found.
        if (this.handlerMappings == null) {
            this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
            if (logger.isDebugEnabled()) {
                logger.debug("No HandlerMappings found in servlet '" + getServletName() + "': using default");
            }
        }
    }

  • 当上面这些玩意儿都读取到之后,springmvc就可以处理http请求了。

有请求来了,看我们的DispatchServlet干了些什么

protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
        if (logger.isDebugEnabled()) {
            String requestUri = new UrlPathHelper().getRequestUri(request);
            logger.debug("DispatcherServlet with name '" + getServletName() +
                    "' processing request for [" + requestUri + "]");
        }

        // Keep a snapshot of the request attributes in case of an include,
        // to be able to restore the original attributes after the include.
        Map attributesSnapshot = null;
        if (WebUtils.isIncludeRequest(request)) {
            logger.debug("Taking snapshot of request attributes before include");
            attributesSnapshot = new HashMap();
            Enumeration attrNames = request.getAttributeNames();
            while (attrNames.hasMoreElements()) {
                String attrName = (String) attrNames.nextElement();
                if (this.cleanupAfterInclude || attrName.startsWith("org.springframework.web.servlet")) {
                    attributesSnapshot.put(attrName, request.getAttribute(attrName));
                }
            }
        }

        // Make framework objects available to handlers and view objects.
        request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, getWebApplicationContext());
        request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver);
        request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver);
        request.setAttribute(THEME_SOURCE_ATTRIBUTE, getThemeSource());

        try {
            doDispatch(request, response);
        }
        finally {
            // Restore the original attribute snapshot, in case of an include.
            if (attributesSnapshot != null) {
                restoreAttributesAfterInclude(request, attributesSnapshot);
            }
        }
    }

入口处,把一些属性丢进request中去,就直接进入doDispatch了(用request的属性来存放这一对信息真的不错哦,看这doDispatch方法,就两个参数,request和response,不错。。),下面进入doDispatch方法(springmvc的核心之核心方法):

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
        HttpServletRequest processedRequest = request;
                // 执行链,包含一堆拦截器和一个真正的hanler(即controller)
        HandlerExecutionChain mappedHandler = null;
        int interceptorIndex = -1;

        // Expose current LocaleResolver and request as LocaleContext.
        LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
        LocaleContextHolder.setLocaleContext(buildLocaleContext(request), this.threadContextInheritable);

        // Expose current RequestAttributes to current thread.
        RequestAttributes previousRequestAttributes = RequestContextHolder.getRequestAttributes();
        ServletRequestAttributes requestAttributes = new ServletRequestAttributes(request);
        RequestContextHolder.setRequestAttributes(requestAttributes, this.threadContextInheritable);

        if (logger.isTraceEnabled()) {
            logger.trace("Bound request context to thread: " + request);
        }
        
        try {
            ModelAndView mv = null;
            boolean errorView = false;

            try {
                                // 如果是上传文件请求,对request进行转换。通过multipartResolver
                processedRequest = checkMultipart(request);

                // Determine handler for the current request.
                                // 这一步尤其至关重要,根据request匹配执行链
                mappedHandler = getHandler(processedRequest, false);
                if (mappedHandler == null || mappedHandler.getHandler() == null) {
                    noHandlerFound(processedRequest, response);
                    return;
                }

                // Apply preHandle methods of registered interceptors.
                                // 执行执行链里所有的拦截器的preHandle方法
                HandlerInterceptor[] interceptors = mappedHandler.getInterceptors();
                if (interceptors != null) {
                    for (int i = 0; i < interceptors.length; i++) {
                        HandlerInterceptor interceptor = interceptors[i];
                        if (!interceptor.preHandle(processedRequest, response, mappedHandler.getHandler())) {
                            triggerAfterCompletion(mappedHandler, interceptorIndex, processedRequest, response, null);
                            return;
                        }
                        interceptorIndex = i;
                    }
                }

                // Actually invoke the handler.
                                // 根据hanlder获取到hanlderAdapter(hanlder的执行者)
                HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
                                // 用HandlerAdapter去执行hanlder得到结果视图
                mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

                // Do we need view name translation?
                if (mv != null && !mv.hasView()) {
                    mv.setViewName(getDefaultViewName(request));
                }

                // Apply postHandle methods of registered interceptors.
                                // 执行完成后执行拦截器的所有postHandle方法
                if (interceptors != null) {
                    for (int i = interceptors.length - 1; i >= 0; i--) {
                        HandlerInterceptor interceptor = interceptors[i];
                        interceptor.postHandle(processedRequest, response, mappedHandler.getHandler(), mv);
                    }
                }
            }
            catch (ModelAndViewDefiningException ex) {
                logger.debug("ModelAndViewDefiningException encountered", ex);
                mv = ex.getModelAndView();
            }
            catch (Exception ex) {
                Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
                mv = processHandlerException(processedRequest, response, handler, ex);
                errorView = (mv != null);
            }

            // Did the handler return a view to render?
            if (mv != null && !mv.wasCleared()) {
                render(mv, processedRequest, response);
                if (errorView) {
                    WebUtils.clearErrorRequestAttributes(request);
                }
            }
            else {
                if (logger.isDebugEnabled()) {
                    logger.debug("Null ModelAndView returned to DispatcherServlet with name '" +
                            getServletName() + "': assuming HandlerAdapter completed request handling");
                }
            }

            // Trigger after-completion for successful outcome.
                        // 执行拦截器的afterCompletion方法
            triggerAfterCompletion(mappedHandler, interceptorIndex, processedRequest, response, null);
        }

        catch (Exception ex) {
            // Trigger after-completion for thrown exception.
            triggerAfterCompletion(mappedHandler, interceptorIndex, processedRequest, response, ex);
            throw ex;
        }
        catch (Error err) {
            ServletException ex = new NestedServletException("Handler processing failed", err);
            // Trigger after-completion for thrown exception.
            triggerAfterCompletion(mappedHandler, interceptorIndex, processedRequest, response, ex);
            throw ex;
        }

        finally {
            // Clean up any resources used by a multipart request.
            if (processedRequest != request) {
                cleanupMultipart(processedRequest);
            }

            // Reset thread-bound context.
            RequestContextHolder.setRequestAttributes(previousRequestAttributes, this.threadContextInheritable);
            LocaleContextHolder.setLocaleContext(previousLocaleContext, this.threadContextInheritable);

            // Clear request attributes.
            requestAttributes.requestCompleted();
            if (logger.isTraceEnabled()) {
                logger.trace("Cleared thread-bound request context: " + request);
            }
        }
    }

可以看到大体流程:通过request匹配执行链,执行前置拦截器,通过执行链获取hanlderadapter,hanlderadapter执行真正的业务逻辑,执行postHandle拦截器,试图渲染,执行后置拦截器

先看看springmvc默认的一些组件实例:

image.png
  • 匹配执行链
protected HandlerExecutionChain getHandler(HttpServletRequest request, boolean cache) throws Exception {
        HandlerExecutionChain handler =
                (HandlerExecutionChain) request.getAttribute(HANDLER_EXECUTION_CHAIN_ATTRIBUTE);
        if (handler != null) {
            if (!cache) {
                request.removeAttribute(HANDLER_EXECUTION_CHAIN_ATTRIBUTE);
            }
            return handler;
        }

                // 可以看到是遍历handlerMappings找到匹配的hanlderchain
        Iterator it = this.handlerMappings.iterator();
        while (it.hasNext()) {
            HandlerMapping hm = (HandlerMapping) it.next();
            if (logger.isTraceEnabled()) {
                logger.trace("Testing handler map [" + hm  + "] in DispatcherServlet with name '" +
                        getServletName() + "'");
            }
            handler = hm.getHandler(request);
            if (handler != null) {
                if (cache) {
                    request.setAttribute(HANDLER_EXECUTION_CHAIN_ATTRIBUTE, handler);
                }
                return handler;
            }
        }
        return null;
    }

不想写了,根据上面的图可以找到默认的handlermaping adapter等可以去看看具体是怎么匹配的...

最后

其实我们可以自己实现handlermapping adapter实现自己的mapping逻辑,自己的hanlder处理方式,springmvc的可扩展性确实很强,核心组件全部是接口,自己想怎么玩就可以怎么玩

相关文章

网友评论

      本文标题:springmvc网上看各种文章和自己粗略读读源码解析

      本文链接:https://www.haomeiwen.com/subject/cnkopxtx.html