SpringBoot的启动原理

    从Spring到SpringBoot,我们可以感受到,框架越来越智能,集成度越来越高,让我们使用越来越简单,解放程序员,更加关注业务本身。用的越简单,后面做的工作就越多。说到底,SpringBoot最终还是对Spring框架的封装,主要任务 还是在做(IOC)控制反转,(DI)依赖出入和(DL)依赖查找,实现自动化装配 ,一切交给spring托管,更好的完成各功能的集成。设计思想类似于Mybaties,Mybaties其实是封装了ibaties,而ibaties封装了JDBC。

1、SpringBoot的出现

Spring配置 发展阶段一:在没有 SpringBoot之前,我们接触最多的就是spring和springMVC,Spring的出现最重要的就是帮我们如何自动管理Bean,例如IOC(控制反转)和 DI (依赖注入),一切都是基于Bean实现的,而这些框架在使用的过程中会需要配置大量繁琐的xml配置, 通过ApplicationContest.xml来配置我们的Bean类即可托管给Spring框架。
<bean id="person" class="com.beijing.ConfigurationTest.bean.Person" init-method="init" destroy-method="destory">
    <constructor-arg index="0" value="name"/>
    <constructor-arg index="1" value="19"/>
    <property name="name" value="king"></property>
    <property name="age" value="18"></property>
</bean>

Spring配置发展阶段二:但是随着Bean实例的加,之后为了减少程序员繁杂的配置工作,spring3开始,java除了支持xml形式外, 还支持了 Javaconfig,注解方式;

Spring配置 发展阶段三:之后为了更加简化我们的开发流程,既然一切基础功能我们都实现了,那是不是可以有一种更好的规范和规则来快速的搭建我们的项目呢?类似于很多项目脚手架一样,避免每次都做一些繁琐且单一的xml文件配置。
    这就有了SpringBoot框架的出现,目标是帮助使用Spring框架的使用者快速高效的搭建一个基于spring生态体系的应用解决方案,它是对 “约定优于配置”这个理念下的一个最佳实践。 基于“约定大于配置”的原则,按需自动加载常用的组件,根据历史经验来预先设置好参数的值,如果有特殊需求也可以自定义设置修改,最大程度的简化了繁杂的xml配置文件,一键式启动服务,得到了开箱即用的便捷。常见的“约定优于配置”功能如下:
  • 1、maven的默认目录结构:
    • 默认会在resource文件夹下存放配置文件;
    • 默认会以jar的方式打包;
  • 2、默认使用classpath下的application.properties/ yml为 项目配置文件;
  • 3、EnableAutoConfiguration 默认对于依赖的starter进行自动装载;
  • 4、默认使用日志slf4j + logback的组合框架等等;
    例如spring-boot-starter-web默认包含SpringMVC相关的依赖以及内置tomcat容器,使得构建一个web应用更加简单。所以了解了这些, 发现SpringBoot并没有什么特殊的存在,底层还是Spring,只是对旧的东西做了一层封装。下面就简单学习一下SpringBoot的特点和原理。
为了更好的理解SpringBoot的工作原理,这里一切从@ SpringBootApplication注解开始,这个是进入SpringBoot世界的大门,看看它里面到底做了什么?

2、SpringBoot的启动

SpringBoot 启动类
@SpringBootApplication //标注这个类是一个springboot的一个应用
public class HelloWorldApplication {

    public static void main(String[] args) {//一键启动的便捷
        SpringApplication.run(HelloWorldApplication.class, args);
    }
}

SpringBoot的启动也十分简单,主要有三种

  • 1 、直接运行 main 方法,因为springboot已经帮我们内置了一个tomcat,并且回去加载classpath下的配置文件;
  • 2 通过spring-boot-plugin方式: mvn spring-boot:run -Drun.arguments="--server.port=8081"
  • 3 通过java -jar的方式:先mvn install,之后通过 java -jar emample.jar --server.port=8082

3、启动原理-@SpringBootApplication

