SpringBoot 源码解析 (七)----- Spring Boot的核心能力 - SpringBoot如何实现SpringMvc的?

目录

 

正文

上一篇我们讲了 SpringBoot 中 Tomcat 的启动过程,本篇我们接着讲在 SpringBoot 中如何向 Tomcat 中添加 Servlet、Filter、Listener

回到顶部

自定义 Servlet、Filter、Listener

Spring 容器中声明 ServletRegistrationBean、FilterRegistrationBean、ServletListenerRegistrationBean

@Bean
public ServletRegistrationBean customServlet() {
    return new ServletRegistrationBean(new CustomServlet(), "/custom");
}

private static class CustomServlet extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.getWriter().write(
"receive by custom servlet");
}
}

先自定义一个Servlet,重写service 实现自己的业务逻辑,然后通过 @Bean 注解往 Spring 容器中注入一个ServletRegistrationBean 类型的 bean 实例,并且实例化一个自定义的 Servlet 作为参数,这样就将自定义的 Servlet 加入 Tomcat 中了。

@ServletComponentScan 注解和 @WebServlet、@WebFilter 以及 @WebListener 注解配合使用

@ServletComponentScan 注解启用 ImportServletComponentScanRegistrar 类,是个 ImportBeanDefinitionRegistrar 接口的实现类,会被 Spring 容器所解析。ServletComponentScanRegistrar 内部会解析 @ServletComponentScan 注解,然后会在 Spring 容器中注册 ServletComponentRegisteringPostProcessor,是个 BeanFactoryPostProcessor,会去解析扫描出来的类是不是有 @WebServlet、@WebListener、@WebFilter 这 3 种注解,有的话把这 3 种类型的类转换成 ServletRegistrationBean、FilterRegistrationBean 或者 ServletListenerRegistrationBean,然后让 Spring 容器去解析:

@SpringBootApplication
@ServletComponentScan
public class EmbeddedServletApplication {...}

@WebServlet(urlPatterns = "/simple")
public class SimpleServlet extends HttpServlet {

@Override
</span><span style="color: rgba(0, 0, 255, 1)">protected</span> <span style="color: rgba(0, 0, 255, 1)">void</span> service(HttpServletRequest req, HttpServletResponse resp) <span style="color: rgba(0, 0, 255, 1)">throws</span><span style="color: rgba(0, 0, 0, 1)"> ServletException, IOException {
    resp.getWriter().write(</span>"receive by SimpleServlet"<span style="color: rgba(0, 0, 0, 1)">);
}

}

在 Spring 容器中声明 Servlet、Filter 或者 Listener

@Bean(name = "dispatcherServlet")
public DispatcherServlet myDispatcherServlet() {
    return new DispatcherServlet();
}

我们发现往 Tomcat 中添加 Servlet、Filter 或者 Listener 还是挺容易的,大家还记得以前 SpringMVC 是怎么配置DispatcherServlet的吗?在 web.xml 中:

<servlet>
    <servlet-name>dispatcher</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:spring-mvc.xml</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
    <servlet-name>dispatcher</servlet-name>
    <url-pattern>/</url-pattern>
</servlet-mapping>

和我们 SpringBoot 中配置 Servlet 相比是不是复杂很多,虽然 SpringBoot 中自定义 Servlet 很简单,但是其底层却不简单,下面我们来分析一下其原理

回到顶部

ServletRegistrationBean、FilterRegistrationBean、ServletListenerRegistrationBean

我们来看看这几个特殊的类:

ServletRegistrationBean

