美文网首页
Springboot 中为什么声明了 ServletContex

Springboot 中为什么声明了 ServletContex

作者: 雁过留声_泪落无痕 | 来源:发表于2023-10-23 15:50 被阅读0次

问题

@SpringBootApplication
public class SampleTomcatApplication {

    private static Log logger = LogFactory.getLog(SampleTomcatApplication.class);

    @Bean
    protected ServletContextListener listener() {
        return new ServletContextListener() {

            @Override
            public void contextInitialized(ServletContextEvent sce) {
                logger.info("ServletContext initialized");
            }

            @Override
            public void contextDestroyed(ServletContextEvent sce) {
                logger.info("ServletContext destroyed");
            }

        };
    }

    public static void main(String[] args) {
        SpringApplication.run(SampleTomcatApplication.class, args);
    }

}

如上是 Springboot 源码中的示例代码,现在的问题是为什么提供了一个 ServletContextListenerBean 后,其中的回调就一定能得到调用呢?

调用流程

  1. 通过 https://www.jianshu.com/p/c54882984197 这篇文章我们知道了 Tomcat 会回调 Spring 的 ServletWebServerApplicationContext#selfInitialize() 方法:
private void selfInitialize(ServletContext servletContext) throws ServletException {
    prepareWebApplicationContext(servletContext);
    registerApplicationScope(servletContext);
    WebApplicationContextUtils.registerEnvironmentBeans(getBeanFactory(), servletContext);
    // getServletContextInitializerBeans() 方法返回的是 ServletContextInitializerBeans 对象
    for (ServletContextInitializer beans : getServletContextInitializerBeans()) {
        beans.onStartup(servletContext);
    }
}
  1. 注意看 getServletContextInitializerBeans() 方法:
protected Collection<ServletContextInitializer> getServletContextInitializerBeans() {
    return new ServletContextInitializerBeans(getBeanFactory());
}