进入 注解:@SpringBootApplication,打可以看到它其实是一个 复合注解,主要由以下几个注解组成:
  • @ ComponentScan (ImportSelector ) ;
  • @Enable Aut oConfiguration (ImportSelector + ImportBeanDefinationRegistrar);
  • @ Configuration  (Component);
@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) })
@ConfigurationPropertiesScan
public @interface SpringBootApplication {

   @AliasFor(annotation = EnableAutoConfiguration.class)
   Class<?>[] exclude() default {};  //动态排除一些类

   @AliasFor(annotation = EnableAutoConfiguration.class)
   String[] excludeName() default {};

   @AliasFor(annotation = ComponentScan.class, attribute = "basePackages")
   String[] scanBasePackages() default {};

   @AliasFor(annotation = Configuration.class)
   boolean proxyBeanMethods() default true;

}

发现@SpringBootApplication这个复合注解主要是由4个注解组成,继续往下:

@SpringBootConfiguration注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration(proxyBeanMethods = false) //配置类
public @interface SpringBootConfiguration {

   @AliasFor(annotation = Configuration.class)
   boolean proxyBeanMethods() default true;
}

@EnableAutoConfiguration注解

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class) //给容器注册一些Bean组件
public @interface EnableAutoConfiguration {

   String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";

   Class<?>[] exclude() default {}; //动态的体现:排除那些组件,不要导入

   String[] excludeName() default {};

}

其注解引用关系图:

不难发现@SpringBootApplication上层主要由三个注解组成:
  • @ComponentScan
  • @EnableAutoConfiguration
  • @Configuration
    其实我们只使用这三个注解,不需要@SpringBootApplication注解也可以启动项目,只是应用三个注解相对比较繁琐,所以直接使用一个复合注解解决了所有的烦恼。或者只要喜欢干脆给@SpringBootApplication改个名字@NewSpring也是OK的。
底层主要也是三个基础注解组成:
  • @ImportSelector
  • @ImportBeanDefinitonRegistrar
  • @Component
     从底层的这几个注解的作用就可以看出注解的功能是给容器添加Bean组件,这还是Spring底层的几个注解,验证了我们前面所说,SpringBoot是对Spring的封装,本质上也是在帮我们做IOC和DI。下面看一下每个注解都干了什么工作。

3.1、@Configuration - 配置类

    在spring3之前的Bean配置都是 基于applicationContext. xml 文件方式,它是spirng3.0 spring 就支持了两种 bean 的配置方式,一种是基于applicationContext. xml 文件方式,另一种就是 JavaConfig 形式, 任何一个标注了 @Configuration注解 java 类都是一个 JavaConfig 配置类,这种 无配置化的设计减少了xml文件的配置。因为SpringBoot本质上就是一个Spring应用,通过这个注解来加载IOC容器的配置是很正常的,所以在启动类上标注了@ Configuration,意味着它其实也是一个 IOC 容器的配置类
    而在这个配置类中,任何一个标注了@Bean的方法,它的返回值都会作为Bean定义注册到spring的ioc容器map中,方法名默认为这个bean的id;
实例演示:
@Configuration //表示这是一个配置类,将会把Person注入到容器中,取代之前的xml配置
public class MyConfig {

    @Bean //托管到spring容器中,beanid=person,每次用的时候通过DL返回一个单例实例给用户;
    public Person person() {
        System.out.println("给容器中添加了person组件");
        return new Person();
    }
}

3.2、@ComponentScan

作用:
  • ComponentScan默认会扫描当前package下的所有加了相关注解标识的类到IOC容器中( @Component  @Repository  @Service  @Controller), 把bean交给 Spring 容器托管;
  • 如果需要扫描其他的子包,配置路径即可;
@ComponentScan(value = "com.king.Config", includeFilters = {
        @ComponentScan.Filter(type = FilterType.CUSTOM, classes = {KingServiceFilter.class})},
        useDefaultFilters = false) //默认为true
public class Cap2MainConfig {

    public Person person02() {
        return new Person("king", 19);
    }
}

