天天看点

JAVA面试题之-SpringMVC的执行流程

JAVA面试题之-SpringMVC的执行流程

1. 前言:
在java的面试过程中,如果是讨论框架方面的话,这个问题被问到的几率就很大;
 身边朋友在面试的时候也会时不时的碰到,固总结在此。
           
2.概念:
那什么是springMVC呢?
 它其实是一种我们做javaWeb开发的一种架构;包括MVC三个层次的架构;
 M:modle:业务模型(也就是sevice+do/mapper层)
 V:View,视图层(如jsp等前端显示层)
 C:Controller,控制器(sevlet/javabean);
           

3.SpringMVC的执行流程:

JAVA面试题之-SpringMVC的执行流程
4. 源码分析:

DispatcherServlet其实是一个Servlet,我们都知道在处理请求的时候会交给service方法进行处理;在DispatcherServlet类中继承了FrameworkServlet,

在FrameworkServlet中的service方法中:

protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        HttpMethod httpMethod = HttpMethod.resolve(request.getMethod());
        if (httpMethod != HttpMethod.PATCH && httpMethod != null) {
        //其他请求类型的方法,经由父类,也就是HttpServlet处理;
            super.service(request, response);
        } else {
      //如果请求类型的方法位PATCH,那么就用processRequest进行处理;
            this.processRequest(request, response);
        }

    }
           

request.getMethod();是表示得到前端客户端传向服务器传送数据的方法;如GET,POST,HREADER,TRACE等;

而HttpMtheod中的resolve代码如下:还是前端的那些传送数据的方法,只是进行了封装;

public enum HttpMethod {
    GET,
    HEAD,
    POST,
    PUT,
    PATCH,
    DELETE,
    OPTIONS,
    TRACE;

    private static final Map<String, HttpMethod> mappings = new HashMap(16);
    private HttpMethod() {
    }

    @Nullable
    public static HttpMethod resolve(@Nullable String method) {
        return method != null ? (HttpMethod)mappings.get(method) : null;
    }
           

如果为PATCH,就会调用processRequest方法;而此方法中父类的doservice,因为子类实现了此方法,根据多态实际上使用的是DIsptcherServet中的doService方法;

protected final void processRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        long startTime = System.currentTimeMillis();
        Throwable failureCause = null;
        LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
        LocaleContext localeContext = this.buildLocaleContext(request);
        RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();
        ServletRequestAttributes requestAttributes = this.buildRequestAttributes(request, response, previousAttributes);
        WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
        asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new FrameworkServlet.RequestBindingInterceptor(null));
        this.initContextHolders(request, localeContext, requestAttributes);

        try {
            this.doService(request, response);
        } catch (IOException | ServletException var16) {
            failureCause = var16;
            throw var16;
        } catch (Throwable var17) {
            failureCause = var17;
            throw new NestedServletException("Request processing failed", var17);
        } finally {
            this.resetContextHolders(request, previousLocaleContext, previousAttributes);
            if (requestAttributes != null) {
                requestAttributes.requestCompleted();
            }

            this.logResult(request, response, (Throwable)failureCause, asyncManager);
            this.publishRequestHandledEvent(request, response, startTime, (Throwable)failureCause);
        }

    }
           

再看DispatcherServlet中的doService方法:可以看到,会执行本类中的doDispatch方法;

protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
        this.logRequest(request);
        //给request中的属性做一份快照;
        Map<String, Object> attributesSnapshot = null;
        if (WebUtils.isIncludeRequest(request)) {
            attributesSnapshot = new HashMap();
            //getAttribute ,setAttribute ,
            Enumeration attrNames = request.getAttributeNames();

            label95:
            while(true) {
                String attrName;
                do {
                    if (!attrNames.hasMoreElements()) {
                        break label95;
                    }

                    attrName = (String)attrNames.nextElement();
                } while(!this.cleanupAfterInclude && !attrName.startsWith("org.springframework.web.servlet"));

                attributesSnapshot.put(attrName, request.getAttribute(attrName));
            }
        }
// 如果我们没有配置类似本地化或者主题的处理器之类的,springMVC 会使用默认的值;默认的配置文件是,DispathcerServlet.properties;
        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());
        if (this.flashMapManager != null) {
            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);
        } finally {
            if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted() && attributesSnapshot != null) {
                this.restoreAttributesAfterInclude(request, attributesSnapshot);
            }

        }

    }
           

