SpringBoot源码解析

SpringBoot源码解析

SpringBoot主要的强大之处,就是通过约定大于配置的方式实现了自动化导入,另外就是通过代码的方式启动tomcat省去了我们导入配置tomcat的工作。所以明白了以上这两点在SpringBoot是怎么实现的,就能够对SpringBoot的原理有一个大概的认知。

SpringApplication启动Spring

这里其实就是SpringBoot帮我们创建并刷新ApplicationContext,比较简单。并且也不是SpringBoot才会这么做,SpringMVC也有自动创建并刷新ApplicationContext的功能。
org.springframework.boot.SpringApplication#run(java.lang.String…)

    public ConfigurableApplicationContext run(String... args) {
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        ConfigurableApplicationContext context = null;
        Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList();
        this.configureHeadlessProperty();
        SpringApplicationRunListeners listeners = this.getRunListeners(args);
        listeners.starting();

        Collection exceptionReporters;
        try {
            ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
            ConfigurableEnvironment environment = this.prepareEnvironment(listeners, applicationArguments);
            this.configureIgnoreBeanInfo(environment);
            Banner printedBanner = this.printBanner(environment);
            // 创建ApplicationContext
            context = this.createApplicationContext();
            exceptionReporters = this.getSpringFactoriesInstances(SpringBootExceptionReporter.class, new Class[]{ConfigurableApplicationContext.class}, context);
            this.prepareContext(context, environment, listeners, applicationArguments, printedBanner);
            // 刷新ApplicationContext
            this.refreshContext(context);
            this.afterRefresh(context, applicationArguments);
            stopWatch.stop();
            if (this.logStartupInfo) {
                (new StartupInfoLogger(this.mainApplicationClass)).logStarted(this.getApplicationLog(), stopWatch);
            }

            listeners.started(context);
            this.callRunners(context, applicationArguments);
        } catch (Throwable var10) {
            this.handleRunFailure(context, var10, exceptionReporters, listeners);
            throw new IllegalStateException(var10);
        }

        try {
            listeners.running(context);
            return context;
        } catch (Throwable var9) {
            this.handleRunFailure(context, var9, exceptionReporters, (SpringApplicationRunListeners)null);
            throw new IllegalStateException(var9);
        }
    }
    protected ConfigurableApplicationContext createApplicationContext() {
        Class<?> contextClass = this.applicationContextClass;
        if (contextClass == null) {
            try {
            	// 根据成员属性webApplicationType,确定要启动的ApplicationContext
                switch(this.webApplicationType) {
                case SERVLET:
                    contextClass = Class.forName("org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext");
                    break;
                case REACTIVE:
                    contextClass = Class.forName("org.springframework.boot.web.reactive.context.AnnotationConfigReactiveWebServerApplicationContext");
                    break;
                default:
                	// 默认创建的就是AnnotationConfigApplicationContext
                    contextClass = Class.forName("org.springframework.context.annotation.AnnotationConfigApplicationContext");
                }
            } catch (ClassNotFoundException var3) {
                throw new IllegalStateException("Unable create a default ApplicationContext, please specify an ApplicationContextClass", var3);
            }
        }
		// 通过反射实例化ApplicationContext的实现类对象,并且强转成ConfigurableApplicationContext返回
        return (ConfigurableApplicationContext)BeanUtils.instantiateClass(contextClass);
    }

SpringBoot自动化配置原理

