SpringMVC實現(xiàn)原理與SpringBootMVC實現(xiàn)原理

SpringMVC實現(xiàn)原理與SpringBootMVC實現(xiàn)原理

一改淑、MVC

MVC是模型(Model)、視圖(View)敞曹、控制器(Controller)的簡寫沼侣,是一種軟件設(shè)計規(guī)范。是將業(yè)務(wù)邏輯蟆沫、數(shù)據(jù)籽暇、顯示分離的方法來組織代碼。

MVC主要作用是降低了視圖與業(yè)務(wù)邏輯間的雙向耦合饭庞。

MVC不是一種設(shè)計模式戒悠,MVC是一種架構(gòu)模式。當然不同的MVC存在差異舟山。

  • Model(模型):數(shù)據(jù)模型绸狐,提供要展示的數(shù)據(jù),因此包含數(shù)據(jù)和行為累盗,可以認為是領(lǐng)域模型或JavaBean組件(包含數(shù)據(jù)和行為)寒矿,不過現(xiàn)在一般都分離開來:Value Object(數(shù)據(jù)Dao) 和 服務(wù)層(行為Service)。也就是模型提供了模型數(shù)據(jù)查詢和模型數(shù)據(jù)的狀態(tài)更新等功能若债,包括數(shù)據(jù)和業(yè)務(wù)符相。

  • View(視圖):負責進行模型的展示,一般就是我們見到的用戶界面拆座,客戶想看到的東西主巍。

  • Controller(控制器):接收用戶請求,委托給模型進行處理(狀態(tài)改變)挪凑,處理完畢后把返回的模型數(shù)據(jù)返回給視圖孕索,由視圖負責展示。也就是說控制器做了個調(diào)度員的工作躏碳。

二搞旭、SpringMVC實現(xiàn)原理

官方文檔:https://docs.spring.io/spring-framework/docs/current/reference/html/web.html#mvc

Spring MVC是Spring Framework的一部分,是基于Java實現(xiàn)MVC的輕量級Web框架。Spring的web框架圍繞DispatcherServlet設(shè)計肄渗。DispatcherServlet的作用是將請求分發(fā)到不同的處理器镇眷。從Spring 2.5開始,使用Java 5或者以上版本的用戶可以采用基于注解的controller聲明方式翎嫡。

Spring MVC框架像許多其他MVC框架一樣, 以請求為驅(qū)動 , 圍繞一個中心Servlet分派請求及提供其他功能欠动,DispatcherServlet是一個實際的Servlet (它繼承自HttpServlet 基類)。

SpringMVC底層實現(xiàn)流程

底層實現(xiàn)流程

1.DispatcherServlet表示前置控制器惑申,是整個SpringMVC的控制中心。用戶發(fā)出請求圈驼,DispatcherServlet接收請求并攔截請求。

我們假設(shè)請求的url為 : http://localhost:8080/SpringMVC/hello

如上url拆分成三部分:

  • http://localhost:8080 服務(wù)器域名
  • SpringMVC 部署在服務(wù)器上的web站點
  • hello 表示控制器

通過分析萤厅,如上url表示為:請求位于服務(wù)器localhost:8080上的SpringMVC站點的hello控制器。

2.HandlerMapping為處理器映射靴迫。DispatcherServlet調(diào)用HandlerMapping,HandlerMapping根據(jù)請求url查找Handler惕味。

3.HandlerExecution表示具體的Handler,其主要作用是根據(jù)url查找控制器矢劲,如上url被查找控制器為:hello。

4.HandlerExecution將解析后的信息傳遞給DispatcherServlet,如解析控制器映射等芬沉。

5.HandlerAdapter表示處理器適配器躺同,其按照特定的規(guī)則去執(zhí)行Handler。

6.Handler讓具體的Controller執(zhí)行丸逸。

7.Controller將具體的執(zhí)行信息返回給HandlerAdapter,如ModelAndView黄刚。

8.HandlerAdapter將視圖邏輯名或模型傳遞給DispatcherServlet。

9.DispatcherServlet調(diào)用視圖解析器(ViewResolver)來解析HandlerAdapter傳遞的邏輯視圖名憔维。

10視圖解析器將解析的邏輯視圖名傳給DispatcherServlet。

11.DispatcherServlet根據(jù)視圖解析器解析的視圖結(jié)果检吆,調(diào)用具體的視圖程储。