public class ServletRegistrationBean extends RegistrationBean {
    //存放目标 Servlet 实例
    private Servlet servlet;
    //存放 Servlet 的 urlMapping
    private Set<String> urlMappings;
    private boolean alwaysMapUrl;
    private int loadOnStartup;
    private MultipartConfigElement multipartConfig;
</span><span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> ServletRegistrationBean(Servlet servlet, String... urlMappings) {
    </span><span style="color: rgba(0, 0, 255, 1)">this</span>(servlet, <span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">, urlMappings);
}

</span><span style="color: rgba(0, 0, 255, 1)">public</span> ServletRegistrationBean(Servlet servlet, <span style="color: rgba(0, 0, 255, 1)">boolean</span><span style="color: rgba(0, 0, 0, 1)"> alwaysMapUrl, String... urlMappings) {
    </span><span style="color: rgba(0, 0, 255, 1)">this</span>.urlMappings = <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> LinkedHashSet();
    </span><span style="color: rgba(0, 0, 255, 1)">this</span>.alwaysMapUrl = <span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">;
    </span><span style="color: rgba(0, 0, 255, 1)">this</span>.loadOnStartup = -1<span style="color: rgba(0, 0, 0, 1)">;
    Assert.notNull(servlet, </span>"Servlet must not be null"<span style="color: rgba(0, 0, 0, 1)">);
    Assert.notNull(urlMappings, </span>"UrlMappings must not be null"<span style="color: rgba(0, 0, 0, 1)">);
    </span><span style="color: rgba(0, 0, 255, 1)">this</span>.servlet =<span style="color: rgba(0, 0, 0, 1)"> servlet;
    </span><span style="color: rgba(0, 0, 255, 1)">this</span>.alwaysMapUrl =<span style="color: rgba(0, 0, 0, 1)"> alwaysMapUrl;
    </span><span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.urlMappings.addAll(Arrays.asList(urlMappings));
}

</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">void</span> onStartup(ServletContext servletContext) <span style="color: rgba(0, 0, 255, 1)">throws</span><span style="color: rgba(0, 0, 0, 1)"> ServletException {
    Assert.notNull(</span><span style="color: rgba(0, 0, 255, 1)">this</span>.servlet, "Servlet must not be null"<span style="color: rgba(0, 0, 0, 1)">);
    String name </span>= <span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.getServletName();
    </span><span style="color: rgba(0, 0, 255, 1)">if</span> (!<span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.isEnabled()) {
        logger.info(</span>"Servlet " + name + " was not registered (disabled)"<span style="color: rgba(0, 0, 0, 1)">);
    } </span><span style="color: rgba(0, 0, 255, 1)">else</span><span style="color: rgba(0, 0, 0, 1)"> {
        logger.info(</span>"Mapping servlet: '" + name + "' to " + <span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.urlMappings);
        Dynamic added </span>= servletContext.addServlet(name, <span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.servlet);
        </span><span style="color: rgba(0, 0, 255, 1)">if</span> (added == <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">) {
            logger.info(</span>"Servlet " + name + " was not registered (possibly already registered?)"<span style="color: rgba(0, 0, 0, 1)">);
        } </span><span style="color: rgba(0, 0, 255, 1)">else</span><span style="color: rgba(0, 0, 0, 1)"> {
            </span><span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.configure(added);
        }
    }
}

</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">略</span>

}

在我们例子中我们通过 return new ServletRegistrationBean(new CustomServlet(), "/custom"); 就知道,ServletRegistrationBean 里会存放目标 Servlet 实例和 urlMapping, 并且继承 RegistrationBean 这个类

FilterRegistrationBean

public class FilterRegistrationBean extends AbstractFilterRegistrationBean {
    //存放目标 Filter 对象
    private Filter filter;
</span></strong><span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> FilterRegistrationBean() {
    </span><span style="color: rgba(0, 0, 255, 1)">super</span>(<span style="color: rgba(0, 0, 255, 1)">new</span> ServletRegistrationBean[0<span style="color: rgba(0, 0, 0, 1)">]);
}

</span><span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> FilterRegistrationBean(Filter filter, ServletRegistrationBean... servletRegistrationBeans) {
    </span><span style="color: rgba(0, 0, 255, 1)">super</span><span style="color: rgba(0, 0, 0, 1)">(servletRegistrationBeans);
    Assert.notNull(filter, </span>"Filter must not be null"<span style="color: rgba(0, 0, 0, 1)">);
    </span><span style="color: rgba(0, 0, 255, 1)">this</span>.filter =<span style="color: rgba(0, 0, 0, 1)"> filter;
}

</span><span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> Filter getFilter() {
    </span><span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.filter;
}

</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> setFilter(Filter filter) {
    Assert.notNull(filter, </span>"Filter must not be null"<span style="color: rgba(0, 0, 0, 1)">);
    </span><span style="color: rgba(0, 0, 255, 1)">this</span>.filter =<span style="color: rgba(0, 0, 0, 1)"> filter;
}

}

abstract class AbstractFilterRegistrationBean extends RegistrationBean {
private static final EnumSet<DispatcherType> ASYNC_DISPATCHER_TYPES;
private static final EnumSet<DispatcherType> NON_ASYNC_DISPATCHER_TYPES;
private static final String[] DEFAULT_URL_MAPPINGS;
private Set<ServletRegistrationBean> servletRegistrationBeans = new LinkedHashSet();
private Set<String> servletNames = new LinkedHashSet();
private Set<String> urlPatterns = new LinkedHashSet();
//重写 onStartup 方法
public void onStartup(ServletContext servletContext) throws ServletException {
Filter filter
= this.getFilter();
Assert.notNull(filter,
"Filter must not be null");
String name
= this.getOrDeduceName(filter);
if (!this.isEnabled()) {
this.logger.info("Filter" + name + "was not registered (disabled)");
}
else {
Dynamic added
= servletContext.addFilter(name, filter);
if (added == null) {
this.logger.info("Filter" + name + "was not registered (possibly already registered?)");
}
else {
this.configure(added);
}
}
}
//略...
}

我们看到 FilterRegistrationBean 中也保存了目标 Filter 对象,并且继承了RegistrationBean

ServletListenerRegistrationBean

