SpringMVC請求處理流程源碼

SpringMVC的請求處理流程圖

springmvc請求處理流程.png

總結(jié)

1、請求進(jìn)入DispatcherServlet,由DispatcherServlet 從HandlerMappings中提取對應(yīng)的Handler

2、根據(jù)Handle,然后去尋找對應(yīng)的處理器適配器(HandlerAdapter),拿到對應(yīng)HandlerAdapter后,這時候開始調(diào)用對應(yīng)的Handler處理業(yè)務(wù)邏輯

3、執(zhí)行完成之后返回一個ModeAndView,將結(jié)果交給我們的ViewResolver通過視圖名稱查找出對應(yīng)的視圖然后返回

4、渲染視圖:返回渲染后的視圖并響應(yīng)請求

請求處理器的初始化

1、DispacterServlet中的靜態(tài)塊

static {
    /**
     * static靜態(tài)塊,在類加載的時候執(zhí)行
     * 加載springWvc的默認(rèn)系統(tǒng)配置文件 DispatcherServlet.properties
     * 配置了 HandlerAdapter HandlerMapping ThemeResolver LocaleResolver ....
     * 放入到 defaultStrategies 對象中
     */
    ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, DispatcherServlet.class);
    defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);
}

DispatcherServlet.properties文件

# 僅列出部分
org.springframework.web.servlet.LocaleResolver=org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver
org.springframework.web.servlet.HandlerMapping=org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,\
    org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping
org.springframework.web.servlet.HandlerAdapter=org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,\
    org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,\
    org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter
org.springframework.web.servlet.HandlerExceptionResolver=org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver,\
    org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver,\
    org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver

2、當(dāng)Servlet容器啟動的時候,tomcat會調(diào)用這個DispatcherServlet的init方法

// HttpServletBean#init
public final void init() throws ServletException {
    // 執(zhí)行子類 FrameServlet的initServletBean方法
    initServletBean();
}
// FrameworkServlet#initServletBean
protected final void initServletBean() throws ServletException {
    // 初始化web環(huán)境 WebApplicationContext  ==child==> ConfigurableWebApplicationContext
    this.webApplicationContext = initWebApplicationContext();
    // 調(diào)用了initFrameworkServlet方法,這是一個空方法
    initFrameworkServlet();
}
// FrameworkServlet#initWebApplicationContext
protected WebApplicationContext initWebApplicationContext() {
    WebApplicationContext rootContext =
                WebApplicationContextUtils.getWebApplicationContext(getServletContext());
    ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.webApplicationContext;
    // 配置并且刷新容器
    configureAndRefreshWebApplicationContext(cwac);
}
// FrameworkServlet#configureAndRefreshWebApplicationContext
protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) {
    // 添加了一個容器刷新的監(jiān)聽器  =>  ContextRefreshListener
    wac.addApplicationListener(new SourceFilteringListener(wac, new ContextRefreshListener()));
    // 空方法
    postProcessWebApplicationContext(wac);
    // 容器的初始化共組
    applyInitializers(wac);
    // 刷新容器,走到AbstractApplicationContext中的refresh方法
    wac.refresh();
}

3、容器刷新時觸發(fā)的監(jiān)聽器ContextRefreshListener

// FrameworkServlet.ContextRefreshListener
private class ContextRefreshListener implements ApplicationListener<ContextRefreshedEvent> {
    @Override
    public void onApplicationEvent(ContextRefreshedEvent event) {
        FrameworkServlet.this.onApplicationEvent(event);
    }
}
public void onApplicationEvent(ContextRefreshedEvent event) {
    this.refreshEventReceived = true;
    synchronized (this.onRefreshMonitor) {
        onRefresh(event.getApplicationContext());
    }
}
// 調(diào)用子類的onRefresh方法
// DispatcherServlet#onRefresh
protected void onRefresh(ApplicationContext context) {
    // 初始化策略組件
    initStrategies(context);
}

4、初始化組件

