springboot web应用(一):servlet 组件的注册流程

409 阅读10分钟

注:本系列源码分析基于springboot 2.2.2.RELEASE,对应的spring版本为5.2.2.RELEASE,源码的gitee仓库仓库地址:gitee.com/funcy/sprin….

在 springboot 中,如果我们需要注册 servlet 三大组件:servletfilterlistener,该怎么做呢,springboot贴心地为我们提供了3种方法,本文就来分析这3种方法的源码实现。

1. 注册方式

1.1 使用 XxxRegistrationBean 注册

springboot提供了三个类型的 RegistrationBean 来处理 servlet 三大组件的注册,分别是ServletRegistrationBeanFilterRegistrationBeanServletListenerRegistrationBean,这里我们简单示意下它们的用法:

/**
 * 准备了一个servlet
 */
public class MyServlet extends HttpServlet {

    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws IOException {
        // 处理一些操作
        ...
    }
}

/**
 * 进行注册操作
 */
@Bean
public ServletRegistrationBean registerServlet() {
    ServletRegistrationBean servletRegistrationBean = new ServletRegistrationBean(
            new MyServlet(), "/myServlet");
    // 处理一些配置操作
    servletRegistrationBean.setXxx();
    ...
    return servletRegistrationBean;
}

以上提供了servlet的注册方式,要注册filterlistener,只需使用对应的 RegistrationBean 即可,这里就不展示了。

1.2 使用 servlet 注解注册

Servlet 3.0,servlet 容器提供了3个注解来处理 servlet 三大组件的注册:

  • @WebServlet: 处理 servlet 注册
  • @WebFilter: 处理 filter 注册
  • @WebListener: 处理 listener 注册

还是以servlet注册为例,先来看看@WebServlet:

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface WebServlet {
    String name() default "";

    String[] value() default {};

    String[] urlPatterns() default {};

    int loadOnStartup() default -1;

    WebInitParam[] initParams() default {};

    boolean asyncSupported() default false;

    String smallIcon() default "";

    String largeIcon() default "";

    String description() default "";

    String displayName() default "";
}

可以看到,@WebServlet 支持多个属性配置,像指定servlet的名称、映射的url都可以在这里指定,我们也提供一个示例:

@WebServlet(name = "myServlet", urlPatterns = "/myServlet")
public class JavaServlet extends HttpServlet {

    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws IOException {
        // 处理一些操作
        ...
    }

}

这样处理后,还要做一个重要的操作,那就是使用@ServletComponentScan来开启扫描功能:

// 使用 @ServletComponentScan 来开启 servlet 组件的扫描功能
@ServletComponentScan
@SpringBootApplication
public class MyApplication {
    public static void main(String[] args) {
        ...
    }
}

1.3 ServletContextInitializer 注册

使用这种方式注册,需要实现ServletContextInitializer接口:

/**
 * 准备一个servlet
 */
public class MyServlet extends HttpServlet {

    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws IOException {
        // 处理一些操作
        ...
    }
}

/**
 * 实现 ServletContextInitializer 
 */
@Component
public class ServletConfig implements ServletContextInitializer {

    @Override
    public void onStartup(ServletContext servletContext) {
        // 使用 servletContext 进行注册
        ServletRegistration initServlet = servletContext.addServlet("myServlet", MyServlet.class);
        // 可以进行一些配置
        initServlet.addMapping("/myServlet");
    }

}

使用这种方式注册,先要实现ServletContextInitializer,然后重写 ServletContextInitializer#onStartup 方法,在 ServletContextInitializer#onStartup 使用 ServletContext 对象进行注册。ServletContext 对象由 servlet 容器提供,这个对象就是注册的终极类,不管是使用RegistrationBean注册,还是使用 @ServletComponentScan 扫描注册,最终都是通过 ServletContext 注册到servlet容器中。

2. 源码实现