public class ServletListenerRegistrationBean<T extends EventListener> extends RegistrationBean {
    //存放了目标 listener
    private T listener;
</span><span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> ServletListenerRegistrationBean() {
}

</span><span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> ServletListenerRegistrationBean(T listener) {
    Assert.notNull(listener, </span>"Listener must not be null"<span style="color: rgba(0, 0, 0, 1)">);
    Assert.isTrue(isSupportedType(listener), </span>"Listener is not of a supported type"<span style="color: rgba(0, 0, 0, 1)">);
    </span><span style="color: rgba(0, 0, 255, 1)">this</span>.listener =<span style="color: rgba(0, 0, 0, 1)"> listener;
}

</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> setListener(T listener) {
    Assert.notNull(listener, </span>"Listener must not be null"<span style="color: rgba(0, 0, 0, 1)">);
    Assert.isTrue(isSupportedType(listener), </span>"Listener is not of a supported type"<span style="color: rgba(0, 0, 0, 1)">);
    </span><span style="color: rgba(0, 0, 255, 1)">this</span>.listener =<span style="color: rgba(0, 0, 0, 1)"> listener;
}

</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">void</span> onStartup(ServletContext servletContext) <span style="color: rgba(0, 0, 255, 1)">throws</span><span style="color: rgba(0, 0, 0, 1)"> ServletException {
    </span><span style="color: rgba(0, 0, 255, 1)">if</span> (!<span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.isEnabled()) {
        logger.info(</span>"Listener " + <span style="color: rgba(0, 0, 255, 1)">this</span>.listener + " was not registered (disabled)"<span style="color: rgba(0, 0, 0, 1)">);
    } </span><span style="color: rgba(0, 0, 255, 1)">else</span><span style="color: rgba(0, 0, 0, 1)"> {
        </span><span style="color: rgba(0, 0, 255, 1)">try</span><span style="color: rgba(0, 0, 0, 1)"> {
            servletContext.addListener(</span><span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.listener);
        } </span><span style="color: rgba(0, 0, 255, 1)">catch</span><span style="color: rgba(0, 0, 0, 1)"> (RuntimeException var3) {
            </span><span style="color: rgba(0, 0, 255, 1)">throw</span> <span style="color: rgba(0, 0, 255, 1)">new</span> IllegalStateException("Failed to add listener '" + <span style="color: rgba(0, 0, 255, 1)">this</span>.listener + "' to servlet context"<span style="color: rgba(0, 0, 0, 1)">, var3);
        }
    }
}
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">略...</span>

}

ServletListenerRegistrationBean 也是一样,那我们来看看 RegistrationBean 这个类

public abstract class RegistrationBean implements ServletContextInitializer, Ordered {...}
public interface ServletContextInitializer {
    void onStartup(ServletContext var1) throws ServletException;
}

我们发现RegistrationBean 实现了ServletContextInitializer 这个接口,并且有一个 onStartup 方法,ServletRegistrationBean、FilterRegistrationBean、ServletListenerRegistrationBean 都实现了onStartup 方法。

ServletContextInitializer是 Servlet 容器初始化的时候,提供的初始化接口。所以,Servlet 容器初始化会获取并触发所有的FilterRegistrationBean、FilterRegistrationBean、ServletListenerRegistrationBean实例中onStartup 方法

那到底是何时触发这些类的 onStartup 方法呢?

当 Tomcat 容器启动时,会执行callInitializers,然后获取所有的ServletContextInitializer,循环执行onStartup方法触发回调方法。那FilterRegistrationBean、FilterRegistrationBean、ServletListenerRegistrationBean实例是何时加入到Initializers 集合的呢?这要回顾一下我们上一篇文章 Tomcat 的启动过程

回到顶部

Servlet 容器的启动

大家可以看看我上一篇文章,我这里简单的复制一下代码

EmbeddedWebApplicationContext

 1 @Override
 2 protected void onRefresh() {
 3     super.onRefresh();
 4     try {
 5         //核心方法:会获取嵌入式的 Servlet 容器工厂,并通过工厂来获取 Servlet 容器
 6         createEmbeddedServletContainer();
 7     }
 8     catch (Throwable ex) {
 9         throw new ApplicationContextException("Unable to start embedded container", ex);
10     }
11 }
12 
13 private void createEmbeddedServletContainer() {
14     EmbeddedServletContainer localContainer = this.embeddedServletContainer;
15     ServletContext localServletContext = getServletContext();
16     if (localContainer == null && localServletContext == null) {
17         //先获取嵌入式 Servlet 容器工厂
18         EmbeddedServletContainerFactory containerFactory = getEmbeddedServletContainerFactory();
19         //根据容器工厂来获取对应的嵌入式 Servlet 容器
20         this.embeddedServletContainer = containerFactory.getEmbeddedServletContainer(getSelfInitializer());
21     }
22     else if (localServletContext != null) {
23         try {
24             getSelfInitializer().onStartup(localServletContext);
25         }
26         catch (ServletException ex) {
27             throw new ApplicationContextException("Cannot initialize servlet context",ex);
28         }
29     }
30     initPropertySources();
31 }

