zoukankan      html  css  js  c++  java
  • Springboot之自动注册DispatcherServlet

    注意:Springboot的版本是2.0.5.release。

        Springboot中我们引入spring-boot-starter-web依赖后,web就自动配置好了,在web.xml的年代,我们需要在web.xml中手动配置DispatcherServlet,但是Springboot中不需要,Springboot是如何替我们做好这一切的呢?

        我们来看下DispatcherServletAutoConfiguration,这个类在Springboot-autoconfig包中,如下List-1

        List-1

     

    @Bean(name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
    public DispatcherServlet dispatcherServlet() {
        DispatcherServlet dispatcherServlet = new DispatcherServlet();
        dispatcherServlet.setDispatchOptionsRequest(
                this.webMvcProperties.isDispatchOptionsRequest());
        dispatcherServlet.setDispatchTraceRequest(
                this.webMvcProperties.isDispatchTraceRequest());
        dispatcherServlet.setThrowExceptionIfNoHandlerFound(
                this.webMvcProperties.isThrowExceptionIfNoHandlerFound());
        return dispatcherServlet;
    }
    
    @Bean(name = DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME)
    @ConditionalOnBean(value = DispatcherServlet.class, name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
    public DispatcherServletRegistrationBean dispatcherServletRegistration(
            DispatcherServlet dispatcherServlet) {
        DispatcherServletRegistrationBean registration = new DispatcherServletRegistrationBean(
                dispatcherServlet, this.serverProperties.getServlet().getPath());
        registration.setName(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME);
        registration.setLoadOnStartup(
                this.webMvcProperties.getServlet().getLoadOnStartup());
        if (this.multipartConfig != null) {
            registration.setMultipartConfig(this.multipartConfig);
        }
        return registration;
    }
    @Bean(name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
    public DispatcherServlet dispatcherServlet() {
        DispatcherServlet dispatcherServlet = new DispatcherServlet();
        dispatcherServlet.setDispatchOptionsRequest(
                this.webMvcProperties.isDispatchOptionsRequest());
        dispatcherServlet.setDispatchTraceRequest(
                this.webMvcProperties.isDispatchTraceRequest());
        dispatcherServlet.setThrowExceptionIfNoHandlerFound(
                this.webMvcProperties.isThrowExceptionIfNoHandlerFound());
        return dispatcherServlet;
    }
    
    @Bean(name = DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME)
    @ConditionalOnBean(value = DispatcherServlet.class, name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
    public DispatcherServletRegistrationBean dispatcherServletRegistration(
            DispatcherServlet dispatcherServlet) {
        DispatcherServletRegistrationBean registration = new DispatcherServletRegistrationBean(
                dispatcherServlet, this.serverProperties.getServlet().getPath());
        registration.setName(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME);
        registration.setLoadOnStartup(
                this.webMvcProperties.getServlet().getLoadOnStartup());
        if (this.multipartConfig != null) {
            registration.setMultipartConfig(this.multipartConfig);
        }
        return registration;
    }
    1. 实例化DispatcherServlet,之后注册到Spring容器中。
    2. 实例化DispatcherServletRegistrationBean,并将DispatcherServlet传入到构造方法法中,注册到Spring容器中。

           所以说,在Springboot中,有个DispatcherServlet的bean,我们可以写个单元测试验证从BeanFactory中获取DispatcherServlet这个bean,接下来看DispatcherServletRegistrationBean。

    如图1所示,DispatcherServletRegistrationBean继承了ServletContextInitializer——见List-2,其中onStartUp的参数ServletContext是Servlet里面的。

        RegistrationBean实现了ServletContextInitializer,之后调用register方法,这个是抽象方法,由子类DynamicRegistrationBean实现,DynamicRegistrationBean再将ServletContext用方法addRegistration传递给子类ServletRegistrationBean——见List-3,List-3中用addServlet方法加入的就是DispatcherServletRegistrationBean传递到父类ServletRegistrationBean中的。这样Springboot利用Servlet3.0+的特性,自动注册DispatcherServlet到ServletContext中。

        List-2

    @FunctionalInterface
    public interface ServletContextInitializer {
    
        /**
         * Configure the given {@link ServletContext} with any servlets, filters, listeners
         * context-params and attributes necessary for initialization.
         * @param servletContext the {@code ServletContext} to initialize
         * @throws ServletException if any call against the given {@code ServletContext}
         * throws a {@code ServletException}
         */
        void onStartup(ServletContext servletContext) throws ServletException;
    }

    List-3

    @Override
    protected ServletRegistration.Dynamic addRegistration(String description,
            ServletContext servletContext) {
        String name = getServletName();
        logger.info("Servlet " + name + " mapped to " + this.urlMappings);
        return servletContext.addServlet(name, this.servlet);
    }
    @Override
    protected ServletRegistration.Dynamic addRegistration(String description,
            ServletContext servletContext) {
        String name = getServletName();
        logger.info("Servlet " + name + " mapped to " + this.urlMappings);
        return servletContext.addServlet(name, this.servlet);
    }

    有个问题,实现了ServletContextInitializer的实例,什么时候会调用onStartup方法呢?来看ServletContextInitializerBeans,这个类在Springboot中,如List-4中:

    1. 实例化的时候会从BeanFactory中获取所有的ServletContextInitializer——在getOrderedBeansOfType方法中,之后用addServletContextInitializerBean方法,将获取到的ServletContextInitializer类型的Bean,添加到属性initializers中。
    2. 这个地方可以看到,实现了ServletContextInitializer的不止是Servlet类型的,还有Listener、Filter类型的,为什么呢,因为他们都需要动态添加到web容器中,即需要ServletContext。

        List-4

    public class ServletContextInitializerBeans
            extends AbstractCollection<ServletContextInitializer> {
    
        private static final String DISPATCHER_SERVLET_NAME = "dispatcherServlet";
    
        private static final Log logger = LogFactory
                .getLog(ServletContextInitializerBeans.class);
    
        /**
         * Seen bean instances or bean names.
         */
        private final Set<Object> seen = new HashSet<>();
    
        private final MultiValueMap<Class<?>, ServletContextInitializer> initializers;
    
        private List<ServletContextInitializer> sortedList;
    
        public ServletContextInitializerBeans(ListableBeanFactory beanFactory) {
            this.initializers = new LinkedMultiValueMap<>();
            addServletContextInitializerBeans(beanFactory);
            addAdaptableBeans(beanFactory);
            List<ServletContextInitializer> sortedInitializers = this.initializers.values()
                    .stream()
                    .flatMap((value) -> value.stream()
                            .sorted(AnnotationAwareOrderComparator.INSTANCE))
                    .collect(Collectors.toList());
            this.sortedList = Collections.unmodifiableList(sortedInitializers);
        }
    
        private void addServletContextInitializerBeans(ListableBeanFactory beanFactory) {
            for (Entry<String, ServletContextInitializer> initializerBean : getOrderedBeansOfType(
                    beanFactory, ServletContextInitializer.class)) {
                addServletContextInitializerBean(initializerBean.getKey(),
                        initializerBean.getValue(), beanFactory);
            }
        }
    
        private void addServletContextInitializerBean(String beanName,
                ServletContextInitializer initializer, ListableBeanFactory beanFactory) {
            if (initializer instanceof ServletRegistrationBean) {
                Servlet source = ((ServletRegistrationBean<?>) initializer).getServlet();
                addServletContextInitializerBean(Servlet.class, beanName, initializer,
                        beanFactory, source);
            }
            else if (initializer instanceof FilterRegistrationBean) {
                Filter source = ((FilterRegistrationBean<?>) initializer).getFilter();
                addServletContextInitializerBean(Filter.class, beanName, initializer,
                        beanFactory, source);
            }
            else if (initializer instanceof DelegatingFilterProxyRegistrationBean) {
                String source = ((DelegatingFilterProxyRegistrationBean) initializer)
                        .getTargetBeanName();
                addServletContextInitializerBean(Filter.class, beanName, initializer,
                        beanFactory, source);
            }
            else if (initializer instanceof ServletListenerRegistrationBean) {
                EventListener source = ((ServletListenerRegistrationBean<?>) initializer)
                        .getListener();
                addServletContextInitializerBean(EventListener.class, beanName, initializer,
                        beanFactory, source);
            }
            else {
                addServletContextInitializerBean(ServletContextInitializer.class, beanName,
                        initializer, beanFactory, initializer);
            }
        }
    ...

    接着引出一个问题,ServletContextInitializerBeans这个在哪被调用呢,在ServletWebServerApplicationContext中,在Servlet类型的Sprringboot Web应用中,ApplicationContext是AnnotationConfigServletWebServerApplicationContext,而ServletWebServerApplicationContext正是其父类。

        ServletWebServerApplicationContext中,方法onRefresh()-->createWebServer()-->getSelfInitializer()-->selfInitialize()-->getServletContextInitializerBeans()-->new ServletContextInitializerBeans(getBeanFactory())。

        ServletWebServerApplicationContext的onRefresh方法覆盖了AbstractApplicationContext的onRefresh方法,AbstractApplicationContext中,方法onRefresh被方法refresh调用。SpringApplication的run()-->refreshContext()-->refresh()-->AbstractApplicationContext的refresh()。

        通过上面的分析可以看出,Springboot利用SpringFramework的特性,将DispatcherServlet、Filter或者Listener通过ServletContextInitializer的ServletContext,添加到tomcat之类的web容器中,这些都发生在Springboot启动的过程中。该接口的实现类不会被SpringServletContainerInitializer识别因此不会被Servlet容器自动执行。
    ServletContextInitializers主要被Spring管理而不是Servlet容器。

    本文转自:https://www.liangzl.com/get-article-detail-133970.html

  • 相关阅读:
    I.MX6 Parallel RGB LCD Datasheet描述
    ubuntu IP 扫描
    I.MX6 按键开关机 PMIC 检测
    java中对List中对象排序实现
    jQuery实现父窗口的问题
    如何在Oracle中复制表结构和表数据
    handsontable常规配置的中文API
    oracle中to_date详细用法示例(oracle日期格式转换)
    js中子页面父页面方法和变量相互调用
    关于Ajax的type为post提交方式出现请求失效问题
  • 原文地址:https://www.cnblogs.com/nizuimeiabc1/p/12542570.html
Copyright © 2011-2022 走看看