源碼簡單解析MVC

在spring-webmvc的jar包下找到DispatcherServlet類臂寝,這是SpringMVC整個Web框架的核心和控制中心摊灭;


DispatcherServlet類

1.DispatcherServlet生成對象時,先對屬性進行初始化的設(shè)置

public static final String WEB_APPLICATION_CONTEXT_ATTRIBUTE = DispatcherServlet.class.getName() + ".CONTEXT";
public static final String LOCALE_RESOLVER_ATTRIBUTE = DispatcherServlet.class.getName() + ".LOCALE_RESOLVER";
public static final String THEME_RESOLVER_ATTRIBUTE = DispatcherServlet.class.getName() + ".THEME_RESOLVER";
public static final String THEME_SOURCE_ATTRIBUTE = DispatcherServlet.class.getName() + ".THEME_SOURCE";

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());

2.DispatcherServlet接收到用戶發(fā)來的請求掏缎,根據(jù)請求的URL調(diào)用處理器映射器來得到對應(yīng)的處理器

@Nullable
    protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
        if (this.handlerMappings != null) {
            Iterator var2 = this.handlerMappings.iterator();
            while(var2.hasNext()) {
                HandlerMapping mapping = (HandlerMapping)var2.next();
                HandlerExecutionChain handler = mapping.getHandler(request);//請求對應(yīng)的處理器
                if (handler != null) {
                    return handler;
                }
            }
        }

        return null;
    }

3.處理器解析請求以后萝挤,DispatcherServlet根據(jù)解析到的信息調(diào)用處理器適配器來得到控制器

protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
        if (this.handlerAdapters != null) {
            Iterator var2 = this.handlerAdapters.iterator();

            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");
    }

4.找到控制器后怜珍,由控制器Controller執(zhí)行真正的處理請求的行為(根據(jù)請求調(diào)用service層對象訪問數(shù)據(jù)庫凤粗、得到數(shù)據(jù)、實現(xiàn)需求等)
并具體的執(zhí)行信息(ModelAndView)返回給HandlerAdapter嫌拣,HandlerAdapter將視圖邏輯名或模型傳遞給DispatcherServlet。

5.DispatcherServlet調(diào)用視圖解析器(ViewResolver)來解析HandlerAdapter傳遞的邏輯視圖名异逐。

 @Nullable
    protected View resolveViewName(String viewName, @Nullable Map<String, Object> model, Locale locale, HttpServletRequest request) throws Exception {
        if (this.viewResolvers != null) {
            Iterator var5 = this.viewResolvers.iterator();

            while(var5.hasNext()) {
                ViewResolver viewResolver = (ViewResolver)var5.next();
                View view = viewResolver.resolveViewName(viewName, locale);
                if (view != null) {
                    return view;
                }
            }
        }
        return null;
    }

6.DispatcherServlet根據(jù)視圖解析器解析的視圖結(jié)果灰瞻,調(diào)用具體的視圖,返回到用戶酝润。

view.render(mv.getModelInternal(), request, response);

三、SpringBootMVC實現(xiàn)原理

與其他Spring開發(fā)功能相似要销,SpringBoot為MVC的框架也實現(xiàn)了自動默認配置构回,這些自動配置由WebMvcAutoConfiguration類來實現(xiàn)疏咐。
在這里主要對SpringBootMVC實現(xiàn)視圖解析器、靜態(tài)資源處理借跪、格式轉(zhuǎn)換器自動配置原理進行簡單記錄缩举。

視圖解析器

SpringBoot中的視圖解析功能由ContentNegotiatingViewResolver類來實現(xiàn)匹颤,由該類的實例對象來根據(jù)方法的返回值取得視圖對象托猩,再進行渲染京腥。WebMvcAutoConfiguration類中有得到視圖解析器viewResolver對象的方法

@Bean
@ConditionalOnBean({ViewResolver.class})
@ConditionalOnMissingBean(name = {"viewResolver"},value = {ContentNegotiatingViewResolver.class})
public ContentNegotiatingViewResolver viewResolver(BeanFactory beanFactory) {
    ContentNegotiatingViewResolver resolver = new ContentNegotiatingViewResolver();
    resolver.setContentNegotiationManager((ContentNegotiationManager)beanFactory.getBean(ContentNegotiationManager.class));
        // ContentNegotiatingViewResolver使用所有其他視圖解析器來定位視圖,具有較高的優(yōu)先級
    resolver.setOrder(-2147483648);
    return resolver;
}

