2020:0704--14--SpringBoot启动配置原理和自定义启动器

387 阅读13分钟

主要内容

1.  启动原理
2.  运行流程    ****

    其中有几个重要的事件回调机制
    
    我们在研究运行流程的时候遇见这几个接口,要核心的关注一下。
    
        ApplicationContextInitializer.
            在容器刷新之前调用该类的 initialize 方法。
            并将 ConfigurableApplicationContext 类的实例传递给该方法
            
        SpringApplicationRunListener
        ApplicationRunner
        CommandLineRunner

3.  自动配置原理

4.  自定义启动器

1. 启动原理分析:源码版本 1.5.10

注意:由于2.3.1版本在这一块的源码和视频中差别较大,我将<parent/>的版本换成了1.5.10

1.  创建一个简单的SpringBoot项目:只导入了web引用模块

2.  在主方法入口的run()方法处打断点 Debug启动

    SpringApplication.run(Day0704Springboot09Application.class, args);
    
    传入主配置类(标注了@SpringBootApplication的类)和命令行参数

3.  进入run()方法

    其中这一段代码:return (new SpringApplication(sources)).run(args);
    先new SpringApplication(sources)
    再调用run()方法
    
4.  所以SpringBoot应用启动相当于分为两步:

注意

        1.  先创建SpringApplication对象
        2.  再运行run()方法

2 new SpringApplication(sources) :创建SpringApplication对象的过程

    分析一下创建SpringApplication对象做了什么
    
    1.  进入new SpringApplication(sources)

        进到构造方法中,详细看看做了什么
        
        给SpringApplication对象中的属性都赋上一些默认值

    2.  方法的最后一步调用了initialize()方法。
        
        点进去看一下:
        
        this.initialize(sources);
    
    3.  也就是initialize()方法创建了SpringApplication对象

        private void initialize(Object[] sources) {
            if (sources != null && sources.length > 0) {
                this.sources.addAll(Arrays.asList(sources));
            }
    
            this.webEnvironment = this.deduceWebEnvironment();
            this.setInitializers(this.getSpringFactoriesInstances(ApplicationContextInitializer.class));
            this.setListeners(this.getSpringFactoriesInstances(ApplicationListener.class));
            this.mainApplicationClass = this.deduceMainApplicationClass();
        }
        1.  首先判断sources是否为空:
        sources是主配置类:com.athuigu.springboot.Day0704Springboot09Application
        
        不为空就存到Object[] sources
        this.sources.addAll(Arrays.asList(sources))
        
        2.  判断当前应用是否是web应用:是(我导入了web模块)
        this.webEnvironment = this.deduceWebEnvironment()
        
        3.1 点进setInitializers(this.getSpringFactoriesInstances(ApplicationContextInitializer.class))
            方法
            
        setInitializers()方法是为initializers赋值

        3.2 我们分析一下
        this.setInitializers(this.getSpringFactoriesInstances(ApplicationContextInitializer.class));

getSpringFactoriesInstances(ApplicationContextInitializer.class)

        其中的参数:getSpringFactoriesInstances(ApplicationContextInitializer.class)
        意思是得到ApplicationContextInitializer,从哪找呢?
        点进去看:

        再点进去getSpringFactoriesInstances(type, new Class[0])

        再点进去loadFactoryNames(type, classLoader)

        3.3 setInitializers(this.getSpringFactoriesInstances(ApplicationContextInitializer.class))
        
        从类路径下的找到META-INF/spring.factories里面配置的所有ApplicationContextInitializer
        然后保存起来
        
        3.4 在相应目录下找到了ApplicationContextInitializer

        3.5 看一下Initializers现在保存的值

        4.  initialize(Object[] sources)方法的第四步
        
        this.setListeners(this.getSpringFactoriesInstances(ApplicationListener.class));
            
        这就很明显了,也是从类路径下的找到META-INF/spring.factories 里面配置的所有
        ApplicationListener。

        4.1 这就是Listeners找到后保存起来的值

        5.  initialize(Object[] sources)方法的第五步
        
        his.mainApplicationClass = this.deduceMainApplicationClass();
        
        决定哪一个ApplicationClass是一个主程序
        
        5.1 点进去

        来判断我们传进来的配置类那个里面有main方法,那个就是主程序。