// DispatcherServlet#initStrategies
protected void initStrategies(ApplicationContext context) {
    // 初始化文件上傳組件 beanName="multipartResolver"
    initMultipartResolver(context);
    // 初始化國際化組件 beanName="localeResolver"
    initLocaleResolver(context);
    // 初始化主題解析器,利用spring來做 beanName="themeResolver"
    initThemeResolver(context);
    // 初始化處理映射器 beanName="handlerMapping"
    initHandlerMappings(context);
    // 初始化處理器適配器 beanName="handlerAdapter"
    initHandlerAdapters(context);
    // 初始化異常處理器 beanName="handlerExceptionResolver"
    initHandlerExceptionResolvers(context);
    // 視圖解析器,在view為空時,根據(jù)請求來獲取視圖名稱 beanName="viewNameTranslator"
    initRequestToViewNameTranslator(context);
    // 初始化視圖解析器 beanName="viewResolver"
    initViewResolvers(context);
    // 重定向數(shù)據(jù)管理器 beanName="flashMapManager"
    initFlashMapManager(context);
}

初始化的組件中MultipartResolver、LocaleResolver、ThemeResolver、RequestToViewNameTranslator和FlashMapManager的初始化是直接去容器中獲取對應(yīng)的bean對象。

其他的組件默認(rèn)會去容器中尋找所有的 對應(yīng)類型 的對象。如果在容器中沒有找到對應(yīng)的對象,那么就會向容器中注冊 DispatcherServlet.properties 文件中的對應(yīng)的類。

// 去DispatcherServlet.properties文件中獲取 strategyInterface 對應(yīng)的組件
// 找到之后會將組件注冊到DispatcherServlet的對應(yīng)屬性上
protected <T> List<T> getDefaultStrategies(ApplicationContext context, Class<T> strategyInterface) {
    // 傳入的class全名,就對應(yīng)了DispatcherServlet.properties中的key
    String key = strategyInterface.getName();
    // 從配置文件中拿出配置
    String value = defaultStrategies.getProperty(key);
    if (value != null) {
        // 截取value中的 , 返回一個className 的數(shù)組
        String[] classNames = StringUtils.commaDelimitedListToStringArray(value);
        List<T> strategies = new ArrayList<>(classNames.length);
        for (String className : classNames) {
            // 根據(jù)反射創(chuàng)建出這個class對象
            Class<?> clazz = ClassUtils.forName(className, DispatcherServlet.class.getClassLoader());
            // 根據(jù)這個class 去createBean,這里就可以將 HandlerMapping 的組件放入容器中
            Object strategy = createDefaultStrategy(context, clazz);
            // 最后把策略對象返回
            strategies.add((T) strategy);
        }
        return strategies;
    } else {
        return new LinkedList<>();
    }
}

請求處理器處理請求

SpringMVC的核心處理器:DispatcherServlet,它本質(zhì)上也是一個HttpServlet,在配置SpringMVC的時候,我們會讓這個servlet隨著servlet容器的啟動而啟動(load-on-startup),并且攔截所有對當(dāng)前應(yīng)用程序的請求。

所以當(dāng)請求來了之后,首先會調(diào)用HttpServlet#service方法。具體會調(diào)用父類FrameworkServlet#service

// FrameworkServlet#service
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    HttpMethod httpMethod = HttpMethod.resolve(request.getMethod());
    // // 增加對HttpMethod.PATCH的支持
    if (httpMethod == HttpMethod.PATCH || httpMethod == null) {
        processRequest(request, response);
    }  else {
        // HttpServlet#service 進(jìn)行請求類型的分發(fā) -> 走到doGet/doPost/doPut
        // 最終都會和上面的一致調(diào)用 processRequest(request, response);
        super.service(request, response);
    }
}

處理請求之前的一些準(zhǔn)備

org.springframework.web.servlet.FrameworkServlet#processRequest
 -> org.springframework.web.servlet.DispatcherServlet#doService
    -> org.springframework.web.servlet.DispatcherServlet#doDispatch

