详细内容可参考官方文档: Spring Boot 官网open in new window
我们分小节研究其相关特性
一、手写模拟 SpringBoot 核心流程
概要
参考:手写模拟 SpringBoot 核心流程open in new window
示例代码见 https://gitee.com/shj.com/sample-projects/tree/master/back/sample-sim-src-code/sample-sim-spring-boot-demo
目前示例代码实现了以下核心流程
- 手写模拟SpringBoot启动过程 
- 手写模拟SpringBoot条件注解功能 
- 手写模拟SpringBoot自动配置功能 
1、maven 依赖导入
导入 spring、servlet、tomcat 等相关依赖
直接见示例 pom.xml 文件
2、创建应用启动入口
即创建 SimSpringApplication
public class SimSpringApplication {
    /**
     * 在run方法中,我们要实现的逻辑如下:
     * 1.创建一个Spring容器
     * 2.创建容器对象,如 Tomcat
     * 3.生成DispatcherServlet对象,并且和前面创建出来的Spring容器进行绑定
     * 4.将DispatcherServlet添加到Tomcat中
     * 5.启动容器
     * @param clazz
     */
    public static void run(Class clazz) {
        // 1. 创建 Spring 容器
        AnnotationConfigWebApplicationContext applicationContext = new AnnotationConfigWebApplicationContext();
        applicationContext.register(clazz);
        applicationContext.refresh();
        // 2. 启动容器
        WebServer webServer = getWebServer(applicationContext);
        webServer.start(applicationContext);
    }
    public static WebServer getWebServer(AnnotationConfigWebApplicationContext applicationContext) {
        // key为beanName, value为Bean对象
        Map<String, WebServer> webServers = applicationContext.getBeansOfType(WebServer.class);
        if (webServers.isEmpty()) {
            throw new NullPointerException();
        }
        if (webServers.size() > 1) {
            throw new IllegalStateException();
        }
        // 返回唯一的一个
        return webServers.values().stream().findFirst().get();
    }
}
3、创建核心注解和核心类
创建 SimSpringBootApplication 注解
该注解作用是 applicationContext.register(clazz); 注册时,扫描到了 @ComponentScan 注解,复用 Spring 的扫描逻辑加载 bean 到容器中
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Configuration
@ComponentScan
@Import(SimImportSelect.class)
public @interface SimSpringBootApplication {
}
4、启动 Tomcat 逻辑实现
首先抽象个 Server 类,方便动态替换容器
然后定义对应的 TomcatWebServer 和 JettyWebServer
public interface WebServer {
    void start(WebApplicationContext applicationContext);
}
public class TomcatWebServer implements WebServer{
    @Override
    public void start(WebApplicationContext applicationContext) {
        System.out.println("启动Tomcat web server");
        //此处复用上方启动tomcat代码
        startTomcat(applicationContext);
    }
    // 启动 tomcat
    public static void startTomcat(WebApplicationContext applicationContext) {
        Tomcat tomcat = new Tomcat();
        Server server = tomcat.getServer();
        Service service = server.findService("Tomcat");
        Connector connector = new Connector();
        connector.setPort(8081);
        Engine engine = new StandardEngine();
        engine.setDefaultHost("localhost");
        Host host = new StandardHost();
        host.setName("localhost");
        String contextPath = "";
        Context context = new StandardContext();
        context.setPath(contextPath);
        context.addLifecycleListener(new Tomcat.FixContextListener());
        host.addChild(context);
        engine.addChild(host);
        service.setContainer(engine);
        service.addConnector(connector);
        tomcat.addServlet(contextPath, "dispatcher", new DispatcherServlet(applicationContext));
        context.addServletMappingDecoded("/*", "dispatcher");
        try {
            tomcat.start();
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }
}
public class JettyWebServer implements WebServer {
    @Override
    public void start(WebApplicationContext applicationContext) {
        System.out.println("启动Jetty Web Server");
    }
}然后模拟实现条件注入
即模拟实现条件注入主键及其自定义匹配逻辑
@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Conditional(SimOnClassCondition.class)
public @interface SimConditionOnClass {
    String value() default "";
}
public class SimOnClassCondition implements Condition {
    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        Map<String, Object> attributes = metadata.getAnnotationAttributes(SimConditionOnClass.class.getName());
        String className = (String) attributes.get("value");
        try{
            context.getClassLoader().loadClass(className);
            return true;
        }catch (ClassNotFoundException ex){
            return false;
        }
    }
}
/**
 * 模拟实现条件注入 
 */
