文章目录

  • 前言
  • @Enable* 注解
    • 思考
      • 演示
        • springboot-enable-other
          • User 类
          • UserConfig 配置类
        • springboot-enable
      • 方案
        • 1. 使用 @ComponentScan
        • 2.使用 @Import 注解
        • 3.对 @Import 注解进行封装
      • 小结
  • @Import 注解
    • @Import 4 种用法
      • 1. 导入Bean
      • 2. 导入配置类
      • 3. 导入 ImportSelector 实现类
        • 放入全限定名加载
        • 读取配置文件动态加载
      • 4. 导入 ImportBeanDefinitionRegistrar 实现类
  • @EnableAutoConfiguration 注解
    • 小结

前言

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


@Enable* 注解

思考

SpringBoot 工程是否可以直接获取 jar 包中定义的 Bean?
答案是否定的,SpringBoot 无法直接引用别人 jar 包里的 Bean。
那么问题就来了:为什么我们之前在工程中引入一个 Redis 的起步依赖就可以直接获取到 RedisTemplate 呢?

演示

我们接下来演示一下 SpringBoot 不能获取第三方 jar 包里的 Bean 这个特点。
因为要模拟演示第三方的,我们需要创建两个模块工程:
springboot-enable、springboot-enable-other。

由于是演示,那么就简单写一些代码实现效果就可以。

springboot-enable-other

在 springboot-enable-other 工程中写一个 Bean:

User 类
package com.xh.config;public class UserConfig {}
UserConfig 配置类
package com.xh.config;import com.xh.domain.User;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;/** 表示配置类 */
@Configuration
public class UserConfig {/** 注入 */@Beanpublic User user(){return new User();}
}

springboot-enable

在 springboot-enable 工程中依赖 springboot-enable-other 工程。
pom.xml 文件中添加代码:

        <dependency><groupId>com.xh</groupId><artifactId>springboot-enable-other</artifactId><version>0.0.1-SNAPSHOT</version></dependency>

修改启动类中添加获取 User 的代码:

