我们都知道,想要构建 Spring MVC 环境,有两种方式,一种利用 XML 来实现,另一种是通过 Java 代码来实现,具体可以通过 《Spring MVC 0-XML 配置的原理》 查看
那么 SpringBoot 是如何实现的呢?其实主要思路差不多,我们可以具体看一下:
@EnableAutoConfiguration
首先通过该注解,来开启自动配置,所谓自动配置,就是预读某些一开始就定义好需要加载到 Spring 容器中的 bean,而 @EnableAutoConfiguration 是通过 @Import(AutoConfigurationImportSelector.class) 来实现的
META-INF/spring.factories
而 AutoConfigurationImportSelector.class 主要就是通过读取 “META-INF/spring.factories” 来实现的
private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {MultiValueMap<String, String> result = cache.get(classLoader);if (result != null) {return result;}try {Enumeration<URL> urls = (classLoader != null ?classLoader.getResources("META-INF/spring.factories") :ClassLoader.getSystemResources("META-INF/spring.factories"));result = new LinkedMultiValueMap<>();while (urls.hasMoreElements()) {URL url = urls.nextElement();UrlResource resource = new UrlResource(url);Properties properties = PropertiesLoaderUtils.loadProperties(resource);for (Map.Entry<?, ?> entry : properties.entrySet()) {String factoryTypeName = ((String) entry.getKey()).trim();for (String factoryImplementationName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {result.add(factoryTypeName, factoryImplementationName.trim());}}}cache.put(classLoader, result);return result;}catch (IOException ex) {throw new IllegalArgumentException("Unable to load factories from location [" +FACTORIES_RESOURCE_LOCATION + "]", ex);}}
我们可以看到 “META-INF/spring.factories” 中预加载了 **DispatcherServletAutoConfiguration**
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration,\
所以 DispatcherServletAutoConfiguration 这个类在 SpringBoot 启动的时候就被实例化了
DispatcherServletAutoConfiguration
在看 DispatcherServletAutoConfiguration 这个类,首先它是一个配置类,既然是配置类的话,Spring 容器在启动的时候,就会加载它,所以它定义了很多 @Bean 方法
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)@Configuration(proxyBeanMethods = false)@ConditionalOnWebApplication(type = Type.SERVLET)@ConditionalOnClass(DispatcherServlet.class)@AutoConfigureAfter(ServletWebServerFactoryAutoConfiguration.class)public class DispatcherServletAutoConfiguration {// ...}
DispatcherServlet
对于 SpringMVC 来说,DispatcherServlet 再熟悉不过了,主要就是来处理和分发请求的,我们看看 SpringBoot 是如何做的
在 DispatcherServletAutoConfiguration 中,定义了 **DispatcherServlet 的 @Bean**
@Bean(name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)public DispatcherServlet dispatcherServlet(WebMvcProperties webMvcProperties) {// 无参的 DispatcherServletDispatcherServlet dispatcherServlet = new DispatcherServlet();dispatcherServlet.setDispatchOptionsRequest(webMvcProperties.isDispatchOptionsRequest());dispatcherServlet.setDispatchTraceRequest(webMvcProperties.isDispatchTraceRequest());dispatcherServlet.setThrowExceptionIfNoHandlerFound(webMvcProperties.isThrowExceptionIfNoHandlerFound());dispatcherServlet.setPublishEvents(webMvcProperties.isPublishRequestHandledEvents());dispatcherServlet.setEnableLoggingRequestDetails(webMvcProperties.isLogRequestDetails());return dispatcherServlet;}
我们可以看到,其实 SpringBoot 就是 new 了一个 DispatcherServlet 而已,然后把它放到容器当中
这里有没有发现很奇怪,DispatcherServlet 只是 new 了一个无参的构造方法,但是我们记得,DispatcherServlet 是需要要给 SpringContext 上下文对象作为参数的呀,这里是不是有问题?
Spring MVC 的 0-XML 实现
我们可以看到 new DispatcherServlet(ac); 传递了一个 Spring 的上下文容器
public class MyWebApplicationInitializer implements WebApplicationInitializer {public void onStartup(ServletContext servletContext) throws ServletException {AnnotationConfigWebApplicationContext ac = new AnnotationConfigWebApplicationContext();ac.register(AppConfig.class);DispatcherServlet servlet = new DispatcherServlet(ac);ServletRegistration.Dynamic registration = servletContext.addServlet("app", servlet);registration.setLoadOnStartup(1);registration.addMapping("*.do");}}
SpringBoot 的实现
而 SpringBoot 只是简单 new 了一个 DispatcherServlet,没有传递任何参数,这样的话,我们继续跟代码
我们发现 DispatcherServlet 继承了 FrameworkServlet
public class DispatcherServlet extends FrameworkServlet
而 FrameworkServlet 又实现了 ApplicationContextAware
public abstract class FrameworkServlet extends HttpServletBean implements ApplicationContextAware
ApplicationContextAware
这个接口,是一个 Aware 接口,在 Spring 容器中,会调用自动调用 Aware 接口,传递一些参数,而 ApplicationContextAware 会调用其 setApplicationContext 传递上下文容器
public interface ApplicationContextAware extends Aware {void setApplicationContext(ApplicationContext var1) throws BeansException;}
那么真相就大白了,原来 Spring 容器初始化完成以后,会调用 ApplicationContextAware 的setApplicationContext 方法,传递上下文容器
@Overridepublic void setApplicationContext(ApplicationContext applicationContext) {if (this.webApplicationContext == null && applicationContext instanceof WebApplicationContext) {this.webApplicationContext = (WebApplicationContext) applicationContext;this.webApplicationContextInjected = true;}}
这样,就等于给 DispatcherServlet 传递了 **applicationContext
DispatcherServletRegistrationBean
同时 SpringBoot 又会继续调用 dispatcherServletRegistration 完成剩余的参数配置
@Bean(name = DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME)@ConditionalOnBean(value = DispatcherServlet.class, name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)public DispatcherServletRegistrationBean dispatcherServletRegistration(DispatcherServlet dispatcherServlet,WebMvcProperties webMvcProperties, ObjectProvider<MultipartConfigElement> multipartConfig) {DispatcherServletRegistrationBean registration = new DispatcherServletRegistrationBean(dispatcherServlet,webMvcProperties.getServlet().getPath());registration.setName(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME);registration.setLoadOnStartup(webMvcProperties.getServlet().getLoadOnStartup());multipartConfig.ifAvailable(registration::setMultipartConfig);return registration;}
至此 SpringBoot 就完成了 SpringMVC 的核心 DispathcServlet 的自动配置
**
总结:
Spring MVC 是把 Spring 容器放到 DispatchServlet 中
Spring Boot 是把 DispatchServlet 放到容器中,再由容器调用 ApplicationAware 给 DispathServlet 添加容器属性