處理請求的核心方法:DispatcherServlet#doDispatch

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
    // 檢查是否存在文件上傳
    HttpServletRequest processedRequest = checkMultipart(request);
    boolean multipartRequestParsed = (processedRequest != request);
    // 確定當(dāng)前請求是由哪一種handlerMapping來處理
    // 一種是選擇一個bean來處理,另一種是映射到一個方法上
    HandlerExecutionChain mappedHandler = getHandler(processedRequest);
    // 根據(jù)當(dāng)前的請求的 handlerMapping 來決定使用哪一種處理器適配器
    // HttpRequestHandlerAdapter
    // SimpleControllerHandlerAdapter
    // RequestMappingHandlerAdapter
    HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
    // 執(zhí)行所有配置這個uri的攔截器的 preHandle 方法
    if (!mappedHandler.applyPreHandle(processedRequest, response)) {
        // 只要存在一個攔截器的 preHandle 方法返回false,這里就會直接return
        return;
    }
   /** 調(diào)用實(shí)際的處理器來處理請求
     如果是匹配一個類的話,這里會直接把這個映射對象傳入 => mappedHandler.getHandler()
    HttpRequestHandlerAdapter => handle() => handler.handleRequest(request, response)
    SimpleControllerHandlerAdapter => handle() => handler.handleRequest(request, response);
    RequestMappingHandlerAdapter
        => AbstractHandlerMethodAdapter#handle
        => RequestMappingHandlerAdapter#handleInternal
          mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
    */
    // 是否是異步請求
    if (asyncManager.isConcurrentHandlingStarted()) {
        return;
    }
    // 如果存在mv對象,但是在mv中不存在view,這里就會應(yīng)用一個默認(rèn)的視圖對象
    applyDefaultViewName(processedRequest, mv);
    // 執(zhí)行所有配置這個uri的攔截器的 postHandle 方法
    mappedHandler.applyPostHandle(processedRequest, response, mv);
    // 處理返回值,渲染頁面,返回響應(yīng)...
    // 在這里面的最后一個執(zhí)行所有配置這個uri的攔截器的 afterCompletion 方法
    processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
}

doDispatch方法就是SpringMVC中請求處理流程的具體代碼實(shí)現(xiàn)。

核心組件 - HandlerMapping

在DispatcherServlet.properties中HandlerMapping組件存在兩種實(shí)現(xiàn)BeanNameUrlHandlerMapping和RequestMappingHandlerMapping

根據(jù)HandlerMapping中的getHandler方法來確定使用哪一種HandlerMapping。

// DispatcherServlet#getHandler
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
    if (this.handlerMappings == null)  return null;
    // 遍歷所有的handlerMappings
    for (HandlerMapping mapping : this.handlerMappings) {
        // 這里返回這個請求對應(yīng)的攔截器鏈
        HandlerExecutionChain handler = mapping.getHandler(request);
        if (handler != null) return handler;
    }
}

這里的getHandler方法非常關(guān)鍵,它不僅找出了需要使用的HandlerMapping還會去構(gòu)建好攔截器鏈。

public final HandlerExecutionChain getHandler(HttpServletRequest request) {
    // 確定使用哪一類的請求處理器
    // AbstractHandlerMethodMapping.getHandlerInternal  -> RequestMappingHandlerMapping
    // AbstractUrlHandlerMapping.getHandlerInternal     -> BeanNameUrlHandlerMapping
    Object handler = getHandlerInternal(request);
    if (handler == null) {
        handler = getDefaultHandler();
    }
    if (handler == null) {
        return null;
    }
    // 構(gòu)建攔截器鏈
    HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);
    return executionChain;
}

HandlerMapping

1、AbstractHandlerMethodMapping.getHandlerInternal

// @RequestMapping這種servlet
protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
    // uri localhost:9090/index => /index
    String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);
    // 讀鎖
    this.mappingRegistry.acquireReadLock();
    try {
        // 根據(jù)uri獲取對應(yīng)的處理方法
        HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request);
        return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null);
    } finally {
        this.mappingRegistry.releaseReadLock();
    }
}

2、AbstractUrlHandlerMapping.getHandlerInternal

// @Component("/index/2") 這種controller
protected Object getHandlerInternal(HttpServletRequest request) throws Exception {
    String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);
    // handlerMap<String, Object>中找可以找到
    Object handler = lookupHandler(lookupPath, request);
    if (handler != null) {
        return handler;
    }
    // 省略部分代碼
}

構(gòu)建攔截器鏈

只有在找出對應(yīng)的HandlerMapping后才會構(gòu)建攔截器鏈