注意:SpringApplication.run(Day0704Springboot09Application.class, args);

        run()方法的参数一,有另一种重载形式:

        参数一:配置类(@SpringBootApplication)可以传多个。
        
        所以在5.1   才会判断哪一个ApplicationClass个主程序
        
    
        
    4.  initialize(Object[] sources)
    
        执行完了,就得到一个SpringApplication对象。
        即Spring应用对象创建完了。

3 运行SpringBoot --- 分析 run(args)

    点进run(args)方法
    public ConfigurableApplicationContext run(String... args) {
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        ConfigurableApplicationContext context = null;
        FailureAnalyzers analyzers = null;
        this.configureHeadlessProperty();
        SpringApplicationRunListeners listeners = this.getRunListeners(args);
        listeners.starting();

        try {
            ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
            ConfigurableEnvironment environment = this.prepareEnvironment(listeners, applicationArguments);
            Banner printedBanner = this.printBanner(environment);
            context = this.createApplicationContext();
            new FailureAnalyzers(context);
            this.prepareContext(context, environment, listeners, applicationArguments, printedBanner);
            this.refreshContext(context);
            this.afterRefresh(context, applicationArguments);
            listeners.finished(context, (Throwable)null);
            stopWatch.stop();
            if (this.logStartupInfo) {
                (new StartupInfoLogger(this.mainApplicationClass)).logStarted(this.getApplicationLog(), stopWatch);
            }

            return context;
        } catch (Throwable var9) {
            this.handleRunFailure(context, listeners, (FailureAnalyzers)analyzers, var9);
            throw new IllegalStateException(var9);
        }
    }
    这里面就包含了SpringBoot项目:SpringApplcation对象运行的整个流程。
    
    我们来看一下关键的步骤
    
    1.  开始启动监听
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
    2.  声明一个IOC容器
        ConfigurableApplicationContext context = null;
    3.  FailureAnalyzers analyzers = null
    
        声明一个FailureAnalyzers
        
    4.  this.configureHeadlessProperty();
    
        点进来,是和做awt应用有关的
    private void configureHeadlessProperty() {
        System.setProperty("java.awt.headless", System.getProperty("java.awt.headless", Boolean.toString(this.headless)));
    }
    5.  关键步骤:SpringApplicationRunListeners listeners = this.getRunListeners(args);
    
        获取SpringApplicationRunListeners
        
    5.1 从哪获取呢,我们点进去看一下
        private SpringApplicationRunListeners getRunListeners(String[] args) {
            Class<?>[] types = new Class[]{SpringApplication.class, String[].class};
            return new SpringApplicationRunListeners(logger, 
                this.getSpringFactoriesInstances(SpringApplicationRunListener.class, 
                types, this, args));
        }
        发现了熟悉的getSpringFactoriesInstances:
        
        也是类路径下的找到META-INF/spring.factories里面配置的所有SpringApplicationRunListener
        
    6.  listeners.starting():
        
        回调所有的SpringApplicationRunListener的start方法
        
        点进去发现它将每个SpringApplicationRunListener的start方法都调用了一遍

    6.1  我们进去SpringApplicationRunListener类中看一下:

        其中相当于有5个回调机制
        
        我们现在先是来回调所有的starting()方法
        
    7.  将args用ApplicationArguments封装一下
        
        封装命令行参数
        ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
    8.  ConfigurableEnvironment environment = 
        this.prepareEnvironment(listeners, applicationArguments);   
        
        准备环境
        
    8.1 点进来看一下
    private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners, 
        ApplicationArguments applicationArguments) {
    
        //创建环境
        ConfigurableEnvironment environment = this.getOrCreateEnvironment();
        //配置环境
        this.configureEnvironment((ConfigurableEnvironment)environment, 
            applicationArguments.getSourceArgs());
        
        //回调了SpringApplicationRunListeners的environmentPrepared()方法
        listeners.environmentPrepared((ConfigurableEnvironment)environment);
        
        //判断是如果是web环境还能转成web环境
        if (!this.webEnvironment) {
            environment = (new EnvironmentConverter(this.getClassLoader())).convertToStandardEnvironmentIfNecessary((ConfigurableEnvironment)environment);
        }

        return (ConfigurableEnvironment)environment;
    }
    8.2 准备好环境后,回调SpringApplicationRunListeners的environmentPrepared()方法
    
        意思是:环境准备完成
        
    8.3 判断是如果是web环境还能转成web环境,返回环境。
        
        环境就创建好了。
        
    9.  Banner printedBanner = this.printBanner(environment);
        
        打印Banner图标

    10. 关键的一步:context = this.createApplicationContext();
    
        创建IOC容器
        
    10.1判断是创建一个webIOC容器还是普通的IOC容器
    protected ConfigurableApplicationContext createApplicationContext() {
        Class<?> contextClass = this.applicationContextClass;
        if (contextClass == null) {
            try {
                contextClass = Class.forName(this.webEnvironment ?
                "org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext"
                : "org.springframework.context.annotation.AnnotationConfigApplicationContext");
            } catch (ClassNotFoundException var3) {
                throw new IllegalStateException("Unable create a default ApplicationContext, please specify an ApplicationContextClass", var3);
            }
        }

        return (ConfigurableApplicationContext)BeanUtils.instantiate(contextClass);
    }
    10.2由于当前项目是web项目,得到一个webIOC容器

    10.3return (ConfigurableApplicationContext)BeanUtils.instantiate(contextClass)
    
    判断之后由反射得到一个IOC容器
    
    11  new FailureAnalyzers(context);
        
        做异常分析报告的
        
    
    12. this.prepareContext(context, environment, listeners, applicationArguments, 
        printedBanner);
        
        准备上下文
        
        传入IOC容器,准备好的环境,listener,封装的args参数
        
        点进去prepareContext()方法
    private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment, SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) {
    
        //将environment保存到IOC中
        context.setEnvironment(environment);
        this.postProcessApplicationContext(context);
        
        //关键点
        this.applyInitializers(context);
        listeners.contextPrepared(context);
        if (this.logStartupInfo) {
            this.logStartupInfo(context.getParent() == null);
            this.logStartupProfileInfo(context);
        }

        context.getBeanFactory().registerSingleton("springApplicationArguments", applicationArguments);
        if (printedBanner != null) {
            context.getBeanFactory().registerSingleton("springBootBanner", printedBanner);
        }

        Set<Object> sources = this.getSources();
        Assert.notEmpty(sources, "Sources must not be empty");
        this.load(context, sources.toArray(new Object[sources.size()]));
        listeners.contextLoaded(context);
    }

    12.1关键点
    
        this.applyInitializers(context);

    12.1.1  点进去发现
    
            获取了所有的getInitializers()
            
            并调用了initialize()方法,initializer.initialize(context)