了解完如何使用后,接下来我们就 进入源码看看这些流程是如何实现。

2.1 @ServletComponentScan 扫描

我们直接进入@ServletComponentScan

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(ServletComponentScanRegistrar.class)
public @interface ServletComponentScan {
    ...
}

这个注册上面标记了@Import注解,引入了一个类:ServletComponentScanRegistrar,我们看看这个类究竟做了啥:

/**
 * 实现了ImportBeanDefinitionRegistrar
 * 向容器中注册了 ServletComponentRegisteringPostProcessor
 */
class ServletComponentScanRegistrar implements ImportBeanDefinitionRegistrar {

    private static final String BEAN_NAME = "servletComponentRegisteringPostProcessor";

    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, 
            BeanDefinitionRegistry registry) {
        Set<String> packagesToScan = getPackagesToScan(importingClassMetadata);
        if (registry.containsBeanDefinition(BEAN_NAME)) {
            updatePostProcessor(registry, packagesToScan);
        }
        else {
            // 注册 BeanFactoryPostProcessor
            addPostProcessor(registry, packagesToScan);
        }
    }

    /**
     * 注册 BeanFactoryPostProcessor
     * 注册了 ServletComponentRegisteringPostProcessor
     */
    private void addPostProcessor(BeanDefinitionRegistry registry, Set<String> packagesToScan) {
        GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
        // ServletComponentRegisteringPostProcessor: 处理扫描的 BeanFactoryPostProcessor
        beanDefinition.setBeanClass(ServletComponentRegisteringPostProcessor.class);
        beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(packagesToScan);
        beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
        // ServletComponentScanRegistrar 就是为了注册 ServletComponentRegisteringPostProcessor
        registry.registerBeanDefinition(BEAN_NAME, beanDefinition);
    }
    
    ...

}

可以看到,这个类实现了ImportBeanDefinitionRegistrar,主要是向 spring 容器中注册了ServletComponentRegisteringPostProcessor。我们继续看下去,进入ServletComponentRegisteringPostProcessor

class ServletComponentRegisteringPostProcessor implements BeanFactoryPostProcessor, 
        ApplicationContextAware {

    /**
     * 需要扫描的包.
     */
    private final Set<String> packagesToScan;

    /**
     * 要扫描的包由构造方法传入
     */
    ServletComponentRegisteringPostProcessor(Set<String> packagesToScan) {
        this.packagesToScan = packagesToScan;
    }

    /**
     * 重写了BeanFactoryPostProcessor的方法
     */
    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) 
            throws BeansException {
        // 判断是否运行在内嵌的 web 容器中
        if (isRunningInEmbeddedWebServer()) {
            // 扫描器,配置了扫描规则
            ClassPathScanningCandidateComponentProvider componentProvider 
                    = createComponentProvider();
            for (String packageToScan : this.packagesToScan) {
                // 进行包扫描操作
                scanPackage(componentProvider, packageToScan);
            }
        }
    }

    ...

}

可以看到,ServletComponentRegisteringPostProcessor实现了BeanFactoryPostProcessor,在重写的BeanFactoryPostProcessor#postProcessBeanFactory方法中处理扫描操作,扫描前,先是创建了扫描器ClassPathScanningCandidateComponentProvider,然后再进行扫描。

我们先来看扫描器的创建方法createComponentProvider()

// 处理各种 handler
private static final List<ServletComponentHandler> HANDLERS;

static {
    List<ServletComponentHandler> servletComponentHandlers = new ArrayList<>();
    servletComponentHandlers.add(new WebServletHandler());
    servletComponentHandlers.add(new WebFilterHandler());
    servletComponentHandlers.add(new WebListenerHandler());
    HANDLERS = Collections.unmodifiableList(servletComponentHandlers);
}

/**
 * 创建扫描器
 */
