1.1.6、SpringBoot高级原理分析

一、SpringBoot自动配置

  1. Condition是在SpringBoot4.0增加的条件判断功能,通过这个功能可以实现选择性的创建Bean操作

  2. 案例:在Spring的IOC容器中有一个User的Bean,现要求:

    1. 导入Jedis坐标后,加载该Bean,没倒入,则不加载。

    2. 将类的判断定义为动态的。判断那个字节码文件是否存在可以动态指定

    3. 代码如下:

       // pom.xml
           <dependencies>
               <dependency>
                   <groupId>org.springframework.boot</groupId>
                   <artifactId>spring-boot-starter</artifactId>
               </dependency>
      
               <dependency>
                   <groupId>org.springframework.boot</groupId>
                   <artifactId>spring-boot-starter-test</artifactId>
               </dependency>
      
               <dependency>
                   <groupId>org.springframework.boot</groupId>
                   <artifactId>spring-boot-starter-data-redis</artifactId>
               </dependency>
      
       <!--        <dependency>-->
       <!--            <groupId>redis.clients</groupId>-->
       <!--            <artifactId>jedis</artifactId>-->
       <!--        </dependency>-->
           </dependencies>

      备注:注释坐标为测试坐标

       // 启动类
       @SpringBootApplication
       public class LearnApplication {
      
           public static void main(String[] args) {
               // 启动SpringBoot应用,返回Spring的IOC容器
               ConfigurableApplicationContext context = SpringApplication.run(LearnApplication.class, args);
      
       //        // 捕获Bean,redisTemplate,输出地址
       //        Object redisTemplate = context.getBean("redisTemplate");
       //        System.out.println(redisTemplate);
      
               // 捕获UserBean,输出地址
               Object userBean = context.getBean("user");
               System.out.println(userBean);
           }
      
       }

      备注:获取IOC容器,查看时候有对应的Bean

       // User类
       public class User {
      
       }

      备注:用来被Bean注入的,表示某些功能

       // Bean注入类
       @Configuration
       public class UserConfig {
      
           @Bean
       //    @Condition(ClassCondition.class)
           @ConditionOnClass("redis.clients.jedis.Jedis")
           public User user() {
               return new User();
           }
      
       }

      备注:注释的内容是原测试内容,下面的是动态内容

       // Condition实现类
       public class ClassCondition implements Condition {
      
           /**
            * @param context       上下文对象,用于获取环境,IOC容器,ClassLoader对象
            * @param metadata      注解元对象,可以用于获取注解定义的属性值
            */
           @Override
           public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
       //        // 获取环境
       //        Environment environment = context.getEnvironment();
       //        environment.getProperty() // 获取配置文件的属性
      
       //        // 导入Jedis坐标后,返回true,创建Bean
       //        try {
       //            // 获取某个类的字节码文件,获取到就有,获取不到就没有
       //            Class<?> aClass = Class.forName("redis.clients.jedis.Jedis");
       //        } catch (ClassNotFoundException e) {
       //            return false;
       //        }
       //        return true;
      
               // 导入指定坐标后,返回true,创建Bean
               // 获取注解属性值
               Map<String, Object> attributes = metadata.getAnnotationAttributes(ConditionOnClass.class.getName());
               String[] value = (String[]) attributes.get("value");
               try {
                   for (String className : value) {
                       Class<?> aClass = Class.forName(className);
                   }
               } catch (ClassNotFoundException e) {
                   return false;
               }
               return true;
      
           }
       }

      备注:相关参数的描述,以及想关功能操作

       // 自定义注解
       @Target({ElementType.TYPE, ElementType.METHOD})
       @Retention(RetentionPolicy.RUNTIME)
       @Documented
       @Conditional(ClassCondition.class)
       public @interface ConditionOnClass {
      
           String[] value();
      
       }

      备注:自定义注解,实现动态功能

  3. SpringBoot内部配置了很多Condition,包地址:org.springframework.boot:spring-boot-autoconfigure,其中有一个文件夹condition,专门编写Condition

  4. 自定义的条件中,可以直接使用autoconfigure包中分装好的Condition去操作

  5. 总结

    1. 自定义条件:

      1. 定义条件类:自定义类实现Condition接口,重写matches方法,在matches方法中进行逻辑判断,返回boolean值

        备注:mathces的两个参数:

        ​ contrxt:上下文对象,可以获取属性值,获取类加载器(IOC容器),获取BeanFactory等

        ​ metadata:元数据对象,用于获取注解属性

      2. 判断条件:在初始化Bean时,使用@Condition(条件类.class)注解

    2. SpringBoot提供的常用条件注解

      1. ConditionOnProperty:判断配置文件中是否有对应属性和值才初始化Bean

      2. ConditionOnClass:判断环境中是否有对应的字节码文件才初始化Bean

      3. ConditionOnMissingBean:判断环境中有没有对应的Bean才初始化Bean

        等等,还有好多SpringBoot配置好的