关键代码在第 20 行,先通过 getSelfInitializer()获取到所有的 Initializer,传入 Servlet 容器中,那核心就在 getSelfInitializer() 方法:

1 private ServletContextInitializer getSelfInitializer() {
2     //只是创建了一个 ServletContextInitializer 实例返回
3     //所以 Servlet 容器启动的时候,会调用这个对象的 onStartup 方法
4     return new ServletContextInitializer() {
5         public void onStartup(ServletContext servletContext) throws ServletException {
6             EmbeddedWebApplicationContext.this.selfInitialize(servletContext);
7         }
8     };
9 }

我们看到只是创建了一个 ServletContextInitializer 实例返回,所以 Servlet 容器启动的时候,会调用这个对象的 onStartup 方法,那我们来分析其 onStartup 中的逻辑,也就是 selfInitialize 方法,并将 Servlet 上下文对象传进去了

selfInitialize

 1 private void selfInitialize(ServletContext servletContext) throws ServletException {
 2     prepareWebApplicationContext(servletContext);
 3     ConfigurableListableBeanFactory beanFactory = getBeanFactory();
 4     ExistingWebApplicationScopes existingScopes = new ExistingWebApplicationScopes(beanFactory);
 5     WebApplicationContextUtils.registerWebApplicationScopes(beanFactory,getServletContext());
 6     existingScopes.restore();
 7     WebApplicationContextUtils.registerEnvironmentBeans(beanFactory,getServletContext());
 8     //这里便是获取所有的 ServletContextInitializer 实现类,会获取所有的注册组件
 9     for (ServletContextInitializer beans : getServletContextInitializerBeans()) {
10         //执行所有 ServletContextInitializer 的 onStartup 方法
11         beans.onStartup(servletContext);
12     }
13 }

关键代码在第 9 和第 11 行,先获取所有的 ServletContextInitializer 实现类,然后遍历执行所有 ServletContextInitializer 的 onStartup 方法

获取所有的 ServletContextInitializer

我们来看看 getServletContextInitializerBeans 方法

protected Collection<ServletContextInitializer> getServletContextInitializerBeans() {
    return new ServletContextInitializerBeans(getBeanFactory());
}

ServletContextInitializerBeans 对象是对ServletContextInitializer的一种包装:

 1 public class ServletContextInitializerBeans extends AbstractCollection<ServletContextInitializer> {
 2     private final MultiValueMap<Class<?>, ServletContextInitializer> initializers = new LinkedMultiValueMap();
 3     //存放所有的 ServletContextInitializer
 4     private List<ServletContextInitializer> sortedList;
 5 
 6     public ServletContextInitializerBeans(ListableBeanFactory beanFactory) {
 7         //执行 addServletContextInitializerBeans
 8         this.addServletContextInitializerBeans(beanFactory);
 9         //执行 addAdaptableBeans
10         this.addAdaptableBeans(beanFactory);
11         List<ServletContextInitializer> sortedInitializers = new ArrayList();
12         Iterator var3 = this.initializers.entrySet().iterator();
13 
14         while(var3.hasNext()) {
15             Entry<?, List<ServletContextInitializer>> entry = (Entry)var3.next();
16             AnnotationAwareOrderComparator.sort((List)entry.getValue());
17             sortedInitializers.addAll((Collection)entry.getValue());
18         }
19         this.sortedList = Collections.unmodifiableList(sortedInitializers);
20     }
21 
22     private void addServletContextInitializerBeans(ListableBeanFactory beanFactory) {
23         Iterator var2 = this.getOrderedBeansOfType(beanFactory, ServletContextInitializer.class).iterator();
24 
25         while(var2.hasNext()) {
26             Entry<String, ServletContextInitializer> initializerBean = (Entry)var2.next();
27             this.addServletContextInitializerBean((String)initializerBean.getKey(), (ServletContextInitializer)initializerBean.getValue(), beanFactory);
28         }
29 
30     }
31 
32     private void addServletContextInitializerBean(String beanName, ServletContextInitializer initializer, ListableBeanFactory beanFactory) {
33         if (initializer instanceof ServletRegistrationBean) {
34             Servlet source = ((ServletRegistrationBean)initializer).getServlet();
35             this.addServletContextInitializerBean(Servlet.class, beanName, initializer, beanFactory, source);
36         } else if (initializer instanceof FilterRegistrationBean) {
37             Filter source = ((FilterRegistrationBean)initializer).getFilter();
38             this.addServletContextInitializerBean(Filter.class, beanName, initializer, beanFactory, source);
39         } else if (initializer instanceof DelegatingFilterProxyRegistrationBean) {
40             String source = ((DelegatingFilterProxyRegistrationBean)initializer).getTargetBeanName();
41             this.addServletContextInitializerBean(Filter.class, beanName, initializer, beanFactory, source);
42         } else if (initializer instanceof ServletListenerRegistrationBean) {
43             EventListener source = ((ServletListenerRegistrationBean)initializer).getListener();
44             this.addServletContextInitializerBean(EventListener.class, beanName, initializer, beanFactory, source);
45         } else {
46             this.addServletContextInitializerBean(ServletContextInitializer.class, beanName, initializer, beanFactory, initializer);
47         }
48 
49     }
50 
51     private void addServletContextInitializerBean(Class<?> type, String beanName, ServletContextInitializer initializer, ListableBeanFactory beanFactory, Object source) {
52         this.initializers.add(type, initializer);
53         if (source != null) {
54             this.seen.add(source);
55         }
56 
57         if (logger.isDebugEnabled()) {
58             String resourceDescription = this.getResourceDescription(beanName, beanFactory);
59             int order = this.getOrder(initializer);
60             logger.debug("Added existing" + type.getSimpleName() + "initializer bean'"+ beanName +"'; order=" + order + ", resource=" + resourceDescription);
61         }
62 
63     }
64 
65     private void addAdaptableBeans(ListableBeanFactory beanFactory) {
66         MultipartConfigElement multipartConfig = this.getMultipartConfig(beanFactory);
67         this.addAsRegistrationBean(beanFactory, Servlet.class, new ServletContextInitializerBeans.ServletRegistrationBeanAdapter(multipartConfig));
68         this.addAsRegistrationBean(beanFactory, Filter.class, new ServletContextInitializerBeans.FilterRegistrationBeanAdapter(null));
69         Iterator var3 = ServletListenerRegistrationBean.getSupportedTypes().iterator();
70 
71         while(var3.hasNext()) {
72             Class<?> listenerType = (Class)var3.next();
73             this.addAsRegistrationBean(beanFactory, EventListener.class, listenerType, new ServletContextInitializerBeans.ServletListenerRegistrationBeanAdapter(null));
74         }
75 
76     }
77     
78     public Iterator<ServletContextInitializer> iterator() {
79         //返回所有的 ServletContextInitializer
80         return this.sortedList.iterator();
81     }
82 
83     //略...
84 }

