• 04SpringMVC之请求处理流程


    SpringMVC之请求处理流程

    我们知道DispatcherServlet就是一个HttpServlet,而HttpServlet的请求就从doGet/doPost开始

    DispatcherServlet本身没有实现doGet/doPost,而由他的父类FrameworkServlet实现,源码如下

    FrameworkServlet.doGet/doPost

    @Override
    protected final void doGet(HttpServletRequest request, HttpServletResponse response)
    throws ServletException, IOException {
    
    	processRequest(request, response);
    }
    
    /**
    * Delegate POST requests to {@link #processRequest}.
    * @see #doService
    */
    @Override
    protected final void doPost(HttpServletRequest request, HttpServletResponse response)
    throws ServletException, IOException {
    
    	processRequest(request, response);
    }
    

    processRequest

    protected final void processRequest(HttpServletRequest request, HttpServletResponse response)
    			throws ServletException, IOException {
    
        long startTime = System.currentTimeMillis();
        Throwable failureCause = null;
        
        //previousLocaleContext获取和当前线程相关的LocaleContext,根据已有请求构造一个新的和当前线程相关的LocaleContext
        LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
        LocaleContext localeContext = buildLocaleContext(request);
    
        //previousAttributes获取和当前线程绑定的RequestAttributes
        RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();
        
        //为已有请求构造新的ServletRequestAttributes,加入预绑定属性
        ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes);
    
    	//异步请求处理
        WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
        asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor());
    
        //initContextHolders让新构造的RequestAttributes和ServletRequestAttributes和当前线程绑定,加入到ThreadLocal,完成绑定
        initContextHolders(request, localeContext, requestAttributes);
    
        try {
            //抽象方法doService由FrameworkServlet子类DispatcherServlet重写
            doService(request, response);
        }catch (ServletException | IOException ex) {
            failureCause = ex;
            throw ex;
        }catch (Throwable ex) {
            failureCause = ex;
            throw new NestedServletException("Request processing failed", ex);
        }finally {
            //解除RequestAttributes,ServletRequestAttributes和当前线程的绑定
            resetContextHolders(request, previousLocaleContext, previousAttributes);
            if (requestAttributes != null) {
                requestAttributes.requestCompleted();
            }
            logResult(request, response, failureCause, asyncManager);
            //注册监听事件ServletRequestHandledEvent,在调用上下文的时候产生Event
            publishRequestHandledEvent(request, response, startTime, failureCause);
        }
    }
    

    doService

    protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
        logRequest(request);
    
        // Keep a snapshot of the request attributes in case of an include,
        // to be able to restore the original attributes after the include.
        Map<String, Object> attributesSnapshot = null;
        if (WebUtils.isIncludeRequest(request)) {
            attributesSnapshot = new HashMap<>();//保存request域中的数据,存一份快照
            Enumeration<?> attrNames = request.getAttributeNames();
            while (attrNames.hasMoreElements()) {
                String attrName = (String) attrNames.nextElement();
                if (this.cleanupAfterInclude || attrName.startsWith(DEFAULT_STRATEGIES_PREFIX)) {
                    attributesSnapshot.put(attrName, request.getAttribute(attrName));
                }
            }
        }
    
        //设置web应用上下文
        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());
    
        //请求刷新时保存属性
        if (this.flashMapManager != null) {
            FlashMap inputFlashMap = this.flashMapManager.retrieveAndUpdate(request, response);
            if (inputFlashMap != null) {
                request.setAttribute(INPUT_FLASH_MAP_ATTRIBUTE, Collections.unmodifiableMap(inputFlashMap));
            }
            //Flash attributes 在对请求的重定向生效之前被临时存储(通常是在session)中,并且在重定向之后被立即移除
            request.setAttribute(OUTPUT_FLASH_MAP_ATTRIBUTE, new FlashMap());
            //FlashMap 被用来管理 flash attributes 而 FlashMapManager 则被用来存储,获取和管理 FlashMap 实体
            request.setAttribute(FLASH_MAP_MANAGER_ATTRIBUTE, this.flashMapManager);
        }
    
        try {
        	//核心方法
            doDispatch(request, response);
        }finally {
            if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
                // Restore the original attribute snapshot, in case of an include.
                if (attributesSnapshot != null) {
                    restoreAttributesAfterInclude(request, attributesSnapshot);//将快照覆盖回去
                }
            }
        }
    }
    
    

    DispatcherServlet.doDispatch

    这个方法就是处理请求的核心方法

    protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
        HttpServletRequest processedRequest = request;
        HandlerExecutionChain mappedHandler = null;
        boolean multipartRequestParsed = false;
    
    	//异步处理,webflux相关
        WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
    
        try {
            ModelAndView mv = null;
            Exception dispatchException = null;
    
            try {
                //将request转换成multipartRequest,并检查是否解析成功(判断是否有文件上传)
                processedRequest = checkMultipart(request);
                multipartRequestParsed = (processedRequest != request);
    
                //根据请求信息获取handler(包含了拦截器),组成HandlerExecutionChain
                mappedHandler = getHandler(processedRequest);
                if (mappedHandler == null) {
                    noHandlerFound(processedRequest, response);
                    return;
                }
    
                //根据handler获取adapter---命名为ha
                HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
    
                // Process last-modified header, if supported by the handler.
                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;
                    }
                }
    			//执行拦截器逻辑
                if (!mappedHandler.applyPreHandle(processedRequest, response)) {
                    return;
                }
    
                //执行业务处理,返回视图模型
                mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
    
                if (asyncManager.isConcurrentHandlingStarted()) {
                    return;
                }
    			//给视图模型设置viewName
                applyDefaultViewName(processedRequest, mv);
                //拦截器逻辑
                mappedHandler.applyPostHandle(processedRequest, response, mv);
            }catch (Exception ex) {
                dispatchException = ex;
            }catch (Throwable err) {
                // As of 4.3, we're processing Errors thrown from handler methods as well,
                // making them available for @ExceptionHandler methods and other scenarios.
                dispatchException = new NestedServletException("Handler dispatch failed", err);
            }
            //处理请求结果,使用了组件LocaleResolver, ViewResolver和ThemeResolver(view#render)
            processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
        }catch (Exception ex) {
            triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
        }catch (Throwable err) {
            triggerAfterCompletion(processedRequest, response, mappedHandler,
                                   new NestedServletException("Handler processing failed", err));
        }finally {
            if (asyncManager.isConcurrentHandlingStarted()) {
                // Instead of postHandle and afterCompletion
                if (mappedHandler != null) {
                    mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
                }
            }else {
                // Clean up any resources used by a multipart request.
                if (multipartRequestParsed) {
                    cleanupMultipart(processedRequest);
                }
            }
        }
    }
    

    DispatcherServlet.getHandler

    @Nullable
    protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
       if (this.handlerMappings != null) {
          for (HandlerMapping mapping : this.handlerMappings) {
             HandlerExecutionChain handler = mapping.getHandler(request);
             if (handler != null) {
                return handler;
             }
          }
       }
       return null;
    }
    

    这里的this.handlerMappings就是9大组件之一,在web容器启动时方法中初始化.

    过滤器和拦截器

    • 过滤器拦截DispatcherServlet,属于web服务器层面
    • 拦截器可以拦截到具体方法,属于springmvc框架

    HandlerExecutionChain对象包含处理器+拦截器链

    public class HandlerExecutionChain {
    
       private static final Log logger = LogFactory.getLog(HandlerExecutionChain.class);
    
    	//真正的处理器
       private final Object handler;
    
    	//配置在springmvc配置文件的拦截器
       @Nullable
       private HandlerInterceptor[] interceptors;
    
    	//所有拦截器
       @Nullable
       private List<HandlerInterceptor> interceptorList;
    
    }
    
    AbstractHandlerMapping.getHandler
    /**
     * 返回请求处理的HandlerExecutionChain,从AbstractHandlerMapping中的adaptedInterceptors和mappedInterceptors属性中获取
     */
    @Override
    @Nullable
    public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
        // getHandlerInternal()为抽象方法,具体需子类实现
        Object handler = getHandlerInternal(request);
        if (handler == null) {
            handler = getDefaultHandler();
        }
        if (handler == null) {
            return null;
        }
        // Bean name or resolved handler?
        if (handler instanceof String) {
            String handlerName = (String) handler;
            handler = obtainApplicationContext().getBean(handlerName);
        }
        
        // 将请求处理器封装为HandlerExectionChain
        HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);
        
        // 对跨域的处理
        if (CorsUtils.isCorsRequest(request)) {
            CorsConfiguration globalConfig = this.globalCorsConfigSource.getCorsConfiguration(request);
            CorsConfiguration handlerConfig = getCorsConfiguration(handler, request);
            CorsConfiguration config = (globalConfig != null ? globalConfig.combine(handlerConfig) : handlerConfig);
            executionChain = getCorsHandlerExecutionChain(request, executionChain, config);
        }
        return executionChain;
    }
    
    
    AbstractHandlerMapping.getHandlerExecutionChain
    /**
     * 构建handler处理器的HandlerExecutionChain,包括拦截器
     */
    protected HandlerExecutionChain getHandlerExecutionChain(Object handler, HttpServletRequest request) {
    	//将handler处理成HandlerExecutionChain
        HandlerExecutionChain chain = (handler instanceof HandlerExecutionChain ?
                (HandlerExecutionChain) handler : new HandlerExecutionChain(handler));
    
        String lookupPath = this.urlPathHelper.getLookupPathForRequest(request);
        // 迭代添加拦截器,private final List<HandlerInterceptor> adaptedInterceptors = new ArrayList<>();
        for (HandlerInterceptor interceptor : this.adaptedInterceptors) {
            // 如果拦截器是MappedInterceptor,判断是否对该handler进行拦截,是的情况下添加
            if (interceptor instanceof MappedInterceptor) {
                MappedInterceptor mappedInterceptor = (MappedInterceptor) interceptor;
                if (mappedInterceptor.matches(lookupPath, this.pathMatcher)) {
                    chain.addInterceptor(mappedInterceptor.getInterceptor());
                }
            } else { 
            	// HandlerInterceptor直接添加,即通过HandingMapping属性配置的拦截器
                chain.addInterceptor(interceptor);
            }
        }
        return chain;
    }
    
    
    
    // new HandlerExecutionChain(handler),将handler变成HandlerExecutionChain
    public HandlerExecutionChain(Object handler, @Nullable HandlerInterceptor... interceptors) {
    	if (handler instanceof HandlerExecutionChain) {
    		HandlerExecutionChain originalChain = (HandlerExecutionChain) handler;
    		this.handler = originalChain.getHandler();
    		//new一个interceptorList
    		this.interceptorList = new ArrayList<>();
    		//将HandlerInterceptor合并到interceptorList
    		CollectionUtils.mergeArrayIntoCollection(originalChain.getInterceptors(), this.interceptorList);
    		CollectionUtils.mergeArrayIntoCollection(interceptors, this.interceptorList);
    	}
    	else {
    		this.handler = handler;
    		this.interceptors = interceptors;
    	}
    }
    
    
    总结一下

    就是把处理器+拦截器链组合成HandlerExecutionChain对象,看一下最普通的返回值

    image

    DispatcherServlet.getHandlerAdapter

    protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
       if (this.handlerAdapters != null) {
          for (HandlerAdapter adapter : this.handlerAdapters) {
          	//判断adapter是否适配handler,适配的话就返回,一般情况下handler是HandlerMethod类型
             if (adapter.supports(handler)) {
                //这里一般返回RequestMappingHandlerAdapter
                return adapter;
             }
          }
       }
       throw new ServletException("No adapter for handler [" + handler +
             "]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler");
             
    }
    
    
    //this.handlerAdapters
    private List<HandlerAdapter> handlerAdapters;
    
    //RequestMappingHandlerAdapter的父类AbstractHandlerMethodAdapter的代码实现
    @Override
    public final boolean supports(Object handler) {
    	return (handler instanceof HandlerMethod && supportsInternal((HandlerMethod) handler));
    }
    

    HandlerAdapter就是适配器对象

    RequestMappingHandlerAdapter

    RequestMappingHandlerAdapter就是处理@RequestMapping注解的适配器

    /**
     * Extension of {@link AbstractHandlerMethodAdapter} that supports
     * {@link RequestMapping @RequestMapping} annotated {@link HandlerMethod HandlerMethods}.
     *
     * <p>Support for custom argument and return value types can be added via
     * {@link #setCustomArgumentResolvers} and {@link #setCustomReturnValueHandlers},
     * or alternatively, to re-configure all argument and return value types,
     * use {@link #setArgumentResolvers} and {@link #setReturnValueHandlers}.
     *
     * @author Rossen Stoyanchev
     * @author Juergen Hoeller
     * @since 3.1
     * @see HandlerMethodArgumentResolver
     * @see HandlerMethodReturnValueHandler
     */
    public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter
          implements BeanFactoryAware, InitializingBean {
          
          ...
          
          
    }
    

    HandlerExecutionChain.applyPreHandle

    执行拦截器逻辑

    boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
       HandlerInterceptor[] interceptors = getInterceptors();
       if (!ObjectUtils.isEmpty(interceptors)) {
          for (int i = 0; i < interceptors.length; i++) {
             HandlerInterceptor interceptor = interceptors[i];
             //执行拦截器preHandle方法
             if (!interceptor.preHandle(request, response, this.handler)) {
             	//拦截后的方法afterCompletion
                triggerAfterCompletion(request, response, null);
                return false;
             }
             this.interceptorIndex = i;
          }
       }
       return true;
    }
    

    HandlerAdapter.handle

    使用适配器执行业务处理,返回视图模型

    @Override
    @Nullable
    public final ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
          throws Exception {
    
       return handleInternal(request, response, (HandlerMethod) handler);
    }
    
    RequestMappingHandlerAdapter.handleInternal
    @Override
    protected ModelAndView handleInternal(HttpServletRequest request,
          HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
    
       ModelAndView mav;
       checkRequest(request);
    
       // Execute invokeHandlerMethod in synchronized block if required.
       if (this.synchronizeOnSession) {
          HttpSession session = request.getSession(false);
          if (session != null) {
             Object mutex = WebUtils.getSessionMutex(session);
             synchronized (mutex) {
                mav = invokeHandlerMethod(request, response, handlerMethod);
             }
          }
          else {
             // No HttpSession available -> no mutex necessary
             mav = invokeHandlerMethod(request, response, handlerMethod);
          }
       }
       else {
       		//核心方法
          // No synchronization on session demanded at all...
          mav = invokeHandlerMethod(request, response, handlerMethod);
       }
    
       if (!response.containsHeader(HEADER_CACHE_CONTROL)) {
          if (getSessionAttributesHandler(handlerMethod).hasSessionAttributes()) {
             applyCacheSeconds(response, this.cacheSecondsForSessionAttributeHandlers);
          }
          else {
             prepareResponse(response);
          }
       }
    
       return mav;
    }
    

    RequestMappingHandlerAdapter.invokeHandlerMethod

    @Nullable
    protected ModelAndView invokeHandlerMethod(HttpServletRequest request,
          HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
    
       ServletWebRequest webRequest = new ServletWebRequest(request, response);
       try {
          WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod);
          ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory);
    
          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 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()) {
             Object result = asyncManager.getConcurrentResult();
             mavContainer = (ModelAndViewContainer) asyncManager.getConcurrentResultContext()[0];
             asyncManager.clearConcurrentResult();
             LogFormatUtils.traceDebug(logger, traceOn -> {
                String formatted = LogFormatUtils.formatValue(result, !traceOn);
                return "Resume with async result [" + formatted + "]";
             });
             invocableMethod = invocableMethod.wrapConcurrentResult(result);
          }
    	 //核心方法
          invocableMethod.invokeAndHandle(webRequest, mavContainer);
          if (asyncManager.isConcurrentHandlingStarted()) {
             return null;
          }
    
          return getModelAndView(mavContainer, modelFactory, webRequest);
       }
       finally {
          webRequest.requestCompleted();
       }
    }
    

    invocableMethod.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()) {
             disableContentCachingIfNecessary(webRequest);
             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(formatErrorForReturnValue(returnValue), ex);
          }
          throw ex;
       }
    }
    
    invokeForRequest();

    这个方法就是在处理@RequestMapper注解的方法,并通过反射得到返回值

    @Nullable
    public Object invokeForRequest(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
          Object... providedArgs) throws Exception {
    	//获取方法参数
       Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
       if (logger.isTraceEnabled()) {
          logger.trace("Arguments: " + Arrays.toString(args));
       }
       //利用反射执行方法,得到返回值
       return doInvoke(args);
    }
    

    doInvoke(Object... args)

    @Nullable
    protected Object doInvoke(Object... args) throws Exception {
       ReflectionUtils.makeAccessible(getBridgedMethod());
       try {
       	  //这里getBridgedMethod()就是Method,得到Method然后反射执行方法,得到返回值
          return getBridgedMethod().invoke(getBean(), args);
       }
       catch (IllegalArgumentException ex) {
          assertTargetBean(getBridgedMethod(), getBean(), args);
          String text = (ex.getMessage() != null ? ex.getMessage() : "Illegal argument");
          throw new IllegalStateException(formatInvokeError(text, args), ex);
       }
       catch (InvocationTargetException ex) {
          // Unwrap for HandlerExceptionResolvers ...
          Throwable targetException = ex.getTargetException();
          if (targetException instanceof RuntimeException) {
             throw (RuntimeException) targetException;
          }
          else if (targetException instanceof Error) {
             throw (Error) targetException;
          }
          else if (targetException instanceof Exception) {
             throw (Exception) targetException;
          }
          else {
             throw new IllegalStateException(formatInvokeError("Invocation failure", args), targetException);
          }
       }
    }
    

    这里得到返回值后,springmvc会进行一系列处理,最后用IO write出去

    HandlerExecutionChain.applyPostHandle

    执行拦截器中postHandle方法

    void applyPostHandle(HttpServletRequest request, HttpServletResponse response, @Nullable ModelAndView mv)
          throws Exception {
    
       HandlerInterceptor[] interceptors = getInterceptors();
       if (!ObjectUtils.isEmpty(interceptors)) {
          for (int i = interceptors.length - 1; i >= 0; i--) {
             HandlerInterceptor interceptor = interceptors[i];
             interceptor.postHandle(request, response, this.handler, mv);
          }
       }
    }
    

    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) {
       		//ModelAndViewDefiningException类型,会携带对应的ModelAndView
          if (exception instanceof ModelAndViewDefiningException) {
             logger.debug("ModelAndViewDefiningException encountered", exception);
             mv = ((ModelAndViewDefiningException) exception).getModelAndView();
          }
          else {
          	//由对应的处理器handler进行异常处理,返回ModelAndView。其中使用了HandlerExceptionResolver。
             Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
             mv = processHandlerException(request, response, handler, exception);
             errorView = (mv != null);
          }
       }
    
       // Did the handler return a view to render?
       if (mv != null && !mv.wasCleared()) {
          render(mv, request, response);
          if (errorView) {
             WebUtils.clearErrorRequestAttributes(request);
          }
       }
       else {
          if (logger.isTraceEnabled()) {
             logger.trace("No view rendering, null ModelAndView returned.");
          }
       }
    
       if (WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
          // Concurrent handling started during a forward
          return;
       }
    
       if (mappedHandler != null) {
          //这里执行拦截器的afterCompletion方法
          // Exception (if any) is already handled..
          mappedHandler.triggerAfterCompletion(request, response, null);
       }
    }
    
    mappedHandler.triggerAfterCompletion
    void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, @Nullable Exception ex)
          throws Exception {
    
       HandlerInterceptor[] interceptors = getInterceptors();
       if (!ObjectUtils.isEmpty(interceptors)) {
          for (int i = this.interceptorIndex; i >= 0; i--) {
             HandlerInterceptor interceptor = interceptors[i];
             try {
                interceptor.afterCompletion(request, response, this.handler, ex);
             }
             catch (Throwable ex2) {
                logger.error("HandlerInterceptor.afterCompletion threw exception", ex2);
             }
          }
       }
    }
    

    HandlerMapping

    public interface HandlerMapping {
        // 返回请求的一个处理程序handler和拦截器interceptors
        @Nullable
        HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception;
    }
    

    HandlerMapping作用是将请求映射到处理程序,以及预处理和处理后的拦截器列表,映射是基于一些标准的,其中的细节因不同的实现而不相同。这是官方文档上一段描述,该接口只有一个方法getHandler(request),返回一个HandlerExecutionChain对象

    如果配置了这个标签,Spring MVC会默认添加RequestMappingHandlerMapping和RequestMappingHandlerAdapter

    <mvc:annotation-driven />
    

    Spring MVC会加载在当前系统中所有实现了HandlerMapping接口的bean,再进行按优先级排序。

    • SimpleUrlHandlerMapping 支持映射bean实例和映射bean名称,需要手工维护urlmap,通过key指定访问路径,

    • BeanNameUrlHandlerMapping 支持映射bean的name属性值,扫描以”/“开头的beanname,通过id指定访问路径

    • RequestMappingHandlerMapping 支持@Controller和@RequestMapping注解,通过注解定义访问路径

    image

    AbstractHandlerMapping

    模板类

    protected void initApplicationContext() throws BeansException {
        // 提供给子类去重写的,不过Spring并未去实现,提供扩展
        extendInterceptors(this.interceptors);
        // 加载拦截器
        detectMappedInterceptors(this.adaptedInterceptors);
        // 归并拦截器
        initInterceptors();
    }
    
    /**
     * 空实现
     */
    protected void extendInterceptors(List<Object> interceptors) {
    }
    
    /**
     * 从上下文中加载MappedInterceptor类型的拦截器,比如我们在配置文件中使用
     * <mvc:interceptors></mvc:interceptors>
     * 标签配置的拦截器
     */
    protected void detectMappedInterceptors(List<HandlerInterceptor> mappedInterceptors) {
        mappedInterceptors.addAll(
                BeanFactoryUtils.beansOfTypeIncludingAncestors(
                        obtainApplicationContext(), MappedInterceptor.class, true, false).values());
    }
    
    /**
     * 合并拦截器,即将<mvc:interceptors></mvc:interceptors>中的拦截器与HandlerMapping中通过属性interceptors设置的拦截器进行合并
     */
    protected void initInterceptors() {
        if (!this.interceptors.isEmpty()) {
            for (int i = 0; i < this.interceptors.size(); i++) {
                Object interceptor = this.interceptors.get(i);
                if (interceptor == null) {
                    throw new IllegalArgumentException("Entry number " + i + " in interceptors array is null");
                }
                // 适配后加入adaptedInterceptors
                this.adaptedInterceptors.add(adaptInterceptor(interceptor));
            }
        }
    }
    
    /**
     * 适配HandlerInterceptor和WebRequestInterceptor
     */
    protected HandlerInterceptor adaptInterceptor(Object interceptor) {
        if (interceptor instanceof HandlerInterceptor) {
            return (HandlerInterceptor) interceptor;
        } else if (interceptor instanceof WebRequestInterceptor) {
            return new WebRequestHandlerInterceptorAdapter((WebRequestInterceptor) interceptor);
        } else {
            throw new IllegalArgumentException("Interceptor type not supported: " + 							   													interceptor.getClass().getName());
        }
    }
    
    /**
     * 返回请求处理的HandlerExecutionChain,从AbstractHandlerMapping中的adaptedInterceptors和mappedInterceptors属性中获取
     */
    @Override
    @Nullable
    public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
        // getHandlerInternal()为抽象方法,具体需子类实现
        Object handler = getHandlerInternal(request);
        if (handler == null) {
            handler = getDefaultHandler();
        }
        if (handler == null) {
            return null;
        }
        // Bean name or resolved handler?
        if (handler instanceof String) {
            String handlerName = (String) handler;
            handler = obtainApplicationContext().getBean(handlerName);
        }
        
        // 将请求处理器封装为HandlerExectionChain
        HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);
        // 对跨域的处理
        if (CorsUtils.isCorsRequest(request)) {
            CorsConfiguration globalConfig = this.globalCorsConfigSource.getCorsConfiguration(request);
            CorsConfiguration handlerConfig = getCorsConfiguration(handler, request);
            CorsConfiguration config = (globalConfig != null ? globalConfig.combine(handlerConfig) : handlerConfig);
            executionChain = getCorsHandlerExecutionChain(request, executionChain, config);
        }
        return executionChain;
    }
    
    /**
     * 钩子函数,需子类实现
     */
    @Nullable
    protected abstract Object getHandlerInternal(HttpServletRequest request) throws Exception;
    
    /**
     * 构建handler处理器的HandlerExecutionChain,包括拦截器
     */
    protected HandlerExecutionChain getHandlerExecutionChain(Object handler, HttpServletRequest request) {
        HandlerExecutionChain chain = (handler instanceof HandlerExecutionChain ?
                (HandlerExecutionChain) handler : new HandlerExecutionChain(handler));
    
        String lookupPath = this.urlPathHelper.getLookupPathForRequest(request);
        // 迭代添加拦截器
        for (HandlerInterceptor interceptor : this.adaptedInterceptors) {
            // 如果拦截器是MappedInterceptor,判断是否对该handler进行拦截,是的情况下添加
            if (interceptor instanceof MappedInterceptor) {
                MappedInterceptor mappedInterceptor = (MappedInterceptor) interceptor;
                if (mappedInterceptor.matches(lookupPath, this.pathMatcher)) {
                    chain.addInterceptor(mappedInterceptor.getInterceptor());
                }
            } else { // HandlerInterceptor直接添加,即通过HandingMapping属性配置的拦截器
                chain.addInterceptor(interceptor);
            }
        }
        return chain;
    }
    

    RequestMappingHandlerMapping

    处理注解@RequestMapping及@Controller

    • 实现InitializingBean接口,增加了bean初始化的能力,也就是说在bean初始化时可以做一些控制
    • 实现EmbeddedValueResolverAware接口,即增加了读取属性文件的能力

    继承自AbstractHandlerMethodMapping

    //RequestMappingHandlerMapping
    @Override
    public void afterPropertiesSet() {
        this.config = new RequestMappingInfo.BuilderConfiguration();
        this.config.setUrlPathHelper(getUrlPathHelper());
        this.config.setPathMatcher(getPathMatcher());
        this.config.setSuffixPatternMatch(this.useSuffixPatternMatch);
        this.config.setTrailingSlashMatch(this.useTrailingSlashMatch);
        this.config.setRegisteredSuffixPatternMatch(this.useRegisteredSuffixPatternMatch);
        this.config.setContentNegotiationManager(getContentNegotiationManager());
    
        super.afterPropertiesSet();
    }
    
    //AbstractHandlerMethodMapping
    @Override
    public void afterPropertiesSet() {
        initHandlerMethods();
    }
    protected void initHandlerMethods() {
        //获取上下文中所有bean的name,不包含父容器
        for (String beanName : getCandidateBeanNames()) {
            if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) {
                processCandidateBean(beanName);
            }
        }
        //日志记录HandlerMethods的总数量
        handlerMethodsInitialized(getHandlerMethods());
    }
    
    
    protected void processCandidateBean(String beanName) {
        Class<?> beanType = null;
        try {
            //根据name找出bean的类型
            beanType = obtainApplicationContext().getType(beanName);
        } catch (Throwable ex) {
            if (logger.isTraceEnabled()) {
                logger.trace("Could not resolve type for bean '" + beanName + "'", ex);
            }
        }
        //处理Controller和RequestMapping
        if (beanType != null && isHandler(beanType)) {
            detectHandlerMethods(beanName);
        }
    }
    
    //RequestMappingHandlerMapping
    @Override
    protected boolean isHandler(Class<?> beanType) {
        //获取@Controller和@RequestMapping
        return (AnnotatedElementUtils.hasAnnotation(beanType, Controller.class) ||
                AnnotatedElementUtils.hasAnnotation(beanType, RequestMapping.class));
    }
    
    //整个controller类的解析过程 
    protected void detectHandlerMethods(Object handler) {
        //根据name找出bean的类型
    	Class<?> handlerType = (handler instanceof String ?
    				obtainApplicationContext().getType((String) handler) : handler.getClass());
    
    	if (handlerType != null) {
            //获取真实的controller,如果是代理类获取父类
    		Class<?> userType = ClassUtils.getUserClass(handlerType);
            //对真实的controller所有的方法进行解析和处理  key为方法对象,T为注解封装后的对象RequestMappingInfo
    		Map<Method, T> methods = MethodIntrospector.selectMethods(userType,
    				(MethodIntrospector.MetadataLookup<T>) method -> {
    						try {
     // 调用子类RequestMappingHandlerMapping的getMappingForMethod方法进行处理,即根据RequestMapping注解信息创建匹配条件RequestMappingInfo对象
    							return getMappingForMethod(method, userType);
    						}catch (Throwable ex) {
    							throw new IllegalStateException("Invalid mapping on handler class [" +
    									userType.getName() + "]: " + method, ex);
    						}
    					});
    		if (logger.isTraceEnabled()) {
    			logger.trace(formatMappings(userType, methods));
    		}
    		methods.forEach((method, mapping) -> {
                    //找出controller中可外部调用的方法
    			Method invocableMethod = AopUtils.selectInvocableMethod(method, userType);
                    //注册处理方法
    				registerHandlerMethod(handler, invocableMethod, mapping);
    		});
    	}
    }
    
    //requestMapping封装成RequestMappingInfo对象
    protected RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType) {
        RequestMappingInfo info = createRequestMappingInfo(method);//解析方法上的requestMapping
        if (info != null) {
            //解析方法所在类上的requestMapping
            RequestMappingInfo typeInfo = createRequestMappingInfo(handlerType);
            if (typeInfo != null) {
                info = typeInfo.combine(info);//合并类和方法上的路径,比如Controller类上有@RequestMapping("/demo"),方法的@RequestMapping("/demo1"),结果为"/demo/demo1"
            }
            String prefix = getPathPrefix(handlerType);//合并前缀
            if (prefix != null) {
                info = RequestMappingInfo.paths(prefix).options(this.config).build().combine(info);
            }
        }
        return info;
    }
    
    private RequestMappingInfo createRequestMappingInfo(AnnotatedElement element) {
        //找到方法上的RequestMapping注解
        RequestMapping requestMapping = AnnotatedElementUtils.findMergedAnnotation(element, 					   													RequestMapping.class);
        //获取自定义的类型条件(自定义的RequestMapping注解)
        RequestCondition<?> condition = (element instanceof Class ?
                                         		getCustomTypeCondition((Class<?>) element) : 		 			  											  getCustomMethodCondition((Method) element));
        return (requestMapping != null ? createRequestMappingInfo(requestMapping, condition) : null);
    }
    
    protected RequestMappingInfo createRequestMappingInfo(
        RequestMapping requestMapping, @Nullable RequestCondition<?> customCondition) {
    
        //获取RequestMapping注解的属性,封装成RequestMappingInfo对象
        RequestMappingInfo.Builder builder = RequestMappingInfo
            .paths(resolveEmbeddedValuesInPatterns(requestMapping.path()))
            .methods(requestMapping.method())
            .params(requestMapping.params())
            .headers(requestMapping.headers())
            .consumes(requestMapping.consumes())
            .produces(requestMapping.produces())
            .mappingName(requestMapping.name());
        if (customCondition != null) {
            builder.customCondition(customCondition);
        }
        return builder.options(this.config).build();
    }
    
    //再次封装成对应的对象    面向对象编程  每一个属性都存在多个值得情况需要排重封装
    @Override
    public RequestMappingInfo build() {
        ContentNegotiationManager manager = this.options.getContentNegotiationManager();
    
        PatternsRequestCondition patternsCondition = new PatternsRequestCondition(
            this.paths, this.options.getUrlPathHelper(), this.options.getPathMatcher(),
            this.options.useSuffixPatternMatch(), this.options.useTrailingSlashMatch(),
            this.options.getFileExtensions());
    
        return new RequestMappingInfo(this.mappingName, patternsCondition,
                                      new RequestMethodsRequestCondition(this.methods),
                                      new ParamsRequestCondition(this.params),
                                      new HeadersRequestCondition(this.headers),
                                      new ConsumesRequestCondition(this.consumes, this.headers),
                                      new ProducesRequestCondition(this.produces, this.headers, manager),
                                      this.customCondition);
    }
    

    registerHandlerMethod

    protected void registerHandlerMethod(Object handler, Method method, T mapping) {
       this.mappingRegistry.register(mapping, handler, method);
    }
    //mapping是RequestMappingInfo对象   handler是controller类的beanName   method为接口方法
    public void register(T mapping, Object handler, Method method) {
        ...
        this.readWriteLock.writeLock().lock();
        try {
            //beanName和method封装成HandlerMethod对象
            HandlerMethod handlerMethod = createHandlerMethod(handler, method);
            //验证RequestMappingInfo是否有对应不同的method,有则抛出异常
            validateMethodMapping(handlerMethod, mapping);
            //RequestMappingInfo和handlerMethod绑定
            this.mappingLookup.put(mapping, handlerMethod);
    
            List<String> directUrls = getDirectUrls(mapping);//可以配置多个url
            for (String url : directUrls) {
                //url和RequestMappingInfo绑定   可以根据url找到RequestMappingInfo,再找到handlerMethod
                this.urlLookup.add(url, mapping);
            }
    
            String name = null;
            if (getNamingStrategy() != null) {
                name = getNamingStrategy().getName(handlerMethod, mapping);
                addMappingName(name, handlerMethod);//方法名和Method绑定
            }
    
            CorsConfiguration corsConfig = initCorsConfiguration(handler, method, mapping);
            if (corsConfig != null) {
                this.corsLookup.put(handlerMethod, corsConfig);
            }
    		//将RequestMappingInfo  url  handlerMethod绑定到MappingRegistration对象  放入map
            this.registry.put(mapping, new MappingRegistration<>(mapping, handlerMethod, directUrls, name));
        }
        finally {
            this.readWriteLock.writeLock().unlock();
        }
    }
    

    HandlerAdapters

    Spring MVC为我们提供了多种处理用户的处理器(Handler),Spring实现的处理器类型有Servlet、Controller、HttpRequestHandler以及注解类型的处理器,即我们可以通过实现这些接口或者注解我们的类来使用这些处理器,那么针对不同类型的处理器,如何将用户请求转发到相应类型的处理器方法中的呢,这就需求Spring MVC的处理器适配器来完成适配操作,这就是处理器适配器要完成的工作。

    • SimpleServletHandlerAdapter 适配Servlet处理器

    • HttpRerquestHandlerAdapter 适配HttpRequestHandler处理器

    • RequestMappingHandlerAdapter 适配注解处理器

    • SimpleControllerHandlerAdapter 适配Controller处理器

    Spring MVC默认使用的处理器适配器为:HttpRequestHandlerAdapter、SimpleServletHandlerAdapter、RequestMappingHandlerAdapter三种。

    image

    RequestMappingHandlerAdapter

    通过继承抽象类AbstractHandlerMethodAdapter实现了HandlerAdapter接口

    请求适配给@RequestMapping类型的Handler处理。

    采用反射机制调用url请求对应的Controller中的方法(这其中还包括参数处理),返回执行结果值,完成HandlerAdapter的使命

    getLastModified直接返回-1

    @Override
    protected long getLastModifiedInternal(HttpServletRequest request, HandlerMethod handlerMethod) {
        return -1;
    }
    
    //通过父类调用
    @Override
    protected ModelAndView handleInternal(HttpServletRequest request,
            HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
     
        ModelAndView mav;
        checkRequest(request);
     
        // 判断当前是否需要支持在同一个session中只能线性地处理请求
        if (this.synchronizeOnSession) {
            // 获取当前请求的session对象
            HttpSession session = request.getSession(false);
            if (session != null) {
                // 为当前session生成一个唯一的可以用于锁定的key
                Object mutex = WebUtils.getSessionMutex(session);
                synchronized (mutex) {
                    // 对HandlerMethod进行参数等的适配处理,并调用目标handler
                    mav = invokeHandlerMethod(request, response, handlerMethod);
                }
            } else {
                // 如果当前不存在session,则直接对HandlerMethod进行适配
                mav = invokeHandlerMethod(request, response, handlerMethod);
            }
        } else {
            // 如果当前不需要对session进行同步处理,则直接对HandlerMethod进行适配
            mav = invokeHandlerMethod(request, response, handlerMethod);
        }
     
        // 判断当前请求头中是否包含Cache-Control请求头,如果不包含,则对当前response进行处理,为其设置过期时间
        if (!response.containsHeader(HEADER_CACHE_CONTROL)) {
            // 如果当前SessionAttribute中存在配置的attributes,则为其设置过期时间。
            // 这里SessionAttribute主要是通过@SessionAttribute注解生成的
            if (getSessionAttributesHandler(handlerMethod).hasSessionAttributes()) {
                applyCacheSeconds(response, this.cacheSecondsForSessionAttributeHandlers);
            } else {
                // 如果当前不存在SessionAttributes,则判断当前是否存在Cache-Control设置,
                // 如果存在,则按照该设置进行response处理,如果不存在,则设置response中的
                // Cache的过期时间为-1,即立即失效
                prepareResponse(response);
            }
        }
        return mav;
    }
    
    //核心处理流程
    @Nullable
    protected ModelAndView invokeHandlerMethod(HttpServletRequest request,
            HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
     
        ServletWebRequest webRequest = new ServletWebRequest(request, response);
        try {
            // 获取容器中全局配置的InitBinder和当前HandlerMethod所对应的Controller中配置的InitBinder,用于进行参数的绑定
            WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod);
            
            // 获取容器中全局配置的ModelAttribute和当前HandlerMethod所对应的Controller
            // 中配置的ModelAttribute,这些配置的方法将会在目标方法调用之前进行调用
            ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory);
     
            // 将handlerMethod封装为一个ServletInvocableHandlerMethod对象,该对象用于对当前request的整体调用流程进行了封装
            ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);
            
            if (this.argumentResolvers != null) {
                // 设置当前容器中配置的所有ArgumentResolver
                invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
            }
            
            if (this.returnValueHandlers != null) {
                // 设置当前容器中配置的所有ReturnValueHandler
                invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
            }
            
            // 将前面创建的WebDataBinderFactory设置到ServletInvocableHandlerMethod中
            invocableMethod.setDataBinderFactory(binderFactory);
            
            // 设置ParameterNameDiscoverer,该对象将按照一定的规则获取当前参数的名称
            invocableMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer);
     
            ModelAndViewContainer mavContainer = new ModelAndViewContainer();
            mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request));
            
            // 这里initModel()方法主要作用是调用前面获取到的@ModelAttribute标注的方法,
            // 从而达到@ModelAttribute标注的方法能够在目标Handler调用之前调用的目的
            modelFactory.initModel(webRequest, mavContainer, invocableMethod);
            mavContainer.setIgnoreDefaultModelOnRedirect(this.ignoreDefaultModelOnRedirect);
     
            // 获取当前的AsyncWebRequest,这里AsyncWebRequest的主要作用是用于判断目标
            // handler的返回值是否为WebAsyncTask或DefferredResult,如果是这两种中的一种,
            // 则说明当前请求的处理应该是异步的。所谓的异步,指的是当前请求会将Controller中
            // 封装的业务逻辑放到一个线程池中进行调用,待该调用有返回结果之后再返回到response中。
            // 这种处理的优点在于用于请求分发的线程能够解放出来,从而处理更多的请求,只有待目标任务
            // 完成之后才会回来将该异步任务的结果返回。
            AsyncWebRequest asyncWebRequest = WebAsyncUtils
                .createAsyncWebRequest(request, response);
            asyncWebRequest.setTimeout(this.asyncRequestTimeout);
     
            // 封装异步任务的线程池,request和interceptors到WebAsyncManager中
            WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
            asyncManager.setTaskExecutor(this.taskExecutor);
            asyncManager.setAsyncWebRequest(asyncWebRequest);
            asyncManager.registerCallableInterceptors(this.callableInterceptors);
            asyncManager.registerDeferredResultInterceptors(this.deferredResultInterceptors);
     
            // 这里就是用于判断当前请求是否有异步任务结果的,如果存在,则对异步任务结果进行封装
            if (asyncManager.hasConcurrentResult()) {
                Object result = asyncManager.getConcurrentResult();
                mavContainer = (ModelAndViewContainer) 
                    asyncManager.getConcurrentResultContext()[0];
                asyncManager.clearConcurrentResult();
                if (logger.isDebugEnabled()) {
                    logger.debug("Found concurrent result value [" + result + "]");
                }
                
                // 封装异步任务的处理结果,虽然封装的是一个HandlerMethod,但只是Spring简单的封装
                // 的一个Callable对象,该对象中直接将调用结果返回了。这样封装的目的在于能够统一的
                // 进行右面的ServletInvocableHandlerMethod.invokeAndHandle()方法的调用
                invocableMethod = invocableMethod.wrapConcurrentResult(result);
            }
     
            // 对请求参数进行处理,调用目标HandlerMethod,并且将返回值封装为一个ModelAndView对象
            invocableMethod.invokeAndHandle(webRequest, mavContainer);
            if (asyncManager.isConcurrentHandlingStarted()) {
                return null;
            }
     
            // 对封装的ModelAndView进行处理,主要是判断当前请求是否进行了重定向,如果进行了重定向,
            // 还会判断是否需要将FlashAttributes封装到新的请求中
            return getModelAndView(mavContainer, modelFactory, webRequest);
        } finally {
            // 调用request destruction callbacks和对SessionAttributes进行处理
            webRequest.requestCompleted();
        }
    }
    
    • 获取当前容器中使用@InitBinder注解注册的属性转换器;
    • 获取当前容器中使用@ModelAttribute标注但没有使用@RequestMapping标注的方法,并且在调用目标方法之前调用这些方法;
    • 判断目标handler返回值是否使用了WebAsyncTask或DefferredResult封装,如果封装了,则按照异步任务的方式进行执行;
    • 处理请求参数,调用目标方法和处理返回值。

    总结一下

    • 所有请求都会经过DispatcherServlet.doDispatch处理
    • 将请求的内容和拦截器链处理成HandlerExecutionChain,一般情况下handler是HandlerMethod类型的,
    • 寻找处理器对应的合适的适配器,一般情况下就是RequestMappingHandlerAdapter,用于处理@RequestMapping注解的适配器
    • 执行拦截器链的preHandle方法
    • 使用找到的适配器利用反射去执行方法,获取返回值,并响应
    • 执行拦截器链的postHandle方法
    • 执行拦截器链的afterCompletion方法

    执行结果如下

    /testJson----------------------preHandle
    testJson
    /testJson----------------------postHandle
    /testJson----------------------afterCompletion
    
  • 相关阅读:
    杭电 1176 免费馅饼
    IE 8 浏览器 F12 调试功能无法使用
    SqlServer 经常使用分页方法总结
    cocos2d-x 2.0下怎样让BOX2D DEBUG DRAW的方法笔记
    在DIV中自己主动换行
    linux之SQL语句简明教程---主键,外来键
    java数据库连接池技术简单使用
    Windows和linux双系统——改动默认启动顺序
    程序员实用的 MySQL sql 语句
    android 多项对话框
  • 原文地址:https://www.cnblogs.com/lusaisai/p/15983121.html
Copyright © 2020-2023  润新知