Spring系列(六) Spring Web MVC 应用构建分析
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相关的Controller,ViewResolver,HandlerMapping等.
另外一个上下文Root WebApplicationContext是由ContextLoaderListener创建的, 包含除了Web组件外的其他bean, 比如包含业务逻辑的Service, 还有数据库相关的组件等.
代码(JavaConfig方式的配置代码)
下面是用JavaConfig方式实现的配置代码, 我们先搭建好一个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;}// 重写以启用默认的处理器, 用来处理静态资源@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查找并调用实现了接口ServletContainerInitializer
的类的onStartup()
方法, Spring中提供了这个接口的一个实现类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
中实现了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实现, 其最终调用了自定义类的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
实现了createServletApplicationContext()
, 可以看到代码中调用了方法getServletConfigClasses()
, 这是个抽象方法, 声明为protected abstract Class<?>[] getServletConfigClasses();
. 最终的实现正是在我们自定义的子类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规范, 注意这是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请求.*/
@Bean
public RequestMappingHandlerMapping requestMappingHandlerMapping() {RequestMappingHandlerMapping mapping = createRequestMappingHandlerMapping();// 顺序为0, 顺便提一句, 静态资源的处理器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;
}
好了, 现在有了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在内部静态类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
方式.
转载于:https://www.cnblogs.com/walkinhalo/p/9732125.html
Spring系列(六) Spring Web MVC 应用构建分析相关推荐
- 玩转群晖NAS套件系列六:Web Station的安装与使用保姆级教程!
本章总结: 上一章节我们讲解<玩转群晖NAS套件系列五:Moments的安装与使用保姆级教程!>,此教程堪称史上手把手的保姆教程,受到广大网友的一致好评. Web Station这个套件是 ...
- Spring系列之Spring Web MVC-20
目录 Spring Web MVC DispatcherServlet 上下文层次结构 特殊Bean Web MVC 配置 程序配置 工作原理 异常 视图解析 配置 重定向 转发 内容协商 过滤器 F ...
- SpringSecurity权限管理框架系列(六)-Spring Security框架自定义配置类详解(二)之authorizeRequests配置详解
1.预置演示环境 这个演示环境继续沿用 SpringSecurit权限管理框架系列(五)-Spring Security框架自定义配置类详解(一)之formLogin配置详解的环境. 2.自定义配置类 ...
- Spring系列 1.Spring概述及IOP
Spring概述 简介 Spring : 春天 ->给软件行业带来了春天 2002年,Rod Jahnson首次推出了Spring框架雏形interface21框架. 2004年3月24日,Sp ...
- 【Spring 系列】Spring知识地图
文章目录 Spring IOC 知道 会用 熟练 掌握 专家 Spring AOP 知道 会用 熟练 掌握 专家 Spring MVC 知道 会用 熟练 掌握 专家 Spring WebFlux 知道 ...
- Spring系列之Spring框架和SpringAOP集成过程分析(十)
转载请注明出处:https://blog.csdn.net/zknxx/article/details/80724180 在开始这个系列之前大家先想一下我们是怎么在项目中使用SpringAOP的(这里 ...
- Spring系列之Spring常用注解总结
参看博客:https://www.cnblogs.com/xiaoxi/p/5935009.html 传统的Spring做法是使用.xml文件来对bean进行注入或者是配置aop.事物,这么做有两个缺 ...
- Spring系列之Spring常用注解总结 原文:https://www.cnblogs.com/xiaoxi/p/5935009.html
传统的Spring做法是使用.xml文件来对bean进行注入或者是配置aop.事物,这么做有两个缺点: 1.如果所有的内容都配置在.xml文件中,那么.xml文件将会十分庞大:如果按需求分开.xml文 ...
- Spring系列之一 Spring MVC
摘要: 最近在看Spring的书,之前一直跟着项目做,虽然项目用到了Spring的很多功能,但是我很少有机会在工作的项目中去配置Spring.感觉只有理论是不够的,虽然只是用Spring的配置让人感觉 ...
最新文章
- ASP.NET 网页- WebGrid 帮助器简介
- java按特殊标志截取_java 字符串分割处理split及特殊符号
- Day20 Ajax
- phpMyAdmin操作之改管理员密码
- map/set/object/array对比
- 增强学习(四) ----- 蒙特卡罗方法(Monte Carlo Methods)
- Java笔记(七)HashMap和HashSet
- mos管工作原理_筋膜枪原理与筋膜枪方案,和筋膜枪烧mos管原理。推荐使用mos管 AP15G04NF...
- PHP代码调试神器Whoops
- 单线程任务 Task.Factory.StartNew 封装
- 基于 Python 自建分布式高并发 RPC 服务
- 探秘采云间:全链路数据处理工具直击传统DW/BI痛点
- 以太网CSMA/CD算法交换机自学习/转发简述
- nginx websocket wss 连接失败 failed_Nginx 配置WSS 解析与实战
- Lua1.0 代码分析 table.c
- Hive-获取本月的第一天,本月的最后一天,本月的天数
- 南方科技大学21年计算机考研情况 不保护一志愿?心比天高,德比纸薄?
- 南京信息工程大学计算机考研怎么样,南京信息工程大学就业怎么样,考研好不好?...
- 神秘AI变脸软件风靡全球,让你秒变身迪士尼在逃主角
- 认识loadrunner及相关性能参数
热门文章
- php 安装xdebug扩展
- 程序员必知8大排序3大查找(一)
- 书摘:35岁之前成功的12条黄金法则
- fork与vfork的区别
- NET Core微服务之路:让我们对上一个Demo通讯进行修改,完成RPC通讯
- Struts2教程9:实现自已的拦截器
- 【LeetCode-面试算法经典-Java实现】【002-Add Two Numbers (单链表表示的两个数相加)】...
- CentOS 6.8安装Python2.7.13
- JS 与Flex交互:html中的js 与flex中的actionScript通信
- Microsoft Lync