这些initializer来自哪里呢?

    就是创建SpringApplication时,从类路径下的找到META-INF/spring.factories里面配置的所有
    ApplicationContextInitializer然后保存起来。
    
    setInitializers(this.getSpringFactoriesInstances(ApplicationContextInitializer.class))
    
            所以在这一步回调之前保存的所有的ApplicationContextInitializer的initialize(context)
            方法
            
            也就是说在IOC容器将环境准备设置好以后,他就来调用这些初始化器ApplicationContextInitializer
            的initialize(context)方法
            
            
    12.2listeners.contextPrepared(context);
    
    12.2.1  点进去发现

            获取了所有的SpringApplicationRunListener(在第5步得到保存的)
            
            并且回调了contextPrepared(context)方法
            
            所有的监听器回调完。
            
    12.3 来到日志相关
        if (this.logStartupInfo) {
            this.logStartupInfo(context.getParent() == null);
            this.logStartupProfileInfo(context);
        }
    12.4 向IOC容器中注册命令行参数args和打印的banner printedBanner    
        context.getBeanFactory().registerSingleton("springApplicationArguments", applicationArguments);
        if (printedBanner != null) {
            context.getBeanFactory().registerSingleton("springBootBanner", printedBanner);
        }
    12.5 Set<Object> sources = this.getSources();
        
        拿到我们的主类。
        
        即@@SpringBootApplication标注的类
        
    12.6 一些非关键步骤
        Assert.notEmpty(sources, "Sources must not be empty");
        this.load(context, sources.toArray(new Object[sources.size()]));
    12.7 listeners.contextLoaded(context);
    
        当prepareContext()方法走到最后一步时,所有的SpringApplicationRunListeners
        回调contextLoaded(context)方法
        
    12.8 prepareContext()执行完了控制台把相关的环境都准备好了        

    13. prepareContext()执行完了来到refreshContext(context)
    
        刷新容器:IOC容器初始化,加载其中所有的组件。
        
        如果是web程序会加载一个嵌入式的tomcat组件
        
    14. this.afterRefresh(context, applicationArguments);
    
    14.1点进afterRefresh()看看

        从IOC容器中获取所有的ApplicationRunner和CommandLineRunner
        再进行回调
        
        先回调ApplicationRunner
        再回调CommandLineRunner
        
        
    14.2 afterRefresh()相当于回调这两个runner
    
    15. listeners.finished(context, (Throwable)null);
    
        所有的SpringApplicationRunListener回调finished()方法