private ClassPathScanningCandidateComponentProvider createComponentProvider() {
    // 创建对象
    ClassPathScanningCandidateComponentProvider componentProvider 
            = new ClassPathScanningCandidateComponentProvider(false);
    componentProvider.setEnvironment(this.applicationContext.getEnvironment());
    componentProvider.setResourceLoader(this.applicationContext);
    for (ServletComponentHandler handler : HANDLERS) {
        // 配置过滤规则
        componentProvider.addIncludeFilter(handler.getTypeFilter());
    }
    return componentProvider;
}

createComponentProvider()方法中,先是创建了扫描器对象,然后设置了一些属性,接着就是配置过滤规则,我们这里重点来看下过滤规则的配置,这些规则由WebServletHandler/WebFilterHandler/WebListenerHandlergetTypeFilter()方法提供:

getTypeFilter()方法位于一个抽象方法中:

abstract class ServletComponentHandler {

    private final TypeFilter typeFilter;

    /**
     * 传入注解,转换为 AnnotationTypeFilter 对象
     */
    protected ServletComponentHandler(Class<? extends Annotation> annotationType) {
        this.typeFilter = new AnnotationTypeFilter(annotationType);
        ...
    }

    /**
     * 返回 TypeFilter
     */
    TypeFilter getTypeFilter() {
        return this.typeFilter;
    }
    ...
}

ServletComponentHandler 中,有一个成员变量 typeFilter,在构造方法中传入注册值后会转换会AnnotationTypeFilter,然后赋值给typeFilter,而getTypeFilter()方法返回的就是这个 typeFilter

了解完这个typeFilter的来源后,我们来看看它的几个实现类:

/**
 * WebFilterHandler 构造方法传入的参数是 WebFilter
 */
class WebFilterHandler extends ServletComponentHandler {
    WebFilterHandler() {
        super(WebFilter.class);
    }
    ...
}

/**
 * WebListenerHandler 构造方法传入的参数是 WebListener
 */
class WebListenerHandler extends ServletComponentHandler {
    WebListenerHandler() {
        super(WebListener.class);
    }
    ...
}

/**
 * WebServletHandler 构造方法传入的参数是 WebServlet
 */
class WebServletHandler extends ServletComponentHandler {
    WebServletHandler() {
        super(WebServlet.class);
    }
    ...
}

由此就明白了,createComponentProvider() 得到的ClassPathScanningCandidateComponentProvider只处理包含3个注解的类:

  • @WebFilter
  • @WebListener
  • @WebServlet

我们继续,接下来看看扫描流程,方法为ServletComponentRegisteringPostProcessor#scanPackage:

private void scanPackage(ClassPathScanningCandidateComponentProvider componentProvider, 
        String packageToScan) {
    for (BeanDefinition candidate : componentProvider.findCandidateComponents(packageToScan)) {
        if (candidate instanceof AnnotatedBeanDefinition) {
            // 处理得到的 BeanDefinition
            for (ServletComponentHandler handler : HANDLERS) {
                handler.handle(((AnnotatedBeanDefinition) candidate),
                        (BeanDefinitionRegistry) this.applicationContext);
            }
        }
    }
}