我们看到 ServletContextInitializerBeans 中有一个存放所有 ServletContextInitializer 的集合 sortedList,就是在其构造方法中获取所有的 ServletContextInitializer,并放入 sortedList 集合中,那我们来看看其构造方法的逻辑,看到第 8 行先调用

addServletContextInitializerBeans 方法:  

1 private void addServletContextInitializerBeans(ListableBeanFactory beanFactory) {
2     //从 Spring 容器中获取所有 ServletContextInitializer.class 类型的 Bean
3     for (Entry<String, ServletContextInitializer> initializerBean : getOrderedBeansOfType(beanFactory, ServletContextInitializer.class)) {
4         //添加到具体的集合中
5         addServletContextInitializerBean(initializerBean.getKey(),initializerBean.getValue(), beanFactory);
6     }
7 }

我们看到先从 Spring 容器中获取所有ServletContextInitializer.class 类型的 Bean,这里我们自定义的 ServletRegistrationBean、FilterRegistrationBean、ServletListenerRegistrationBean 就被获取到了,然后调用 addServletContextInitializerBean 方法:

 1 private void addServletContextInitializerBean(String beanName, ServletContextInitializer initializer, ListableBeanFactory beanFactory) {
 2     //判断 ServletRegistrationBean 类型
 3     if (initializer instanceof ServletRegistrationBean) {
 4         Servlet source = ((ServletRegistrationBean)initializer).getServlet();
 5         //将 ServletRegistrationBean 加入到集合中
 6         this.addServletContextInitializerBean(Servlet.class, beanName, initializer, beanFactory, source);
 7     //判断 FilterRegistrationBean 类型
 8     } else if (initializer instanceof FilterRegistrationBean) {
 9         Filter source = ((FilterRegistrationBean)initializer).getFilter();
10         //将 ServletRegistrationBean 加入到集合中
11         this.addServletContextInitializerBean(Filter.class, beanName, initializer, beanFactory, source);
12     } else if (initializer instanceof DelegatingFilterProxyRegistrationBean) {
13         String source = ((DelegatingFilterProxyRegistrationBean)initializer).getTargetBeanName();
14         this.addServletContextInitializerBean(Filter.class, beanName, initializer, beanFactory, source);
15     } else if (initializer instanceof ServletListenerRegistrationBean) {
16         EventListener source = ((ServletListenerRegistrationBean)initializer).getListener();
17         this.addServletContextInitializerBean(EventListener.class, beanName, initializer, beanFactory, source);
18     } else {
19         this.addServletContextInitializerBean(ServletContextInitializer.class, beanName, initializer, beanFactory, initializer);
20     }
21 
22 }
23 
24 private void addServletContextInitializerBean(Class<?> type, String beanName, 
25                             ServletContextInitializer initializer, ListableBeanFactory beanFactory, Object source) {
26     //加入到 initializers 中
27     this.initializers.add(type, initializer);
28 }