到这一步整个SpringBoot应用就启动完了。

    16. stopWatch.stop();
        保存当前应用状态,应用已经启动完成了
        
    17. 返回IOC容器
    
        return context;

4. 小结

    1.  这几个接口:事件监听机制
    
        1. 要在META-INF/spring.factories文件中进行配置
        ApplicationContextInitializer.
        SpringApplicationRunListener
        2.  只需要放在IOC容器中即可。
        ApplicationRunner
        CommandLineRunner
public ConfigurableApplicationContext run(String... args) {
   StopWatch stopWatch = new StopWatch();
   stopWatch.start();
   ConfigurableApplicationContext context = null;
   FailureAnalyzers analyzers = null;
   configureHeadlessProperty();
    
   //获取SpringApplicationRunListeners;从类路径下META-INF/spring.factories
   SpringApplicationRunListeners listeners = getRunListeners(args);
   
   ---------------------------------------------------------------------------------------
    //回调所有的获取SpringApplicationRunListener.starting()方法
   listeners.starting();
   
   try {
       //封装命令行参数
      ApplicationArguments applicationArguments = new DefaultApplicationArguments(
            args);
      //准备环境
      ConfigurableEnvironment environment = prepareEnvironment(listeners,
            applicationArguments);
        ---------------------------------------------------------------------------------------
       	//创建环境完成后回调SpringApplicationRunListener.environmentPrepared();表示环境准备完成
       
      Banner printedBanner = printBanner(environment);
       
       //创建ApplicationContext;决定创建web的ioc还是普通的ioc
      context = createApplicationContext();
       
      analyzers = new FailureAnalyzers(context);
       //准备上下文环境;将environment保存到ioc中;而且applyInitializers();
       //applyInitializers():回调之前保存的所有的ApplicationContextInitializer的initialize方法
       
       ---------------------------------------------------------------------------------------
       //回调所有的SpringApplicationRunListener的contextPrepared();
       
      prepareContext(context, environment, listeners, applicationArguments,
            printedBanner);
       //prepareContext运行完成以后回调所有的SpringApplicationRunListener的contextLoaded();
       
       //s刷新容器;ioc容器初始化(如果是web应用还会创建嵌入式的Tomcat);Spring注解版
       //扫描,创建,加载所有组件的地方;(配置类,组件,自动配置)
      refreshContext(context);
      
      ---------------------------------------------------------------------------------------
       //从ioc容器中获取所有的ApplicationRunner和CommandLineRunner进行回调
       //ApplicationRunner先回调,CommandLineRunner再回调
      afterRefresh(context, applicationArguments);
      
      ---------------------------------------------------------------------------------------
       //所有的SpringApplicationRunListener回调finished方法
      listeners.finished(context, null);
      stopWatch.stop();
      if (this.logStartupInfo) {
         new StartupInfoLogger(this.mainApplicationClass)
               .logStarted(getApplicationLog(), stopWatch);
      }
       //整个SpringBoot应用启动完成以后返回启动的ioc容器;
      return context;
   }
   catch (Throwable ex) {
      handleRunFailure(context, listeners, analyzers, ex);
      throw new IllegalStateException(ex);
   }
}
    2.  注意这些事件的回调
        
        这些事件都是接口。
        想要干预运行SpringBoot --- run(args)的哪个阶段,就用实现相应的监听回调器就行了。
        
        
    3.  注意IOC容器的刷新refreshContext(context);
    
        扫描,创建,加载所有组件的地方;
        
        我们自己写的配置类,组件,自动配置就是在这里加载的。

5. 事件监听机制

    1.  这几个接口:事件监听机制
    
        1.  要在META-INF/spring.factories文件中进行配置
        ApplicationContextInitializer.
        SpringApplicationRunListener
        
        
        2.  只需要放在IOC容器中即可。
        ApplicationRunner
        CommandLineRunner
        
    2.  我的疑问:
    
        当我自己写一个HelloApplicationContextInitializer实现了ApplicationContextInitializer
        后。我只在里面写了一些及其简单的输出语句。
        
        那么当回调相应的监听事件方法时,不就只会执行我得实现类中的方法了吗?
        
        这点输出语句根本无法满足启动一个SpringBoot程序的!
        
    3.  解惑:
        
        因为ApplicationContextInitializer,SpringApplicationRunListener要在
        META-INF/spring.factories文件中进行配置,这个文件在类路径下。
        
        那么可以有这几种情况:
            jar包中的

            当前项目resource下的

            而且每个spring.factories都可以配置多个相关的监听实现类

        而这些配置在类路径下的监听器,都会被加载访问。