DispatcherServlet中的doDispatch方法;

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
        HttpServletRequest processedRequest = request;
        HandlerExecutionChain mappedHandler = null;
        boolean multipartRequestParsed = false;
        //springMVC中的异步请求的管理
        WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);

        try {
            try {
                ModelAndView mv = null;
                Object dispatchException = null;

                try {
                //先检查是不是multipart类型的,比如上传等,
                //如果是multipart类型的,就转化为MultipartHttpServletRequest类型;
                    processedRequest = this.checkMultipart(request);
                    multipartRequestParsed = processedRequest != request;
                     //获得当前请求的Handler
                    mappedHandler = this.getHandler(processedRequest);
                    if (mappedHandler == null) {
                        this.noHandlerFound(processedRequest, response);
                        return;
                    }
                      // 获得当前请求的HandlerAdapter
                    HandlerAdapter ha = this.getHandlerAdapter(mappedHandler.getHandler());
                    //对于header中的last-modified的处理
                    String method = request.getMethod();
                    boolean isGet = "GET".equals(method);
                    if (isGet || "HEAD".equals(method)) {
                        long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
                        if ((new ServletWebRequest(request, response)).checkNotModified(lastModified) && isGet) {
                            return;
                        }
                    }
           //拦截器的preHandle方法进行处理;
                    if (!mappedHandler.applyPreHandle(processedRequest, response)) {
                        return;
                    }
              //真正调用handle的地方
                    mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
                    if (asyncManager.isConcurrentHandlingStarted()) {
                        return;
                    }
                      //处理默认视图名,即添加前缀后缀名
                    this.applyDefaultViewName(processedRequest, mv);
                    //拦截器postHandle进行处理
                    mappedHandler.applyPostHandle(processedRequest, response, mv);
                } catch (Exception var20) {
                    dispatchException = var20;
                } catch (Throwable var21) {
                    dispatchException = new NestedServletException("Handler dispatch failed", var21);
                }
                //处理最后的结果,渲染之类的都在这里
                this.processDispatchResult(processedRequest, response, mappedHandler, mv, (Exception)dispatchException);
            } catch (Exception var22) {
                this.triggerAfterCompletion(processedRequest, response, mappedHandler, var22);
            } catch (Throwable var23) {
                this.triggerAfterCompletion(processedRequest, response, mappedHandler, new NestedServletException("Handler processing failed", var23));
            }

        } finally {
            if (asyncManager.isConcurrentHandlingStarted()) {
             // Instead of postHandle and afterCompletion
                if (mappedHandler != null) {
                    mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
                }
                  // Clean up any resources used by a multipart request
            } else if (multipartRequestParsed) {
                this.cleanupMultipart(processedRequest);
            }

        }
    }
           

//springmvc中关于dispatcherServlet中的步骤都在这;

查找请求对应的handle对象

对应的上面 mappedHandler = this.getHandler(processedRequest)代码;

具体getHandler()方法如下:

@Nullable
    protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
        if (this.handlerMappings != null) {
        //遍历所有的handlerMapping
            Iterator var2 = this.handlerMappings.iterator();

            while(var2.hasNext()) {
                HandlerMapping mapping = (HandlerMapping)var2.next();
                HandlerExecutionChain handler = mapping.getHandler(request);
                if (handler != null) {
                    return handler;
                }
            }
        }

        return null;
    }
           

这个方法又调用了getHandler(request);在AbstractHandlerMapping类中,