很明显,判断从 Spring 容器中获取的 ServletContextInitializer 类型,如 ServletRegistrationBean、FilterRegistrationBean、ServletListenerRegistrationBean,并加入到 initializers 集合中去,我们再来看构造器中的另外一个方法addAdaptableBeans(beanFactory):

 1 private void addAdaptableBeans(ListableBeanFactory beanFactory) {
 2     //从 beanFactory 获取所有 Servlet.class 和 Filter.class 类型的 Bean,并封装成 RegistrationBean 对象,加入到集合中
 3     this.addAsRegistrationBean(beanFactory, Servlet.class, new ServletContextInitializerBeans.ServletRegistrationBeanAdapter(multipartConfig));
 4     this.addAsRegistrationBean(beanFactory, Filter.class, new ServletContextInitializerBeans.FilterRegistrationBeanAdapter(null));
 5 }
 6 
 7 private <T, B extends T> void addAsRegistrationBean(ListableBeanFactory beanFactory, Class<T> type, Class<B> beanType, ServletContextInitializerBeans.RegistrationBeanAdapter<T> adapter) {
 8     //从 Spring 容器中获取所有的 Servlet.class 和 Filter.class 类型的 Bean
 9     List<Entry<String, B>> beans = this.getOrderedBeansOfType(beanFactory, beanType, this.seen);
10     Iterator var6 = beans.iterator();
11 
12     while(var6.hasNext()) {
13         Entry<String, B> bean = (Entry)var6.next();
14         if (this.seen.add(bean.getValue())) {
15             int order = this.getOrder(bean.getValue());
16             String beanName = (String)bean.getKey();
17             //创建 Servlet.class 和 Filter.class 包装成 RegistrationBean 对象
18             RegistrationBean registration = adapter.createRegistrationBean(beanName, bean.getValue(), beans.size());
19             registration.setName(beanName);
20             registration.setOrder(order);
21             this.initializers.add(type, registration);
22             if (logger.isDebugEnabled()) {
23                 logger.debug("Created" + type.getSimpleName() + "initializer for bean'"+ beanName +"'; order=" + order + ", resource=" + this.getResourceDescription(beanName, beanFactory));
24             }
25         }
26     }
27 
28 }

我们看到先从 beanFactory 获取所有 Servlet.class 和 Filter.class 类型的 Bean,然后通过ServletRegistrationBeanAdapter 和FilterRegistrationBeanAdapter 两个适配器将 Servlet.class 和 Filter.class 封装成RegistrationBean

private static class ServletRegistrationBeanAdapter implements ServletContextInitializerBeans.RegistrationBeanAdapter<Servlet> {
    private final MultipartConfigElement multipartConfig;
ServletRegistrationBeanAdapter(MultipartConfigElement multipartConfig) {
    </span><span style="color: rgba(0, 0, 255, 1)">this</span>.multipartConfig =<span style="color: rgba(0, 0, 0, 1)"> multipartConfig;
}

</span><span style="color: rgba(0, 0, 255, 1)">public</span> RegistrationBean createRegistrationBean(String name, Servlet source, <span style="color: rgba(0, 0, 255, 1)">int</span><span style="color: rgba(0, 0, 0, 1)"> totalNumberOfSourceBeans) {
    String url </span>= totalNumberOfSourceBeans == 1 ? "/" : "/" + name + "/"<span style="color: rgba(0, 0, 0, 1)">;
    </span><span style="color: rgba(0, 0, 255, 1)">if</span> (name.equals("dispatcherServlet"<span style="color: rgba(0, 0, 0, 1)">)) {
        url </span>= "/"<span style="color: rgba(0, 0, 0, 1)">;
    }
    </span><strong><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">还是将Servlet.class实例封装成ServletRegistrationBean对象
    </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">这和我们自己创建ServletRegistrationBean对象是一模一样的</span>
    ServletRegistrationBean bean = <span style="color: rgba(0, 0, 255, 1)">new</span> ServletRegistrationBean(source, <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> String[]{url});
    bean.setMultipartConfig(</span><span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.multipartConfig);
    </span></strong><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> bean;
}

}

private static class FilterRegistrationBeanAdapter implements ServletContextInitializerBeans.RegistrationBeanAdapter<Filter> {
private FilterRegistrationBeanAdapter() {
}

</span><span style="color: rgba(0, 0, 255, 1)">public</span> RegistrationBean createRegistrationBean(String name, Filter source, <span style="color: rgba(0, 0, 255, 1)">int</span><span style="color: rgba(0, 0, 0, 1)"> totalNumberOfSourceBeans) {
    </span><strong><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">Filter.class实例封装成FilterRegistrationBean对象</span>
    <span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(0, 0, 255, 1)">new</span> FilterRegistrationBean(source, <span style="color: rgba(0, 0, 255, 1)">new</span> ServletRegistrationBean[0</strong><span style="color: rgba(0, 0, 0, 1)"><strong>]);</strong>
}

}

