暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

spring之springboot启动草纲

1024行日记 2021-10-29
274

本文泛式讲解springboot的启动过程。

springboot的启动方式

方式一:
一个简单的springBoot项目,大致格式如下:

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

SpringApplication这个类是可以设置属性的。
方式二:
修改后的启动方式:

@SpringBootApplication
public class DemowebApplication {

    public static void main(String[] args) {
        SpringApplication application = new SpringApplication();
        application.setAdditionalProfiles("prod");//设置profile
        application.setWebApplicationType(WebApplicationType.SERVLET);//设置项目类型为web项目
        HashSet<String> source = new HashSet<>();
        //此处可以设置多个类名,便于启动之前写好的组件或者插件
        //此处的类名不强求是当前类,但是这里设置的每个类上面都必须要有@SpringBootApplication注解
        source.add(DemowebApplication.class.getName());
        application.setSources(source);
        application.run(args);
    }

}

springboot的项目,有以下应用类型:

  • SERVLET类型:平时的交互式springboot项目。

  • REACTIVE类型:响应式springboot项目,比如webflux。

  • NONE类型的:无容器项目,单纯作为jar包的。

关于流式springboot项目,webflux,支持一个请求,然后每隔一段时间(比如每秒)向前端输出内容,但是也需要前端进行部分支持。

方式三:
下面的启动方式,以流式启动方式:

@SpringBootApplication
public class DemowebApplication {

    public static void main(String[] args) {

        new SpringApplicationBuilder()
                .profiles("prod")
                .web(WebApplicationType.SERVLET)
                .sources(DemowebApplication.class)
                .run(args);
    }

}

其中,source如果是类,可以直接设置在SpringApplication
SpringApplicationBuilder
类的构造方法中。但如果source如果是xml,那么只能用方式二的那一种了。

springboot启动内部

springboot的启动分为两部分:一部分是上面的注解,我们叫做注解部分,一部分是SpringApplication.run(…)方法,我们叫做run部分。

注解部分

springboot的启动,是从@SpringBootApplication
注解开始的。

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = {
        @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
        @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
  //...
}

这是一个标记类,就和@Service标明这是一个Service一样。同时,他也是触发开启自动配置和扫描的入口。
@SpringBootApplication
注解上,有@SpringBootConfiguration
@EnableAutoConfiguration
两个重要的注解。前者是标记springboot自动化配置的。后者是加载资源和类文件的。
关于前者的使用在org.springframework.boot.test.context.SpringBootConfigurationFinder
类的构造器中:

SpringBootConfigurationFinder() {
        this.scanner = new ClassPathScanningCandidateComponentProvider(false);
        this.scanner.addIncludeFilter(
                new AnnotationTypeFilter(SpringBootConfiguration.class));
        this.scanner.setResourcePattern("*.class");
    }

如果扫描这个类,那么配置文件会添加入class文件,然后会去找@Configuration的类了。
关于后者,代码如下:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
//...
}

其中@AutoConfigurationPackage这个注解,标明包含这个注解的包,都会被注册进去。所以,一般的项目启动文件XXXApplication.java在外面,而其他类会被再包裹一次package。


在其注解上有@Import(AutoConfigurationImportSelector.class)
注解,会加载Servlet、Bean、Resource、BeanFactory、Env环境。下面可以看到这些基本都是这个类的父接口。

public class AutoConfigurationImportSelector
        implements DeferredImportSelectorBeanClassLoaderAwareResourceLoaderAware,
        BeanFactoryAwareEnvironmentAwareOrdered 
{
//...
}

在其继承的接口中,而Ordered是用来排序的,等价于注解@Order,是绝对顺序。而相对顺序的有@Conditional系列注解的。关于@Conditional系列注解的使用,在别处一话。


DispatcherServlet的初始化

众所周知,在springmvc中,由dispatcherServlet来进行请求分发。而在springboot中,DispatcherServlet的注册,依赖于org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration
,在其构造方法中,初始化了DispatcherServlet:

@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;
        }

