
文章结构
- 源码阅读环境的搭建
- SpringBoot中SpringMVC自动配置原理
DispatcherServlet的初始化逻辑- web容器的初始化
源码阅读环境搭建
为了简单起见,再一个就是现在这个年代也没有啥项目使用JSP了。所以本次分析使用SpringBoot结合thymeleaf来搞
首先pom文件依赖
<parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.0.0.RELEASE</version></parent><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-thymeleaf</artifactId></dependency>
application.properties
spring.thymeleaf.prefix=classpath:/templates/spring.thymeleaf.suffix=.htmlspring.thymeleaf.mode=LEGACYHTML5spring.thymeleaf.encoding=UTF-8spring.thymeleaf.content-type=text/htmlspring.thymeleaf.cache=false
test.html
<!DOCTYPE HTML><html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org"><head><meta content="text/html;charset=UTF-8"/></head><body><span th:text="'用户名:'+${name}+',年龄:'+${age}"></span></body></html>
Controller
@Controllerpublic class MVCDemoController {@RequestMapping(value = "/testMVC",method = RequestMethod.GET )public String testMVC(Model model){model.addAttribute("name","张三");model.addAttribute("age","18");return "test";}}
启动demo
@SpringBootApplication(scanBasePackages="cn.shiyujun.controller")public class MVCDemo {public static void main (String args[]){SpringApplication.run(MVCDemo.class, args);}}
至此Deno工程搭建完毕,有需要源码的同学可以从下方地址获取
https://github.com/shiyujun/spring-framework
源码分析
SpringMVC自动配置
我们知道在SpringBoot中使用SpringMVC的时候是不需要像传统Spring中配置web.xml和配置文件等等的。那么大家知道这是为什么吗
答案就在这个类WebMvcAutoConfiguration里面
@Configuration@ConditionalOnWebApplication(type = Type.SERVLET)@ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class })@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)@AutoConfigureAfter({ DispatcherServletAutoConfiguration.class,ValidationAutoConfiguration.class })public class WebMvcAutoConfiguration {public static final String DEFAULT_PREFIX = "";public static final String DEFAULT_SUFFIX = "";private static final String[] SERVLET_LOCATIONS = { "/" };@Bean@ConditionalOnMissingBean(HiddenHttpMethodFilter.class)public OrderedHiddenHttpMethodFilter hiddenHttpMethodFilter() {return new OrderedHiddenHttpMethodFilter();}@Bean@ConditionalOnMissingBean(HttpPutFormContentFilter.class)@ConditionalOnProperty(prefix = "spring.mvc.formcontent.putfilter", name = "enabled", matchIfMissing = true)public OrderedHttpPutFormContentFilter httpPutFormContentFilter() {return new OrderedHttpPutFormContentFilter();}@Configuration@Import(EnableWebMvcConfiguration.class)@EnableConfigurationProperties({ WebMvcProperties.class, ResourceProperties.class })public static class WebMvcAutoConfigurationAdapterimplements WebMvcConfigurer, ResourceLoaderAware {@Bean@ConditionalOnMissingBeanpublic InternalResourceViewResolver defaultViewResolver() {InternalResourceViewResolver resolver = new InternalResourceViewResolver();resolver.setPrefix(this.mvcProperties.getView().getPrefix());resolver.setSuffix(this.mvcProperties.getView().getSuffix());return resolver;}@Bean@ConditionalOnBean(View.class)@ConditionalOnMissingBeanpublic BeanNameViewResolver beanNameViewResolver() {BeanNameViewResolver resolver = new BeanNameViewResolver();resolver.setOrder(Ordered.LOWEST_PRECEDENCE - 10);return resolver;}@Bean@ConditionalOnBean(ViewResolver.class)@ConditionalOnMissingBean(name = "viewResolver", value = ContentNegotiatingViewResolver.class)public ContentNegotiatingViewResolver viewResolver(BeanFactory beanFactory) {ContentNegotiatingViewResolver resolver = new ContentNegotiatingViewResolver();resolver.setContentNegotiationManager(beanFactory.getBean(ContentNegotiationManager.class));// ContentNegotiatingViewResolver uses all the other view resolvers to locate// a view so it should have a high precedenceresolver.setOrder(Ordered.HIGHEST_PRECEDENCE);return resolver;}@Bean@ConditionalOnMissingBean@ConditionalOnProperty(prefix = "spring.mvc", name = "locale")public LocaleResolver localeResolver() {if (this.mvcProperties.getLocaleResolver() == WebMvcProperties.LocaleResolver.FIXED) {return new FixedLocaleResolver(this.mvcProperties.getLocale());}AcceptHeaderLocaleResolver localeResolver = new AcceptHeaderLocaleResolver();localeResolver.setDefaultLocale(this.mvcProperties.getLocale());return localeResolver;}@Beanpublic WelcomePageHandlerMapping welcomePageHandlerMapping(ApplicationContext applicationContext) {return new WelcomePageHandlerMapping(new TemplateAvailabilityProviders(applicationContext),applicationContext, getWelcomePage(),this.mvcProperties.getStaticPathPattern());}@Bean@ConditionalOnMissingBean({ RequestContextListener.class,RequestContextFilter.class })public static RequestContextFilter requestContextFilter() {return new OrderedRequestContextFilter();}@Configuration@ConditionalOnProperty(value = "spring.mvc.favicon.enabled", matchIfMissing = true)public static class FaviconConfiguration implements ResourceLoaderAware {@Beanpublic SimpleUrlHandlerMapping faviconHandlerMapping() {SimpleUrlHandlerMapping mapping = new SimpleUrlHandlerMapping();mapping.setOrder(Ordered.HIGHEST_PRECEDENCE + 1);mapping.setUrlMap(Collections.singletonMap("**/favicon.ico",faviconRequestHandler()));return mapping;}@Beanpublic ResourceHttpRequestHandler faviconRequestHandler() {ResourceHttpRequestHandler requestHandler = new ResourceHttpRequestHandler();requestHandler.setLocations(resolveFaviconLocations());return requestHandler;}}}/*** Configuration equivalent to {@code @EnableWebMvc}.*/@Configurationpublic static class EnableWebMvcConfiguration extends DelegatingWebMvcConfiguration {@Bean@Overridepublic RequestMappingHandlerAdapter requestMappingHandlerAdapter() {RequestMappingHandlerAdapter adapter = super.requestMappingHandlerAdapter();adapter.setIgnoreDefaultModelOnRedirect(this.mvcProperties == null|| this.mvcProperties.isIgnoreDefaultModelOnRedirect());return adapter;}@Bean@Primary@Overridepublic RequestMappingHandlerMapping requestMappingHandlerMapping() {// Must be @Primary for MvcUriComponentsBuilder to workreturn super.requestMappingHandlerMapping();}@Bean@Overridepublic FormattingConversionService mvcConversionService() {WebConversionService conversionService = new WebConversionService(this.mvcProperties.getDateFormat());addFormatters(conversionService);return conversionService;}@Bean@Overridepublic Validator mvcValidator() {if (!ClassUtils.isPresent("javax.validation.Validator",getClass().getClassLoader())) {return super.mvcValidator();}return ValidatorAdapter.get(getApplicationContext(), getValidator());}@Bean@Overridepublic ContentNegotiationManager mvcContentNegotiationManager() {ContentNegotiationManager manager = super.mvcContentNegotiationManager();List<ContentNegotiationStrategy> strategies = manager.getStrategies();ListIterator<ContentNegotiationStrategy> iterator = strategies.listIterator();while (iterator.hasNext()) {ContentNegotiationStrategy strategy = iterator.next();if (strategy instanceof PathExtensionContentNegotiationStrategy) {iterator.set(new OptionalPathExtensionContentNegotiationStrategy(strategy));}}return manager;}}@Configuration@ConditionalOnEnabledResourceChainstatic class ResourceChainCustomizerConfiguration {@Beanpublic ResourceChainResourceHandlerRegistrationCustomizer resourceHandlerRegistrationCustomizer() {return new ResourceChainResourceHandlerRegistrationCustomizer();}}}
仔细看这个类,你就会发现这些自动注入的一些类都是之前需要我们在xml文件中配置的,只不过是SpringBoot帮我们做了这个操作。只有当我们需要自定义一些东西的才有必要去关心这些配置,平常使用的时候拿来就用即可。这就是大名鼎鼎的约定大于配置
初始化DispatcherServlet
Tomcat.java
->loadServlet()
->GenericServlet.init()
->Servlet.init()
DispatcherServlet是一个实现了Servlet接口的类,Servlet的初始化阶段会调用它的init()方法,而DispatcherServlet的方法是继承自父类HttpServletBean的
public final void init() throws ServletException {if (logger.isDebugEnabled()) {logger.debug("Initializing servlet '" + getServletName() + "'");}//处理init-param参数,但是SpringBoot中默认是没有的PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);if (!pvs.isEmpty()) {try {BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));initBeanWrapper(bw);bw.setPropertyValues(pvs, true);}catch (BeansException ex) {if (logger.isErrorEnabled()) {logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex);}throw ex;}}// 初始化Servlet,往下看initServletBean();if (logger.isDebugEnabled()) {logger.debug("Servlet '" + getServletName() + "' configured successfully");}}protected final void initServletBean() throws ServletException {getServletContext().log("Initializing Spring FrameworkServlet '" + getServletName() + "'");if (this.logger.isInfoEnabled()) {this.logger.info("FrameworkServlet '" + getServletName() + "': initialization started");}long startTime = System.currentTimeMillis();try {//初始化web容器this.webApplicationContext = initWebApplicationContext();//扩展点initFrameworkServlet();}catch (ServletException ex) {this.logger.error("Context initialization failed", ex);throw ex;}catch (RuntimeException ex) {this.logger.error("Context initialization failed", ex);throw ex;}if (this.logger.isInfoEnabled()) {long elapsedTime = System.currentTimeMillis() - startTime;this.logger.info("FrameworkServlet '" + getServletName() + "': initialization completed in " +elapsedTime + " ms");}}
初始化web容器
protected WebApplicationContext initWebApplicationContext() {//获取AnnotationConfigServletWebServerApplicationContext类型的web容器WebApplicationContext rootContext =WebApplicationContextUtils.getWebApplicationContext(getServletContext());WebApplicationContext wac = null;if (this.webApplicationContext != null) {wac = this.webApplicationContext;if (wac instanceof ConfigurableWebApplicationContext) {ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;if (!cwac.isActive()) {if (cwac.getParent() == null) {cwac.setParent(rootContext);}configureAndRefreshWebApplicationContext(cwac);}}}if (wac == null) {wac = findWebApplicationContext();}if (wac == null) {wac = createWebApplicationContext(rootContext);}if (!this.refreshEventReceived) {// 刷新应用上下文,这里是重点,接着往下看onRefresh(wac);}if (this.publishContext) {// 推送事件通知String attrName = getServletContextAttributeName();getServletContext().setAttribute(attrName, wac);if (this.logger.isDebugEnabled()) {this.logger.debug("Published WebApplicationContext of servlet '" + getServletName() +"' as ServletContext attribute with name [" + attrName + "]");}}return wac;}
下面会看到一些熟悉的东西
protected void onRefresh(ApplicationContext context) {initStrategies(context);}protected void initStrategies(ApplicationContext context) {initMultipartResolver(context);initLocaleResolver(context);initThemeResolver(context);initHandlerMappings(context);initHandlerAdapters(context);initHandlerExceptionResolvers(context);initRequestToViewNameTranslator(context);initViewResolvers(context);initFlashMapManager(context);}
可以看到上方这一块代码都是一些常用组件的初始化,初始化的逻辑都比较简单,下面随意挑取2个看一下
private void initMultipartResolver(ApplicationContext context) {try {this.multipartResolver = context.getBean(MULTIPART_RESOLVER_BEAN_NAME, MultipartResolver.class);if (logger.isDebugEnabled()) {logger.debug("Using MultipartResolver [" + this.multipartResolver + "]");}}catch (NoSuchBeanDefinitionException ex) {// Default is no multipart resolver.this.multipartResolver = null;if (logger.isDebugEnabled()) {logger.debug("Unable to locate MultipartResolver with name '" + MULTIPART_RESOLVER_BEAN_NAME +"': no multipart request handling provided");}}}private void initHandlerMappings(ApplicationContext context) {this.handlerMappings = null;if (this.detectAllHandlerMappings) {// 加载所有实现HandlerMapping接口的beanMap<String, HandlerMapping> matchingBeans =BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);if (!matchingBeans.isEmpty()) {this.handlerMappings = new ArrayList<>(matchingBeans.values());// 排序,Spring处理请求就是根据这个排序的结果进行处理,如果当前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.}}// Ensure we have at least one HandlerMapping, by registering// a default HandlerMapping if no other mappings are found.if (this.handlerMappings == null) {this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);if (logger.isDebugEnabled()) {logger.debug("No HandlerMappings found in servlet '" + getServletName() + "': using default");}}}
至此,DispatcherServlet的初始化就完成了