代码中注释很清楚了还是将 Servlet.class 实例封装成 ServletRegistrationBean 对象,将 Filter.class 实例封装成 FilterRegistrationBean 对象,这和我们自己定义 ServletRegistrationBean 对象是一模一样的,现在所有的 ServletRegistrationBean、FilterRegistrationBean

Servlet.class、Filter.class 都添加到 List<ServletContextInitializer> sortedList 这个集合中去了,接着就是遍历这个集合,执行其onStartup方法了

ServletContextInitializer 的onStartup方法

ServletRegistrationBean

public class ServletRegistrationBean extends RegistrationBean {
    private static final Log logger = LogFactory.getLog(ServletRegistrationBean.class);
    private static final String[] DEFAULT_MAPPINGS = new String[]{"/*"};
    private Servlet servlet;
</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">void</span> onStartup(ServletContext servletContext) <span style="color: rgba(0, 0, 255, 1)">throws</span><span style="color: rgba(0, 0, 0, 1)"> ServletException {
    Assert.notNull(</span><span style="color: rgba(0, 0, 255, 1)">this</span>.servlet, "Servlet must not be null"<span style="color: rgba(0, 0, 0, 1)">);
    String name </span>= <span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.getServletName();
    </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">调用ServletContext的addServlet</span>
    Dynamic added = servletContext.addServlet(name, <span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.servlet);
}

</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">略...</span>

}

private javax.servlet.ServletRegistration.Dynamic addServlet(String servletName, String servletClass, Servlet servlet, Map<String, String> initParams) throws IllegalStateException {
if (servletName != null && !servletName.equals("")) {
if (!this.context.getState().equals(LifecycleState.STARTING_PREP)) {
throw new IllegalStateException(sm.getString("applicationContext.addServlet.ise", new Object[]{this.getContextPath()}));
}
else {
Wrapper wrapper
= (Wrapper)this.context.findChild(servletName);
if (wrapper == null) {
wrapper
= this.context.createWrapper();
wrapper.setName(servletName);
this.context.addChild(wrapper);
}
else if (wrapper.getName() != null && wrapper.getServletClass() != null) {
if (!wrapper.isOverridable()) {
return null;
}

            wrapper.setOverridable(</span><span style="color: rgba(0, 0, 255, 1)">false</span><span style="color: rgba(0, 0, 0, 1)">);
        }

        </span><span style="color: rgba(0, 0, 255, 1)">if</span> (servlet == <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">) {
            wrapper.setServletClass(servletClass);
        } </span><span style="color: rgba(0, 0, 255, 1)">else</span><span style="color: rgba(0, 0, 0, 1)"> {
            wrapper.setServletClass(servlet.getClass().getName());
            wrapper.setServlet(servlet);
        }

        </span><span style="color: rgba(0, 0, 255, 1)">if</span> (initParams != <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">) {
            Iterator i$ </span>=<span style="color: rgba(0, 0, 0, 1)"> initParams.entrySet().iterator();

            </span><span style="color: rgba(0, 0, 255, 1)">while</span><span style="color: rgba(0, 0, 0, 1)">(i$.hasNext()) {
                Entry</span>&lt;String, String&gt; initParam =<span style="color: rgba(0, 0, 0, 1)"> (Entry)i$.next();
                wrapper.addInitParameter((String)initParam.getKey(), (String)initParam.getValue());
            }
        }

        </span><span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.context.dynamicServletAdded(wrapper);
    }
} </span><span style="color: rgba(0, 0, 255, 1)">else</span><span style="color: rgba(0, 0, 0, 1)"> {
    </span><span style="color: rgba(0, 0, 255, 1)">throw</span> <span style="color: rgba(0, 0, 255, 1)">new</span> IllegalArgumentException(sm.getString("applicationContext.invalidServletName", <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> Object[]{servletName}));
}

}

看到没,ServletRegistrationBean 中的 onStartup 先获取 Servlet 的 name,然后调用 ServletContext 的 addServlet 将 Servlet 加入到 Tomcat 中,这样我们就能发请求给这个 Servlet 了。

AbstractFilterRegistrationBean

public void onStartup(ServletContext servletContext) throws ServletException {
    Filter filter = this.getFilter();
    Assert.notNull(filter, "Filter must not be null");
    String name = this.getOrDeduceName(filter);
    //调用 ServletContext 的 addFilter
    Dynamic added = servletContext.addFilter(name, filter);
}