ComponentScan这个注解是大家接触的最多的了,相当于xml配置文件中<context:component-scan>。它的主要作用就是扫描指定路径下的标示了需要装配的类,自动装配到spring的ioc容器中。

<context:component-scan base-package="com.*"></context:component-scan>
 

3.3、@EnableAutoConfiguration 

作用:帮助 springboot 应用把所有符合条件的 @Configuration 配置都自动加载注册到 容器map当中 .
其实Spring3.1开始,java也支持了@Enablexx,例如@EnableWebMvc @EnableCache等以注解的形式来配置我们的Bean,省去了繁杂的xml文件配置,它是 JavaConfig框架上更进一步的完善,使得用户在使用spring相关框架时,避免配置大量的代码从而降低使用难度,其实EnableAutoConfiguration并不是一个全新的东西, 只是它的出现对 springboot 来说意义重大;
比如以前常见的Enable注解:
  • @ EnableWebMvc(这个注解引入了MVC框架在spring应用中所需要的所有的bean)
  • @ EnableScheduling, 开启计划任务的支持:
  • @ EnableAspectJAutoProcy 开启aop功能
  • @ EnableTransactionalManagement 事务
  • @ EnableAsync 开启异步注解
设计思想:通过查看源码就会发现这里的 @ EnableAutoConfiguration设计思想有一个套路,大抵相似。就是 每个涉及到@ Enablexxx 开头的注解,都会有一个 @Import 的注解,另外 注册几个相关的Bean前置或者后置处理器BeanPostProcessor, 在tomcat启动的时候,JVM会扫描jar包class文件,将相关加了注解的Bean 根据条件或者 BeanPostProcessor后置处理器动态代理增强后, 自动添加到map容器中,完成IOC功能。
下面看一下它的具体实现:
@EnableAutoConfiguration注解的实现
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import({ EnableAutoConfigurationImportSelector.class, //向容器中导入组件EnableAutoConfigurationImportSelector
            AutoConfigurationPackages.Registrar.class })//向容器中导入组件AutoConfigurationPackages.Registrar
public @interface EnableAutoConfiguration {

   /**
    * Exclude specific auto-configuration classes such that they will never be applied.
    */
   Class<?>[] exclude() default {};

}

3.3.1、@Import注解

    为了更好的了解@EnableAutoConfiguration注解,这里就需要了解 Import + (ImportSelector/ ImportBeanDefinitionRegistor)的组合功能。
作用:通过条件ConditionBean,自动注入相关的组件bean,帮助springboot应用把所有符合条件的@Configuration配置类都加载到spring容器中。
联想到xml下有一个 <import resource/>形式的注解,就明白它的作用了。
<import resource="classpath:application-beans.xml"/>
<import resource="classpath:application-common.xml"/>
<import resource="classpath:application-db.xml" />
<import resource="classpath:application-aop.xml" />

Import就是把多个分开的容器配置合并在一个配置中。在 javaConf中所表达的意义是一样的,可以导入不同子包的Bean内容;

Import使用示例:
 
    @Configuration //Import导入其他包相关的Bean
    @Import({KingImportSelector.class, KingImportBeanDefinitionRegistrar.class, KingFactoryBean.class})
    public class KingMainConfig {
        @Bean("zhouzhou")
        public Person person() {
            return new Person("zhouzhou", 28);
        }
    }

下面来看@EnableAutoConfiguration的两个重要的类

  • EnableAutoConfigurationImportSelector
  • AutoConfigurationPackage.Registrar

3.3.2、Import+ ImportSelector

点开 EnableAutoConfigurationImportSelector源码发现其中主要使用了 Import+ ImportSelector来给容器添加一些组件。
思考:普通bean的注入很简单,但是缺少灵活性控制,能不能 根据条件或者上下文来控制bean的加载呢?或者是批量的加载呢?
这里就用到了 ImportSelector和ImportBeanDefinitionRegistrar,让@Enablexxx注解不仅仅可以像前面演示的案例一样很简单的实现多个Configuration的整合,还可以实现一些复杂的场景,比如可以 根据上下文条件来激活不同的 Bean动态注入。实现示例如下:
  • 1、实现 ImportSelector接口根据条件选择进行批量bean的 动态加载,SelectImports返回的数组(类的全类名)都会被加载到spring容器中;;
  • 2、实现 ImportBeanDefinitionRegistrar接口进行 动态选择注入bean