public class WebServiceAutoConfiguration implements AutoConfiguration {
    @Bean
    @SimConditionOnClass("org.apache.catalina.startup.Tomcat")
    public TomcatWebServer tomcatWebServer(){
        return new TomcatWebServer();
    }
    @Bean
    @SimConditionOnClass("org.eclipse.jetty.server.Server")
    public JettyWebServer jettyWebServer(){
        return new JettyWebServer();
    }
}
5、发现自动配置类
使用 Spring SPI 发现自动配置类
即 META-INF/services 下新增 com.felix.sim.spring.boot.config.AutoConfiguration 文件,文件内容如下
com.felix.sim.spring.boot.config.WebServiceAutoConfiguration再使用 Import 导入这些配置类
public class SimImportSelect implements DeferredImportSelector {
    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        ServiceLoader<AutoConfiguration> loader = ServiceLoader.load(AutoConfiguration.class);
        List<String> list = new ArrayList<>();
        for (AutoConfiguration configuration : loader) {
            list.add(configuration.getClass().getName());
        }
        return list.toArray(new String[0]);
    }
}
6、验证
启动如下 main 函数,controller 随便写个返回 String 类型的 Http 接口,访问测试即可
详见样例代码(想要支持返回其他类型,解析为 String 类型还得拓展功能)
@SimSpringBootApplication
public class SimSpringBootApplicationDemo {
    public static void main(String[] args) {
        SimSpringApplication.run(SimSpringBootApplicationDemo.class);
    }
}二、SpringBoot 的 Spring MVC
1、概念
为了更好理解相应概念,也会例举部分源码进行分析。
官方文档说明:Servlet Web Applicationsopen in new window。
要点翻译如下:
		Spring Boot 为 Spring MVC 提供了自动配置,可以很好地与大多数应用程序配合使用
它取代了 @EnableWebMvc 的需要,两者不能一起使用
除了 Spring MVC 的默认设置外,自动配置还提供了以下功能:
- 包含 ContentNegotiatingViewResolver 和 BeanNameViewResolver bean 
- 支持提供静态资源,包括支持 WebJars(本文稍后介绍) 
- Converter、GenericConverter 和格式化 bean 的自动注册 
- 支持 HttpMessageConverters(本文稍后介绍) 
- MessageCodesResolver 的自动注册(本文稍后介绍) 
- 静态 index.html 支持 
- 自动使用 ConfigurableWebBindingInitializer bean(本文稍后介绍) 
(本文稍后介绍)是翻译原话
我们这里参考源码、以及简单示例理解下部分功能,详细可以阅读官方文档:SpringBoot SpringMVC Demo 源码open in new window。
2、Spring Boot 两种方式使用 SpringMVC
默认的自动配置类 WebMvcAutoConfiguration 是启用的,如下
@AutoConfiguration(
    after = {DispatcherServletAutoConfiguration.class, TaskExecutionAutoConfiguration.class, ValidationAutoConfiguration.class}
)
@ConditionalOnWebApplication(
    type = Type.SERVLET
)
@ConditionalOnClass({Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class})
@ConditionalOnMissingBean({WebMvcConfigurationSupport.class})
@AutoConfigureOrder(-2147483638)
public class WebMvcAutoConfiguration {
  @Bean
  @ConditionalOnMissingBean
  public InternalResourceViewResolver defaultViewResolver() {
      InternalResourceViewResolver resolver = new InternalResourceViewResolver();
      resolver.setPrefix(this.mvcProperties.getView().getPrefix());
      resolver.setSuffix(this.mvcProperties.getView().getSuffix());
      return resolver;
  }
  @Bean
  @ConditionalOnBean({View.class})
  @ConditionalOnMissingBean
  public BeanNameViewResolver beanNameViewResolver() {
      BeanNameViewResolver resolver = new BeanNameViewResolver();
      resolver.setOrder(2147483637);
      return resolver;
  }
  @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));
      resolver.setOrder(Integer.MIN_VALUE);
      return resolver;
  }
}注意 @EnableWebMvc 启用时,导入了一个 WebMvcConfigurationSupport 类,因此 WebMvcAutoConfiguration 会失效,如下代码
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(DelegatingWebMvcConfiguration.class)
public @interface EnableWebMvc {
}
@Configuration(proxyBeanMethods = false)
public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport {
}3、相关核心类介绍
ViewResolver 都是 SpringMVC 内置的视图解析器
- InternalResourceViewResolver - 内部资源视图解析器,通过类似 prefix=/WEB-INF/、suffix=jsp 配置支持如 jsp、html 文件的解析 
 