AbstractFilterRegistrationBean 也是同样的原理,先获取目标 Filter,然后调用 ServletContext 的addFilter将 Filter 加入到 Tomcat 中,这样 Filter 就能拦截我们请求了。

回到顶部

DispatcherServletAutoConfiguration

最熟悉的莫过于,在 Spring Boot 在自动配置 SpringMVC 的时候,会自动注册 SpringMVC 前端控制器:DispatcherServlet,该控制器主要在DispatcherServletAutoConfiguration自动配置类中进行注册的。DispatcherServlet 是 SpringMVC 中的核心分发器。DispatcherServletAutoConfiguration 也在 spring.factories 中配置了

DispatcherServletConfiguration

 1 @Configuration
 2 @ConditionalOnWebApplication
 3 // 先看下 ClassPath 下是否有 DispatcherServlet.class 字节码
 4 // 我们引入了 spring-boot-starter-web,同时引入了 tomcat 和 SpringMvc, 肯定会存在 DispatcherServlet.class 字节码
 5 @ConditionalOnClass({DispatcherServlet.class})
 6 // 这个配置类的执行要在 EmbeddedServletContainerAutoConfiguration 配置类生效之后执行
 7 // 毕竟要等 Tomcat 启动后才能往其中注入 DispatcherServlet
 8 @AutoConfigureAfter({EmbeddedServletContainerAutoConfiguration.class})
 9 protected static class DispatcherServletConfiguration {
10   public static final String DEFAULT_DISPATCHER_SERVLET_BEAN_NAME = "dispatcherServlet";
11   public static final String DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME = "dispatcherServletRegistration";
12   @Autowired
13   private ServerProperties server;
14 
15   @Autowired
16   private WebMvcProperties webMvcProperties;
17 
18   @Autowired(required = false)
19   private MultipartConfigElement multipartConfig;
20 
21   // Spring 容器注册 DispatcherServlet
22   @Bean(name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
23   public DispatcherServlet dispatcherServlet() {
24     // 直接构造 DispatcherServlet,并设置 WebMvcProperties 中的一些配置
25     DispatcherServlet dispatcherServlet = new DispatcherServlet();
26     dispatcherServlet.setDispatchOptionsRequest(this.webMvcProperties.isDispatchOptionsRequest());
27     dispatcherServlet.setDispatchTraceRequest(this.webMvcProperties.isDispatchTraceRequest());
28     dispatcherServlet.setThrowExceptionIfNoHandlerFound(this.webMvcProperties.isThrowExceptionIfNoHandlerFound());
29     return dispatcherServlet;
30   }
31 
32   @Bean(name = DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME)
33   public ServletRegistrationBean dispatcherServletRegistration() {
34     // 直接使用 DispatcherServlet 和 server 配置中的 servletPath 路径构造 ServletRegistrationBean
35     // ServletRegistrationBean 实现了 ServletContextInitializer 接口,在 onStartup 方法中对应的 Servlet 注册到 Servlet 容器中
36     // 所以这里 DispatcherServlet 会被注册到 Servlet 容器中,对应的 urlMapping 为 server.servletPath 配置
37     ServletRegistrationBean registration = new ServletRegistrationBean(dispatcherServlet(), this.server.getServletMapping());
38     registration.setName(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME);
39     if (this.multipartConfig != null) {
40       registration.setMultipartConfig(this.multipartConfig);
41     }
42     return registration;
43   }
44 
45   @Bean // 构造文件上传相关的 bean
46   @ConditionalOnBean(MultipartResolver.class)
47   @ConditionalOnMissingBean(name = DispatcherServlet.MULTIPART_RESOLVER_BEAN_NAME)
48   public MultipartResolver multipartResolver(MultipartResolver resolver) {
49     return resolver;
50   }
51 
52 }

先看下 ClassPath 下是否有 DispatcherServlet.class 字节码, 我们引入了 spring-boot-starter-web,同时引入了 tomcat 和 SpringMvc, 肯定会存在 DispatcherServlet.class 字节码,如果没有导入 spring-boot-starter-web,则这个配置类将不会生效

然后往 Spring 容器中注册 DispatcherServlet 实例,接着又加入 ServletRegistrationBean 实例,并把 DispatcherServlet 实例作为参数,上面我们已经学过了 ServletRegistrationBean 的逻辑,在 Tomcat 启动的时候,会获取所有的 ServletRegistrationBean,并执行其中的 onstartup 方法,将 DispatcherServlet 注册到 Servlet 容器中,这样就类似原来的 web.xml 中配置的 dispatcherServlet。

<servlet>
    <servlet-name>dispatcherServlet</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
    <servlet-name>dispatcherServlet</servlet-name>
    <url-pattern>/</url-pattern>
</servlet-mapping>

所以只要导入了 spring-boot-starter-web 这个 starter,SpringBoot 就有了 Tomcat 容器,并且往 Tomcat 容器中注册了 DispatcherServlet 对象,这样就能接收到我们的请求了