Springboot之自動註冊DispatcherServlet

    注意: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;
}
  1. 實例化DispatcherServlet,以後註冊到Spring容器中。
  2. 實例化DispatcherServletRegistrationBean,並將DispatcherServlet傳入到構造方法法中,註冊到Spring容器中。

       因此說,在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中:

  1. 實例化的時候會從BeanFactory中獲取全部的ServletContextInitializer——在getOrderedBeansOfType方法中,以後用addServletContextInitializerBean方法,將獲取到的ServletContextInitializer類型的Bean,添加到屬性initializers中。
  2. 這個地方能夠看到,實現了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啓動的過程當中。

Reference

  1. 源碼
相關文章
相關標籤/搜索