您现在的位置是:网站首页> 内容页

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

  • 拉霸laba360
  • 2019-05-10
  • 393人已阅读
简介DispatcherServletDispatcherServlet是SpringMVC的前端控制器名称用户的请求到达这里进行集中处理在SpringMVC中它的作用是为不同

DispatcherServlet

DispatcherServlet 是Spring MVC的前端控制器名称 用户的请求到达这里进行集中处理 在Spring MVC中 它的作用是为不同请求匹配对应的处理器 将结果传递给视图解析器最终呈现给客户端.

前端控制器模式(Front Controller Pattern)是用来提供一个集中的请求处理机制,所有的请求都将由一个单一的处理程序处理。该处理程序可以做认证/授权/记录日志,或者跟踪请求,然后把请求传给相应的处理程序。

Servlet WebApplicationContext 和 Root WebApplicationContext

Spring MVC 存在两个应用上下文 分别为Servlet WebApplicationContext和Root WebApplicationContext. 他们分别初始化不同类型的bean.

下图来自Spring官方文档

在DispatcherServlet启动的时候 它会创建Spring上下文Servlet WebApplicationContext 其中包含Web相关的ControllerViewResolverHandlerMapping等.

另外一个上下文Root WebApplicationContext是由ContextLoaderListener创建的 包含除了Web组件外的其他bean 比如包含业务逻辑的Service 还有数据库相关的组件等.

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

下面是用JavaConfig方式实现的配置代码 我们先搭建好一个Spring MVC 项目然后结合源码分析Spring如何注册DispatcherServlet实例的.

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

通过重写AbstractAnnotationConfigDispatcherServletInitializer的三个方法完成配置 WebConfig用来配置Web组件 RootConfig用来配置非Web组件.

@EnableWebMvc // 启用MVC@ComponentScan(basePackages = {"com.xlx.mvc.web"}) // 启用组件扫描只扫描web相关的组件@Configurationpublic class WebConfig implements WebMvcConfigurer { // 视图解析器jsp @Bean public ViewResolver viewResolver(){ InternalResourceViewResolver resolver = new InternalResourceViewResolver() resolver.setPrefix("/WEB-INF/views/") resolver.setSuffix(".jsp") resolver.setExposeContextBeansAsAttributes(true) return resolver } // 重写以启用默认的处理器 用来处理静态资源 @Override public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer){ configurer.enable() }}@Configuration@ComponentScan(basePackages = {"com.xlx.mvc"} excludeFilters = { @ComponentScan.Filter(type = FilterType.ANNOTATIONclasses = EnableWebMvc.class)}) // 扫描包 但排除EnableWebMvc注解的类public class RootConfig {}

源码分析

Servlet 3.0 旨在支持基于代码的方式配置Servlet容器 当3.0兼容的servlet容器启动的时候会在ClassPath查找并调用实现了接口ServletContainerInitializer的类的onStartup()方法 Spring中提供了这个接口的一个实现类SpringServletContainerInitializer. 其启动方法的代码如下:

@Overridepublic 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 中实现了onStartup()方法 最终调用registerDispatcherServlet()方法完成注册 两个方法的代码如下:

@Overridepublic 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实现 其最终调用了自定义类的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()) // 注册DispatcherServlet ServletRegistration.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实现了createServletApplicationContext() 可以看到代码中调用了方法getServletConfigClasses() 这是个抽象方法 声明为protected abstract Class<?>[] getServletConfigClasses(). 最终的实现正是在我们自定义的子类MvcWebAppInitializer中.

@Overrideprotected WebApplicationContext createServletApplicationContext() { AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext() // 读取配置类 Class<?>[] configClasses = getServletConfigClasses() if (!ObjectUtils.isEmpty(configClasses)) { context.register(configClasses) } return context}

上面完成了DispatcherServlet的注册和启动 接下来可以定义Controller了.

请求映射

在此之前需要了解下关于URL映射的Servlet规范 注意这是Servlet的规范 当然也适用于DispatcherServlet 代码中我们为DispatcherServlet映射为"/" 规范中"/"为使用"default"Servlet 也就意味着所有的请求默认通过DispatcherServlet处理.

为了处理静态资源 在WebConfig中覆盖了方法configureDefaultServletHandling()已启用静态资源处理器DefaultServletHttpRequestHandler 它的优先级是最低 这意味着在匹配不到其他handler的时候servlet会将请求交给这个handler处理.

规则按顺序执行匹配到就直接返回.

    精确匹配 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请求. */@Beanpublic RequestMappingHandlerMapping requestMappingHandlerMapping() { RequestMappingHandlerMapping mapping = createRequestMappingHandlerMapping() // 顺序为0 顺便提一句 静态资源的处理器Handler的顺序为Integer.Max mapping.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}

好了 现在有了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) // 获取当前请求对应的handler mappedHandler = 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 afterCompletion if (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在内部静态类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使用的前端控制器实现类是DispatcherServlet 其在Servlet容器启动的时候实例化 并初始化容器中的Handler处理器. 当请求到达DispatcherServlet时会调用其doDispatcher()方法选择最合适的处理器. 最后我们扫了一眼Spring Boot的自动装配DispatcherServlet方式.

文章评论

Top