该方法返回了一个 ServletContextInitializerBeans 对象,并把 beanFactory 传了进去。

  1. 再看 ServletContextInitializerBeans 类:
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 final List<Class<? extends ServletContextInitializer>> initializerTypes;

    private List<ServletContextInitializer> sortedList;

    @SafeVarargs
    @SuppressWarnings("varargs")
    public ServletContextInitializerBeans(ListableBeanFactory beanFactory,
            Class<? extends ServletContextInitializer>... initializerTypes) {
        this.initializers = new LinkedMultiValueMap<>();
        this.initializerTypes = (initializerTypes.length != 0) ? Arrays.asList(initializerTypes)
                : Collections.singletonList(ServletContextInitializer.class);
        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);
        logMappings(this.initializers);
    }

    private void addServletContextInitializerBeans(ListableBeanFactory beanFactory) {
        for (Class<? extends ServletContextInitializer> initializerType : this.initializerTypes) {
            for (Entry<String, ? extends ServletContextInitializer> initializerBean : getOrderedBeansOfType(beanFactory,
                    initializerType)) {
                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);
        }
    }

    private void addServletContextInitializerBean(Class<?> type, String beanName, ServletContextInitializer initializer,
            ListableBeanFactory beanFactory, Object source) {
        this.initializers.add(type, initializer);
        if (source != null) {
            // Mark the underlying source as seen in case it wraps an existing bean
            this.seen.add(source);
        }
        if (logger.isTraceEnabled()) {
            String resourceDescription = getResourceDescription(beanName, beanFactory);
            int order = getOrder(initializer);
            logger.trace("Added existing " + type.getSimpleName() + " initializer bean '" + beanName + "'; order="
                    + order + ", resource=" + resourceDescription);
        }
    }

    private String getResourceDescription(String beanName, ListableBeanFactory beanFactory) {
        if (beanFactory instanceof BeanDefinitionRegistry) {
            BeanDefinitionRegistry registry = (BeanDefinitionRegistry) beanFactory;
            return registry.getBeanDefinition(beanName).getResourceDescription();
        }
        return "unknown";
    }

    @SuppressWarnings("unchecked")
    protected void addAdaptableBeans(ListableBeanFactory beanFactory) {
        MultipartConfigElement multipartConfig = getMultipartConfig(beanFactory);
        addAsRegistrationBean(beanFactory, Servlet.class, new ServletRegistrationBeanAdapter(multipartConfig));
        addAsRegistrationBean(beanFactory, Filter.class, new FilterRegistrationBeanAdapter());
        for (Class<?> listenerType : ServletListenerRegistrationBean.getSupportedTypes()) {
            addAsRegistrationBean(beanFactory, EventListener.class, (Class<EventListener>) listenerType,
                    new ServletListenerRegistrationBeanAdapter());
        }
    }

    private MultipartConfigElement getMultipartConfig(ListableBeanFactory beanFactory) {
        List<Entry<String, MultipartConfigElement>> beans = getOrderedBeansOfType(beanFactory,
                MultipartConfigElement.class);
        return beans.isEmpty() ? null : beans.get(0).getValue();
    }

    protected <T> void addAsRegistrationBean(ListableBeanFactory beanFactory, Class<T> type,
            RegistrationBeanAdapter<T> adapter) {
        addAsRegistrationBean(beanFactory, type, type, adapter);
    }

    private <T, B extends T> void addAsRegistrationBean(ListableBeanFactory beanFactory, Class<T> type,
            Class<B> beanType, RegistrationBeanAdapter<T> adapter) {
        List<Map.Entry<String, B>> entries = getOrderedBeansOfType(beanFactory, beanType, this.seen);
        for (Entry<String, B> entry : entries) {
            String beanName = entry.getKey();
            B bean = entry.getValue();
            if (this.seen.add(bean)) {
                // One that we haven't already seen
                RegistrationBean registration = adapter.createRegistrationBean(beanName, bean, entries.size());
                int order = getOrder(bean);
                registration.setOrder(order);
                this.initializers.add(type, registration);
                if (logger.isTraceEnabled()) {
                    logger.trace("Created " + type.getSimpleName() + " initializer for bean '" + beanName + "'; order="
                            + order + ", resource=" + getResourceDescription(beanName, beanFactory));
                }
            }
        }
    }

    private int getOrder(Object value) {
        return new AnnotationAwareOrderComparator() {
            @Override
            public int getOrder(Object obj) {
                return super.getOrder(obj);
            }
        }.getOrder(value);
    }

    private <T> List<Entry<String, T>> getOrderedBeansOfType(ListableBeanFactory beanFactory, Class<T> type) {
        return getOrderedBeansOfType(beanFactory, type, Collections.emptySet());
    }

    private <T> List<Entry<String, T>> getOrderedBeansOfType(ListableBeanFactory beanFactory, Class<T> type,
            Set<?> excludes) {
        String[] names = beanFactory.getBeanNamesForType(type, true, false);
        Map<String, T> map = new LinkedHashMap<>();
        for (String name : names) {
            if (!excludes.contains(name) && !ScopedProxyUtils.isScopedTarget(name)) {
                T bean = beanFactory.getBean(name, type);
                if (!excludes.contains(bean)) {
                    map.put(name, bean);
                }
            }
        }
        List<Entry<String, T>> beans = new ArrayList<>(map.entrySet());
        beans.sort((o1, o2) -> AnnotationAwareOrderComparator.INSTANCE.compare(o1.getValue(), o2.getValue()));
        return beans;
    }

    private void logMappings(MultiValueMap<Class<?>, ServletContextInitializer> initializers) {
        if (logger.isDebugEnabled()) {
            logMappings("filters", initializers, Filter.class, FilterRegistrationBean.class);
            logMappings("servlets", initializers, Servlet.class, ServletRegistrationBean.class);
        }
    }

    private void logMappings(String name, MultiValueMap<Class<?>, ServletContextInitializer> initializers,
            Class<?> type, Class<? extends RegistrationBean> registrationType) {
        List<ServletContextInitializer> registrations = new ArrayList<>();
        registrations.addAll(initializers.getOrDefault(registrationType, Collections.emptyList()));
        registrations.addAll(initializers.getOrDefault(type, Collections.emptyList()));
        String info = registrations.stream().map(Object::toString).collect(Collectors.joining(", "));
        logger.debug("Mapping " + name + ": " + info);
    }

    @Override
    public Iterator<ServletContextInitializer> iterator() {
        return this.sortedList.iterator();
    }

    @Override
    public int size() {
        return this.sortedList.size();
    }

    /**
     * Adapter to convert a given Bean type into a {@link RegistrationBean} (and hence a
     * {@link ServletContextInitializer}).
     *
     * @param <T> the type of the Bean to adapt
     */
    @FunctionalInterface
    protected interface RegistrationBeanAdapter<T> {

        RegistrationBean createRegistrationBean(String name, T source, int totalNumberOfSourceBeans);

    }

    /**
     * {@link RegistrationBeanAdapter} for {@link Servlet} beans.
     */
    private static class ServletRegistrationBeanAdapter implements RegistrationBeanAdapter<Servlet> {

        private final MultipartConfigElement multipartConfig;

        ServletRegistrationBeanAdapter(MultipartConfigElement multipartConfig) {
            this.multipartConfig = multipartConfig;
        }

        @Override
        public RegistrationBean createRegistrationBean(String name, Servlet source, int totalNumberOfSourceBeans) {
            String url = (totalNumberOfSourceBeans != 1) ? "/" + name + "/" : "/";
            if (name.equals(DISPATCHER_SERVLET_NAME)) {
                url = "/"; // always map the main dispatcherServlet to "/"
            }
            ServletRegistrationBean<Servlet> bean = new ServletRegistrationBean<>(source, url);
            bean.setName(name);
            bean.setMultipartConfig(this.multipartConfig);
            return bean;
        }

    }

    /**
     * {@link RegistrationBeanAdapter} for {@link Filter} beans.
     */
    private static class FilterRegistrationBeanAdapter implements RegistrationBeanAdapter<Filter> {

        @Override
        public RegistrationBean createRegistrationBean(String name, Filter source, int totalNumberOfSourceBeans) {
            FilterRegistrationBean<Filter> bean = new FilterRegistrationBean<>(source);
            bean.setName(name);
            return bean;
        }

    }

    /**
     * {@link RegistrationBeanAdapter} for certain {@link EventListener} beans.
     */
    private static class ServletListenerRegistrationBeanAdapter implements RegistrationBeanAdapter<EventListener> {

        @Override
        public RegistrationBean createRegistrationBean(String name, EventListener source,
                int totalNumberOfSourceBeans) {
            return new ServletListenerRegistrationBean<>(source);
        }

    }

}