springboot的自动化配置原理就是,扫描每个starter的META-INF/spring.factories配置文件,然后获取里面的配置类的全全路径名,然后加载成BeanDefinition交给Spring容器管理,对他们进行实例化和初始化。所以我们可以自行实现一个starter(需要引入对应的依赖jar),然后在resources下创建META-INF/spring.factories指定的配置类,配置类添加@Configuration和@ComponentScan注解,@Configuration表示他是个配置类,交给Spring管理,@ComponentScan就是告诉Spring还要扫着对应的路径下的Bean。完成后打成jar包,导入到SpringBoot
工程中,我们就可以自动注入里面的Bean。所以我们会发现我们引入了一个SpringBootStarter,我们啥配置都不需要写,就可以自动的自动注入里面的Bean。
SpringBootApplication注解的内容

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
// SpringBoot的全局开关,start生效的地方
@EnableAutoConfiguration
@ComponentScan(
    excludeFilters = {@Filter(
    type = FilterType.CUSTOM,
    classes = {TypeExcludeFilter.class}
), @Filter(
    type = FilterType.CUSTOM,
    classes = {AutoConfigurationExcludeFilter.class}
)}
)
public @interface SpringBootApplication {
	...
}

EnableAutoConfiguration是一个关键性的注解,因为注解具有传递性。EnableAutoConfiguration是SpringBoot的全局开关,如果把这个注解去掉,则一切的Start都会失效,这里面就是“约定大于配置”的规则。
org.springframework.boot.autoconfigure.EnableAutoConfiguration

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import({AutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration {
    String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";

    Class<?>[] exclude() default {};

    String[] excludeName() default {};
}

AutoConfigurationImportSelector就是springboot自动化导入的关键,里面的selectImports方法就是处理自动化导入的地方
org.springframework.boot.autoconfigure.AutoConfigurationImportSelector#selectImports

    public String[] selectImports(AnnotationMetadata annotationMetadata) {
        if (!this.isEnabled(annotationMetadata)) {
            return NO_IMPORTS;
        } else {
            AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);
            // 获取内部类的实例对象AutoConfigurationEntry
            AutoConfigurationImportSelector.AutoConfigurationEntry autoConfigurationEntry = this.getAutoConfigurationEntry(autoConfigurationMetadata, annotationMetadata);
            // 调用autoConfigurationEntry的getConfigurations,返回配置类的字符串数组
            return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
        }
    }

先看AutoConfigurationEntry 的创建

    protected AutoConfigurationImportSelector.AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata, AnnotationMetadata annotationMetadata) {
        if (!this.isEnabled(annotationMetadata)) {
            return EMPTY_ENTRY;
        } else {
            AnnotationAttributes attributes = this.getAttributes(annotationMetadata);
            /*
            * 这里一步非常关键,会把所有start中(jar包)META-INF/spring.factories里的配置获取出来,作为字符串list
            */
            List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
            configurations = this.removeDuplicates(configurations);
            Set<String> exclusions = this.getExclusions(annotationMetadata, attributes);
            this.checkExcludedClasses(configurations, exclusions);
            configurations.removeAll(exclusions);
            configurations = this.filter(configurations, autoConfigurationMetadata);
            this.fireAutoConfigurationImportEvents(configurations, exclusions);
            return new AutoConfigurationImportSelector.AutoConfigurationEntry(configurations, exclusions);
        }
    }

org.springframework.boot.autoconfigure.AutoConfigurationImportSelector#getCandidateConfigurations

    protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
    	// 这里就会返回所有spring.factories配置的value
        List<String> configurations = SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.getBeanClassLoader());
        Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you are using a custom packaging, make sure that file is correct.");
        return configurations;
    }

org.springframework.core.io.support.SpringFactoriesLoader#loadFactoryNames

    public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {
        String factoryClassName = factoryClass.getName();
        // 看loadSpringFactories方法
        return (List)loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());
    }

