Springboot之自動註冊DispatcherServlet
注意:Springboot的版本是2.0.5.release。
Springboot中我們引入spring-boot-starter-web依賴後,web就自動配置好了,在web.xml的年代,我們需要在web.xml中手動配置DispatcherServlet,但是Springboot中不需要,Springboot是如何替我們做好這一切的呢?
我們來看下DispatcherServletAutoConfiguration,這個類在Springboot-autoconfig包中,如下List-1
List-1
@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; }
- 例項化DispatcherServlet,之後註冊到Spring容器中。
- 例項化DispatcherServletRegistrationBean,並將DispatcherServlet傳入到構造方法法中,註冊到Spring容器中。
所以說,在Springboot中,有個DispatcherServlet的bean,我們可以寫個單元測試驗證從BeanFactory中獲取DispatcherServlet這個bean,接下來看DispatcherServletRegistrationBean。
圖1
如圖1所示,DispatcherServletRegistrationBean繼承了ServletContextInitializer——見List-2,其中onStartUp的引數ServletContext是Servlet裡面的。
RegistrationBean實現了ServletContextInitializer,之後呼叫register方法,這個是抽象方法,由子類DynamicRegistrationBean實現,DynamicRegistrationBean再將ServletContext用方法addRegistration傳遞給子類ServletRegistrationBean——見List-3,List-3中用addServlet方法加入的就是DispatcherServletRegistrationBean傳遞到父類ServletRegistrationBean中的。這樣Springboot利用Servlet3.0+的特性,自動註冊DispatcherServlet到ServletContext中。
List-2
@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中:
- 例項化的時候會從BeanFactory中獲取所有的ServletContextInitializer——在getOrderedBeansOfType方法中,之後用addServletContextInitializerBean方法,將獲取到的ServletContextInitializer型別的Bean,新增到屬性initializers中。
- 這個地方可以看到,實現了ServletContextInitializer的不止是Servlet型別的,還有Listener、Filter型別的,為什麼呢,因為他們都需要動態新增到web容器中,即需要ServletContext。
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啟動的過程中。