@Nullable
    public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
        // 根据request,获取handler;
        Object handler = this.getHandlerInternal(request);
        //如果没有找到就使用默认的handler
        if (handler == null) {
            handler = this.getDefaultHandler();
        }

        if (handler == null) {
            return null;
        } else {
        //如果handler属于String类,表示是一个bean的名称
        //需要对照对应的bean
            if (handler instanceof String) {
                String handlerName = (String)handler;
                handler = this.obtainApplicationContext().getBean(handlerName);
            }
       //封装handler执行链
            HandlerExecutionChain executionChain = this.getHandlerExecutionChain(handler, request);
            //打印相关log信息
            if (this.logger.isTraceEnabled()) {
                this.logger.trace("Mapped to " + handler);
            } else if (this.logger.isDebugEnabled() && !request.getDispatcherType().equals(DispatcherType.ASYNC)) {
                this.logger.debug("Mapped to " + executionChain.getHandler());
            }
//判断是否是跨域问题;
            if (this.hasCorsConfigurationSource(handler) || CorsUtils.isPreFlightRequest(request)) {
           //与跨域配置信息映射的容器
                CorsConfiguration config = this.corsConfigurationSource != null ? this.corsConfigurationSource.getCorsConfiguration(request) : null;
                CorsConfiguration handlerConfig = this.getCorsConfiguration(handler, request);
                config = config != null ? config.combine(handlerConfig) : handlerConfig;
                //得到跨域的handeler执行链
                executionChain = this.getCorsHandlerExecutionChain(request, executionChain, config);
            }

            return executionChain;
        }
    }

           

再看, 根据request,获取handler,

Object handler = this.getHandlerInternal(request);

往下追,在AbstractHandlerMethodMapping类中,源码为:

protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
 //获取request中的url,用来匹配handler
        String lookupPath = this.getUrlPathHelper().getLookupPathForRequest(request);
        request.setAttribute(LOOKUP_PATH, lookupPath);
        this.mappingRegistry.acquireReadLock();

        HandlerMethod var4;
        try {
        //根据路径寻找Handler;
            HandlerMethod handlerMethod = this.lookupHandlerMethod(lookupPath, request);
            // 根据HandlerMethod中的bean来实例化Handler并添加进HandlerMethod;
            var4 = handlerMethod != null ? handlerMethod.createWithResolvedBean() : null;
        } finally {
            this.mappingRegistry.releaseReadLock();
        }

        return var4;
    }
           

根据路径来寻找HandlerMethod;

HandlerMethod handlerMethod = this.lookupHandlerMethod(lookupPath, request);

源码:

@Nullable
    protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {
        List<AbstractHandlerMethodMapping<T>.Match> matches = new ArrayList();//直接匹配
        List<T> directPathMatches = this.mappingRegistry.getMappingsByUrl(lookupPath);
        //如果有就直接添加到匹配列表中
        if (directPathMatches != null) {
            this.addMatchingMappings(directPathMatches, matches, request);
        }
 // 还没有匹配的,就遍历所有的处理方法查找
        if (matches.isEmpty()) { 
        // No choice but to go through all 
      this.addMatchingMappings(this.mappingRegistry.getMappings().keySet(), matches, request);
        }
 // 找到了1匹配的
        if (!matches.isEmpty()) {
             //排序之后,获取第一个
            AbstractHandlerMethodMapping<T>.Match bestMatch = (AbstractHandlerMethodMapping.Match)matches.get(0);
            //如果有多个匹配的,会找到第二个最适合的进行比较一下
            if (matches.size() > 1) {
                Comparator<AbstractHandlerMethodMapping<T>.Match> comparator = new AbstractHandlerMethodMapping.MatchComparator(this.getMappingComparator(request));
                
                matches.sort(comparator);
           
                bestMatch = (AbstractHandlerMethodMapping.Match)matches.get(0);
                
                if (this.logger.isTraceEnabled()) {
                    this.logger.trace(matches.size() + " matching mappings: " + matches);
                }

                if (CorsUtils.isPreFlightRequest(request)) {
                    return PREFLIGHT_AMBIGUOUS_MATCH;
                }

                AbstractHandlerMethodMapping<T>.Match secondBestMatch = (AbstractHandlerMethodMapping.Match)matches.get(1);
                if (comparator.compare(bestMatch, secondBestMatch) == 0) {
                    Method m1 = bestMatch.handlerMethod.getMethod();
                    Method m2 = secondBestMatch.handlerMethod.getMethod();
                    String uri = request.getRequestURI();
                    throw new IllegalStateException("Ambiguous handler methods mapped for '" + uri + "': {" + m1 + ", " + m2 + "}");
                }
            }

            request.setAttribute(BEST_MATCHING_HANDLER_ATTRIBUTE, bestMatch.handlerMethod);
            //设置request参数
            this.handleMatch(bestMatch.mapping, lookupPath, request);
            //返回匹配处理url的方法
            return bestMatch.handlerMethod;
        } else {// 最后还没有找到,返回null
            return this.handleNoMatch(this.mappingRegistry.getMappings().keySet(), lookupPath, request);
        }
    }
           