接下來可以探究ContentNegotiatingViewResolver類中解析視圖(獲取視圖)操作的源碼他宛,類中定義了一個方法如下

@Nullable
public View resolveViewName(String viewName, Locale locale) throws Exception {
    RequestAttributes attrs = RequestContextHolder.getRequestAttributes();
    Assert.state(attrs instanceof ServletRequestAttributes, "No current ServletRequestAttributes");
    List<MediaType> requestedMediaTypes = this.getMediaTypes(((ServletRequestAttributes)attrs).getRequest());
    if (requestedMediaTypes != null) {//獲取候選的視圖對象
        List<View> candidateViews = this.getCandidateViews(viewName, locale, requestedMediaTypes);
        View bestView = this.getBestView(candidateViews, requestedMediaTypes, attrs);//選擇一個最合適的試圖對象bestView然后返回這個對象
        if (bestView != null) {
            return bestView;
        }
    }

如何獲取候選的對象欠气,點進getCandidateViews的源碼

private List<View> getCandidateViews(String viewName, Locale locale, List<MediaType> requestedMediaTypes) throws Exception {
    List<View> candidateViews = new ArrayList();
    if (this.viewResolvers != null) {
        Assert.state(this.contentNegotiationManager != null, "No ContentNegotiationManager set");
        Iterator var5 = this.viewResolvers.iterator();

        while(var5.hasNext()) {
            ViewResolver viewResolver = (ViewResolver)var5.next();
            View view = viewResolver.resolveViewName(viewName, locale);
            if (view != null) {
                candidateViews.add(view);
            }

            Iterator var8 = requestedMediaTypes.iterator();

            while(var8.hasNext()) {
                MediaType requestedMediaType = (MediaType)var8.next();
                List<String> extensions = this.contentNegotiationManager.resolveFileExtensions(requestedMediaType);
                Iterator var11 = extensions.iterator();

                while(var11.hasNext()) {
                    String extension = (String)var11.next();
                    String viewNameWithExtension = viewName + '.' + extension;
                    view = viewResolver.resolveViewName(viewNameWithExtension, locale);
                    if (view != null) {
                        candidateViews.add(view);
                    }
                }
            }
        }
    }

可得预柒,該方法getCandidateViews是將所有的視圖都放到List中,然后把所有的視圖解析器拿來進行while循環(huán)逐個解析

如何獲得最合適的視圖憔古,點進getBestView方法的源碼

private View getBestView(List<View> candidateViews, List<MediaType> requestedMediaTypes, RequestAttributes attrs) {
        Iterator var4 = candidateViews.iterator();

        while(var4.hasNext()) {
            View candidateView = (View)var4.next();
            if (candidateView instanceof SmartView) {
                SmartView smartView = (SmartView)candidateView;
                if (smartView.isRedirectView()) {
                    return candidateView;
                }
            }
        }

        var4 = requestedMediaTypes.iterator();

        while(var4.hasNext()) {
            MediaType mediaType = (MediaType)var4.next();
            Iterator var10 = candidateViews.iterator();

            while(var10.hasNext()) {
                View candidateView = (View)var10.next();
                if (StringUtils.hasText(candidateView.getContentType())) {
                    MediaType candidateContentType = MediaType.parseMediaType(candidateView.getContentType());
                    if (mediaType.isCompatibleWith(candidateContentType)) {
                        if (this.logger.isDebugEnabled()) {
                            this.logger.debug("Selected '" + mediaType + "' given " + requestedMediaTypes);
                        }

                        attrs.setAttribute(View.SELECTED_CONTENT_TYPE, mediaType, 0);
                        return candidateView;
                    }
                }
            }
        }

        return null;
    }
}

可得鸿市,也就是在候選的view中根據(jù)需求條件得到一個最合適的視圖返回即碗;
結(jié)論:ContentNegotiatingViewResolver這個視圖解析器是組合了所有的視圖解析器對象來實現(xiàn)視圖解析功能;視圖解析器的組合邏輯在ContentNegotiatingViewResolver類中的initServletContext方法中定義烙样;

protected void initServletContext(ServletContext servletContext) {
        Collection<ViewResolver> matchingBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors(this.obtainApplicationContext(), ViewResolver.class).values();
        ViewResolver viewResolver;
        if (this.viewResolvers == null) {
            this.viewResolvers = new ArrayList(matchingBeans.size());
            Iterator var3 = matchingBeans.iterator();

            while(var3.hasNext()) {
                viewResolver = (ViewResolver)var3.next();
                if (this != viewResolver) {
                    this.viewResolvers.add(viewResolver);
                }
            }
        } else {
            for(int i = 0; i < this.viewResolvers.size(); ++i) {
                viewResolver = (ViewResolver)this.viewResolvers.get(i);
                if (!matchingBeans.contains(viewResolver)) {
                    String name = viewResolver.getClass().getName() + i;
                    this.obtainApplicationContext().getAutowireCapableBeanFactory().initializeBean(viewResolver, name);
                }
            }
        }

        AnnotationAwareOrderComparator.sort(this.viewResolvers);
        this.cnmFactoryBean.setServletContext(servletContext);
    }

靜態(tài)資源處理

靜態(tài)資源:前端寫好的固定頁面

訪問webjars中的靜態(tài)資源

使用SpringBoot導(dǎo)入靜態(tài)資源谒获,需要使用webjars導(dǎo)入壁却;當我們想要訪問webjars中的靜態(tài)資源時,WebMvcAutoConfigurationAdapter中的方法addResourceHandlers規(guī)定了訪問該靜態(tài)資源的規(guī)則:

public void addResourceHandlers(ResourceHandlerRegistry registry) {
            if (!this.resourceProperties.isAddMappings()) {
                logger.debug("Default resource handling disabled");
            } else {
                Duration cachePeriod = this.resourceProperties.getCache().getPeriod();
                CacheControl cacheControl = this.resourceProperties.getCache().getCachecontrol().toHttpCacheControl();
                if (!registry.hasMappingForPattern("/webjars/**")) {
                    this.customizeResourceHandlerRegistration(registry.addResourceHandler(new String[]{"/webjars/**"}).addResourceLocations(new String[]{"classpath:/META-INF/resources/webjars/"}).setCachePeriod(this.getSeconds(cachePeriod)).setCacheControl(cacheControl).setUseLastModified(this.resourceProperties.getCache().isUseLastModified()));
                }

                String staticPathPattern = this.mvcProperties.getStaticPathPattern();
                if (!registry.hasMappingForPattern(staticPathPattern)) {
                    this.customizeResourceHandlerRegistration(registry.addResourceHandler(new String[]{staticPathPattern}).addResourceLocations(WebMvcAutoConfiguration.getResourceLocations(this.resourceProperties.getStaticLocations())).setCachePeriod(this.getSeconds(cachePeriod)).setCacheControl(cacheControl).setUseLastModified(this.resourceProperties.getCache().isUseLastModified()));
                }

            }
        }

源碼中指出:當我們訪問所有路徑為/webjars/**的靜態(tài)資源時赔硫,都需要去classpath:/META-INF/resources/webjars/里找到對應(yīng)的資源

訪問自己編寫的靜態(tài)資源

當我們需要訪問自己編寫的靜態(tài)資源時盐肃,訪問的路徑是什么权悟?再看到上面addResourceHandlers方法中有一行代碼:

String staticPathPattern = this.mvcProperties.getStaticPathPattern();

這里通過getStaticPathPattern方法設(shè)置了靜態(tài)資源的路徑峦阁;查看WebMvcProperties類中這個方法的源碼耘成;

public String getStaticPathPattern() {
        return this.staticPathPattern;
    }

查看staticPathPattern這個屬性在構(gòu)造器中的初始化:

this.staticPathPattern = "/**";

可得,該類中構(gòu)造器對staticPathPattern(靜態(tài)資源路徑)的初始化為"/**"撒会,訪問目錄下的任何資源师妙;
而這個屬性的定義會找WebProperties這個類中內(nèi)部類Resources的getStaticLocations()方法;

 public String[] getStaticLocations() {
            return this.staticLocations;
        }

Resources是WebProperties類中的一個靜態(tài)內(nèi)部類默穴,其構(gòu)造器對staticLocations的初始化值為一個常量;

public Resources() {
            this.staticLocations = CLASSPATH_RESOURCE_LOCATIONS;

該常量屬性在Resources類中的定義為

private static final String[] CLASSPATH_RESOURCE_LOCATIONS = new String[]{"classpath:/META-INF/resources/", "classpath:/resources/", "classpath:/static/", "classpath:/public/"};

至此,我們得知了靜態(tài)資源自動配置的默認訪問路徑若专;可知:這里聲明了訪問靜態(tài)資源時蝴猪,查找靜態(tài)資源的路徑目錄,即上面定義的靜態(tài)String[]數(shù)組里的內(nèi)容

"classpath:/META-INF/resources/"

"classpath:/resources/"

"classpath:/static/"

"classpath:/public/"

因此嚎莉,在這四個目錄下存放的靜態(tài)資源可以被識別和訪問沛豌,我們可以在根目錄resources下創(chuàng)建相應(yīng)的文件夾來存放靜態(tài)資源;
eg:訪問http://localhost:8080/hello.js加派,就會在以上路徑目錄中查找相對應(yīng)的文件

格式轉(zhuǎn)換器

在WebMvcAutoConfiguration類中定義了格式轉(zhuǎn)換的方法

@Bean
        public FormattingConversionService mvcConversionService() {
            Format format = this.mvcProperties.getFormat();
            WebConversionService conversionService = new WebConversionService((new DateTimeFormatters()).dateFormat(format.getDate()).timeFormat(format.getTime()).dateTimeFormat(format.getDateTime()));
            this.addFormatters(conversionService);
            return conversionService;
        }

getXxx()方法就規(guī)定了相應(yīng)的格式芍锦;

以上;
需要注意的是娄琉,spring中導(dǎo)入的web-mvc的jar包不同版本源碼可能會有所不同吓歇。SpringBoot版本不同票腰,mvc配置類中的源碼也會有所差別。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末析命,一起剝皮案震驚了整個濱河市逃默,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌完域,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,386評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件凹耙,死亡現(xiàn)場離奇詭異肠仪,居然都是意外死亡,警方通過查閱死者的電腦和手機意述,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,142評論 3 394
  • 文/潘曉璐 我一進店門荤崇,熙熙樓的掌柜王于貴愁眉苦臉地迎上來潮针,“玉大人,你說我怎么就攤上這事每篷。” “怎么了带兜?”我有些...
    開封第一講書人閱讀 164,704評論 0 353
  • 文/不壞的土叔 我叫張陵吨灭,是天一觀的道長。 經(jīng)常有香客問我无畔,道長,這世上最難降的妖魔是什么浑彰? 我笑而不...
    開封第一講書人閱讀 58,702評論 1 294
  • 正文 為了忘掉前任郭变,我火速辦了婚禮,結(jié)果婚禮上诉濒,老公的妹妹穿的比我還像新娘。我一直安慰自己专挪,他們只是感情好片排,可當我...
    茶點故事閱讀 67,716評論 6 392
  • 文/花漫 我一把揭開白布率寡。 她就那樣靜靜地躺著迫卢,像睡著了一般。 火紅的嫁衣襯著肌膚如雪靖避。 梳的紋絲不亂的頭發(fā)上比默,一...
    開封第一講書人閱讀 51,573評論 1 305
  • 那天盆犁,我揣著相機與錄音,去河邊找鬼醋奠。 笑死伊佃,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的航揉。 我是一名探鬼主播,決...
    沈念sama閱讀 40,314評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼议薪,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了产捞?” 一聲冷哼從身側(cè)響起哼御,我...
    開封第一講書人閱讀 39,230評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎看靠,沒想到半個月后焰雕,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,680評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡辟宗,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,873評論 3 336
  • 正文 我和宋清朗相戀三年吝秕,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片容客。...
    茶點故事閱讀 39,991評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡约郁,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出供置,到底是詐尸還是另有隱情绽快,我是刑警寧澤,帶...
    沈念sama閱讀 35,706評論 5 346
  • 正文 年R本政府宣布续担,位于F島的核電站活孩,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜这敬,卻給世界環(huán)境...
    茶點故事閱讀 41,329評論 3 330
  • 文/蒙蒙 一蕉朵、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧冷蚂,春花似錦汛闸、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,910評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至厘肮,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間耍属,已是汗流浹背巩检。 一陣腳步聲響...
    開封第一講書人閱讀 33,038評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留溯捆,地道東北人厦瓢。 一個月前我還...
    沈念sama閱讀 48,158評論 3 370
  • 正文 我出身青樓啤月,卻偏偏與公主長得像,于是被迫代替她去往敵國和親谎仲。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,941評論 2 355

推薦閱讀更多精彩內(nèi)容