org.springframework.core.io.support.SpringFactoriesLoader#loadSpringFactories

    private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
        MultiValueMap<String, String> result = (MultiValueMap)cache.get(classLoader);
        if (result != null) {
            return result;
        } else {
            try {
            	// 这里就是把每个starter的META-INF/spring.factories扫描出来的地方
                Enumeration<URL> urls = classLoader != null ? classLoader.getResources("META-INF/spring.factories") : ClassLoader.getSystemResources("META-INF/spring.factories");
                LinkedMultiValueMap result = new LinkedMultiValueMap();

                while(urls.hasMoreElements()) {
                    URL url = (URL)urls.nextElement();
                    UrlResource resource = new UrlResource(url);
                    Properties properties = PropertiesLoaderUtils.loadProperties(resource);
                    Iterator var6 = properties.entrySet().iterator();

                    while(var6.hasNext()) {
                        Entry<?, ?> entry = (Entry)var6.next();
                        String factoryClassName = ((String)entry.getKey()).trim();
                        String[] var9 = StringUtils.commaDelimitedListToStringArray((String)entry.getValue());
                        int var10 = var9.length;

                        for(int var11 = 0; var11 < var10; ++var11) {
                            String factoryName = var9[var11];
                            result.add(factoryClassName, factoryName.trim());
                        }
                    }
                }

                cache.put(classLoader, result);
                return result;
            } catch (IOException var13) {
                throw new IllegalArgumentException("Unable to load factories from location [META-INF/spring.factories]", var13);
            }
        }
    }

上面仅仅是获取到了所有的配置类的类路径,所以肯定有个地方调用它。
其实就是在ApplicationContext调用refresh方法时,在invokeBeanFactoryPostProcesser这一步中做的处理,invokeBeanFactoryPostProcesser是Spring容器刷新时,在BeanFactory创建完毕后,调用BeanFactory后置处理器的步骤,在Spring中实现了BeanFactoryPostProcesser的Bean都会在这里得到回调,可以对BeanFactory进行操作。会调用到一个叫做ConfigurationClassPostProcesser的BeanFactory后置处理器,顾名思义就是专门做配置类的解析处理的。就会根据每个配置类的全路径名,最后调用到loadBeanDefinitions方法,加载他们的BeanDefinition到Spring容器的BeanDefinitionMaps中。如果配置类中有@ComponentScan注解,他就会递归的解析,加载扫描路径下所有的BeanDefinition。
上两张图,从《Spring源码深度解析》里面摘取出来的
在这里插入图片描述
在这里插入图片描述

SpringBoot中Tomcat的启动

Tomcat的启动

org.springframework.boot.SpringApplication#run(java.lang.String…)

	/**
	 * 运行 Spring application, 创建并 refreshing 一个新的
	 * {@link ApplicationContext}.
	 * @param args the application arguments (usually passed from a Java main method)
	 * @return a running {@link ApplicationContext}
	 */
    public ConfigurableApplicationContext run(String... args) {
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        ConfigurableApplicationContext context = null;
        Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList();
        this.configureHeadlessProperty();
        SpringApplicationRunListeners listeners = this.getRunListeners(args);
        listeners.starting();

        Collection exceptionReporters;
        try {
            ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
            ConfigurableEnvironment environment = this.prepareEnvironment(listeners, applicationArguments);
            this.configureIgnoreBeanInfo(environment);
            Banner printedBanner = this.printBanner(environment);
            /*创建ApplicationContext*/
            context = this.createApplicationContext();
            exceptionReporters = this.getSpringFactoriesInstances(SpringBootExceptionReporter.class, new Class[]{ConfigurableApplicationContext.class}, context);
            this.prepareContext(context, environment, listeners, applicationArguments, printedBanner);
            this.refreshContext(context);
            /*刷新ApplicationContext,看这一行*/
            this.afterRefresh(context, applicationArguments);
            stopWatch.stop();
            if (this.logStartupInfo) {
                (new StartupInfoLogger(this.mainApplicationClass)).logStarted(this.getApplicationLog(), stopWatch);
            }

            listeners.started(context);
            this.callRunners(context, applicationArguments);
        } catch (Throwable var10) {
            this.handleRunFailure(context, var10, exceptionReporters, listeners);
            throw new IllegalStateException(var10);
        }

        try {
            listeners.running(context);
            return context;
        } catch (Throwable var9) {
            this.handleRunFailure(context, var9, exceptionReporters, (SpringApplicationRunListeners)null);
            throw new IllegalStateException(var9);
        }
    }