在AbstractHandlerMapping类中的getHandler方法中,对handler进行根据request的handler直接获取。

(具体在AbstracHandlerMethodMapping类中)

然候如果没有就

获取默认Handler

如果上面没有获取到Handler,就会获取默认的Handler。
如果还获取不到就返回null。
           

处理String类型的Handler

如果上面处理完的Handler是String类型的,
就会根据这个handlerName获取bean。
           

封装Handler执行链

上面获取完Handler,就开始封装执行链了,
就是将我们配置的拦截器加入到执行链中去,getHandlerExecutionChain:
           
protected HandlerExecutionChain getHandlerExecutionChain(Object handler, HttpServletRequest request) {
	//如果当前Handler不是执行链类型,就使用一个新的执行链实例封装起来
    HandlerExecutionChain chain =
        (handler instanceof HandlerExecutionChain) ?
            (HandlerExecutionChain) handler : new HandlerExecutionChain(handler);
	//根据请求查询路径,获得当前的url
	String lookupPath = this.urlPathHelper.getLookupPathForRequest(request, LOOKUP_PATH);
	     //先获取适配类型的拦截器添加进去拦截器链的迭代器
        Iterator var5 = this.adaptedInterceptors.iterator();
         while(var5.hasNext()) {
            HandlerInterceptor interceptor = (HandlerInterceptor)var5.next();
            if (interceptor instanceof MappedInterceptor) {
                MappedInterceptor mappedInterceptor = (MappedInterceptor)interceptor;
                if (mappedInterceptor.matches(lookupPath, this.pathMatcher)) {
           //遍历拦截器,找到跟当前url对应的,添加进执行链中去 
               chain.addInterceptor(mappedInterceptor.getInterceptor());
                }
            } else {
            //获取适配类型的拦截器添加进去拦截器链
                chain.addInterceptor(interceptor);
            }
        }
    return chain;
}
           

获取对应请求的Handler适配器: HandlerAdapter ha = this.getHandlerAdapter(mappedHandler.getHandler());(DispatcherServlet类中的doDipatch()方法中)

getHandlerAdapter:

protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
        if (this.handlerAdapters != null) {
            Iterator var2 = this.handlerAdapters.iterator();
	//遍历所有的HandlerAdapter,找到和当前Handler匹配的就返回
    //我们这里会匹配到RequestMappingHandlerAdapter
            while(var2.hasNext()) {
                HandlerAdapter adapter = (HandlerAdapter)var2.next();
                if (adapter.supports(handler)) {
                    return adapter;
                }
            }
        }

        throw new ServletException("No adapter for handler [" + handler + "]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler");
    }
	
           

缓存的处理

也就是对last-modified的处理

执行拦截器的preHandle方法

 就是遍历所有的我们定义的interceptor,执行preHandle方法
           

使用Handler适配器执行当前的Handler:也就是:

mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

ha.handle执行当前handlerInternal,我们这里使用的是RequestMappingHandlerAdapter,首先会进入AbstractHandlerMethodAdapter的handle方法,然后该方法调用了本类的抽象的handlerInternal方法,RequestMappingHandlerAdapter集成了它:

protected ModelAndView handleInternal(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
        this.checkRequest(request);
        ModelAndView mav;
        if (this.synchronizeOnSession) {
            HttpSession session = request.getSession(false);
            if (session != null) {
                Object mutex = WebUtils.getSessionMutex(session);
                  // Execute invokeHandlerMethod in synchronized block if required.
                synchronized(mutex) {
                    mav = this.invokeHandlerMethod(request, response, handlerMethod);
                }
            } else {
                mav = this.invokeHandlerMethod(request, response, handlerMethod);
            }
        } else {
            mav = this.invokeHandlerMethod(request, response, handlerMethod);
        }

        if (!response.containsHeader("Cache-Control")) {
            if (this.getSessionAttributesHandler(handlerMethod).hasSessionAttributes()) {
            //Always prevent caching in case of session attribute management.
                this.applyCacheSeconds(response, this.cacheSecondsForSessionAttributeHandlers);
            } else {
                this.prepareResponse(response);
            }
        }
//返回封装的ModelAndView的mav
        return mav;
    }
           