实例一:使用Import+ ImportSelector给容器注册Person和King实例:   
 @Service //通过实现ImportSelector自定义逻辑需要注册的组件名称Person和King
    public class KingImportSelector implements ImportSelector {

        //返回值就是导入到容器的全类名
        //annotationMetadata 获取标注了Import及其他@Service注解的所有类的元信息
        public String[] selectImports(AnnotationMetadata annotationMetadata) {
            //这里可进行业务逻辑,选择性的动态加载Bean实例;
            System.out.println(" 使用ImportSelector  selectImoports 来注册组件。。");
            //注意:如果这里的类写成可配置文件,这不就是AutoConfiguration的原理吗?
            return new String[]{"com.beijing.entity..Person", "com.beijing.entity.King"};
        }
    }

3.3.3Import+ ImportBeanDefinitionRegitrar

实现 ImportBeanDefinitionRegistrar的registerBeanDefinitions给容器注册一个bean。
实例二:使用Import+ImportBeanDefinitionRegitrar给容器注册Person实例:
 @Service //通过实现ImportSelector自定义逻辑需要注册的组件名称Person和King
    public class KingImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
        /*
         * annotationMetadata 当前类的注解元信息
         * beanDefinitionRegistry beanDefinitionRegistry注册类
         * 把所需要注册的类通过 BeanDefinitionsRegister.registerBeanDefinitions来手工定义注册
         */
        public void registerBeanDefinitions(AnnotationMetadata annoMetadata, BeanDefinition definitionRegistry) {
            boolean definitionBeanKing = beanDefinitionRegistry.containsBeanDefinition("com.beijing.entity.King");
            //这里可进行业务逻辑,选择性的动态加载Bean实例;
            //todo 通过条件判断是否存在Bean King后开始注册bean Person
            if (definitionBeanKing) {
            if(true){
                RootBeanDefinition beanDefinition = new RootBeanDefinition(Person.class);
                System.out.println("ImportBeanDefinitionRegistrar: registerBeanDefinition被调用 ");
                //开始加载注册Bean实例;
                beanDefinitionRegistry.registerBeanDefinition("Person",beanDefinition);
            }
        }
    }

3.3.4、给容器注册一个Bean

这里可以小结一下,容器中添加注册组件Bean的方式有那些?
  • 方式一:@Bean【导入第三方的类核组件】或者xm配置 <bean id=person calss= "com.king.Person">;
  • 方式二:@ComponentScan包扫描+组件的标注注解(@Controller,@Service @Reponsitory @Component)一般都是我们自己写的业务类;
  • 方式三:@Import:快速给容器导入一个组件;
    • (1). @import要导入到容器中的组件,容器会自动注册这个组件,bean的id为全类名;
    • (2). ImportSelector:是一个接口,返回需要导入到容器的组件的全类名数组;
    • (3). ImportBeanDefinitionRegistrar:可以手动添加组件到IOC容器,所有的Bean注册都是使用BeanDefinitionRegistry;
  • 方式四:使用spring提供的FactoryBean的getObject方法来向容器中注入一个Bean;
      从这里也可以看出来,Spring对bean的注册一直在升级优化,从xml文件到javaConfig,再到现在的AutoConfiguration。
    了解了ImportSelector和ImportBeanDefinitionRegistrar后,对于EnableAutoConfiguration的理解就容易一些了,它们可以配合@Import导入第三方提供的bean的配置类, 根据条件注册相应的bean。 以前我们是通过xml文件配置去装载Bean,但是现在我们通过@Import + ImportSelector/ImportBeanDefinitionRegistor 可以实现批量的bean的装载, 这就是EnableAutoConfiguration的作用。
 