在下面的dispatcherServletRegistration方法中,予以注册:

        @Bean(name = DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME)
        @ConditionalOnBean(value = DispatcherServlet.class, name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
        public ServletRegistrationBean<DispatcherServlet> dispatcherServletRegistration(
                DispatcherServlet dispatcherServlet) {
            ServletRegistrationBean<DispatcherServlet> registration = new ServletRegistrationBean<>(
                    dispatcherServlet,
                    this.serverProperties.getServlet().getServletMapping());
            registration.setName(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME);
            registration.setLoadOnStartup(
                    this.webMvcProperties.getServlet().getLoadOnStartup());
            if (this.multipartConfig != null) {
                registration.setMultipartConfig(this.multipartConfig);
            }
            return registration;
        }


Web自动化配置

Web自动化配置在org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration
类中。这也是一个@Configuration类。该类中有众多重要的子类。


其内部有静态内部类org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration.WebMvcAutoConfigurationAdapter

    @Configuration
    
@Import(EnableWebMvcConfiguration.class)
    
@EnableConfigurationProperties({ WebMvcProperties.class, ResourceProperties.class })
    
@Order(0)
    
public static class WebMvcAutoConfigurationAdapterimplements WebMvcConfigurer {
//...
}

其中,父接口WebMvcConfigurer
是用来实现java-config的。


导入类org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration.EnableWebMvcConfiguration
类,相当于@EnableWebMvc
注解:

@Configuration(proxyBeanMethods = false)
    public static class EnableWebMvcConfiguration extends DelegatingWebMvcConfiguration implements ResourceLoaderAware {
  //...
}


run部分

    public ConfigurableApplicationContext run(String... args) {
        //StopWatch是一个计时器
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        ConfigurableApplicationContext context = null;
        Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
        //强制设置,没有窗口也可以启动
        configureHeadlessProperty();
        //设置日志,监听
        SpringApplicationRunListeners listeners = getRunListeners(args);
        listeners.starting();
        try {
            ApplicationArguments applicationArguments = new DefaultApplicationArguments(
                    args);
            ConfigurableEnvironment environment = prepareEnvironment(listeners,
                    applicationArguments);
            configureIgnoreBeanInfo(environment);
            //打印Spring字母的图画,如果定制,可以重写这个
            Banner printedBanner = printBanner(environment);
            //创建webApplicationContext
            context = createApplicationContext();
            //生成错误报告器类
            exceptionReporters = getSpringFactoriesInstances(
                    SpringBootExceptionReporter.class,
                    new Class[] { ConfigurableApplicationContext.class }, context);
            //预备上下文
            prepareContext(context, environment, listeners, applicationArguments,
                    printedBanner);
            //该方法最终执行的是org.springframework.context.support.AbstractApplicationContext#refresh方法,目的是配置可以加入后面的子类及其工厂,注册bean,国际化,以及事件监听器等信息
            refreshContext(context);
            //该方法留以扩展
            afterRefresh(context, applicationArguments);
            stopWatch.stop();
            if (this.logStartupInfo) {
                new StartupInfoLogger(this.mainApplicationClass)
                        .logStarted(getApplicationLog(), stopWatch);
            }
            listeners.started(context);
            //在项目启动后执行所有的Runner
            //如果有什么事情是需要在项目启动后要做的,就继承ApplicationRunner或者CommandLineRunner
            callRunners(context, applicationArguments);
        }
        catch (Throwable ex) {
            handleRunFailure(context, ex, exceptionReporters, listeners);
            throw new IllegalStateException(ex);
        }
        try {
            //监听context
            listeners.running(context);
        }
        catch (Throwable ex) {
            handleRunFailure(context, ex, exceptionReporters, null);
            throw new IllegalStateException(ex);
        }
        return context;
    }

org.springframework.boot.SpringApplication
中,会判断项目的类型,该方法会在SpringApplication构造方法中最后一层的构造方法SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources)
中进行调用:

    private WebApplicationType deduceWebApplicationType() {
        if (ClassUtils.isPresent(REACTIVE_WEB_ENVIRONMENT_CLASS, null)
                && !ClassUtils.isPresent(MVC_WEB_ENVIRONMENT_CLASS, null)) {
            return WebApplicationType.REACTIVE;
        }
        for (String className : WEB_ENVIRONMENT_CLASSES) {
            if (!ClassUtils.isPresent(className, null)) {
                return WebApplicationType.NONE;
            }
        }
        return WebApplicationType.SERVLET;
    }

预备上下文方法:

private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment,
            SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) 
{
        //设置环境参数
        context.setEnvironment(environment);
        //注册单例beanNameGenerator(beanNameGenerator是用来生成扫描Bean的名字到容器中)
        postProcessApplicationContext(context);
       //对于已经创建但为初始化的Initializer进行初始化
        applyInitializers(context);
       //所有监听器预备监听
        listeners.contextPrepared(context);
        if (this.logStartupInfo) {
           //调用log打印日志
            logStartupInfo(context.getParent() == null);
           // 输出当前的profile,默认为default
            logStartupProfileInfo(context);
        }
        // 注册springboot指定的单例bean
        ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
        beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
        if (printedBanner != null) {
            beanFactory.registerSingleton("springBootBanner", printedBanner);
        }
        if (beanFactory instanceof DefaultListableBeanFactory) {
            ((DefaultListableBeanFactory) beanFactory)
                    .setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
        }
        // Load the sources
        Set<Object> sources = getAllSources();
        Assert.notEmpty(sources, "Sources must not be empty");
        //设置bean加载器,加载bean
        load(context, sources.toArray(new Object[0]));
        //监听器监听上下文
        listeners.contextLoaded(context);
    }


补充:
在底层看的时候有很多自动装配类,对于自动装配的相对顺序与绝对顺序注解:

  • AutoConfigureOrder:绝对顺序(指定顺序的数值)

  • AutoConfigureAfter:相对顺序后(指定类是在哪些类之后)

  • AutoConfigureBefore:相对顺序前

可以一起共用,辅助看代码。




本文作于2019年9月,隐于简书。

今日示于公众号。

文章转载自1024行日记,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论