// AbstractHandlerMapping#getHandlerExecutionChain
protected HandlerExecutionChain getHandlerExecutionChain(Object handler, HttpServletRequest request) {
    HandlerExecutionChain chain = (handler instanceof HandlerExecutionChain ?
                                    (HandlerExecutionChain) handler : new HandlerExecutionChain(handler));
    // uri
    String lookupPath = this.urlPathHelper.getLookupPathForRequest(request);
    // 遍歷所有的攔截器 我們自己實(shí)現(xiàn)了HandlerInterceptor會被springMVC包裝為MappedInterceptor
    for (HandlerInterceptor interceptor : this.adaptedInterceptors) {
        if (interceptor instanceof MappedInterceptor) {
            MappedInterceptor mappedInterceptor = (MappedInterceptor) interceptor;
            if (mappedInterceptor.matches(lookupPath, this.pathMatcher)) {
                chain.addInterceptor(mappedInterceptor.getInterceptor());
            }
        } else {
            chain.addInterceptor(interceptor);
        }
    }
    return chain;
}

調(diào)用MappedInterceptor#matches進(jìn)行攔截器匹配

// addPathPatterns -> includePatterns
// excludePathPatterns -> excludePatterns
public boolean matches(String lookupPath, PathMatcher pathMatcher) {
    PathMatcher pathMatcherToUse = (this.pathMatcher != null ? this.pathMatcher : pathMatcher);
    if (!ObjectUtils.isEmpty(this.excludePatterns)) {
        for (String pattern : this.excludePatterns) {
            if (pathMatcherToUse.match(pattern, lookupPath)) {
                return false;
            }
        }
    }
    if (ObjectUtils.isEmpty(this.includePatterns)) {
        return true;
    }
    for (String pattern : this.includePatterns) {
        if (pathMatcherToUse.match(pattern, lookupPath)) {
            return true;
        }
    }
    return false;
}

獲取處理器適配器

這里的handlerAdapters默認(rèn)就是DispatcherServlet.properties中配置的三種攔截器適配器

  • HttpRequestHandlerAdapter 匹配HttpRequestHandler處理器
  • SimpleControllerHandlerAdapter 匹配實(shí)現(xiàn)了Controller接口或繼承了AbstractController的類
  • RequestMappingHandlerAdapter 匹配HandlerMethod,@Controller注解
// HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
    if (this.handlerAdapters != null) {
        for (HandlerAdapter adapter : this.handlerAdapters) {
            // 核心的supports方法
            if (adapter.supports(handler)) {
                return adapter;
            }
        }
    }
}
// HttpRequestHandlerAdapter#supports
public boolean supports(Object handler) {
    return (handler instanceof HttpRequestHandler);
}
// SimpleControllerHandlerAdapter#supports
public boolean supports(Object handler) {
    return (handler instanceof Controller);
}
// AbstractHandlerMethodAdapter#supports
public final boolean supports(Object handler) {
    return (handler instanceof HandlerMethod && supportsInternal((HandlerMethod) handler));
}
// RequestMappingHandlerAdapter#supportsInternal
protected boolean supportsInternal(HandlerMethod handlerMethod) {
    return true;
}

攔截器回調(diào) - preHandle

// if (!mappedHandler.applyPreHandle(processedRequest, response)) {}
boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) {
    HandlerInterceptor[] interceptors = getInterceptors();
    if (!ObjectUtils.isEmpty(interceptors)) {
        for (int i = 0; i < interceptors.length; i++) {
            HandlerInterceptor interceptor = interceptors[i];
            // 每一個攔截器的 preHandle 需要返回true,整個請求處理才能繼續(xù)向下執(zhí)行
            if (!interceptor.preHandle(request, response, this.handler)) {
                // 如果有一個攔截器的 preHandle 方法返回了false,就會直接執(zhí)行直接攔截器 afterCompletion 方法,并返回false
                triggerAfterCompletion(request, response, null);
                // 返回false意味著這個請求的處理就到此結(jié)束了
                return false;
            }
            this.interceptorIndex = i;
        }
    }
    return true;
}

處理器處理請求

以處理@Controller注解為例:AbstractHandlerMethodAdapter#handle

// mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
public final ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) {
   /** 調(diào)用子類的 handleInternal 方法
     * 不同的處理器適配器實(shí)現(xiàn)了不同功能的 handleInternal 方法
     * RequestMappingHandlerAdapter#handleInternal  處理@RequestMapping方法映射
     */
    return handleInternal(request, response, (HandlerMethod) handler);
}
// RequestMappingHandlerAdapter#handleInternal
protected ModelAndView handleInternal(HttpServletRequest request,
                                      HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {

    ModelAndView mav;
    checkRequest(request);
    // 在這個同步塊中執(zhí)行controller回調(diào)方法
    // 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) {
                // 執(zhí)行映射方法
                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);
    }
    // json 數(shù)據(jù)格式的響應(yīng)在上面就完成了 invokeHandlerMethod 方法中
    if (!response.containsHeader(HEADER_CACHE_CONTROL)) {
        if (getSessionAttributesHandler(handlerMethod).hasSessionAttributes()) {
            applyCacheSeconds(response, this.cacheSecondsForSessionAttributeHandlers);
        }
        else {
            prepareResponse(response);
        }
    }
    return mav;
}