4、SpringFactoriesLoader实现原理-SPI思想

EnableAutoConfigurationImportSelector的实现原理
从名字可以猜到它是基于ImportSelector来实现基于动态bean的加载功能。前面说过springboot@Enablexxx注解的工作原理,通过@Import及ImportSelector接口
实现bean的批量加载,那么可以猜想这里的实现原理也是一样的。那么它到底是如何自动加载的呢?
为什么在resource/META-INF/spring.factories中加入如下内容就OK了呢?
这里spring提供了一种扩展点机制,设置固定的文件路径 classpath/META-INF/spring.factories ,通过工具类 SpringFactoreiesLoader去得到文件中注解key对应的所有的value值,然后在tomcat启动的时候,将需要加载的value实例类class文件 加载注册到spring容器中。 SpringFactoriesLoader这个类的使用其实和java中的SPI机制的原理是一样的,不过它比SPI更好的点在于不会一次性加载所有的类,而是根据key进行加载。满足规则如下:
  • 文件目录结构一致:classpath/META-INF
  • 文件名一致:spring.factories
  • 基于key全路径名的类存在且符合当前的加载;
加载流程如下:
  • (1).  SpringFactoriesLoader扫描spring.factories 文件中,拿到需要加载的class类全路径;
  • (2). 查询容器中类是否加载,如果没有,通过类加载器ClassLoader将Bean的字节码加载到内存;
  • (3). 通过 反射创建对象-赋值-初始化,加载对应的类到Spring IOC容器map中;
  • (4). 返回给用户使用;
    这也是非常经典的面向接口编程思想,让应用和实现解耦,大大提高了程序的可扩展性和灵活性。
   同样的SPI思想还应用在:
    1. Java的SPI机制,tomcat在启动的时候 ServiceLoader会扫描 META-INF/service路径下依赖的jar包,将相关的bean注入到spring 中托管;
        2、servlet中运行时插件机制: :tomcat启动的时候,利用运行时插件机制来加载感兴趣的 filter/listener/servlet Bean组件信息,首先创建目录:META-INF/services,然后创建一个javax.servlet.ServletContainerInitializer文件,配置需要加载Bean信息。
    3、dubbo的SPI机制,在META-INF/dubbo或者service下配置需要加载的key=感兴趣的bean,这样系统的在启动的时候就会优先加载相应的Bean.

5、条件过滤@Conditional

自动装载如何进行Bean的筛选呢?
    这里还是固定的模式,只需要在classpath下建立一个META-INF/spring-autoconfigure-metadata.properties文件,将条件Bean的加载条件配置在这个文件里面即可。
    在分析AutoConfigurationImportSelector的源码时会发现,tomcat会先扫描 spring-autoconfiguration-metadata.properties文件,最后在扫描spring.factories对应的类时, 会结合前面的元数据进行过滤。
还有一些条件过滤注解Conditions:
  • @ConditionalOnBean         //在存在某个 bean 的时候
  • @ConditionalOnMissingBean  //不存在某个 bean 的时候
  • @ConditionalOnClass        //当前 classpath 可以找到某个类型的类时
  • @ConditionalOnMissingClass //当前 classpath 不可以找到某个类型的类 时
  • @ConditionalOnResource     //当前 classpath 是否存在某个资源文件    
  • @ConditionalOnProperty     //当前 jvm 是否包含某个系统属性为某个值
  • @ConditionalOnWebApplication  //当前 spring context 是否是 web 应用程序
思考: 为什么需要对Bean的加载进行过滤呢?
    原因是很多的@Configuration其实是依托于其他的框架来加载的,如果当前的classpath环境下没有相关联的依赖,则意味着这些类没必要进行加载,所以通过这种条件过滤可以有效的减少@Configuration类的数量从而降低SpringBoot的启动时间,也节约了内存。

6、SPI机制自动加载源码解析