5.1 事件监听机制相关测试

    1.  ApplicationContextInitializer
        
        在容器刷新之前调用该类的 initialize 方法。
        并将 ConfigurableApplicationContext 类的实例传递给该方法
        
        注意泛型:<ConfigurableApplicationContext>监听IOC容器
        //要在META-INF/spring.factories文件中进行配置
        public class HelloApplicationContextInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
        
            @Override
            public void initialize(ConfigurableApplicationContext applicationContext) {
                System.out.println("ApplicationContextInitializer...运行了...");
        
            }
        }
    2.  SpringApplicationRunListener
    //要在META-INF/spring.factories文件中进行配置
    public class HelloSpringApplicationRunListener implements SpringApplicationRunListener {
    
        //必须要写一个有参构造
        public HelloSpringApplicationRunListener(SpringApplication application, String[] args) {
        }
    
        @Override
        public void starting() {
            System.out.println("IOC还没初始化,获取到了所有的SpringApplicationRunListener,...开始starting");
        }
    
        @Override
        public void environmentPrepared(ConfigurableEnvironment environment) {
            System.out.println("SpringApplicationRunListener...容器环境准备好了"+environment.getSystemProperties().get("os.name"));
        }
    
        @Override
        public void contextPrepared(ConfigurableApplicationContext configurableApplicationContext) {
            System.out.println("SpringApplicationRunListener...IOC容器准备好了");
        }
    
        @Override
        public void contextLoaded(ConfigurableApplicationContext configurableApplicationContext) {
            System.out.println("容器环境加载完成....");
        }
    
        @Override
        public void finished(ConfigurableApplicationContext configurableApplicationContext, Throwable throwable) {
            System.out.println("SpringBoot应用完整启动,之后要返回IOC容器了...");
        }
    }

    3.  ApplicationRunner
        @Component  //ApplicationRunner这个事件监听机制需要放进容器中
        public class HelloApplicationRunner implements ApplicationRunner {
            @Override
            public void run(ApplicationArguments args) throws Exception {
                System.out.println("ApplicationArguments...run...");
            }
        }
    4.  CommandLineRunner
        @Component  //CommandLineRunner这个事件监听机制需要放进容器中
        public class HelloCommandLineRunner implements CommandLineRunner {
            @Override
            public void run(String... strings) throws Exception {
                System.out.println("CommandLineRunner....run...."+ Arrays.asList());
            }
        }

    5.  ApplicationContextInitializer,SpringApplicationRunListener要在
        META-INF/spring.factories文件中进行配置
        org.springframework.context.ApplicationContextInitializer=\
        com.athuigu.springboot.listener.HelloApplicationContextInitializer
        
        org.springframework.boot.SpringApplicationRunListener=\
        com.athuigu.springboot.listener.HelloSpringApplicationRunListener
    6.  测试

6. SpringBoot自定义starters

6.1 概述

    SpringBoot最强大的一个特点,它将许多的场景都抽取成了一个个的starters(场景启动器)。
    
    我们通过引入SpringBoot为我们提供的这些众多的场景启动器,就能使用到相应的功能。
    
    而且引入了这些场景启动器后,SpringBoot也为我们自动配置好了相应的的配置。我们只需要
    在进行少量的定制配置即可满足我们的需求。
    
    当然SpringBoot也无法考虑到所有的场景和情况,所以这时就需要我们自定义一个场景启动器。
    来简化我们对SpringBoot的使用。
    
    当我们写好一个场景的starters时,别的开发人员可以直接引用我们这个starters。
    
    如何自定义starters?我们需要明确以下:
    
        1.  这个场景需要用到的依赖是哪些?
        2.  有了这些依赖后,我们需要自动配置些什么?
            编写自动配置。

6.2 自动配置类的编写规则

@Configuration  //指定这个类是一个配置类
@ConditionalOnXXX  //在指定条件成立的情况下自动配置类生效
@AutoConfigureAfter  //指定自动配置类的顺序
@Bean  //给容器中添加组件