org.springframework.boot.SpringApplication#refreshContext

    private void refreshContext(ConfigurableApplicationContext context) {
    	/*刷新ApplicationContext*/
        this.refresh(context);
        if (this.registerShutdownHook) {
            try {
                context.registerShutdownHook();
            } catch (AccessControlException var3) {
            }
        }

    }

org.springframework.boot.SpringApplication#refresh

    protected void refresh(ApplicationContext applicationContext) {
        Assert.isInstanceOf(AbstractApplicationContext.class, applicationContext);
        /**
		 * 强转成AbstractApplicationContext,
		 * 并且调用refresh方法进行刷新, 
		 * 如果引入web-starter,
		 * 那么这里既是AnnotationConfigServletWebServerApplicationContext
		 */
        ((AbstractApplicationContext)applicationContext).refresh();
    }

首先调用refresh方法,肯定会走到AbstractApplicationContext的refresh方法,这是一个模板方法,已经在AbstractApplicationContext中定义好refresh时的执行流程
org.springframework.context.support.AbstractApplicationContext#refresh

    public void refresh() throws BeansException, IllegalStateException {
        synchronized(this.startupShutdownMonitor) {
            this.prepareRefresh();
            ConfigurableListableBeanFactory beanFactory = this.obtainFreshBeanFactory();
            this.prepareBeanFactory(beanFactory);

            try {
                this.postProcessBeanFactory(beanFactory);
                this.invokeBeanFactoryPostProcessors(beanFactory);
                this.registerBeanPostProcessors(beanFactory);
                this.initMessageSource();
                this.initApplicationEventMulticaster();
                // 最后会调用到这里的onRefresh方法,这里Spring留给子类ApplicationContext在容器刷新时的一个扩展点
                this.onRefresh();
                this.registerListeners();
                this.finishBeanFactoryInitialization(beanFactory);
                this.finishRefresh();
            } catch (BeansException var9) {
                if (this.logger.isWarnEnabled()) {
                    this.logger.warn("Exception encountered during context initialization - cancelling refresh attempt: " + var9);
                }

                this.destroyBeans();
                this.cancelRefresh(var9);
                throw var9;
            } finally {
                this.resetCommonCaches();
            }

        }
    }

SpringBoot使用的ApplicationContext是ServletWebServerApplicationContext,就是在上面SpringApplication启动这里,有一个选择ApplicationContext的步骤,普通SpringBoot的话,就会走到AnnotationConfigApplicationContext对应的分支。而当引入了web-Starter后,就会走第一个分支:AnnotationConfigServletWebServerApplicationContext。
org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext#onRefresh

	/**
	 * 在父类AbstractApplicationContext中的refresh方法中,会预留一个空方法onRrefresh供子类实现,
	 * 这里就会调用到
	 */
    protected void onRefresh() {
        super.onRefresh();

        try {
        	// 顾名思义,创建一个web服务器
            this.createWebServer();
        } catch (Throwable var2) {
            throw new ApplicationContextException("Unable to start web server", var2);
        }
    }

org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext#createWebServer

    private void createWebServer() {
        WebServer webServer = this.webServer;
        ServletContext servletContext = this.getServletContext();
        if (webServer == null && servletContext == null) {
        	// 重点:获取WebServer工厂,有连个子类:TomcatServletWebServerFactory和JettyServletWebServerFactory
        	// 一个对应启动tomcat,一个对应启动jetty
        	// 这里默认是TomcatServletWebServerFactory
            ServletWebServerFactory factory = this.getWebServerFactory();
            // 通过TomcatServletWebServerFactory获取web服务器,保存到成员变量中
            this.webServer = factory.getWebServer(new ServletContextInitializer[]{this.getSelfInitializer()});
        } else if (servletContext != null) {
            try {
                this.getSelfInitializer().onStartup(servletContext);
            } catch (ServletException var4) {
                throw new ApplicationContextException("Cannot initialize servlet context", var4);
            }
        }

        this.initPropertySources();
    }