public static void main(String[] args) {ConfigurableApplicationContext context = SpringApplication.run(SpringbootEnableApplication.class, args);// 获取 BeanObject user = context.getBean("user");System.out.println(user);}

运行项目,提示没有获取到为 user 的 Bean:

通过演示我们可以看到 SpringBoot 不能直接获取我们在其他工程中定义的 Bean。
原因:在启动类中 @SpringBootApplication 注解中有一个 @ComponentScan 注解,这个注解扫描的范围是当前引导类所在包及其子包。

我们项目的引导类包路径为:om.xh.springbootenable
而 UserConfig 所在的包路径为:com.xh.config
两者并没有关联关系,那么我们如果想解决这个问题其实是有很多种方案的。

方案

1. 使用 @ComponentScan

我们可以在引导类上使用 @ComponentScan 注解扫描配置类所在的包

@SpringBootApplication
@ComponentScan("com.xh.config")
public class SpringbootEnableApplication {public static void main(String[] args) {ConfigurableApplicationContext context = SpringApplication.run(SpringbootEnableApplication.class, args);// 获取 BeanObject user = context.getBean("user");System.out.println(user);}
}

启动项目,输出成功获取到的 user

这样的方案虽然可以解决这个问题,但是看起来还是不太行,如果我们后期需要用到第三方的一些功能,还需要把第三方的包扫描一下,如果获取的特别多的话,那么写一排实在是太 low 了!而且也很难能记住那么多的包路径,所以这种方案是不推荐的。

2.使用 @Import 注解

被 @Import 注解所导入的类,都会被 Spring 创建,并放入 IOC 容器中。
如图可以看到 @Import 注解的 value 值是一个数组,可以传多个值。

修改引导类

@SpringBootApplication
//@ComponentScan("com.xh.config")
@Import(UserConfig.class)
public class SpringbootEnableApplication {public static void main(String[] args) {ConfigurableApplicationContext context = SpringApplication.run(SpringbootEnableApplication.class, args);// 获取 BeanObject user = context.getBean("user");System.out.println(user);}
}

启动项目,输出成功获取到的 user

这种方案比方案一稍微好一些,但同样会面临同样的问题,我们需要记住很多类的名字,所以仍然不是很方便。

3.对 @Import 注解进行封装

在 springboot-enable-other 工程中编写注解 @EnableUser,在注解中使用 @Import 注解导入 UserConfig,并且添加 @Import 的元注解

package com.xh.config;import org.springframework.context.annotation.Import;
import java.lang.annotation.*;@Import(UserConfig.class)
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface EnableUser {}

修改 springboot-enable 工程的引导类,现在可以使用我们自定义的注解。

@SpringBootApplication
//@ComponentScan("com.xh.config")
//@Import(UserConfig.class)
@EnableUser
public class SpringbootEnableApplication {public static void main(String[] args) {ConfigurableApplicationContext context = SpringApplication.run(SpringbootEnableApplication.class, args);// 获取 BeanObject user = context.getBean("user");System.out.println(user);}
}

启动项目,输出成功获取到的 user

这种自定义注解的方式和方案二是一个原理,只不过是在使用上简化了一下。

小结

SpringBoot 底层是使用 @Import 注解导入一些配置类,实现 Bean 的动态加载。
例如 @SpringBootApplication 其中的 @EnableAutoConfiguration 就使用了 @Import 来实现导入其他的类。

@Import 注解

@Enable* 底层依赖于 @Import 注解导入一些类,使用 @Import 导入的类会被 Spring 加载到 IOC 容器中。

@Import 4 种用法

@Import 提供了 4 种用法:

  1. 导入Bean
  2. 导入配置类
  3. 导入 ImportSelector 实现类。一般用于加载配置文件中的类
  4. 导入 ImportBeanDefinitionRegistrar 实现类。

1. 导入Bean

修改引导类:

/*** Import 4 种用法* 1. 导入Bean* 2. 导入配置类* 3. 导入 ImportSelector 实现类。一般用于加载配置文件中的类* 4. 导入 ImportBeanDefinitionRegistrar 实现类。*/
@SpringBootApplication
//@ComponentScan("com.xh.config")
//@Import(UserConfig.class)
//@EnableUser
@Import(User.class)
public class SpringbootEnableApplication {public static void main(String[] args) {ConfigurableApplicationContext context = SpringApplication.run(SpringbootEnableApplication.class, args);// 获取 Bean
//        Object user = context.getBean("user");
//        System.out.println(user);// 由于使用 @Import 注解导入 User.class 获取到的 Bean 名称不叫 user// 所以通过类型获取 BeanUser user = context.getBean(User.class);System.out.println("user:" + user);// 获取 Spring 容器中所有 UserBean 的名称以及 Bean 对应的值Map<String, User> map = context.getBeansOfType(User.class);System.out.println("map:" + map);}
}

启动项目,成功输出 user 以及 userBeanMap

2. 导入配置类

使用 @Import(UserConfig.class) 这种方式就是导入配置类,我们可以再创建一个对象并修改配置类的代码,看看一个配置类是否可以加载两个对象
在 enable-other 工程中创建 Role:

package com.xh.domain;public class Role {}

在 UserConfig 中添加代码:

    @Beanpublic Role role(){return new Role();}

修改 enable 工程引导类:

@SpringBootApplication
//@ComponentScan("com.xh.config")
//@EnableUser
//@Import(User.class)
@Import(UserConfig.class)
public class SpringbootEnableApplication {public static void main(String[] args) {ConfigurableApplicationContext context = SpringApplication.run(SpringbootEnableApplication.class, args);// 获取 Bean
//        Object user = context.getBean("user");
//        System.out.println(user);// 由于使用 @Import 注解导入 User.class 获取到的 Bean 名称不叫 user// 所以通过类型获取 BeanUser user = context.getBean(User.class);System.out.println("user:" + user);Role role = context.getBean(Role.class);System.out.println("role:" + role);// 获取 Spring 容器中所有 UserBean 的名称以及 Bean 对应的值
//        Map<String, User> map = context.getBeansOfType(User.class);
//        System.out.println("map:" + map);}
}

启动项目,成功输出 user 和 role

3. 导入 ImportSelector 实现类

进入 ImportSelector 接口,我们可以看到 selectImports 方法。
这个方法参数为 AnnotationMetadata,可以用来获取一些注解的值。
返回值为 String 类型的数组,这个方法被复写之后这个方法要返回一些类的全限定名。

在 enable-other 工程中创建 MyImportSelector 并实现 ImportSelector 接口,复写 selectImports 方法:

放入全限定名加载

package com.xh.config;import org.springframework.context.annotation.ImportSelector;
import org.springframework.core.type.AnnotationMetadata;public class MyImportSelector implements ImportSelector {@Overridepublic String[] selectImports(AnnotationMetadata importingClassMetadata) {// 放入 user、role 的全限定名之后,就会自动去加载 user 和对应的 role 了// return new String[]{"com.xh.domain.User", "com.xh.domain.Role"};// 或者可以使用方法获取return new String[]{User.class.getName(), Role.class.getName()};}
}

修改 enable 工程引导类:

@SpringBootApplication
//@ComponentScan("com.xh.config")
//@EnableUser
//@Import(User.class)
//@Import(UserConfig.class)
@Import(MyImportSelector.class)
public class SpringbootEnableApplication {public static void main(String[] args) {ConfigurableApplicationContext context = SpringApplication.run(SpringbootEnableApplication.class, args);// 获取 Bean
//        Object user = context.getBean("user");
//        System.out.println(user);// 由于使用 @Import 注解导入 User.class 获取到的 Bean 名称不叫 user// 所以通过类型获取 BeanUser user = context.getBean(User.class);System.out.println("user:" + user);Role role = context.getBean(Role.class);System.out.println("role:" + role);
//        // 获取 Spring 容器中所有 UserBean 的名称以及 Bean 对应的值
//        Map<String, User> map = context.getBeansOfType(User.class);
//        System.out.println("map:" + map);}
}

启动项目,成功输出 user 和 role

读取配置文件动态加载

看到这里的朋友可能会感觉这种方式跟之前的没什么区别,好像还挺麻烦的,其实我们在复写 selectImports 方法时,返回的数组是字符串形式的,我们可以把全限定名添加到配置文件中,那么在项目启动时就可以动态的加载出来。
由于我们在 enable-other 工程中创建的所有类都没有在引导类的同级或者子级目录下,为了方便演示我们要把当前的目录放到 springbootenableother 包下:

修改 application.properties 配置文件名称为 application.yml,并放入属性值:

name: com.example.springbootenableother.domain.User,com.example.springbootenableother.domain.Role

修改 MyImportSelector,通过读取配置文件实现动态加载:

package com.xh.springbootenableother.config;import org.springframework.context.annotation.ImportSelector;
import org.springframework.core.type.AnnotationMetadata;import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;public class MyImportSelector implements ImportSelector {private static Properties properties = new Properties();@Overridepublic String[] selectImports(AnnotationMetadata importingClassMetadata) {// 放入 user、role 的全限定名之后,就会自动去加载 user 和对应的 role 了// return new String[]{"com.xh.domain.User", "com.xh.domain.Role"};// 或者可以使用方法获取
//        return new String[]{User.class.getName(), Role.class.getName()};// 读取配置文件InputStream resourceAsStream = Object.class.getResourceAsStream("/application.yml");try {// 加载配置文件properties.load(resourceAsStream);} catch (IOException e) {e.printStackTrace();}// 根据 key 获取并拆分返回return properties.getProperty("name").split(",");}
}

启动项目,成功输出:

4. 导入 ImportBeanDefinitionRegistrar 实现类

在 enable-other 工程中创建 MyImportBeanDefinitionRegistrar 类,实现 ImportBeanDefinitionRegistrar 接口并重写 registerBeanDefinitions 方法,这种方式是使用注入的形式。

package com.example.springbootenableother.config;import com.example.springbootenableother.domain.User;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.type.AnnotationMetadata;/*** @author XH* @create 2021/12/13* @since 1.0.0*/
public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {@Overridepublic void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {//在 IOC 容器中注册 Bean,Bean 名称为 user,类型为 User.class// 获取 beanDefinitionAbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.rootBeanDefinition(User.class).getBeanDefinition();// 注入 userregistry.registerBeanDefinition("user", beanDefinition);}
}

修改 enable 工程中的引导类,可以根据类型或者 Bean 名称获取:

@SpringBootApplication
//@EnableUser
//@Import(MyImportSelector.class)
@Import(MyImportBeanDefinitionRegistrar.class)
public class SpringbootEnableApplication {public static void main(String[] args) {ConfigurableApplicationContext context = SpringApplication.run(SpringbootEnableApplication.class, args);// 根据类型获取Object user = context.getBean(User.class);System.out.println(user);// 根据 Bean 名称获取Object user1 = context.getBean("user");System.out.println(user1);
//        Object role = context.getBean(Role.class);
//        System.out.println(role);}
}

启动项目,两种获取方式都可以输出:

@EnableAutoConfiguration 注解

我们可以看看 Spring 是如何使用 @Import 注解的:
进入引导类上的 @SpringBootApplication 注解可以看到其注解依赖了 @EnableAutoConfiguration 注解。

进入 @EnableAutoConfiguration 注解可以看到它使用了 @Import 注解导入了 AutoConfigurationImportSelector 这个自动配置的 Selector。

进入 AutoConfigurationImportSelector 之后我们可以找到 selectImports 方法,根据上文所述,这个方法返回了一个 String 类型的数组,数组中定义了很多需要被加载的类:

进入加载方法可以看到有一个 getCandidateConfigurations 方法会返回一个名为 configurations 的 List 集合,在下面的代码中根据条件筛选了这个集合并放入创建的 AutoConfigurationEntry 中返回:

进入 getCandidateConfigurations 可以看到他通过 SpringFactoriesLoader 加载了一些配置信息并返回了一个名为 configurations 的 List 集合,下面的断言表示如果这个集合为空的,那么就会出现异常,大概意思为:“不能自动配置一个 claesses,在 META-INF 目录下的 spring.factories 文件下”,如果没有定义 spring.factories 这个文件,那么他就加载不到,加载不到就会出现断言:

接下来我们在 External Libraries 中找到 org.springframework.boot:spring-boot-autoconfigure:2.6.1 下的
spring-boot-autoconfigure-2.6.1.jar -> META-INF -> spring.factories 并进入:

可以看到这个配置文件中有一个 key 为 org.springframework.boot.autoconfigure.EnableAutoConfiguration 的配置,其中配置了许多的 Configuration,那么这些 Configuration 将来都会被加载。
当然这些 Configuration 能否加载出来还得看他们的条件是否满足,比如我们可以找到前几个章节讲到的 Redis,Redis 在当前配置文件中有一个 RedisAutoConfiguration:

进入 RedisAutoConfiguration 我们可以看到其中有一个 @ConditionalOnClass 条件注解,当这个注解里的条件被满足时,这个类中的 Bean 才会被创建。
之前的章节中讲过这个注解,有不了解的朋友可以传送过去看看:SpringBoot 自动配置之 Condition

当然不止这一个会有条件注解,比如我们再随便挑一个进去看看:
在 KafkaAutoConfiguration 中同样定义了条件注解,当环境中存在 KafkaProperties 时,这个类中的 Bean 才会被加载。

小结

  • @EnableAutoConfiguration 注解内部使用 @Import(AutoconfigurationImportSelector.class) 来加载配置类。
  • 配置文件位置:META-INF/spring.factories,该配置文件中定义了大量的配置类,当 SpringBoot 应用启动时,会自动加载这些配置类,初始化 Bean。
  • 并不是所有的 Bean 都会被初始化,在配置类中使用 Condition 来加载满足条件的 Bean。

SpringBoot 自动配置之 Enable 注解原理相关推荐

  1. SpringBoot实战之SpringBoot自动配置原理

    www.cnblogs.com/leihuazhe/p- SpringBoot 自动配置主要通过 @EnableAutoConfiguration, @Conditional, @EnableConf ...

  2. springboot自动配置的原理_SpringBoot实战:详解SpringBoot自动配置原理

    SpringBoot 自动配置主要通过 @EnableAutoConfiguration, @Conditional, @EnableConfigurationProperties 或者 @Confi ...

  3. 【详解】面试必问:SpringBoot自动配置原理

    前言 SpringBoot框架是开发中的一大利器,其简化了spring的xml的配置,遵循了"约定大于配置"的原则,使用注解对常用的配置做默认配置,减少使用xml配置模式.Spri ...

  4. SpringBoot自动配置的原理及实现

    SpringBoot的核心就是自动配置,自动配置是基于条件判断配置Bean 自动配置的源码在spring-boot-autoconfigure-2.2.13.RELEASE SpringBoot运行原 ...

  5. 这样讲 SpringBoot 自动配置原理,你应该能明白了吧

    点击上方"方志朋",选择"设为星标" 回复"666"获取新整理的面试资料 作者:你在我家门口 juejin.im/post/5ce5effb ...

  6. SpringBoot | 自动配置原理

    微信公众号:一个优秀的废人.如有问题,请后台留言,反正我也不会听. 前言 这个月过去两天了,这篇文章才跟大家见面,最近比较累,大家见谅下.下班后闲着无聊看了下 SpringBoot 中的自动配置,把我 ...

  7. SpringBoot 自动配置原理

    创建项目 通过Spring Initialize创建SpringBoot项目 而接下来要说的是关于配置文件的事情.关乎配置文件可以参考官方文档. 对于配置文件来说到底在配置文件里面可以进行配置那些内容 ...

  8. springboot自动配置原理_今日份学习之Spring Boot自动配置实现原理

    通过前面章节的学习,我们掌握了使用Spring Boot框架进行实际应用开发的方法.在使用Spring Boot 的过程中,我们时常会为一些看似简单,但实际上蕴藏了强大功能的实现而惊呼,下面就让我们来 ...

  9. springboot自动配置原理

    概述 Springboot的基本认识 对于Spring框架,我们接触得比较多的是Spring mvc,Spring IOC.AOP.DI.而这框架如果在使用过程中,随着项目越来越大,引入的技术越来越多 ...

最新文章

  1. .Net winform中嵌入Flash
  2. tp连接mysql mysql_thinkphp学习简易教程(二) thinkphp连接读取MySQL数据库
  3. 规模化微服务——《微服务设计》读书笔记
  4. 从Java执行可执行的命令行
  5. javascript数组去重方法汇总
  6. oracle排名怎么去除空值影响,Oracle排序中常用的NULL值处理方法
  7. anaconda linux安装_deepin系统启动Anaconda时图形界面出问题
  8. [UVA] 704 Colour Hash
  9. maven下载,安装与eclipse中maven配置
  10. Exception in Tkinter callback
  11. SRv6可编程技术-SRv6 Policy
  12. DXBC2HLSL Tool
  13. 22款奔驰GLE350升级原厂360全景倒车影像,智能科技化繁为简
  14. 全球打工人的抗争!谷歌员工希望更公平,国内只求告别996
  15. 个人所得税法应充分体现经济法原则
  16. 招行金葵花,经典白,银钻,AE白问题总结贴
  17. 二元二次方程例题_二元二次方程组例题_相关文章专题_写写帮文库
  18. SQL语句按照姓名首字母排序
  19. 详解物联网常用协议:IIC和RS485通信协议
  20. 波长链(TRON)---发币

热门文章

  1. 模拟退火中关于Boltzmann分布和Metropolis采样方法的应用探讨
  2. 计算机换硬盘有什么影响,硬盘分区有什么影响 硬盘分区会不会影响电脑性能...
  3. 基于物理文件的HBase备份还原
  4. java 进程假死原因_Java进程假死案例集合
  5. 华为ospf综合实验
  6. springboot使用JWT,并自动获取用户信息
  7. 为什么低碳水饮食对减肥有效?给你科学的解释
  8. 5种方法轻松解决电流麦
  9. Linux中安装有道词典
  10. 中职计算机教学实施方案,论中职计算机专业教学方案的制定与实施