美文网首页
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