二、切换内置Web服务器

  1. SpringBoot的web环境中默认使用tomcat作为内置服务器,其实SpringBoot提供了4种内置服务器供我们选择,我们可以很方便的进行切换

  2. 查看内置服务器的提供种类,包地址:org.springframework.boot:spring-boot-autoconfigure,其中有一个包名叫做web/embedded的包中,下面四个就是内置的服务

  3. 查看包中的源代码,就发现如何修切换嵌服务器——导入不同的Web服务器坐标即可

  4. 方法:

    备注:引入web坐标,排除tomcat依赖,引入jetty等想要的服务器依赖即可,实现方法:参考上面的Condition注解

三、Enable*注解

  1. SpringBoot中提供了很多Enable开头的注解,这些注解都是用于动态启用某些功能的。而期底层原理是使用@import注解导入一些配置类,实现Bean的动态加载

  2. @SpringBootApplication包扫描的范围是当前引导类所在包及其子包

  3. 如何扫描其他位置的包

    1. 使用@ComponentScan("地址")可以指定扫描地址

    2. 使用@Import(文件.class)注解指定引入某个文件,加载类。这些类会被Spring创建,并放入IOC容器中

    3. 可以对@Import注解进行封装,封装成一个注解,直接引用(这就是@Enable*注解的由来)

四、Import注解

  1. @Enabel*底层依赖于@Import注解导入一些类,使用@Import注解导入的类会被Spring容器加载到IOC容器中。而@Import提供4种用法:

    1. 导入Bean——直接导入类

    2. 导入配置类——导入带有@Configuration注解的文件

    3. 导入ImportSelector实现类。一般用于加载配置文件中的类——导入实现类文件即可

    4. 导入ImportBeanDefinitionRegistrar实现类——导入实现类文件即可

      备注:4的实现有点复杂,在这里附加代码

五、@EnableAutoConfiguration注解

  1. 注解内部使用@Import(AutoConfigurationImportSelector.class)来加载配置类

  2. 配置文件位置:META-INF/spring.factories,该配置文件中定义了大量的配置类,当SpringBoot启动时,会自动加载这些配置类,初始化Bean

  3. 并不是所有的Bean都会被初始化,在配置类中使用@Condition来加载满足条件的Bean

六、自定义starter步骤(强化上面内容)

  1. 需求:自定义redis-starter。要求当导入redis坐标时,SpringBoot自动创建Jedis的Bean

  2. 实现步骤:

    1. 创建redis-spring-boot-autoconfigure模块

    2. 创建redis-spring-boot-starter模块,依赖redis-spring-boot-autoconfigure的模块

    3. 在redis-spring-boot-autoconfigure模块中初始化Jedis的Bean。并定义META_INF/spring.factories文件

    4. 在测试模块中引入自定义的redis-starter依赖测试获取Jedis的Bean,操作redis

  3. 实现代码:

    1. redis-spring-boot-autoconfigure模块

    2. redis-spring-boot-starter模块

    3. 测试包:

      引入redis-spring-boot-starter模块坐标即可,余下操作正常即可

七、SpringBoot事件监听机制

  1. SpringBoot的监听机制,其实是对Java提供的事件监听的封装

    1. Java中的事件监听机制定义了以下几个角色

      1. 事件:Event,继承java.util.EventObject类的借口

      2. 事件源:Source,任意对象Object

      3. 监听器:Listener,实现java.util.EventListener接口的对象

  2. SpringBoot在项目启动时,会对几个监听器进行回调,我们可以实现这些监听器接口,在项目启动时完成一些操作

    1. ApplicationContextInitializer:需要在META-INF/spring.factories中配置,检测资源是否存在

      备注:接口文件路径=实现类文件路径

    2. SpringApplicationRunListener:需要在META-INF/spring.factories中配置

      备注:接口文件路径=实现类文件路径

    3. CommandLineRunner:实现后可以直接执行,当项目启动后执行run方法,参数时启动时候传递的参数

    4. ApplicationRunner:实现后可以直接执行,当项目启动后执行run方法,参数是启动时候传递的参数

    5. 上面都加上运行结果:

八、流程分析

  1. 初始化操作:

    1. 配置source

    2. 配置是否为web环境

    3. 创建初始化构造器——只创建,未操作

    4. 创建应用监听器——只创建,未操作

    5. 配置应用的主方法所在类

  2. run

    1. 应用启动计时器开始计时,应用启动监听器开始监听

    2. 应用启动监听模块:SpringApplicationRunListener

      1. 启动

      2. 监听

        1. 配置环境

        2. 应用上下文

        3. ......

      3. 结束

    3. 配置环境模块

      1. 创建配置环境

      2. 加载属性文件资源

      3. 配置监听

    4. 图标

    5. 应用上下文模块

      1. 创建应用上下文对象

      2. 基础属性配置

        1. 加载配置环境

        2. ResourceLoader资源加载器

        3. 配置监听

        4. 加载启动参数

      3. 更新应用上下文

        1. 配置环境所需bean工厂

        2. 通过工厂生产所需的bean

          1. 应用启动时结束计时,应用启动监听器结束监听

            1. SpringApplication启动结束

Last updated

Was this helpful?