日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 前端技术 > javascript >内容正文

javascript

Spring系列(六) Spring Web MVC 应用构建分析

發(fā)布時間:2023/12/20 javascript 22 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Spring系列(六) Spring Web MVC 应用构建分析 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

DispatcherServlet

DispatcherServlet 是Spring MVC的前端控制器名稱, 用戶的請求到達這里進行集中處理, 在Spring MVC中, 它的作用是為不同請求匹配對應的處理器, 將結果傳遞給視圖解析器最終呈現(xiàn)給客戶端.

前端控制器模式(Front Controller Pattern)是用來提供一個集中的請求處理機制,所有的請求都將由一個單一的處理程序處理。該處理程序可以做認證/授權/記錄日志,或者跟蹤請求,然后把請求傳給相應的處理程序。

Servlet WebApplicationContext 和 Root WebApplicationContext

Spring MVC 存在兩個應用上下文, 分別為Servlet WebApplicationContext和Root WebApplicationContext. 他們分別初始化不同類型的bean.

下圖來自Spring官方文檔

在DispatcherServlet啟動的時候, 它會創(chuàng)建Spring上下文Servlet WebApplicationContext, 其中包含Web相關的Controller,ViewResolver,HandlerMapping等.

另外一個上下文Root WebApplicationContext是由ContextLoaderListener創(chuàng)建的, 包含除了Web組件外的其他bean, 比如包含業(yè)務邏輯的Service, 還有數據庫相關的組件等.

代碼(JavaConfig方式的配置代碼)

下面是用JavaConfig方式實現(xiàn)的配置代碼, 我們先搭建好一個Spring MVC 項目,然后結合源碼分析Spring如何注冊DispatcherServlet實例的.

// 繼承AbstractAnnotationConfigDispatcherServletInitializer并重寫其中的三個方法 public class MvcWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {// 指定Root上下文的配置類@Overrideprotected Class<?>[] getRootConfigClasses() {return new Class[]{ RootConfig.class };}// 指定Web上下文的配置類@Overrideprotected Class<?>[] getServletConfigClasses() {return new Class[]{ WebConfig.class };}// url映射@Overrideprotected String[] getServletMappings() {return new String[]{"/"};} }

通過重寫AbstractAnnotationConfigDispatcherServletInitializer的三個方法完成配置, WebConfig用來配置Web組件, RootConfig用來配置非Web組件.

@EnableWebMvc // 啟用MVC @ComponentScan(basePackages = {"com.xlx.mvc.web"}) // 啟用組件掃描,只掃描web相關的組件 @Configuration public class WebConfig implements WebMvcConfigurer {// 視圖解析器,jsp@Beanpublic ViewResolver viewResolver(){InternalResourceViewResolver resolver = new InternalResourceViewResolver();resolver.setPrefix("/WEB-INF/views/");resolver.setSuffix(".jsp");resolver.setExposeContextBeansAsAttributes(true);return resolver;}// 重寫以啟用默認的處理器, 用來處理靜態(tài)資源@Overridepublic void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer){configurer.enable();}}@Configuration @ComponentScan(basePackages = {"com.xlx.mvc"}, excludeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION,classes = EnableWebMvc.class) }) // 掃描包, 但排除EnableWebMvc注解的類 public class RootConfig {}

源碼分析

Servlet 3.0 旨在支持基于代碼的方式配置Servlet容器, 當3.0兼容的servlet容器啟動的時候會在ClassPath查找并調用實現(xiàn)了接口ServletContainerInitializer的類的onStartup()方法, Spring中提供了這個接口的一個實現(xiàn)類SpringServletContainerInitializer. 其啟動方法的代碼如下:

@Override public void onStartup(@Nullable Set<Class<?>> webAppInitializerClasses, ServletContext servletContext)throws ServletException {List<WebApplicationInitializer> initializers = new LinkedList<>();// 應用中WebApplicationInitializer的bean生成到一個列表中.if (webAppInitializerClasses != null) {for (Class<?> waiClass : webAppInitializerClasses) {if (!waiClass.isInterface() && !Modifier.isAbstract(waiClass.getModifiers()) &&WebApplicationInitializer.class.isAssignableFrom(waiClass)) {try {initializers.add((WebApplicationInitializer)ReflectionUtils.accessibleConstructor(waiClass).newInstance());}catch (Throwable ex) {throw new ServletException("Failed to instantiate WebApplicationInitializer class", ex);}}}}if (initializers.isEmpty()) {servletContext.log("No Spring WebApplicationInitializer types detected on classpath");return;}servletContext.log(initializers.size() + " Spring WebApplicationInitializers detected on classpath");AnnotationAwareOrderComparator.sort(initializers);// 遍歷所有WebApplicationInitializer, 并調用其onStartup方法for (WebApplicationInitializer initializer : initializers) {initializer.onStartup(servletContext);} }

在上面方法的最后, 可以看到其將控制權交給WebApplicationInitializer的實例并遍歷調用了onStartup()方法, 而我們定義的類MvcWebAppInitializer 就是它的子類. 完整的繼承關系為

WebApplicationInitializer <--
AbstractContextLoaderInitializer <--
AbstractDispatcherServletInitializer <--
AbstractAnnotationConfigDispatcherServletInitializer <--
MvcWebAppInitializer

在類 AbstractDispatcherServletInitializer 中實現(xiàn)了onStartup()方法, 最終調用registerDispatcherServlet()方法完成注冊, 兩個方法的代碼如下:

@Override public void onStartup(ServletContext servletContext) throws ServletException {super.onStartup(servletContext);registerDispatcherServlet(servletContext); }protected void registerDispatcherServlet(ServletContext servletContext) {// 獲取Sevlet名稱, 這個方法返回了默認值"dispatcher"String servletName = getServletName();Assert.hasLength(servletName, "getServletName() must not return null or empty");// 此處調用的方法是抽象方法, 由子類AbstractAnnotationConfigDispatcherServletInitializer實現(xiàn), 其最終調用了自定義類的getServletConfigClasses()方法獲取配置信息(源碼附在本段后面). 用來生成Servlet上下文.WebApplicationContext servletAppContext = createServletApplicationContext();Assert.notNull(servletAppContext, "createServletApplicationContext() must not return null");// 生成dispatcherServlet實例FrameworkServlet dispatcherServlet = createDispatcherServlet(servletAppContext);Assert.notNull(dispatcherServlet, "createDispatcherServlet(WebApplicationContext) must not return null");dispatcherServlet.setContextInitializers(getServletApplicationContextInitializers());// 注冊DispatcherServletServletRegistration.Dynamic registration = servletContext.addServlet(servletName, dispatcherServlet);if (registration == null) {throw new IllegalStateException("Failed to register servlet with name '" + servletName + "'. " +"Check if there is another servlet registered under the same name.");}registration.setLoadOnStartup(1);registration.addMapping(getServletMappings());registration.setAsyncSupported(isAsyncSupported());Filter[] filters = getServletFilters();if (!ObjectUtils.isEmpty(filters)) {for (Filter filter : filters) {registerServletFilter(servletContext, filter);}}customizeRegistration(registration); }

下面附讀取Servlet配置類的代碼: 類AbstractAnnotationConfigDispatcherServletInitializer實現(xiàn)了createServletApplicationContext(), 可以看到代碼中調用了方法getServletConfigClasses(), 這是個抽象方法, 聲明為protected abstract Class<?>[] getServletConfigClasses();. 最終的實現(xiàn)正是在我們自定義的子類MvcWebAppInitializer中.

@Override protected WebApplicationContext createServletApplicationContext() {AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();// 讀取配置類Class<?>[] configClasses = getServletConfigClasses();if (!ObjectUtils.isEmpty(configClasses)) {context.register(configClasses);}return context; }

上面完成了DispatcherServlet的注冊和啟動, 接下來可以定義Controller了.

請求映射

在此之前需要了解下關于URL映射的Servlet規(guī)范, 注意這是Servlet的規(guī)范, 當然也適用于DispatcherServlet, 代碼中我們?yōu)镈ispatcherServlet映射為"/", 規(guī)范中"/"為使用"default"Servlet, 也就意味著所有的請求默認通過DispatcherServlet處理.

為了處理靜態(tài)資源, 在WebConfig中覆蓋了方法configureDefaultServletHandling()已啟用靜態(tài)資源處理器DefaultServletHttpRequestHandler, 它的優(yōu)先級是最低, 這意味著在匹配不到其他handler的時候,servlet會將請求交給這個handler處理.

規(guī)則按順序執(zhí)行,匹配到就直接返回.

  • 精確匹配, url完全與模式匹配
  • 最長路徑匹配, 查找模式中路徑最長的匹配項, 例如/user/list/1匹配模式/user/list/, 而不是/user/
  • 擴展名匹配
  • 默認Servlet
  • 代碼

    @Controller @RequestMapping(value = "/home") public class HomeController {@RequestMapping(value = "/default",method = RequestMethod.GET)public String home(){return "home";} }

    源碼分析

    我們的Controller以注解(@RequestMapping,@GetMapping等)方式定義, RequestMappingHandlerMapping用來生成請求url與處理方法的映射關系(mapping),這個mapping最終是由DispatcherServlet調用找到匹配到url對應的controller方法并調用.

    通過查看Spring的bean依賴關系圖(找到類WebConfig, Ctrl+Alt+U并選spring beans dependency)可以找到RequestMappingHandlerMapping生成的線索.

    簡化的關系圖如下:

    可以看到WebmvcConfigurationSupport中有個@Bean注解的方法生成RequestMappingHandlerMapping的實例, 而WebmvcConfigurationSupport繼承了DelegatingWebMvcConfiguration, 后者是由@EnableWebMvc注解導入.

    /*** * 返回排序為0的RequestMappingHandlerMapping實例bean, 用來處理注解方式的Controller請求.*/ @Bean public RequestMappingHandlerMapping requestMappingHandlerMapping() {RequestMappingHandlerMapping mapping = createRequestMappingHandlerMapping();// 順序為0, 順便提一句, 靜態(tài)資源的處理器Handler的順序為Integer.Maxmapping.setOrder(0);mapping.setInterceptors(getInterceptors());mapping.setContentNegotiationManager(mvcContentNegotiationManager());mapping.setCorsConfigurations(getCorsConfigurations());PathMatchConfigurer configurer = getPathMatchConfigurer();Boolean useSuffixPatternMatch = configurer.isUseSuffixPatternMatch();if (useSuffixPatternMatch != null) {mapping.setUseSuffixPatternMatch(useSuffixPatternMatch);}Boolean useRegisteredSuffixPatternMatch = configurer.isUseRegisteredSuffixPatternMatch();if (useRegisteredSuffixPatternMatch != null) {mapping.setUseRegisteredSuffixPatternMatch(useRegisteredSuffixPatternMatch);}Boolean useTrailingSlashMatch = configurer.isUseTrailingSlashMatch();if (useTrailingSlashMatch != null) {mapping.setUseTrailingSlashMatch(useTrailingSlashMatch);}UrlPathHelper pathHelper = configurer.getUrlPathHelper();if (pathHelper != null) {mapping.setUrlPathHelper(pathHelper);}PathMatcher pathMatcher = configurer.getPathMatcher();if (pathMatcher != null) {mapping.setPathMatcher(pathMatcher);}Map<String, Predicate<Class<?>>> pathPrefixes = configurer.getPathPrefixes();if (pathPrefixes != null) {mapping.setPathPrefixes(pathPrefixes);}return mapping; }

    好了, 現(xiàn)在有了DispatcherServlet, 并且有了可以處理映射關系的RequestMappingHandlerMapping, 接下來再看下當請求到達時, DispatcherServlet 如何為Url找到對應的Handler方法.

    DispatcherServlet中定義了處理請求的doService()方法, 最終這個方法委托doDispatch()處理請求, 特別注意中文注釋的幾個語句, 除此之外, 這個方法還提供了生命周期的一些處理工作.

    protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {HttpServletRequest processedRequest = request;HandlerExecutionChain mappedHandler = null;boolean multipartRequestParsed = false;WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);try {ModelAndView mv = null;Exception dispatchException = null;try {processedRequest = checkMultipart(request);multipartRequestParsed = (processedRequest != request);// 獲取當前請求對應的handlermappedHandler = getHandler(processedRequest);if (mappedHandler == null) {noHandlerFound(processedRequest, response);return;}// 獲取當前請求對應handler的適配器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;}// 最終調用Handler的方法mv = ha.handle(processedRequest, response, mappedHandler.getHandler());if (asyncManager.isConcurrentHandlingStarted()) {return;}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);}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 afterCompletionif (mappedHandler != null) {mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);}}else {// Clean up any resources used by a multipart request.if (multipartRequestParsed) {cleanupMultipart(processedRequest);}}} }

    上面代碼中, 重點關注getHandler方法.

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

    可以看到請求所需的handler是取自實例變量this.handlerMappings,接下來順藤摸瓜, 看這個變量是何時初始化的.通過引用, 我們查找到了下面方法.

    private void initHandlerMappings(ApplicationContext context) {this.handlerMappings = null;if (this.detectAllHandlerMappings) {// 找到上下文中的所有HandlerMapping, 包括祖先上下文Map<String, HandlerMapping> matchingBeans =BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);if (!matchingBeans.isEmpty()) {this.handlerMappings = new ArrayList<>(matchingBeans.values());// HandlerMapping排序AnnotationAwareOrderComparator.sort(this.handlerMappings);}}else {try {HandlerMapping hm = context.getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class);this.handlerMappings = Collections.singletonList(hm);}catch (NoSuchBeanDefinitionException ex) {// Ignore, we'll add a default HandlerMapping later. // 這個注釋...}}// 保證至少要有一個HandlerMapping.if (this.handlerMappings == null) {this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);if (logger.isTraceEnabled()) {logger.trace("No HandlerMappings declared for servlet '" + getServletName() +"': using default strategies from DispatcherServlet.properties");}} }

    整理下調用關系: DispatcherServlet initHandlerMappings <-- initStrategies <-- onRefresh <--
    FrameworkServlet initWebApplicationContext <-- initServletBean <--
    HttpServletBean init <--
    GenericServlet init(ServletConfig config)
    最后的GenericServlet是servlet Api的.

    Spring Boot 中的DispatcherServlet

    Spring Boot微服務中的DispatcherServlet裝配, 因為其一般使用內置的Servlet容器, 是通過DispatcherServletAutoConfiguration來完成的. 下面是生成DispatcherServlet bean的代碼, 這個bean在內部靜態(tài)類DispatcherServletConfiguration中.

    @Bean(name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME) public DispatcherServlet dispatcherServlet() {DispatcherServlet dispatcherServlet = new DispatcherServlet();dispatcherServlet.setDispatchOptionsRequest(this.webMvcProperties.isDispatchOptionsRequest());dispatcherServlet.setDispatchTraceRequest(this.webMvcProperties.isDispatchTraceRequest());dispatcherServlet.setThrowExceptionIfNoHandlerFound(this.webMvcProperties.isThrowExceptionIfNoHandlerFound());return dispatcherServlet; }

    上面我們通過注解方式構建了一個MVC應用程序, 并且通過源碼分析其構建原理, 其中Spring使用的前端控制器實現(xiàn)類是DispatcherServlet, 其在Servlet容器啟動的時候實例化, 并初始化容器中的Handler處理器. 當請求到達DispatcherServlet時會調用其doDispatcher()方法選擇最合適的處理器. 最后我們掃了一眼Spring Boot的自動裝配DispatcherServlet方式.

    轉載于:https://www.cnblogs.com/walkinhalo/p/9732125.html

    創(chuàng)作挑戰(zhàn)賽新人創(chuàng)作獎勵來咯,堅持創(chuàng)作打卡瓜分現(xiàn)金大獎

    總結

    以上是生活随笔為你收集整理的Spring系列(六) Spring Web MVC 应用构建分析的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。