zoukankan      html  css  js  c++  java
  • SpringBoot入门(三)——入口类解析

    本文来自网易云社区


    上一篇介绍了起步依赖,这篇我们先来看下SpringBoot项目是如何启动的。

    入口类

    再次观察工程的Maven配置文件,可以看到工程的默认打包方式是jar格式的。

    <packaging>jar</packaging>

    SpringBoot默认的打包方式为jar,并且内嵌web容器。因此我们可以用运行jar包的方式启动一个web程序:

    java -jar xxx.jar

    linux服务器上可以用下面命令让服务常驻:

    nohup java -jar xxx.jar &

    我们知道jar包方式运行需要main方法,SpringBoot已为我们自动生成,这个类便是项目启动入口。

    我的项目名是blog-demo,对应生成的main方法在BlogDemoApplication.java,其代码如下:

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

    main方法中执行SpringApplication的静态方法run,并将当前类和启动参数传入。

    静态方法中实例化一个SpringApplication,并调用实例的run方法:

    public static ConfigurableApplicationContext run(Class<?>[] primarySources,
                String[] args) {        return new SpringApplication(primarySources).run(args);
        }

    先来看下调用的SpringApplication的构造方法:

    public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {    this.resourceLoader = resourceLoader;
        Assert.notNull(primarySources, "PrimarySources must not be null");    // 这里的primarySources就是我们传入的入口类
        this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));    // 推断应用类型
        this.webApplicationType = deduceWebApplicationType();    // 设置初始化器
        setInitializers((Collection) getSpringFactoriesInstances(
            ApplicationContextInitializer.class));    // 设置监听器
        setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));    // 很有意思的方法,通过异常栈获取应用入口类
        this.mainApplicationClass = deduceMainApplicationClass();
    }

    注意我们传入的启动类被保存到了primarySources变量中,将作为后续context加载beans时的资源,其他细节不再展开。

    接着看实例的run方法:

    /**
     * Run the Spring application, creating and refreshing a new
     * {@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<>();
        configureHeadlessProperty();    // 设置系统参数-无图形化界面
        // 获取监听器
        SpringApplicationRunListeners listeners = getRunListeners(args);
        listeners.starting();    try {
            ApplicationArguments applicationArguments = new DefaultApplicationArguments(
                args);
            ConfigurableEnvironment environment = prepareEnvironment(listeners,
                                                                     applicationArguments);
            configureIgnoreBeanInfo(environment);
            Banner printedBanner = printBanner(environment);        // 创建上下文
            context = createApplicationContext();
            exceptionReporters = getSpringFactoriesInstances(
                SpringBootExceptionReporter.class,            new Class[] { ConfigurableApplicationContext.class }, context);        // 上下文前置处理,这里会解析我们传入的入口类
            prepareContext(context, environment, listeners, applicationArguments,
                           printedBanner);        // 刷新上下文
            refreshContext(context);        // 后置处理
            afterRefresh(context, applicationArguments);
            stopWatch.stop();        if (this.logStartupInfo) {            new StartupInfoLogger(this.mainApplicationClass)
                    .logStarted(getApplicationLog(), stopWatch);
            }
            listeners.started(context);
            callRunners(context, applicationArguments);
        }    catch (Throwable ex) {
            handleRunFailure(context, listeners, exceptionReporters, ex);        throw new IllegalStateException(ex);
        }
        listeners.running(context);    return context;
    }

    通过方法注释也可以看出来该run方法引发了一系列复杂的内部调用和加载过程,从而创建了一个SpringContext。

    在prepareContext方法中会解析我们传入的入口类,解析其上的注解。下面来看下入口类上的注解。

    @SpringBootApplication

    入口类上的注解@SpringBootApplication是SpringBoot自动配置的关键。其定义如下:

    @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 {
        ...
    }

    说明它是@ComponentScan、@SpringBootConfiguration和@EnableAutoConfiguration三个注解的组合。

    @ComponentScan

    @ComponentScan是Spring框架原有的注解,在spring-context组件下,用来开启自动扫描Bean并解析注解注入。

    可以用basePackages指定扫描的包,缺省情况下默认扫描被注解类所在的包。SpringBoot项目中一般会将入口类放在顶层目录,这样默认就会扫描整个项目。

    @SpringBootConfiguration

    @SpringBootConfiguration是SpringBoot新增的注解,在spring-boot组件下,定义如下:

    @Target(ElementType.TYPE)@Retention(RetentionPolicy.RUNTIME)@Documented@Configurationpublic @interface SpringBootConfiguration {
    
    }

    相当于注解@Configuration,配备了该注解的类就能够以JavaConfig的方式完成一些配置,可以不再使用XML配置。

    所以在入口类内也可以以JavaConfig的方式定义Bean。

    @EnableAutoConfiguration

    @EnableAutoConfiguration是SpringBoot新增的注解,在spring-boot-autoconfigurate组件下,它是SpringBoot开启自动配置的关键。放到下一节再讲。

    小结

    这一节简单解析了SpringBoot的入口类,一个由SpringBoot自动生成的java类,虽然只有短短几行代码,却引发了Spring上下文的创建的一系列事件。

    首先SpringBoot将入口类传入作为资源的起点,当解析到入口类的时候发现其上的注解又开启了自动配置和包扫描,这样我们自定义的Bean就会被加载进去完成创建和依赖。




    相关阅读:SpringBoot入门(一)——开箱即用

    SpringBoot入门(二)——起步依赖

    SpringBoot入门(三)——入口类解析

    SpringBoot入门(四)——自动配置

    SpringBoot入门(五)——自定义配置

     

    网易云新用户大礼包:https://www.163yun.com/gift

    本文来自网易实践者社区,经作者金港生授权发布。


  • 相关阅读:
    [Codevs 1230]元素查找(手写哈希表)
    bat+sqlcmd 批量执行脚本
    为Redmine的项目加上起止时间
    SDUT 1068-Number Steps(数学:直线)
    对象间的联动--观察者模式
    《千与千寻》给读者带来了什么?
    二叉树中和为某一值的路径
    关于Win8 用不了USB转串口驱动
    Android Socket编程学习笔记
    java中的正则操作总结
  • 原文地址:https://www.cnblogs.com/163yun/p/9529132.html
Copyright © 2011-2022 走看看