美文网首页
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