SpringBoot 零配置及内嵌tomcat原理

零配置原理
  1. Spring JavaConfig是Spring社区的产品,使用java代码配置Spring IoC容器。不需要使用XML配置。
    JavaConfig的优点:
  • 面向对象的配置。配置被定义为JavaConfig类,因此用户可以充分利用Java中的面向对象功能。一个配置类可以继承另一个,重写它的@Bean方法等。
  • 减少或消除XML配置。许多开发人员不希望在XML和Java之间来回切换。JavaConfig为开发人员提供了一种纯Java方法来配置与XML配置概念相似的Spring容器。
  • 类型安全和重构友好。JavaConfig提供了一种类型安全的方法来配置Spring容器。由于Java 5.0对泛型的支持,现在可以按类型而不是按名称检索bean,不需要任何强制转换或基于字符串的查找
  1. web.xml:
    ContextLoaderListener:监听servlet启动、销毁,初始化ioc完成依赖注入DispatcherServlet:接收tomcat解析之后的http请求,匹配controller处理业务
    代码替换:
AnnotationConfigWebApplicationContext ac = new AnnotationConfigWebApplicationContext();
ac.register(Start.class);
//ac.refresh();

DispatcherServlet servlet = new DispatcherServlet(ac);
  1. applicationContext.xml:
    component-scan:扫描 注解替换: @ComponentScan
    注解替换: @Configuration +@Bean
  2. springmvc.xml:
    component-scan:扫描 只扫描@Controller 注解替换: @ComponentScan
  3. 视图解析器,json转换,国际化,编码。。。。 注解替换: @Configuration +@Bean
    代码替换:
@Configuration
@EnableWebMvc
public class MyConfig implements WebMvcConfigurer{

    @Override
    public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
        FastJsonHttpMessageConverter builder = new FastJsonHttpMessageConverter();
        converters.add(builder);
    }
}
tomcat内嵌原理
  • SpringBoot的启动主要是通过实例化SpringApplication来启动的,启动过程主要做了以下几件事情:配置属性、获取监听器,发布应用开始启动事件初、始化输入参数、配置环境,输出banner、创建上下文、预处理上下文、刷新上下文、再刷新上下文、发布应用已经启动事件、发布应用启动完成事件。
  • 在SpringBoot中启动tomcat的工作在刷新上下这一步。而tomcat的启动主要是实例化两个组件:Connector、Container,一个tomcat实例就是一个Server,一个Server包含多个Service,也就是多个应用程序,每个Service包含多个Connector和一个Container,而一个Container下又包含多个子容器。

1.由启动类进入

@SpringBootApplication
public class Application {

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

}
//这里run方法返回的是ConfigurableApplicationContext
public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
 return (new SpringApplication(primarySources)).run(args);

2.具体启动 ConfigurableApplicationContext 类

//打印banner,这里你可以自己涂鸦一下,换成自己项目的logo

  Banner printedBanner = this.printBanner(environment);

  *//创建应用上下文*

  context = this.createApplicationContext();

  exceptionReporters = this.getSpringFactoriesInstances(SpringBootExceptionReporter.class, new Class[]{ConfigurableApplicationContext.class}, context);

  *//预处理上下文*

  this.prepareContext(context, environment, listeners, applicationArguments, printedBanner);

  *//刷新上下文*

  this.refreshContext(context);

  *//再刷新上下文*

  this.afterRefresh(context, applicationArguments);

  listeners.started(context);

3.创建上下文 createApplicationContext

protected ConfigurableApplicationContext createApplicationContext() {
    Class<?> contextClass = this.applicationContextClass;
    if (contextClass == null) {
        try {
            switch(this.webApplicationType) {
            case SERVLET:
                    //创建AnnotationConfigServletWebServerApplicationContext
                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:
                contextClass = Class.forName("org.springframework.context.annotation.AnnotationConfigApplicationContext");
            }
        } catch (ClassNotFoundException var3) {
            throw new IllegalStateException("Unable create a default ApplicationContext, please specify an ApplicationContextClass", var3);
        }
    }

    return (ConfigurableApplicationContext)BeanUtils.instantiateClass(contextClass);
}

创建好ApplicationContext之后,看到refreshContext(context),联系本文开头Spring启动的AbstractApplicationContext#refresh方法,该方法其实就是触发了后者的执行