这里可以看到,new了一个Tomcat。
org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory#getWebServer

	/**
	 * 创建并返回Web服务器
	 * @param initializers
	 * @return
	 */
    public WebServer getWebServer(ServletContextInitializer... initializers) {
    	/*new 一个Tomcat对象*/
        Tomcat tomcat = new Tomcat();
        File baseDir = this.baseDirectory != null ? this.baseDirectory : this.createTempDir("tomcat");
        tomcat.setBaseDir(baseDir.getAbsolutePath());
        Connector connector = new Connector(this.protocol);
        tomcat.getService().addConnector(connector);
        this.customizeConnector(connector);
        tomcat.setConnector(connector);
        tomcat.getHost().setAutoDeploy(false);
        this.configureEngine(tomcat.getEngine());
        Iterator var5 = this.additionalTomcatConnectors.iterator();

        while(var5.hasNext()) {
            Connector additionalConnector = (Connector)var5.next();
            tomcat.getService().addConnector(additionalConnector);
        }

        this.prepareContext(tomcat.getHost(), initializers);
        /*通过把Tomcat对象封装成一个TomcatWebServer返回*/
        return this.getTomcatWebServer(tomcat);
    }

进入this.getTomcatWebServer(tomcat)
org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory#getTomcatWebServer

    protected TomcatWebServer getTomcatWebServer(Tomcat tomcat) {
    	// new了一个TomcatWebServer,并且把刚刚new的Tomca传入进去
        return new TomcatWebServer(tomcat, this.getPort() >= 0);
    }

org.springframework.boot.web.embedded.tomcat.TomcatWebServer#TomcatWebServer(org.apache.catalina.startup.Tomcat, boolean)

	/**
	 * 创建一个新的{@link TomcatWebServer}实例。
	 * @param 底层tomcat服务器
	 * @param 如果服务器应该启动,则自动启动
	 */
    public TomcatWebServer(Tomcat tomcat, boolean autoStart) {
        this.monitor = new Object();
        this.serviceConnectors = new HashMap();
        Assert.notNull(tomcat, "Tomcat Server must not be null");
        // 把Tomcat对象保存为当前WebServer对象的成员变量
        this.tomcat = tomcat;
        this.autoStart = autoStart;
        // 这里就是真正把Tomcat启动起来的方法
        this.initialize();
    }

org.springframework.boot.web.embedded.tomcat.TomcatWebServer#initialize

    private void initialize() throws WebServerException {
        logger.info("Tomcat initialized with port(s): " + this.getPortsDescription(false));
        synchronized(this.monitor) {
            try {
                this.addInstanceIdToEngineName();
                Context context = this.findContext();
                context.addLifecycleListener((event) -> {
                    if (context.equals(event.getSource()) && "start".equals(event.getType())) {
                        this.removeServiceConnectors();
                    }

                });
                // 启动服务器以触发初始化侦听器
                this.tomcat.start();
                this.rethrowDeferredStartupExceptions();

                try {
                    ContextBindings.bindClassLoader(context, context.getNamingToken(), this.getClass().getClassLoader());
                } catch (NamingException var5) {
                }
				// 不像Jetty, 所有的Tomcat线程都是守护线程。 我们创建了一个阻塞的非守护进程来停止立即关闭
				// 创建一个线程,调用tomcat的await,阻塞接受请求
                this.startDaemonAwaitThread();
            } catch (Exception var6) {
                this.stopSilently();
                this.destroySilently();
                throw new WebServerException("Unable to start embedded Tomcat", var6);
            }

        }
    }
    private void startDaemonAwaitThread() {
        Thread awaitThread = new Thread("container-" + containerCounter.get()) {
            public void run() {
            	// 调用tomcat的await,阻塞接受请求
                TomcatWebServer.this.tomcat.getServer().await();
            }
        };
        awaitThread.setContextClassLoader(this.getClass().getClassLoader());
        awaitThread.setDaemon(false);
        // 启动线程
        awaitThread.start();
    }