@ConfigurationPropertie结合相关xxxProperties类来绑定相关的配置
@EnableConfigurationProperties //让xxxProperties生效加入到容器中

自动配置类要能加载
将需要启动就加载的自动配置类,配置在META-INF/spring.factories
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\

6.3 看一些SpringBoot是怎么编写starters的

    1.  启动器(starter)模块

        启动器模块是一个空JAR 文件,仅提供辅助性依赖管理,再专门写一个自动配置模块。
        启动器依赖自动配置。
        
        xxxx-starter
        
    2.  自动配置模块
        
        别人只需要引入启动器,就相当于也引入了自动配置模块
        xxxx-starter-autoconfigurer
        
    3.  命名推荐:后缀
        
        –模式:自定义启动器名-spring-boot-starter
        –举例:mybatis-spring-boot-starter

6.4. 写一个自定义启动器

    1.  创建一个空的工程
        
        spring-boot-10-starter
    
    2.  创建好后要添加module  --- 启动器(做依赖引入)
        atguigu-spring-boot-starter

        Maven module

    3.  再创建一个module  --- 自动配置相关
        atguigu-spring-boot-starter-autoconfigurer

        不引入模块

    4.  在启动器pom中引入自定义的自动配置模块
        <dependencies>
            <dependency>
                <!--引入自定义的自动配置模块-->
                <groupId>com.atguigu.starter</groupId>
                <artifactId>atguigu-springboot-starter-autoconfigurer</artifactId>
                <version>0.0.1-SNAPSHOT</version>
            </dependency>
        </dependencies>
    5.  删掉 自动配置类中的这些
    
        appication.properties
        主配置类
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
			<exclusions>
				<exclusion>
					<groupId>org.junit.vintage</groupId>
					<artifactId>junit-vintage-engine</artifactId>
				</exclusion>
			</exclusions>
		</dependency>
		
	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>

   6.   只引入一个spring-boot-starter

    7.  删掉test

6.5 编写自定义的自动配置类

    1.  HelloService
    
        用到了HelloProperties中的一些属性
        public class HelloService {
        
            @Autowired
            HelloProperties helloProperties;
        
            public HelloProperties getHelloProperties() {
                return helloProperties;
            }
        
            public void setHelloProperties(HelloProperties helloProperties) {
                this.helloProperties = helloProperties;
            }
        
            public String sayHelloAtguigu(String name){
                return helloProperties.getPrefix()+"-"+name+helloProperties.getSuffix();
            }
        }
   2.   HelloProperties
        /**
         * 把所有能配置的属性都绑在这里。
         */
        @ConfigurationProperties(prefix = "atguigu.hello")
        public class HelloProperties {
        
            private String prefix;
            private String suffix;
        
            public String getPrefix() {
                return prefix;
            }
        
            public void setPrefix(String prefix) {
                this.prefix = prefix;
            }
        
            public String getSuffix() {
                return suffix;
            }
        
            public void setSuffix(String suffix) {
                this.suffix = suffix;
            }
        }
    3.  自动配置类 HelloServiceAutoConfiguration

注意我这里加了个条件:@ConditionalOnWebApplication //Web应用才生效

        @Configuration
        @ConditionalOnWebApplication  //Web应用才生效
        @EnableConfigurationProperties(HelloProperties.class) //属性文件生效,可以直接在这个类中使用
        public class HelloServiceAutoConfiguration {
        
            //自动配置类能生效:要在spring.factories中配置
        
            /**
             * 这个自动配置向IOC中添加了HelloService组件
             * HelloService用到的所有属性和HelloProperties绑定的。
             */
            @Autowired
            HelloProperties helloProperties;
        
            @Bean //向容器中加了一个HelloService,别人想用就能用了
            public HelloService helloService(){
                HelloService service = new HelloService();
        
                service.setHelloProperties(helloProperties);
        
                return service;
            }
        }
    4.  自动配置类能生效:要在spring.factories中配置

        org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
        com.atguigu.springboot.HelloServiceAutoConfiguration

    5.  atguigu-spring-boot-starter依赖了atguigu-springboot-starter-autoconfigurer
    
        所以安装atguigu-springboot-starter-autoconfigurer到仓库

        启动器atguigu-spring-boot-starter安装到仓库中

    6.  创建一个新的项目测试
        
        引入自定义的stater坐标
        <groupId>org.atguigu</groupId>
        <artifactId>atguigu-spring-boot-starter</artifactId>
        <version>1.0-SNAPSHOT</version>