具体实现源码如下:
//批量动态加载bean - 主要是扫描META-INF/spring.factories下的文件:
public class AutoConfigurationImportSelector implements ImportSelector, BeanClassLoaderAware,
      ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {
    @Override
    public String[] selectImports(AnnotationMetadata annotationMetadata) {
        //第一步:加载类元信息配置文件,主要进行加载类的过滤和筛选
        //条件过滤加载:META-INF/spring-autoconfigure-metadata.properties 
       AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
             .loadMetadata(this.beanClassLoader);
        //第二步:通过SPI机制加载获取配置类的实体文件,META-INF/spring.factories文件
                //根据key扫描:        key = org.springframework.boot.autoconfigure.EnableAutoConfiguration
                //自动加载配置类全路径名 value = com.beijing.starter.config.KingInfoAutoConfiguration 
       AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(autoConfigurationMetadata,
             annotationMetadata);
        //第三步:这里面包含了key对应的所有的class类名,它们将会被加载到内存中;
       return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}

(1)加载类元信息配置文件 : 该path下的配置文件
protected static final String PATH = "META-INF/spring-autoconfigure-metadata.properties";


static AutoConfigurationMetadata loadMetadata(ClassLoader classLoader) {
   return loadMetadata(classLoader, PATH);
}

(2): 获取需要自动加载的实例类class文件;
protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata,
      AnnotationMetadata annotationMetadata) {
   if (!isEnabled(annotationMetadata)) {
      return EMPTY_ENTRY;
   }
   AnnotationAttributes attributes = getAttributes(annotationMetadata);
   //获取配置的value的值:需要加载的class类路径;
   List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
    //过滤重复的bean,value是会重复的;
   configurations = removeDuplicates(configurations);
    //获取注解里排除掉的value值;
   Set<String> exclusions = getExclusions(annotationMetadata, attributes);
   checkExcludedClasses(configurations, exclusions); 
    //删除注解里排除掉的value属性值,不加载的类;
   configurations.removeAll(exclusions);
   configurations = filter(configurations, autoConfigurationMetadata);
   fireAutoConfigurationImportEvents(configurations, exclusions);
   return new AutoConfigurationEntry(configurations, exclusions);
}
(3)通过SPI机制SpringFactoreiesLoader 动态批量加载满足条件的bean,实现bean的加载,它会加载classpath下:META-INF/spring.factories 中key对应的所有的class文件;
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
   List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),
         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;
}

AutoConfigurationPackage.Registrar

AutoConfigurationPackage.Registrar其实也是同样的原理,通过实现ImportBeanDefinitionRegistrar接口,按条件自动向容器中注册一个bean实例;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage {

}
//方式二:扫描jar包获取加有相关注解的类注册到ioc容器
static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {

   @Override
   public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
      register(registry, new PackageImport(metadata).getPackageName());
   }

   @Override
   public Set<Object> determineImports(AnnotationMetadata metadata) {
      return Collections.singleton(new PackageImport(metadata));
   }

}

至此,我们就初步简单的分析了下@SpringBootApplication注解的自动配置装载的工作内容及流程。虽然花了很多的时间,但是明白后的感觉很舒服。

7、小结

    这里主要学习了下SpringBoot的AutoConfiguration自动装配的功能,其实所有的操作目的都是通过配置类的注解方式把Bean自动加载注册交给spring IOC容器去托管。除此之外 还有启动器Starter的实现也是其一大特色,另外还有基于JMX实现的Actuator监控器,SpringBoot CCL 命令终端等还有很多,需要慢慢使用慢慢体会和学习,这里就不展开了。
    可以体会到,SpringBoot其实并不是完全是一个全新的东西,它只是对Spring的一个更好的封装,将看似散乱繁琐的工作进一步抽象规范化,进行了一个整合,让我们可以从配置的丛林里解放出来,站在高处欣赏和使用它。比如它的类加载和容器的启动流程及工作原理还是和spring一样,从refreash开始,并没有什么不同。
 
 
 
 
水滴石穿,积少成多。学习笔记,内容简单,用于复习,梳理巩固。
参考文件:
 
 
 
 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值