执行refreshContext方法,来到前面根据webApplicationType创建的容器类ServletWebServerApplicationContext

4.刷新上下文refreshContext(context)

public void refresh() throws BeansException, IllegalStateException {
    Object var1 = this.startupShutdownMonitor;
    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()方法,也就说这里要回到子类:ServletWebServerApplicationContext,调用该类的onRefresh()方法
            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();
        }

    }
}
protected void onRefresh() {
        super.onRefresh();

        try {
            this.createWebServer();
        } catch (Throwable var2) {
            throw new ApplicationContextException("Unable to start web server", var2);
        }
    }

5.创建 webServer

private void createWebServer() {
    WebServer webServer = this.webServer;
    ServletContext servletContext = this.getServletContext();
    if (webServer == null && servletContext == null) {
        ServletWebServerFactory factory = this.getWebServerFactory();
        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();
}

6.创建Tomcat对象getWebServer

public WebServer getWebServer(ServletContextInitializer... initializers) {
    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);
    return this.getTomcatWebServer(tomcat);
}

springboot配置排除启动Configuration类 springboot排除tomcat_实例化

没有xml的情况 dispatcherServlet 如何装配

1.DispatcherServlet在Spring Boot中的自动配置是通过DispatcherServletAutoConfiguration类来完成的。

@AutoConfigureOrder(-2147483648)
@Configuration
@ConditionalOnWebApplication(
    type = Type.SERVLET
)
@ConditionalOnClass({DispatcherServlet.class})
@AutoConfigureAfter({ServletWebServerFactoryAutoConfiguration.class})
public class DispatcherServletAutoConfiguration {}

@AutoConfigureOrder指定该自动配置的优先级;@Configuration指定该类为自动配置类;@ConditionalOnWebApplication指定自动配置需要满足是基于SERVLET的web应用;@ConditionalOnClass指定类路径下必须有DispatcherServlet类存在;@AutoConfigureAfter指定该自动配置必须基于ServletWebServerFactoryAutoConfiguration的自动配置。

2.DispatcherServletAutoConfiguration中关于DispatcherServlet实例化的代码如下:

@Configuration(proxyBeanMethods = false) // 实例化配置类
@Conditional(DefaultDispatcherServletCondition.class) // 实例化条件:通过该类来判断
@ConditionalOnClass(ServletRegistration.class) // 存在指定的ServletRegistration类
// 加载HttpProperties和WebMvcProperties
@EnableConfigurationProperties({ HttpProperties.class, WebMvcProperties.class })
protected static class DispatcherServletConfiguration {

    @Bean(name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
    public DispatcherServlet dispatcherServlet(HttpProperties httpProperties, WebMvcProperties webMvcProperties) {
        // 创建DispatcherServlet
        DispatcherServlet dispatcherServlet = new DispatcherServlet();
        // 初始化DispatcherServlet各项配置
        dispatcherServlet.setDispatchOptionsRequest(webMvcProperties.isDispatchOptionsRequest());
        dispatcherServlet.setDispatchTraceRequest(webMvcProperties.isDispatchTraceRequest());
        dispatcherServlet.setThrowExceptionIfNoHandlerFound(webMvcProperties.isThrowExceptionIfNoHandlerFound());
        dispatcherServlet.setPublishEvents(webMvcProperties.isPublishRequestHandledEvents());
        dispatcherServlet.setEnableLoggingRequestDetails(httpProperties.isLogRequestDetails());
        return dispatcherServlet;
    }

    // 初始化上传文件的解析器
    @Bean
    @ConditionalOnBean(MultipartResolver.class)
    @ConditionalOnMissingBean(name = DispatcherServlet.MULTIPART_RESOLVER_BEAN_NAME)
    public MultipartResolver multipartResolver(MultipartResolver resolver) {
        // Detect if the user has created a MultipartResolver but named it incorrectly
        return resolver;
    }

}

内部类DispatcherServletConfiguration同样需要满足指定的条件才会进行初始化,具体看代码中的注释。

其中的dispatcherServlet方法中实现了DispatcherServlet的实例化,并设置了基础参数。这对照传统的配置就是web.xml中DispatcherServlet的配置。

另外一个方法multipartResolver,用于初始化上传文件的解析器,主要作用是当用户定义的MultipartResolver名字不为“multipartResolver”时,通过该方法将其修改为“multipartResolver”,相当于重命名。

其中DispatcherServletConfiguration的注解@Conditional限定必须满足DefaultDispatcherServletCondition定义的匹配条件才会自动配置。而DefaultDispatcherServletCondition类同样为内部类。

@Order(Ordered.LOWEST_PRECEDENCE - 10)
private static class DefaultDispatcherServletCondition extends SpringBootCondition {