再往下追,就是把handler封装的handlerMethod,完成请求参数的封装,和jason格式的转换;

@Nullable
    protected ModelAndView invokeHandlerMethod(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
        ServletWebRequest webRequest = new ServletWebRequest(request, response);

        Object result;
        try {
            WebDataBinderFactory binderFactory = this.getDataBinderFactory(handlerMethod);
            ModelFactory modelFactory = this.getModelFactory(handlerMethod, binderFactory);
            ServletInvocableHandlerMethod invocableMethod = this.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 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);
            asyncManager.setTaskExecutor(this.taskExecutor);
            asyncManager.setAsyncWebRequest(asyncWebRequest);
            asyncManager.registerCallableInterceptors(this.callableInterceptors);
            asyncManager.registerDeferredResultInterceptors(this.deferredResultInterceptors);
            if (asyncManager.hasConcurrentResult()) {
                result = asyncManager.getConcurrentResult();
                mavContainer = (ModelAndViewContainer)asyncManager.getConcurrentResultContext()[0];
                asyncManager.clearConcurrentResult();
                LogFormatUtils.traceDebug(this.logger, (traceOn) -> {
                    String formatted = LogFormatUtils.formatValue(result, !traceOn);
                    return "Resume with async result [" + formatted + "]";
                });
                invocableMethod = invocableMethod.wrapConcurrentResult(result);
            }

            invocableMethod.invokeAndHandle(webRequest, mavContainer, new Object[0]);
            if (!asyncManager.isConcurrentHandlingStarted()) {
                ModelAndView var15 = this.getModelAndView(mavContainer, modelFactory, webRequest);
                return var15;
            }

            result = null;
        } finally {
            webRequest.requestCompleted();
        }

        return (ModelAndView)result;
    }
           

组装默认视图名称

前缀和后缀名都加上

执行拦截器的postHandle方法

遍历intercepter的postHandle方法。

处理最后的结果,渲染之类的
           

DispatcherServlet类中的processDispatchResult方法:

private void processDispatchResult(HttpServletRequest request, HttpServletResponse response, @Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv, @Nullable Exception exception) throws Exception {
        boolean errorView = false;
        if (exception != null) {
            if (exception instanceof ModelAndViewDefiningException) {
                this.logger.debug("ModelAndViewDefiningException encountered", exception);
                mv = ((ModelAndViewDefiningException)exception).getModelAndView();
            } else {
                Object handler = mappedHandler != null ? mappedHandler.getHandler() : null;
                mv = this.processHandlerException(request, response, handler, exception);
                errorView = mv != null;
            }
        }
   // Did the handler return a view to render?
        if (mv != null && !mv.wasCleared()) {
          	//渲染
            this.render(mv, request, response);
            if (errorView) {
                WebUtils.clearErrorRequestAttributes(request);
            }
        } else if (this.logger.isTraceEnabled()) {
            this.logger.trace("No view rendering, null ModelAndView returned.");
        }

        if 
           // Concurrent handling started during a forward
           (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
            if (mappedHandler != null) {
                mappedHandler.triggerAfterCompletion(request, response, (Exception)null);
            }

        }
    }
    
           

重点看下render方法,进行渲染:

protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {
       //设置本地化
        Locale locale = this.localeResolver != null ? this.localeResolver.resolveLocale(request) : request.getLocale();
        response.setLocale(locale);
        String viewName = mv.getViewName();
        View view;
        if (viewName != null) {
            //解析视图名,得到视图
            view = this.resolveViewName(viewName, 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 = 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.isTraceEnabled()) {
            this.logger.trace("Rendering view [" + view + "] ");
        }

        try {
            if (mv.getStatus() != null) {
                response.setStatus(mv.getStatus().value());
            }
                 //委托给视图进行渲染
            view.render(mv.getModelInternal(), request, response);
        } catch (Exception var8) {
            if (this.logger.isDebugEnabled()) {
                this.logger.debug("Error rendering view [" + view + "]", var8);
            }

            throw var8;
        }
    }
    
           

view.render就是进行视图的渲染,然后跳转页面等处理。

到这里大概的流程就走完了。其中涉及到的东西还有很多,暂先不做详细处理。

继续阅读