该类在构造方法里通过调用 addAdaptableBeans(ListableBeanFactory) 方法,将相关的 bean 添加到了 initializers 成员里,然后又复制了一份到 sortedList 成员中。

注意到该类是继承了 AbstractCollection<ServletContextInitializer> 类,而在 iterator() 方法中返回的 this.sortedList.iterator(),也也就是为什么在 ServletWebServerApplicationContext#selfInitialize() 方法中可以对该类的对象进行迭代的原因。

  1. 重点看 addAdaptableBeans 方法:
protected void addAdaptableBeans(ListableBeanFactory beanFactory) {
        MultipartConfigElement multipartConfig = getMultipartConfig(beanFactory);
        addAsRegistrationBean(beanFactory, Servlet.class, new ServletRegistrationBeanAdapter(multipartConfig));
        addAsRegistrationBean(beanFactory, Filter.class, new FilterRegistrationBeanAdapter());
        for (Class<?> listenerType : ServletListenerRegistrationBean.getSupportedTypes()) {
            addAsRegistrationBean(beanFactory, EventListener.class, (Class<EventListener>) listenerType,
                    new ServletListenerRegistrationBeanAdapter());
        }
    }

该方法创建了几个 Adapter 对象并调用了 addAsRegistrationBean 方法,其中一个就是 ServletListenerRegistrationBeanAdapter 对象,而添加该类型对象的时候是一个 for 循环添加的,循环的对象是 ServletListenerRegistrationBean.getSupportedTypes() 方法的返回:

public class ServletListenerRegistrationBean<T extends EventListener> extends RegistrationBean {

    private static final Set<Class<?>> SUPPORTED_TYPES;

    static {
        Set<Class<?>> types = new HashSet<>();
        types.add(ServletContextAttributeListener.class);
        types.add(ServletRequestListener.class);
        types.add(ServletRequestAttributeListener.class);
        types.add(HttpSessionAttributeListener.class);
        types.add(HttpSessionIdListener.class);
        types.add(HttpSessionListener.class);
        types.add(ServletContextListener.class);
        SUPPORTED_TYPES = Collections.unmodifiableSet(types);
    }

    public static Set<Class<?>> getSupportedTypes() {
        return SUPPORTED_TYPES;
    }

    ...
}

可以看到是包含了 7 个对应的类型,也就是说会添加 7 个 ServletListenerRegistrationBeanAdapter 对象,创建了该类型的对象后,会调用 addAsRegistrationBean() 方法并把该类型的对象传递进去:

private <T, B extends T> void addAsRegistrationBean(ListableBeanFactory beanFactory, Class<T> type,
        Class<B> beanType, RegistrationBeanAdapter<T> adapter) {
    List<Map.Entry<String, B>> entries = getOrderedBeansOfType(beanFactory, beanType, this.seen);
    for (Entry<String, B> entry : entries) {
        String beanName = entry.getKey();
        B bean = entry.getValue();
        if (this.seen.add(bean)) {
            // One that we haven't already seen
            RegistrationBean registration = adapter.createRegistrationBean(beanName, bean, entries.size());
            int order = getOrder(bean);
            registration.setOrder(order);
            this.initializers.add(type, registration);
            if (logger.isTraceEnabled()) {
                logger.trace("Created " + type.getSimpleName() + " initializer for bean '" + beanName + "'; order="
                        + order + ", resource=" + getResourceDescription(beanName, beanFactory));
            }
        }
    }
}

而在该方法中,会调用对应 Adapter 的 createRegistrationBean() 方法,我们这里是 ServletListenerRegistrationBeanAdapter 类型的 Adapter,来看 ServletListenerRegistrationBeanAdapter#createRegistrationBean() 方法:

private static class ServletListenerRegistrationBeanAdapter implements RegistrationBeanAdapter<EventListener> {

    @Override
    public RegistrationBean createRegistrationBean(String name, EventListener source,
            int totalNumberOfSourceBeans) {
        return new ServletListenerRegistrationBean<>(source);
    }

}

可以看到实际是返回了一个 ServletListenerRegistrationBean 对象并添加到了 ServletContextInitializerBeansinitializers 成员中,而在 selfInitialize 方法中迭代的对象就是 initializers 中的内容(其复制到了 sortedList 成员中)。
另外,构造 ServletListenerRegistrationBean 对象时,把真正的 EventListener 对象传递了进去,这也就是我们在 SampleTomcatApplication 中用 @Bean 声明的 ServletContextListener 对象。

  1. 现在回到 selfInitialize 方法中,for 循环迭代时,也就是迭代 ServletContextInitializerBeans 对象的 sortedList 成员时,调用了每个 sortedList 成员中每个对象的 onStarup() 方法,上面提到其中一个是 ServletListenerRegistrationBean 对象,来看 ServletListenerRegistrationBean#onStarup() 方法:
public final void onStartup(ServletContext servletContext) throws ServletException {
    String description = getDescription();
    if (!isEnabled()) {
        logger.info(StringUtils.capitalize(description) + " was not registered (disabled)");
        return;
    }
    register(description, servletContext);
}

再看 register 方法:

protected void register(String description, ServletContext servletContext) {
    try {
        servletContext.addListener(this.listener);
    }
    catch (RuntimeException ex) {
        throw new IllegalStateException("Failed to add listener '" + this.listener + "' to servlet context", ex);
    }
}

可以看到,核心的就是通过 ServletContext 对象,调用了其 addListener() 方法把真正的 ServletContextListener 对象给传递了过去。

  1. 注意到在 addAdaptableBeans() 方法中调用 addAsRegistrationBean 方法时,是把具体的 listenerType 也就是支持的类型传递进去了的:
protected void addAdaptableBeans(ListableBeanFactory beanFactory) {
    MultipartConfigElement multipartConfig = getMultipartConfig(beanFactory);
    addAsRegistrationBean(beanFactory, Servlet.class, new ServletRegistrationBeanAdapter(multipartConfig));
    addAsRegistrationBean(beanFactory, Filter.class, new FilterRegistrationBeanAdapter());
    // 注意 addAsRegistrationBean() 方法的第三个参数,是具体支持的类型
    for (Class<?> listenerType : ServletListenerRegistrationBean.getSupportedTypes()) {
        addAsRegistrationBean(beanFactory, EventListener.class, (Class<EventListener>) listenerType,
                new ServletListenerRegistrationBeanAdapter());
    }
}

这样在 addAsRegistrationBean 方法中根据 beanFactory 寻找对应类型的对象时就把 SampleTomcatApplication 中我们声明的 ServletContextListener 对象找出来了:

private <T, B extends T> void addAsRegistrationBean(ListableBeanFactory beanFactory, Class<T> type,
        Class<B> beanType, RegistrationBeanAdapter<T> adapter) {
    // 寻找支持类型的对象,这里是 ServletContextListener 对象
    List<Map.Entry<String, B>> entries = getOrderedBeansOfType(beanFactory, beanType, this.seen);
    for (Entry<String, B> entry : entries) {
        String beanName = entry.getKey();
        B bean = entry.getValue();
        if (this.seen.add(bean)) {
            // One that we haven't already seen
            RegistrationBean registration = adapter.createRegistrationBean(beanName, bean, entries.size());
            int order = getOrder(bean);
            registration.setOrder(order);
            this.initializers.add(type, registration);
            if (logger.isTraceEnabled()) {
                logger.trace("Created " + type.getSimpleName() + " initializer for bean '" + beanName + "'; order="
                        + order + ", resource=" + getResourceDescription(beanName, beanFactory));
            }
        }
    }
}
  1. 我们成功把 ServletContextListener 对象通过 ServletContext 对象传递给了 Tomcat,那也就可以坐等回调被调用就可以了。

总结

  1. Tomcat 会回调 Spring 的 selfInitialize 方法
  2. 在 selfInitialize 方法中会创建一个可迭代的 ServletContextInitializerBeans 对象
  3. 迭代 ServletContextInitializerBeans 对像时其中一个对象是 ServletListenerRegistrationBean 对象
  4. 所以会调用 ServletListenerRegistrationBean#onStartup() 方法
  5. 在 ServletListenerRegistrationBean#onStartup() 方法中会调用 register 方法
  6. 在 register 方法中会调用 ServletContext#addListener() 方法将真正的 ServletContextListener 传递给 Tomcat,以坐等回调
  7. 为什么可以得到 ServletContextListener 对象?那是因为使用了 beanFactory 来寻找了 ServletListenerRegistrationBean 类中明确支持的类型,这里是 ServletContextListener 类型

相关文章

网友评论

      本文标题:Springboot 中为什么声明了 ServletContex

      本文链接:https://www.haomeiwen.com/subject/putjidtx.html