- BeanNameViewResolver - 简单示例见 MvcViewController.helloJavaView 和 HelloJavaView 对象代码 
- 要点: - 注意 Controller 不要有 @ResponseBody 注解,不然不会进入视图解析器 
- 找的 Bean 要实现了 View 接口 
- 是根据 Bean 的名称找的视图解析器 
 
 
- ContentNegotiatingViewResolver - ContentNegotiatingViewResolver 本身不解析视图,而是委托给其他 ViewResolver 
- 具体解析视图对应逻辑为 ContentNegotiatingViewResolver.resolveViewName() 方法 
- 关注以下要点: - 返回 Json 对象时,返回的不是视图 
- 所有视图解析器,都会根据返回的视图名称进行 resolveViewName 
 
- 源码如下,关注下逻辑: - 初始化视图解析器 
- 获得所有匹配的视图 
- 委托给其他 ViewResolver 处理 
- 获得最终匹配的视图 
 
 
public class ContentNegotiatingViewResolver extends WebApplicationObjectSupport
		implements ViewResolver, Ordered, InitializingBean {
  @Override
	protected void initServletContext(ServletContext servletContext) {
		Collection<ViewResolver> matchingBeans =
				BeanFactoryUtils.beansOfTypeIncludingAncestors(obtainApplicationContext(), ViewResolver.class).values();
		if (this.viewResolvers == null) {
      // 初始化视图解析器,可以打个断点看下有哪些数据
			this.viewResolvers = new ArrayList<>(matchingBeans.size());
			for (ViewResolver viewResolver : matchingBeans) {
				if (this != viewResolver) {
					this.viewResolvers.add(viewResolver);
				}
			}
		}
		else {
			for (int i = 0; i < this.viewResolvers.size(); i++) {
				ViewResolver vr = this.viewResolvers.get(i);
				if (matchingBeans.contains(vr)) {
					continue;
				}
				String name = vr.getClass().getName() + i;
				obtainApplicationContext().getAutowireCapableBeanFactory().initializeBean(vr, name);
			}
		}
		AnnotationAwareOrderComparator.sort(this.viewResolvers);
		this.cnmFactoryBean.setServletContext(servletContext);
	}
   public View resolveViewName(String viewName, Locale locale) throws Exception {
		RequestAttributes attrs = RequestContextHolder.getRequestAttributes();
		Assert.state(attrs instanceof ServletRequestAttributes, "No current ServletRequestAttributes");
		List<MediaType> requestedMediaTypes = getMediaTypes(((ServletRequestAttributes) attrs).getRequest());
		if (requestedMediaTypes != null) {
      // 获得所有匹配的视图
			List<View> candidateViews = getCandidateViews(viewName, locale, requestedMediaTypes);
      // 获取最终的视图
			View bestView = getBestView(candidateViews, requestedMediaTypes, attrs);
			if (bestView != null) {
				return bestView;
			}
		}
		String mediaTypeInfo = logger.isDebugEnabled() && requestedMediaTypes != null ?
				" given " + requestedMediaTypes.toString() : "";
		if (this.useNotAcceptableStatusCode) {
			if (logger.isDebugEnabled()) {
				logger.debug("Using 406 NOT_ACCEPTABLE" + mediaTypeInfo);
			}
			return NOT_ACCEPTABLE_VIEW;
		}
		else {
			logger.debug("View remains unresolved" + mediaTypeInfo);
			return null;
		}
	}
  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");
      // ContentNegotiatingViewResolver 本身不解析视图,而是委托给其他 ViewResolver
			for (ViewResolver viewResolver : this.viewResolvers) {
				View view = viewResolver.resolveViewName(viewName, locale);
				if (view != null) {
					candidateViews.add(view);
				}
				for (MediaType requestedMediaType : requestedMediaTypes) {
					List<String> extensions = this.contentNegotiationManager.resolveFileExtensions(requestedMediaType);
					for (String extension : extensions) {
						String viewNameWithExtension = viewName + '.' + extension;
						view = viewResolver.resolveViewName(viewNameWithExtension, locale);
						if (view != null) {
							candidateViews.add(view);
						}
					}
				}
			}
		}
		if (!CollectionUtils.isEmpty(this.defaultViews)) {
			candidateViews.addAll(this.defaultViews);
		}
		return candidateViews;
	}
}4、支持提供静态资源
以前要访问 jps\css、js 等静态资源文件,需要在 web.xml 配置
在 SpringBoot 不需要配置,只需要放在约定文件夹中就可以(阅读大于配置)
源码如下,注意如下要点(各版本差别较大的话,源码和配置会略有不同)
- webjars 是用 jar 包装了一些 js、css 的 jar 包,https://www.webjars.org/ 可以看到列表 
- 使用步骤是 pom.xml 添加对应 webjars 依赖,然后就可以使用了,见 Demo,使用 http://localhost:8081/webjars/jquery/3.5.1/jquery.js 访问验证 
- 静态资源地址,看源码有这些地址 - /META-INF/resources/、- classpath:/resources/、- classpath:/static/、- classpath:/public/,可以通过配置覆盖- spring.web.resources.static-locations
- 配置欢迎页面,WebMvcAutoConfiguration 搜 - index.html可以找到默认的欢迎页面名字,注意是找 staticLocations 下找欢迎页面
public class WebMvcAutoConfiguration {
  public void addResourceHandlers(ResourceHandlerRegistry registry) {
    if (!this.resourceProperties.isAddMappings()) {
      logger.debug("Default resource handling disabled");
    } else {
      // 去 webjars 里面去找资源
      this.addResourceHandler(registry, "/webjars/**", "classpath:/META-INF/resources/webjars/");
      this.addResourceHandler(registry, this.mvcProperties.getStaticPathPattern(), (Consumer)((registration) -> {
        // this.resourceProperties 里面有静态资源地址
        registration.addResourceLocations(this.resourceProperties.getStaticLocations());
        if (this.servletContext != null) {
            ServletContextResource resource = new ServletContextResource(this.servletContext, "/");
            registration.addResourceLocations(new Resource[]{resource});
        }
      }));
    }
  }
  // 获取欢迎页
  private Resource getIndexHtmlResource() {
    for(String location : this.resourceProperties.getStaticLocations()) {
      Resource indexHtml = this.getIndexHtmlResource(location);
      if (indexHtml != null) {
        return indexHtml;
      }
    }
    ServletContext servletContext = this.getServletContext();
    if (servletContext != null) {
      return this.getIndexHtmlResource((Resource)(new ServletContextResource(servletContext, "/")));
    } else {
      return null;
    }
  } 
  private Resource getIndexHtmlResource(String location) {
    return this.getIndexHtmlResource(this.resourceLoader.getResource(location));
  }
  private Resource getIndexHtmlResource(Resource location) {
    try {
      Resource resource = location.createRelative("index.html");
      if (resource.exists() && resource.getURL() != null) {
        return resource;
      }
    } catch (Exception var3) {
    }
    return null;
  }
}
public static class WebMvcAutoConfigurationAdapter implements WebMvcConfigurer, ServletContextAware {
  private final WebProperties.Resources resourceProperties;
}
@ConfigurationProperties("spring.web")
public class WebProperties {
  private final Resources resources;
  public WebProperties() {
      this.localeResolver = WebProperties.LocaleResolver.ACCEPT_HEADER;
      this.resources = new Resources();
  }
  public static class Resources {
    private static final String[] CLASSPATH_RESOURCE_LOCATIONS = new String[]{"classpath:/META-INF/resources/", "classpath:/resources/", "classpath:/static/", "classpath:/public/"};
    public Resources() {
        this.staticLocations = CLASSPATH_RESOURCE_LOCATIONS;
        this.addMappings = true;
        this.customized = false;
        this.chain = new Chain();
        this.cache = new Cache();
    }
  }
  public Resources getResources() {
        return this.resources;
    }
}5、类型转换、数据格式化、数据验证
略
也可以了解下 WebMvcAutoConfiguration 默认的对象
6、支持 HttpMessageConverters
HttpMessageConverters 负责 Http 请求和响应报文的处理。比如数据接收和响应是用 XML 还是 JSON 处理
默认的 HttpMessageConverters 源码如下
@AutoConfiguration(
  after = {GsonAutoConfiguration.class, JacksonAutoConfiguration.class, JsonbAutoConfiguration.class}
)
@ConditionalOnClass({HttpMessageConverter.class})
@Conditional({NotReactiveWebApplicationCondition.class})
@Import({JacksonHttpMessageConvertersConfiguration.class, GsonHttpMessageConvertersConfiguration.class, JsonbHttpMessageConvertersConfiguration.class})
public class HttpMessageConvertersAutoConfiguration {
}
@Configuration(
    proxyBeanMethods = false
)
class JacksonHttpMessageConvertersConfiguration {
  
}
@Configuration(
  proxyBeanMethods = false
)
@ConditionalOnClass({ObjectMapper.class})
@ConditionalOnBean({ObjectMapper.class})
@ConditionalOnProperty(
  name = {"spring.mvc.converters.preferred-json-mapper"},
  havingValue = "jackson",
  matchIfMissing = true
)
static class MappingJackson2HttpMessageConverterConfiguration {
  MappingJackson2HttpMessageConverterConfiguration() {
  }
  @Bean
  @ConditionalOnMissingBean(
    value = {MappingJackson2HttpMessageConverter.class},
    ignoredType = {"org.springframework.hateoas.server.mvc.TypeConstrainedMappingJackson2HttpMessageConverter", "org.springframework.data.rest.webmvc.alps.AlpsJsonHttpMessageConverter"}
  )
  MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter(ObjectMapper objectMapper) {
    return new MappingJackson2HttpMessageConverter(objectMapper);
  }
}7、MessageCodesResolver 的自动注册
比如验证 Bean 报错时的提示信息格式(errorCode.objectName.field 或 objectName.field.errorCode)
略
8、静态 index.html 支持
即使用 InternalResourceViewResolver 默认的视图解析器,将后缀配置为 html,即可支持 html
9、自动使用 ConfigurableWebBindingInitializer bean
和 HttpMessageConverters 不同,这个其实是处理 json 的入参转换
如 @RequestBody
public class WebMvcAutoConfiguration {
  protected ConfigurableWebBindingInitializer getConfigurableWebBindingInitializer(FormattingConversionService mvcConversionService, Validator mvcValidator) {
    try {
      return (ConfigurableWebBindingInitializer)this.beanFactory.getBean(ConfigurableWebBindingInitializer.class);
    } catch (NoSuchBeanDefinitionException var4) {
      return super.getConfigurableWebBindingInitializer(mvcConversionService, mvcValidator);
    }
  }
}
// 跟踪代码,找到 getConfigurableWebBindingInitializer.requestMappingHandlerAdapter 时会使用
public class WebMvcConfigurationSupport implements ApplicationContextAware, ServletContextAware {
  @Bean
  public RequestMappingHandlerAdapter requestMappingHandlerAdapter(@Qualifier("mvcContentNegotiationManager") ContentNegotiationManager contentNegotiationManager, @Qualifier("mvcConversionService") FormattingConversionService conversionService, @Qualifier("mvcValidator") Validator validator) {
    RequestMappingHandlerAdapter adapter = this.createRequestMappingHandlerAdapter();
    adapter.setContentNegotiationManager(contentNegotiationManager);
    adapter.setMessageConverters(this.getMessageConverters());
    adapter.setWebBindingInitializer(this.getConfigurableWebBindingInitializer(conversionService, validator));
  }
}使用示例
1、视图解析器简单验证
在 ContentNegotiatingViewResolver.getCandidateViews() 方法上打个断点,触发 Controller 请求,看下是否会进入视图解析器处理逻辑
- 非视图解析器的验证 - 代码见 CommonController,浏览器输入 http://localhost:8081/common/hello 
- 会正常的返回数据,不会进入 getCandidateViews() 方法 
 
