注意:Springboot的版本是2.0.5.release。java
Springboot中咱們引入spring-boot-starter-web依賴後,web就自動配置好了,在web.xml的年代,咱們須要在web.xml中手動配置DispatcherServlet,可是Springboot中不須要,Springboot是如何替咱們作好這一切的呢?git
咱們來看下DispatcherServletAutoConfiguration,這個類在Springboot-autoconfig包中,以下List-1github
List-1web
@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; } @Bean(name = DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME) @ConditionalOnBean(value = DispatcherServlet.class, name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME) public DispatcherServletRegistrationBean dispatcherServletRegistration( DispatcherServlet dispatcherServlet) { DispatcherServletRegistrationBean registration = new DispatcherServletRegistrationBean( dispatcherServlet, this.serverProperties.getServlet().getPath()); registration.setName(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME); registration.setLoadOnStartup( this.webMvcProperties.getServlet().getLoadOnStartup()); if (this.multipartConfig != null) { registration.setMultipartConfig(this.multipartConfig); } return registration; }
因此說,在Springboot中,有個DispatcherServlet的bean,咱們能夠寫個單元測試驗證從BeanFactory中獲取DispatcherServlet這個bean,接下來看DispatcherServletRegistrationBean。spring
圖1tomcat
如圖1所示,DispatcherServletRegistrationBean繼承了ServletContextInitializer——見List-2,其中onStartUp的參數ServletContext是Servlet裏面的。app
RegistrationBean實現了ServletContextInitializer,以後調用register方法,這個是抽象方法,由子類DynamicRegistrationBean實現,DynamicRegistrationBean再將ServletContext用方法addRegistration傳遞給子類ServletRegistrationBean——見List-3,List-3中用addServlet方法加入的就是DispatcherServletRegistrationBean傳遞到父類ServletRegistrationBean中的。這樣Springboot利用Servlet3.0+的特性,自動註冊DispatcherServlet到ServletContext中。ide
List-2spring-boot
@FunctionalInterface public interface ServletContextInitializer { /** * Configure the given {@link ServletContext} with any servlets, filters, listeners * context-params and attributes necessary for initialization. * @param servletContext the {@code ServletContext} to initialize * @throws ServletException if any call against the given {@code ServletContext} * throws a {@code ServletException} */ void onStartup(ServletContext servletContext) throws ServletException; }
List-3單元測試
@Override protected ServletRegistration.Dynamic addRegistration(String description, ServletContext servletContext) { String name = getServletName(); logger.info("Servlet " + name + " mapped to " + this.urlMappings); return servletContext.addServlet(name, this.servlet); }
有個問題,實現了ServletContextInitializer的實例,何時會調用onStartup方法呢?來看ServletContextInitializerBeans,這個類在Springboot中,如List-4中:
List-4
public class ServletContextInitializerBeans extends AbstractCollection<ServletContextInitializer> { private static final String DISPATCHER_SERVLET_NAME = "dispatcherServlet"; private static final Log logger = LogFactory .getLog(ServletContextInitializerBeans.class); /** * Seen bean instances or bean names. */ private final Set<Object> seen = new HashSet<>(); private final MultiValueMap<Class<?>, ServletContextInitializer> initializers; private List<ServletContextInitializer> sortedList; public ServletContextInitializerBeans(ListableBeanFactory beanFactory) { this.initializers = new LinkedMultiValueMap<>(); addServletContextInitializerBeans(beanFactory); addAdaptableBeans(beanFactory); List<ServletContextInitializer> sortedInitializers = this.initializers.values() .stream() .flatMap((value) -> value.stream() .sorted(AnnotationAwareOrderComparator.INSTANCE)) .collect(Collectors.toList()); this.sortedList = Collections.unmodifiableList(sortedInitializers); } private void addServletContextInitializerBeans(ListableBeanFactory beanFactory) { for (Entry<String, ServletContextInitializer> initializerBean : getOrderedBeansOfType( beanFactory, ServletContextInitializer.class)) { addServletContextInitializerBean(initializerBean.getKey(), initializerBean.getValue(), beanFactory); } } private void addServletContextInitializerBean(String beanName, ServletContextInitializer initializer, ListableBeanFactory beanFactory) { if (initializer instanceof ServletRegistrationBean) { Servlet source = ((ServletRegistrationBean<?>) initializer).getServlet(); addServletContextInitializerBean(Servlet.class, beanName, initializer, beanFactory, source); } else if (initializer instanceof FilterRegistrationBean) { Filter source = ((FilterRegistrationBean<?>) initializer).getFilter(); addServletContextInitializerBean(Filter.class, beanName, initializer, beanFactory, source); } else if (initializer instanceof DelegatingFilterProxyRegistrationBean) { String source = ((DelegatingFilterProxyRegistrationBean) initializer) .getTargetBeanName(); addServletContextInitializerBean(Filter.class, beanName, initializer, beanFactory, source); } else if (initializer instanceof ServletListenerRegistrationBean) { EventListener source = ((ServletListenerRegistrationBean<?>) initializer) .getListener(); addServletContextInitializerBean(EventListener.class, beanName, initializer, beanFactory, source); } else { addServletContextInitializerBean(ServletContextInitializer.class, beanName, initializer, beanFactory, initializer); } } ...
接着引出一個問題,ServletContextInitializerBeans這個在哪被調用呢,在ServletWebServerApplicationContext中,在Servlet類型的Sprringboot Web應用中,ApplicationContext是AnnotationConfigServletWebServerApplicationContext,而ServletWebServerApplicationContext正是其父類。
ServletWebServerApplicationContext中,方法onRefresh()-->createWebServer()-->getSelfInitializer()-->selfInitialize()-->getServletContextInitializerBeans()-->new ServletContextInitializerBeans(getBeanFactory())。
ServletWebServerApplicationContext的onRefresh方法覆蓋了AbstractApplicationContext的onRefresh方法,AbstractApplicationContext中,方法onRefresh被方法refresh調用。SpringApplication的run()-->refreshContext()-->refresh()-->AbstractApplicationContext的refresh()。
經過上面的分析能夠看出,Springboot利用SpringFramework的特性,將DispatcherServlet、Filter或者Listener經過ServletContextInitializer的ServletContext,添加到tomcat之類的web容器中,這些都發生在Springboot啓動的過程當中。