回調(diào)攔截器 - postHandle

檢查返回的ModelAndView和回調(diào)攔截器 - postHandle

// 如果存在mv對象,但是在mv中不存在view,這里就會應(yīng)用一個默認(rèn)的視圖對象
applyDefaultViewName(processedRequest, mv);
// 執(zhí)行所有配置這個uri的攔截器的 postHandle 方法
mappedHandler.applyPostHandle(processedRequest, response, mv);
// 是否需要創(chuàng)建默認(rèn)的視圖對象
private void applyDefaultViewName(HttpServletRequest request, ModelAndView mv) {
    if (mv != null && !mv.hasView()) {
        // 存在mv對象,但是不存在view對象才會執(zhí)行到這里:DefaultRequestToViewNameTranslator
        // DefaultRequestToViewNameTranslator#getViewName 方法
        // 會根據(jù)請求的URI返回視圖名稱,去除后綴,前后/,如果完全匹配,就返回請求的URI
        String defaultViewName = getDefaultViewName(request);
        if (defaultViewName != null) {
            mv.setViewName(defaultViewName);
        }
    }
}

調(diào)用攔截器 - afterCompletion

// processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,HandlerExecutionChain mappedHandler,
                                   ModelAndView mv,Exception exception) {
    boolean errorView = false;
    if (exception != null) {
        // 如果存在異常,就把異常交給HandlerExceptionResolver來處理
        if (exception instanceof ModelAndViewDefiningException) {
            logger.debug("ModelAndViewDefiningException encountered", exception);
            mv = ((ModelAndViewDefiningException) exception).getModelAndView();
        } else {
            Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
            // 調(diào)用 HandlerExceptionResolver#resolveException
            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);
        }
    }
    if (WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
        // Concurrent handling started during a forward
        return;
    }
    // 在視圖渲染等一切工作完成之后執(zhí)行攔截器的 afterCompletion 方法
    // interceptor.afterCompletion(request, response, this.handler, ex);
    if (mappedHandler != null) {
        mappedHandler.triggerAfterCompletion(request, response, null);
    }
}

SpringMVC對于beanName請求的處理

如果一個請求可以匹配到一個實(shí)現(xiàn)了Controller接口或者繼承了AbstractController的bean對象。那么可以直接調(diào)用接口或者抽象方法去處理請求。

// SimpleControllerHandlerAdapter  ->  直接調(diào)用Controller接口的方法 
// AbstractController會在handleRequest中調(diào)用子類的handleRequestInternal方法
public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
    return ((Controller) handler).handleRequest(request, response);
}
// HttpRequestHandlerAdapter
public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
    ((HttpRequestHandler) handler).handleRequest(request, response);
    return null;
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 0 系列目錄# WEB請求處理 WEB請求處理一:瀏覽器請求發(fā)起處理 WEB請求處理二:Nginx請求反向代理 W...
    七寸知架構(gòu)閱讀 4,583評論 3 55
  • 對于java中的思考的方向,1必須要看前端的頁面,對于前端的頁面基本的邏輯,如果能理解最好,不理解也要知道幾點(diǎn)。 ...
    神尤魯?shù)婪?/span>閱讀 901評論 0 0
  • 1.Spring整體架構(gòu) 1)核心容器(Core Container) Core模塊,主要包含了Spring框架基...
    Sponge1128閱讀 1,257評論 0 1
  • SpringMVC的筆記 MVC M 代表 模型(Model)模型就是數(shù)據(jù),如:dao,bean V 代表 視圖(...
    JasonChen8888閱讀 440評論 0 1
  • SpringMVC的工作原理圖: SpringMVC的工作原理圖: SpringMVC流程 1、 用戶發(fā)送請求至前...
    我不餓我不想吃東西閱讀 914評論 0 1

友情鏈接更多精彩內(nèi)容