- 进入视图解析器的验证 - 浏览器输入不存在的 URL,如 http://localhost:8081/common/404Test 
- 页面报错: Whitelabel Error Page 
 
- 自定义视图解析器 - 即使用 @Bean 注入自定义的 ViewResolver 对象即可 
 
- 自定义 Excel 视图解析器 - 添加自定义 View,实现 AbstractXlsxView,编写自己的业务逻辑即可 
- 可以见 MyXlsxView 代码,数据对象应该是 Controller 的返回数据 
 
2、定制 SpringMVC ViewController
以前写 SpringMVC 的时候,如果需要访问一个页面,必须要写 Controller 类,然后再写一个方法跳转到页面
感觉好麻烦,其实重写 WebMvcConfigurer 中的 addViewControllers 方法即可达到效果了
@Override
public void addViewControllers(ViewControllerRegistry registry) {
    registry.addViewController("/toLogin").setViewName("login");
}3、定制 SpringMVC configurePathMatch
重写 WebMvcConfigurer 中的 configurePathMatch 方法并通过调用 setUseSuffixPatternMatch 和 setUseRegisteredSuffixPatternMatch 方法来设置 PathMatch 的属性
public void configurePathMatch(PathMatchConfigurer configurer) {
  configurer.setUseSuffixPatternMatch(false);
  configurer.setUseTrailingSlashMatch(true);
}- setUseSuffixPatternMatch(false):禁用后缀模式匹配,例如,/user 和 /user.json 将被视为不同的 URL 
- setUseTrailingSlashMatch(true): 启用尾部斜杠匹配,例如, /user 和 /user/ 将被视为相同的 URL 
4、定制 SpringMVC 拦截器 Interceptor
自定义实现了 HandlerInterceptor 的拦截器类,并重写 WebMvcConfigurer.addInterceptors 到 Spring 容器中即可
5、定制 SpringMVC CORS 配置
略。
6、定制 SpringMVC JSON
略。
7、定制 SpringMVC 国际化
略。
8、定制 SpringMVC 异常处理
略。
WebMvcConfigure 原理
实现 WebMvcConfigure 接口可以拓展 MVC 实现,又既保留 SpringBoot 的自动配置
- 在 WebMvcAutoConfiguration 有一个实现了 WebMvcConfigure 的配置类 WebMvcAutoConfigurationAdapter 
- WebMvcAutoConfigurationAdapter 它也是利用这种方式去进行扩展,所以我们通过查看这个类它帮我们实现了其他不常用的方法,帮助我们进行自动配置,我们只需定制(拦截器、视图控制器、CORS 在开发中需要额外定制的功能),这块主要逻辑为 - @Import({EnableWebMvcConfiguration.class})配合处理的,代码如下
@Configuration(
  proxyBeanMethods = false
)
@Import({EnableWebMvcConfiguration.class})
@EnableConfigurationProperties({WebMvcProperties.class, WebProperties.class})
@Order(0)
public static class WebMvcAutoConfigurationAdapter implements WebMvcConfigurer, ServletContextAware {
}- EnableWebMvcConfiguration 它的父类上 setConfigurers 使用 @Autowired 注解 
 (1) 将容器中所有实现了 WebMvcConfigurer 接口的 Bean 都自动注入进来,添加到 configurers 中
 (2) 添加到 delegates 委派器中
 (3) 底层调用 WebMvcConfigure 对应的方法时,就是循环调用 delegates 处理
 (4) 注意:当使用了 @EnableWebMvc 注解时,就不会使用 SpringMVC 自动配置类的默认配置了