关于具体的扫描流程(ClassPathScanningCandidateComponentProvider#findCandidateComponents方法),同spring的包扫描流程基本一致,这里就不展开细讲了,我们把重点放在BeanDefinition的处理上,也就是ServletComponentHandler#handle方法:

void handle(AnnotatedBeanDefinition beanDefinition, BeanDefinitionRegistry registry) {
    // 这里的 annotationType 就是构造方法中传入的注解,如@WebFilter,@WebListener 等
    Map<String, Object> attributes = beanDefinition.getMetadata()
            .getAnnotationAttributes(this.annotationType.getName());
    // 判断对应的注解是否存在,存在则处理
    if (attributes != null) {
        doHandle(attributes, beanDefinition, registry);
    }
}

在处理扫描得到的BeanDefinition时,先遍历所有的handler(WebServletHandler/WebFilterHandler/WebListenerHandler),然后调用ServletComponentHandler#handle方法进行处理。在ServletComponentHandler#handle中,又会根据是否存在对应的注解是否存在(使用AnnotatedBeanDefinition#getMetadata获取对应注册的信息)来决定是否调用子类的doHandler()方法。

那么这个doHandler()方法干了什么呢?我们进入WebServletHandler#doHandle

public void doHandle(Map<String, Object> attributes, AnnotatedBeanDefinition beanDefinition,
        BeanDefinitionRegistry registry) {
    // 注册的是 ServletRegistrationBean 对应的 BeanDefinition
    BeanDefinitionBuilder builder = BeanDefinitionBuilder
            .rootBeanDefinition(ServletRegistrationBean.class);
    builder.addPropertyValue("asyncSupported", attributes.get("asyncSupported"));
    builder.addPropertyValue("initParameters", extractInitParameters(attributes));
    builder.addPropertyValue("loadOnStartup", attributes.get("loadOnStartup"));
    // 获取 servlet 名称,如果指定了名称,就使用指定名称,如果没有指定,就使用是bean的名称
    String name = determineName(attributes, beanDefinition);
    builder.addPropertyValue("name", name);
    builder.addPropertyValue("servlet", beanDefinition);
    builder.addPropertyValue("urlMappings", extractUrlPatterns(attributes));
    builder.addPropertyValue("multipartConfig", determineMultipartConfig(beanDefinition));
    registry.registerBeanDefinition(name, builder.getBeanDefinition());
}

可以看到,这个方法主要是处理Servlet的配置,最终向spring容器中注册的是ServletRegistrationBean对应的beanDefinition

其他两个Handler类的doHandle()方法处理流程也差不多,不过最终向spring容器中注册的beanDefinition有所不同,这里就不细读了。

这里总结下这几个注解最终向spring容器中注册的beanDefinition

  • @WebServlet: 注册了ServletRegistrationBean对应的beanDefinition
  • @WebFilter: 注册了FilterRegistrationBean对应的beanDefinition
  • @WebListener: 注册了ServletListenerRegistrationBean对应的beanDefinition

在使用 XxxRegistrationBean 注册时,我们是手动创建了XxxRegistrationBean,然后通过@Bean注解注册到spring容器中,而使用@WebServlet/@WebFilter/@WebListener闹了一圈,最终也是回到了XxxRegistrationBean

2.2 XxxRegistrationBean 的注册

无论是使用 XxxRegistrationBean 注册,还是使用@ServletComponentScan 扫描注册,最终都会得到XxxRegistrationBean对应的 bean,接下来我们就来探究下这些bean是如何注册到servlet容器中的。

从代码上看,ServletRegistrationBeanFilterRegistrationBeanServletListenerRegistrationBean都是ServletContextInitializer接口的实现类,ServletRegistrationBean的继承结构如下:

图片来自网络

ServletContextInitializer 中只有一个方法onStartup(...)

@FunctionalInterface
public interface ServletContextInitializer {

    /**
     * 就是这个 servletContext,有了它,就是进行 Servlet,filter,listener 的注册了
     */
    void onStartup(ServletContext servletContext) throws ServletException;

}

我们在介绍 servlet 三大组件的注册方式时,我们提到可以通过实现ServletContextInitializer,重写其onStartup()来实现servlet组件的注册,而XxxRegistrationBean 的底层实现也是这么做的。

ServletRegistrationBean,我们来看看它的onStartup(...)方法做了啥:

ServletRegistrationBean没有重写onStartup(...)方法,直接继承自RegistrationBean:

public final void onStartup(ServletContext servletContext) throws ServletException {
    // 获取描述信息 
    String description = getDescription();
    // 是否开启注册,默认为true
    if (!isEnabled()) {
        logger.info(StringUtils.capitalize(description) + " was not registered (disabled)");
        return;
    }
    // 进行注册操作
    register(description, servletContext);
}

这个方法并不复杂,先获取了一下描述信息,然后判断是否开启了注册,接着就是进行注册操作了。我们直接查看注册操作,进入DynamicRegistrationBean#register:

protected final void register(String description, ServletContext servletContext) {
    D registration = addRegistration(description, servletContext);
    if (registration == null) {
        logger.info(...);
        return;
    }
    // 处理配置
    configure(registration);
}

这个方法主要做了两个件事:注册servlet与处理配置,我们先来看看注册操作,进入ServletRegistrationBean#addRegistration方法:

protected ServletRegistration.Dynamic addRegistration(String description, 
        ServletContext servletContext) {
    String name = getServletName();
    // 注册
    return servletContext.addServlet(name, this.servlet);
}

注册操作还是比较简单的,直接调用ServletContext#addServlet进行。

继续查看配置处理,进入ServletRegistrationBean#configure方法:

protected void configure(ServletRegistration.Dynamic registration) {
    // 调用父类
    super.configure(registration);
    // 配置urlMapping
    String[] urlMapping = StringUtils.toStringArray(this.urlMappings);
    if (urlMapping.length == 0 && this.alwaysMapUrl) {
        urlMapping = DEFAULT_MAPPINGS;
    }
    if (!ObjectUtils.isEmpty(urlMapping)) {
        registration.addMapping(urlMapping);
    }
    // 配置loadOnStartup
    registration.setLoadOnStartup(this.loadOnStartup);
    // 还处理了一些其他配置
    if (this.multipartConfig != null) {
        registration.setMultipartConfig(this.multipartConfig);
    }
}

这个方法先是调用了父类的方法,然后就是配置处理了,在这个方法里主要是处理了urlMappingloadOnStartup,就不多做分析了。

我们再来看看super.configure(...)配置了啥,进入DynamicRegistrationBean#configure:

/**
 * 也是处理一些配置
 */
protected void configure(D registration) {
    registration.setAsyncSupported(this.asyncSupported);
    // 配置初始化参数
    if (!this.initParameters.isEmpty()) {
        registration.setInitParameters(this.initParameters);
    }
}

这个方法主要处理了初始参数的配置。

从上面的分析来看,ServletRegistrationBeanonStartup(...)方法主要处理了两个操作:

  1. servlet添加到容器中
  2. 处理servlet参数配置

FilterRegistrationBeanServletListenerRegistrationBean 的注册操作类似,这里就不多说了。

2.3 ServletContextInitializer#onStartup的执行

注册流程已经捋完了,我们再来看看ServletContextInitializer#onStartup是在哪里执行的。注:这一步的流程比较复杂,会涉及到tomcat的启动流程,因此这部分只关注重点代码,不具体分析一步步流程。

以 tomcat 容器为例,经过一系列的调试与代码追踪,发现它是在TomcatStarter中运行的,代码如下:

class TomcatStarter implements ServletContainerInitializer {

    private final ServletContextInitializer[] initializers;

    TomcatStarter(ServletContextInitializer[] initializers) {
        this.initializers = initializers;
    }

    @Override
    public void onStartup(Set<Class<?>> classes, ServletContext servletContext) 
            throws ServletException {
        try {
            for (ServletContextInitializer initializer : this.initializers) {
                // 这里执行 ServletContextInitializer#onStartup方法
                initializer.onStartup(servletContext);
            }
        }
        catch (Exception ex) {
            this.startUpException = ex;
            ...
        }
    }

    ...

}

TomcatStarter是springboot提供的类,它实现了ServletContainerInitializer,区别于ServletContextInitializerServletContainerInitializer是由tomcat 提供的,在 tomcat 启动时,会执行ServletContainerInitializer#onStartup方法(servlt 3.0 规范)。

那么 TomcatStarter 是如何添加到 tomcat 容器中的呢?虽然 servlt 3.0 规范可以通过spi技术扫描到ServletContainerInitializer的实现,但是这里明显不是这样做的,因为如果由tomcat通过spi扫描得到 TomcatStarter 的实例, 那它的成员变量 initializers 就无法赋值了,所以在添加到tomcat前,TomcatStarter就要实例化并且initializers就要被赋值。

经过多次调试,发现TomcatStarter 是在 TomcatServletWebServerFactory#configureContext 中添加到tomcat容器的,关键代码如下:

可以看到,initializers 被当作构造参数传入到TomcatStarter的构造方法中,得到TomcatStarter的实例,再手动添加到tomcat容器中了。

那么,这个initializers是在哪里获取到的呢?事实上,我们的XxxRegistrationBean都要spring容器中,要获取的话,只要调用beanFactory.getBeansOfType(...)就可以了,ServletContextInitializerBeans#addServletContextInitializerBean(String, ServletContextInitializer, ListableBeanFactory)就是干这件事的:

private void addServletContextInitializerBeans(ListableBeanFactory beanFactory) {
    for (Class<? extends ServletContextInitializer> initializerType : this.initializerTypes) {
        // 获取 ServletContextInitializer: getOrderedBeansOfType(beanFactory, initializerType)
        for (Entry<String, ? extends ServletContextInitializer> initializerBean 
                : getOrderedBeansOfType(beanFactory, initializerType)) {
            addServletContextInitializerBean(initializerBean.getKey(), 
                initializerBean.getValue(), beanFactory);
        }
    }
}

/**
 * 处理添加操作
 */
private void addServletContextInitializerBean(String beanName, 
        ServletContextInitializer initializer, ListableBeanFactory beanFactory) {
    // 添加 ServletRegistrationBean
    if (initializer instanceof ServletRegistrationBean) {
        Servlet source = ((ServletRegistrationBean<?>) initializer).getServlet();
        addServletContextInitializerBean(Servlet.class, beanName, initializer, 
                beanFactory, source);
    }
    // 添加 FilterRegistrationBean
    else if (initializer instanceof FilterRegistrationBean) {
        Filter source = ((FilterRegistrationBean<?>) initializer).getFilter();
        addServletContextInitializerBean(Filter.class, beanName, initializer, 
                beanFactory, source);
    }
    // 添加 DelegatingFilterProxyRegistrationBean
    else if (initializer instanceof DelegatingFilterProxyRegistrationBean) {
        String source = ((DelegatingFilterProxyRegistrationBean) initializer).getTargetBeanName();
        addServletContextInitializerBean(Filter.class, beanName, initializer, beanFactory, source);
    }
    // 添加 ServletListenerRegistrationBean
    else if (initializer instanceof ServletListenerRegistrationBean) {
        EventListener source = ((ServletListenerRegistrationBean<?>) initializer).getListener();
        addServletContextInitializerBean(EventListener.class, beanName, initializer, 
                beanFactory, source);
    }
    else {
        // 其他的 ServletContextInitializer Bean
        addServletContextInitializerBean(ServletContextInitializer.class, beanName, 
                initializer, beanFactory, initializer);
    }
}

3. 总结

本文分析了 springboot 注册servlet三大组件的流程:

  1. Servlet为例,介绍了3种注册方式:使用 XxxRegistrationBean 注册、使用 servlet 注解(@WebServlet/@WebFilter/@WebListener)注册,以及实现ServletContextInitializer接口手动注册;
  2. 分析了@ServletComponentScan 注册的扫描流程
  3. ServletRegistrationBean为例,分析了将ServletRegistrationBean注册到servlet的流程
  4. 分析了ServletContainerInitializer#onStartup的执行

本文原文链接:my.oschina.net/funcy/blog/… ,限于作者个人水平,文中难免有错误之处,欢迎指正!原创不易,商业转载请联系作者获得授权,非商业转载请注明出处。

【springboot源码分析】springboot源码分析系列文章汇总