我们都知道,想要构建 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) {
// 无参的 DispatcherServlet
DispatcherServlet 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 方法,传递上下文容器
@Override
public 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 添加容器属性