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)流程
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框架的核心和控制中心摊灭;
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配置類中的源碼也會有所差別。