@Configuration(
  proxyBeanMethods = false
)
@EnableConfigurationProperties({WebProperties.class})
public static class EnableWebMvcConfiguration extends DelegatingWebMvcConfiguration implements ResourceLoaderAware {
}
@Configuration(
    proxyBeanMethods = false
)
public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport {
  @Autowired(required = false)
  public void setConfigurers(List<WebMvcConfigurer> configurers) {
    // (1) 将容器中所有实现了 WebMvcConfigurer 接口的 Bean 都自动注入进来,添加到 configurers 中。  
    if (!CollectionUtils.isEmpty(configurers)) {
      this.configurers.addWebMvcConfigurers(configurers);
    }
  }
}
class WebMvcConfigurerComposite implements WebMvcConfigurer {
  private final List<WebMvcConfigurer> delegates = new ArrayList();
  public void addWebMvcConfigurers(List<WebMvcConfigurer> configurers) {
    // (2) 添加到 delegates 委派器中。  
    if (!CollectionUtils.isEmpty(configurers)) {
      this.delegates.addAll(configurers);
    }
  }
  public void addInterceptors(InterceptorRegistry registry) {
    // (3) 底层调用 WebMvcConfigure 对应的方法时,就是循环调用 delegates 处理。
    for(WebMvcConfigurer delegate : this.delegates) {
      delegate.addInterceptors(registry);
    }
  }
}
参考文献
- 比较基础,更多的是教程 SpringBoot 夺命连环 70 问 
 
             
           
             
                         
             
            
评论区