    @Override
    public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
        ConditionMessage.Builder message = ConditionMessage.forCondition("Default DispatcherServlet");
        ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
        List<String> dispatchServletBeans = Arrays
                .asList(beanFactory.getBeanNamesForType(DispatcherServlet.class, false, false));
        if (dispatchServletBeans.contains(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)) {
            return ConditionOutcome
                    .noMatch(message.found("dispatcher servlet bean").items(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME));
        }
        if (beanFactory.containsBean(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)) {
            return ConditionOutcome.noMatch(
                    message.found("non dispatcher servlet bean").items(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME));
        }
        if (dispatchServletBeans.isEmpty()) {
            return ConditionOutcome.match(message.didNotFind("dispatcher servlet beans").atAll());
        }
        return ConditionOutcome.match(message.found("dispatcher servlet bean", "dispatcher servlet beans")
                .items(Style.QUOTE, dispatchServletBeans)
                .append("and none is named " + DEFAULT_DISPATCHER_SERVLET_BEAN_NAME));
    }

}

该类的核心功能,总结起来就是:检验Spring容器中是否已经存在一个名字为“dispatcherServlet”的DispatcherServlet,如果不存在,则满足条件。

3.在该自动配置类中还有用于实例化ServletRegistrationBean的内部类:

@Configuration(proxyBeanMethods = false)
@Conditional(DispatcherServletRegistrationCondition.class)
@ConditionalOnClass(ServletRegistration.class)
@EnableConfigurationProperties(WebMvcProperties.class)
@Import(DispatcherServletConfiguration.class)
protected static class DispatcherServletRegistrationConfiguration {

    @Bean(name = DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME)
    @ConditionalOnBean(value = DispatcherServlet.class, name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
    public DispatcherServletRegistrationBean dispatcherServletRegistration(DispatcherServlet dispatcherServlet,
                                                                           WebMvcProperties webMvcProperties, ObjectProvider<MultipartConfigElement> multipartConfig) {
        // 通过ServletRegistrationBean将dispatcherServlet注册为servlet,这样servlet才会生效。
        DispatcherServletRegistrationBean registration = new DispatcherServletRegistrationBean(dispatcherServlet,
                webMvcProperties.getServlet().getPath());
        // 设置名称为dispatcherServlet
        registration.setName(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME);
        // 设置加载优先级,设置值默认为-1,存在于WebMvcProperties类中
        registration.setLoadOnStartup(webMvcProperties.getServlet().getLoadOnStartup());
        multipartConfig.ifAvailable(registration::setMultipartConfig);
        return registration;
    }

}

DispatcherServletRegistrationConfiguration类的核心功能就是注册dispatcherServlet使其生效并设置一些初始化的参数。

其中,DispatcherServletRegistrationBean继承自ServletRegistrationBean,主要为DispatcherServlet提供服务。DispatcherServletRegistrationBean和DispatcherServlet都提供了注册Servlet并公开DispatcherServletPath信息的功能。

Spring Boot通过上面的自动配置类就完成了之前我们在web.xml中的配置操作。

4.如何将DispatcherServlet和Tomcat取得联系

DispatcherServletRegistrationBean extends ServletRegistrationBean<DispatcherServlet> implements DispatcherServletPath
ServletRegistrationBean<T extends Servlet> extends DynamicRegistrationBean<Dynamic>
DynamicRegistrationBean<D extends Dynamic> extends RegistrationBean
RegistrationBean implements ServletContextInitializer, Ordered
public interface ServletContextInitializer {
    void onStartup(ServletContext servletContext) throws ServletException;
}

onStartup会调用register方法然后调用addRegistration完成servletContext.addServlet

springboot配置排除启动Configuration类 springboot排除tomcat_tomcat_02