DispatchServlet的实例化

在刚刚上面获取TomcatServletWebServerFactory的时候,Spring会执行它的生命周期。当执行到初始化回调前的调用BeanPostProcesser的postProcesserBeforeInitialization方法时,其中有一个BeanPostProcesser叫ErrorPageRegistrarBeanPostProcessor,会要求参加一个ErrorPageRegistry接口的对象,他的作用是给tomcat设置一个处理错误页面的方法。ErrorPageRegistry接口接口的唯一实现类ErrorPageCustomizer,它在实例化时要求传入一个构成方法参数DispatcherServletPath,所以要先实例化DispatcherServletPath
org.springframework.boot.web.server.ErrorPageRegistrarBeanPostProcessor#postProcessBeforeInitialization(java.lang.Object, java.lang.String)

    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        if (bean instanceof ErrorPageRegistry) {
            this.postProcessBeforeInitialization((ErrorPageRegistry)bean);
        }

        return bean;
    }

org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration#errorPageCustomizer

    @Bean
    public ErrorMvcAutoConfiguration.ErrorPageCustomizer errorPageCustomizer() {
        return new ErrorMvcAutoConfiguration.ErrorPageCustomizer(this.serverProperties, this.dispatcherServletPath);
    }

但是实际上DispatcherServletPath也是一个接口,他的实现类是DispatcherServletRegistrationBean
org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration.DispatcherServletRegistrationConfiguration#dispatcherServletRegistration

        public DispatcherServletRegistrationBean dispatcherServletRegistration(DispatcherServlet dispatcherServlet) {
            DispatcherServletRegistrationBean registration = new DispatcherServletRegistrationBean(dispatcherServlet, this.webMvcProperties.getServlet().getPath());
            registration.setName("dispatcherServlet");
            registration.setLoadOnStartup(this.webMvcProperties.getServlet().getLoadOnStartup());
            if (this.multipartConfig != null) {
                registration.setMultipartConfig(this.multipartConfig);
            }

            return registration;
        }

org.springframework.boot.autoconfigure.web.servlet.DispatcherServletRegistrationBean#DispatcherServletRegistrationBean

    public DispatcherServletRegistrationBean(DispatcherServlet servlet, String path) {
        super(servlet, new String[0]);
        Assert.notNull(path, "Path must not be null");
        this.path = path;
        super.addUrlMappings(new String[]{this.getServletUrlMapping()});
    }

而DispatcherServletRegistrationBean的构造方法中,有一个我们熟悉的参数,就是DispatcherServlet,所以Spring又要先实例化DispatcherServlet
org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration.DispatcherServletConfiguration#dispatcherServlet

        @Bean(
            name = {"dispatcherServlet"}
        )
        public DispatcherServlet dispatcherServlet() {
            DispatcherServlet dispatcherServlet = new DispatcherServlet();
            dispatcherServlet.setDispatchOptionsRequest(this.webMvcProperties.isDispatchOptionsRequest());
            dispatcherServlet.setDispatchTraceRequest(this.webMvcProperties.isDispatchTraceRequest());
            dispatcherServlet.setThrowExceptionIfNoHandlerFound(this.webMvcProperties.isThrowExceptionIfNoHandlerFound());
            dispatcherServlet.setEnableLoggingRequestDetails(this.httpProperties.isLogRequestDetails());
            return dispatcherServlet;
        }

所以也就是说,还没等到Tomcat创建,只是实例化ServletWebServerFactory的时候,DispatchServlet已经作为Bean交给了Spring容器管理了。最后这个Servlet就会注册给Tomcat。


版权声明:本文为weixin_43889578原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。