得物面试:Springboot自动装配机制是什么?如何控制一个bean 是否加载,使用什么注解?

文章很长,且持续更新,建议收藏起来,慢慢读!疯狂创客圈总目录 博客园版 为您奉上珍贵的学习资源 :

免费赠送 :《尼恩Java面试宝典》 持续更新+ 史上最全 + 面试必备 2000页+ 面试必备 + 大厂必备 +涨薪必备
免费赠送 :《尼恩技术圣经+高并发系列PDF》 ,帮你 实现技术自由,完成职业升级, 薪酬猛涨!加尼恩免费领
免费赠送 经典图书:《Java高并发核心编程(卷1)加强版》 面试必备 + 大厂必备 +涨薪必备 加尼恩免费领
免费赠送 经典图书:《Java高并发核心编程(卷2)加强版》 面试必备 + 大厂必备 +涨薪必备 加尼恩免费领
免费赠送 经典图书:《Java高并发核心编程(卷3)加强版》 面试必备 + 大厂必备 +涨薪必备 加尼恩免费领

免费赠送 资源宝库: Java 必备 百度网盘资源大合集 价值>10000元 加尼恩领取


得物面试:Springboot自动装配机制是什么?如何控制一个bean 是否加载,使用什么注解?

尼恩特别说明: 尼恩的文章,都会在 《技术自由圈》 公号 发布, 并且维护最新版本。 如果发现图片 不可见, 请去 《技术自由圈》 公号 查找

尼恩说在前面

在40岁老架构师 尼恩的读者交流群(50+)中,最近有小伙伴拿到了一线互联网企业如得物、阿里、滴滴、极兔、有赞、希音、百度、网易、美团、蚂蚁、得物的面试资格,遇到很多很重要的相关面试题:

Springboot自动装配机制是什么?

如何控制一个bean 是否加载,使用什么注解?

starter的作用是什么? 你用过哪些starter?

最近有小伙伴在面试得物,又被问到了相关的面试题,可以说,这个问题是逢面必问。

小伙伴没有系统的去梳理和总结,所以支支吾吾的说了几句,面试官不满意,面试挂了。

所以,尼恩给大家做一下系统化、体系化的梳理,使得大家内力猛增,可以充分展示一下大家雄厚的 “技术肌肉”,让面试官爱到 “不能自已、口水直流”

最终,机会爆表,实现”offer自由” 。

当然,这道面试题,以及参考答案,也会收入咱们的 《尼恩Java面试宝典PDF》V175版本,供后面的小伙伴参考,提升大家的 3高 架构、设计、开发水平。

《尼恩 架构笔记》《尼恩高并发三部曲》《尼恩Java面试宝典》的PDF,请到文末公号【技术自由圈】获取

本文作者:

  • 第一作者 Jayen (Jayen 是尼恩团队高级架构师,负责写此文的第一稿,初稿 )
  • 第二作者 尼恩 (42岁老架构师, 负责提升此文的 技术高度,让大家有一种 俯视 技术的感觉

SpringBoot 自动装配和模块化、复用化的关系

SpringBoot 自动装配使得开发人员可以轻松地搭建、配置和运行应用程序,而无需手动管理大部分的 bean 和配置。

Spring Boot 的自动装配机制与模块化和复用化密切相关,它们之间存在着相互促进和互补的关系。

模块化

  • Spring Boot 通过 Spring Starter 的方式提供了一种模块化的解决方案。每个 Starter 都是一个独立的模块,它包含了特定功能的配置、依赖和自动装配。
  • 开发者可以根据项目的需求选择性地引入需要的 Starter,从而实现功能上的模块化。这使得项目结构更清晰、功能更独立,并且可以根据需求进行定制和组合。
  • 同时,Spring Boot 本身也是一个模块化的框架,它将各种功能划分为不同的模块,如 Web、数据访问、安全等,使得开发者能够根据需要选择性地引入和使用这些功能模块。

复用化

  • Spring Boot 的自动装配机制大大提高了代码的复用性。通过自动扫描和自动装配,Spring Boot 能够将各种功能模块自动集成到应用程序中,避免了重复编写和配置的工作。
  • Starter 的设计也是为了提高代码的复用性。开发者可以将常用的功能打包成 Starter,供其他项目引入和使用,从而避免了重复开发相似的功能。
  • 此外,Spring Boot 的约定大于配置的原则也促进了代码的复用。通过统一的项目结构、配置规范和命名规范,开发者能够更容易地理解和使用他人编写的代码,提高了代码的可维护性和可复用性。

总的来说,Spring Boot 的自动装配机制使得模块化和复用化更加便捷和高效。开发者可以根据项目的需求选择性地引入功能模块,同时也可以将常用的功能封装成 Starter,以供其他项目复用。

这种模块化和复用化的设计理念使得 Spring Boot 在开发中得到了广泛的应用和认可。

正因为如此,SpringBoot 自动装配是每个 Java Coder 逃不开的话题,SpringBoot 自动装配 也是面试中的重点内容。

想回答好这个问题,首先我们要抓住几个重点:

  1. 什么是 SpringBoot 自动装配?
  2. SpringBoot 是如何实现自动装配的?如何实现按需加载?
  3. 如何自定义一个自己的可以服用的 SpringBoot starter 起步依赖?

什么是 SpringBoot 自动装配?

SpringBoot 会根据类路径中的 jar包、类,为 jar 包里的类进行自动配置,这样就可以大大的减少配置的数量。

简单点说,就是 SpringBoot 会根据定义在 classpath 下的类,自动给你生成一些 Bean,并且加载到 Spring 的 Context 中。

那么,它的原理是什么呢?哪些 Bean 可以自动装配到容器里面呢?

其实在 SpringBoot 内部,会读取 classpath 下 META-INF/spring.factories 文件中的所配置的类的全类名。

我们可以找到 key为 org.springframework.boot.autoconfigure.EnableAutoConfiguration 对应的值。

可以发现就是一系列的 xxxAutoConfiguration,随便找两个类看一下,就是标记了 @Configuration 的配置类,并且通过一些条件注解,来判断、决定哪些 Bean 应该自动注入到容器中。

⚠️注意:

在SpringBoot 2.7 版本后,spring.factories 方式已经被标记为废弃,

SpringBoot 2.7 版本后建议使用新的配置文件:

/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports

但是,在加载到时候,会同时加载这两个文件。

SpringBoot 是如何实现自动装配的?如何实现按需加载?

SpringBoot 是如何实现自动装配的,先看一个大概步骤:

  1. 组件扫描(Component Scanning):Spring Boot 使用组件扫描来查找和注册标有特定注解的 bean。默认情况下,Spring Boot 将扫描主应用程序类所在的包及其子包下的所有类。标有 @Component@Service@Repository@Controller 注解的类将被注册为 bean。

  2. 条件化的 bean 注册(Conditional Bean Registration):Spring Boot 使用条件化的 bean 注册来根据条件自动注册特定的 bean。这些条件可以基于环境属性、系统属性、类路径是否包含特定类等。当条件满足时,相应的 bean 将被注册到 Spring 的应用程序上下文中。

  3. 自动配置(Auto-Configuration):Spring Boot 基于类路径中的 jar 包和已配置的条件自动配置应用程序上下文。Spring Boot 提供了大量的自动配置类,这些类负责根据条件注册特定的 bean,以及设置应用程序的默认配置。这样,开发人员就无需手动配置大部分常见的 bean 和配置,而是可以依赖于 Spring Boot 的自动配置。

在这里插入图片描述

这个图其实就是我们前面说的流程,SpringBoot 会去扫描指定的配置类,然后通过条件判断是否加载 Bean。

Spring Boot 实现自动装配的几个核心注解

Spring Boot 实现自动装配的几个核心注解有哪些呢?还是先看一张图:

在这里插入图片描述

Spring Boot 实现自动装配的几个核心注解包括:

  1. @SpringBootApplication:这是一个组合注解,相当于同时使用了 @Configuration@EnableAutoConfiguration@ComponentScan 注解。它标记了主应用程序类,并告诉 Spring Boot 开始组件扫描、自动配置和装配。
  2. @EnableAutoConfiguration:该注解用于启用 Spring Boot 的自动配置功能。它会根据应用程序的依赖关系和当前环境,自动注册所需的 bean。
  3. @ComponentScan:该注解用于启用组件扫描,以便 Spring Boot 可以自动发现和注册标有 @Component@Service@Repository@Controller 注解的类。
  4. @ConditionalOnClass@ConditionalOnMissingClass:这两个条件化注解用于根据类路径上是否存在特定的类来决定是否注册 bean。@ConditionalOnClass 在类路径上存在指定类时生效,而 @ConditionalOnMissingClass 在类路径上不存在指定类时生效。
  5. @ConditionalOnBean@ConditionalOnMissingBean:这两个条件化注解用于根据是否存在特定的 bean 来决定是否注册 bean。@ConditionalOnBean 在容器中存在指定的 bean 时生效,而 @ConditionalOnMissingBean 在容器中不存在指定的 bean 时生效。
  6. @ConditionalOnProperty:该条件化注解用于根据配置属性的值来决定是否注册 bean。它可以根据配置文件中的属性值来决定是否启用或禁用特定的 bean。

这些注解结合在一起,为 Spring Boot 提供了自动装配的能力。

从源码层面来说,Spring Boot 自动装配的核心实现,主要就是依赖 @SpringbootApplication 这个注解,它又是个组合注解,由3部分组成:

  1. @SpringBootConfiguration,标注是一个配置类
  2. @EnableAutoConfiguration,开启自动装配
    1. @AutoConfigurationPackage,指定默认的包规则,将主程序类所在包及其子包下的组件扫描到容器里
    2. @Import(AutoConfigurationImportSelector.class),导入AutoConfigurationImportSelector,并通过 selectImports 方法读取 META-INF/spring.factories 文件中配置的全类名,并按照条件过滤,注入需要的 bean
  3. @ComponentScan,配置扫描路径,用来加载 bean

接下来我们就顺着源码,来和大家详细聊聊 SpringBoot 自动装配是如何实现的。

源码解析: @SpringBootApplication 注解

首先我们找到主程序类,也就是程序的入口,执行 main 方法的地方。

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

@SpringBootApplication 标注在主程序类上,说明这个类是主配置类,SpringBoot 项目会运行这个类的 main 方法启动应用。

我们继续往里跟一下,看看@SpringBootApplication 这个注解的庐山真面目。

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

典型的组合注解,最上面4个是 Java 提供的元注解,

重点看下 @SpringBootConfiguration、@EnableAutoConfiguration、@ComponentScan 这三个注解。

源码解析: @SpringBootConfiguration注解

先来看下 @SpringBootConfiguration:

@Configuration
@Indexed
public @interface SpringBootConfiguration {
	... ...
}

可以看到它也是个组合注解,除去元注解,可以看到有一个老朋友 @Configuration,

@Configuration 用于声明某个类是 SpringBoot 的配置类。配置类也是容器中的一个组件,底层也是通过 @Component.

@Component
public @interface Configuration {
  ... ...
}

源码解析: @EnableAutoConfiguration注解

接着我们来看下 @EnableAutoConfiguration :

@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
  String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
  ... ...
}

@EnableAutoConfiguration 也是一个组合注解,

@EnableAutoConfiguration 的作用其实就是告诉 SpringBoot 开启自动装配的功能,这样自动配置才能生效。

源码解析: @AutoConfigurationPackage注解

我们来进一步分析,看下 @AutoConfigurationPackage。

@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage {
	... ...
}

它的底层其实是通过 @Import 给容器导入一个组件,@Import 注解的作用是在 Spring 配置类中用于导入其他配置类,从而将其配置信息合并到当前配置类中,以便统一管理和组织配置信息。

如果不了解 @Import , 那么以下是 @Import 注解的一个简单示例:

假设有一个配置类 TransactionConfig 用于配置事务管理器。

@Configuration
public class TransactionConfig {
    // 事务管理器配置
    @Bean
    public PlatformTransactionManager transactionManager() {
        // 配置事务管理器
    }
}

另设计一个配置类 DataSourceConfig 用于配置数据源,那么,配置类 DataSourceConfig 可以使用 @Import 注解将 TransactionConfig 导入到自己内部 ,从而在配置数据源的时候,具备统一管理 事务 配置。

@Configuration
@Import(TransactionConfig.class)
public class DataSourceConfig {
    // 数据源配置
    @Bean
    public DataSource dataSource() {
        // 配置数据源
    }
}

在上面的示例中,DataSourceConfig 配置类使用 @Import(TransactionConfig.class) 注解导入了 TransactionConfig 配置类。这样一来,DataSourceConfig 就可以统一管理数据源和事务管理器的配置,使得配置信息更加清晰和有序。

需要注意的是,@Import 注解只是将其他配置类导入到当前配置类中,@Import 注解并不会触发其他配置类中的 bean 的注册过程。

回到 SpringBoot的自动装配源码。

@AutoConfigurationPackage 导入的组件是 AutoConfigurationPackages.Registrar,将主配置类所在的包及其子包里面所有的组件扫描到 Spring 容器里。

AutoConfigurationPackages.Registrar,这个组件的作用是为自动配置包注册一个 BeanDefinition。

在这里插入图片描述

可以debug查看具体的值:

在这里插入图片描述

就是通过以上这个方法,获取扫描的包路径,然后通过AutoConfigurationPackages.register()方法 去 注册 自动配置的Bean。

来看一下AutoConfigurationPackages.register() 方法,这个是 Spring Boot 中用于注册自动配置包的静态方法。

这里 register() 的作用是将指定的包路径注册到 Spring 容器中,以便 Spring Boot 能够在这些包下进行自动配置。

register() 方法接受的第一个参数 BeanDefinitionRegistry 对象,用于注册 BeanDefinition。

在这里插入图片描述

代码中,DefaultListableBeanFactory#registerBeanDefinition 方法的作用是向容器注册一个新的 BeanDefinition。

顺便说说 registerBeanDefinition 方法,这个方法会将一个 BeanDefinition 对象添加到容器的 beanDefinitionMap 中,从而使得容器在实例化和管理 bean 时能够根据注册的信息进行相应的处理。以下是 registerBeanDefinition 方法的主要作用:

  1. 注册新的 BeanDefinition:该方法用于注册一个新的 BeanDefinition 对象到容器中。这个 BeanDefinition 包含了 bean 的元数据信息,如 bean 的类名、作用域、依赖关系等。
  2. 检查是否已存在同名的 BeanDefinition:在向容器注册新的 BeanDefinition 之前,会先检查容器中是否已存在同名的 BeanDefinition。如果已存在同名的 BeanDefinition,并且允许覆盖,则会替换已存在的 BeanDefinition;否则会抛出异常。
  3. 更新容器的 beanDefinitionMap:注册新的 BeanDefinition 后,会将其添加到容器的 beanDefinitionMap 中,其中 key 为 bean 的名称,value 为对应的 BeanDefinition 对象。
  4. 更新容器的 beanNameSet 集合:同时,也会更新容器的 beanNameSet 集合,该集合包含了容器中所有注册的 bean 的名称。

总的来说,registerBeanDefinition 方法的作用是向 Spring 容器注册一个新的 BeanDefinition,使得容器能够根据这个注册信息正确地实例化和管理 bean.

回到前面 AutoConfigurationPackages.register()方法。

这个方法中,通过 beanDefinition.setBeanClass(BasePackages.class) 设置了BeanClass 类型,具体源码如下:

        //**创建bean定义
		GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
		//**class为BasePackages
		beanDefinition.setBeanClass(BasePackages.class);
		//**设置packageNames为构造函数参数值
		beanDefinition.getConstructorArgumentValues().addIndexedArgumentValue(0, packageNames);
		beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
		//**注册名称为AutoConfigurationPackages的bean
		registry.registerBeanDefinition(BEAN, beanDefinition);

这里BasePackages.class表示,Spring项目启动时就会加载该包和其子包的带有特定注解的类到Spring容器中。这些注解有@Component、@Bean、@Controller、@Service、@Repository等等。

呵呵, 这里扯得太远了。

再一次, 回到 AutoConfigurationPackages.register() ,具体作用如下:

  1. 注册自动配置包AutoConfigurationPackages.Registrar 在 Spring Boot 应用程序启动时会被触发,它会扫描当前应用程序的主类所在的包及其子包,并将这些包路径注册为自动配置包。这样一来,Spring Boot 就知道在哪些包下寻找自动配置类。

  2. 启用自动配置类的扫描:注册自动配置包后,Spring Boot 将会在这些包下扫描并加载自动配置类。自动配置类是通过条件化注册机制实现的,它们会根据条件来判断是否需要注册到 Spring 容器中。

总的来说,AutoConfigurationPackages.Registrar 的作用是为自动配置包注册 BeanDefinition,从而告诉 Spring Boot 在哪些包下扫描并加载自动配置类。这是 Spring Boot 实现自动配置的重要步骤之一。

源码解析:AutoConfigurationImportSelector 类

再一次看 @EnableAutoConfiguration 注解


@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
  String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
  ... ...
}

@EnableAutoConfiguration 也是一个组合注解, 通过 @Import 给容器导入 AutoConfigurationImportSelector 组件。

AutoConfigurationImportSelector 是 Spring Boot 中的一个核心组件。

简单来说,AutoConfigurationImportSelector 用于实现自动配置的选择和导入。

AutoConfigurationImportSelector` 的主要作用是根据类路径上的条件和配置,选择性地导入自动配置类,以便根据应用程序的需求进行自动配置。

具体来说,AutoConfigurationImportSelector 实现了 ImportSelector 接口,其中的 selectImports() 方法用于选择需要导入的自动配置类。

Spring Boot 在启动时会自动调用 AutoConfigurationImportSelector,根据一系列条件来决定需要导入哪些自动配置类。

AutoConfigurationImportSelector 主要的工作包括:

  1. 根据条件选择自动配置类:根据一系列条件,如类路径上的 jar 包、配置文件、注解等,决定需要导入哪些自动配置类。这些条件包括 @ConditionalOnClass@ConditionalOnMissingClass@ConditionalOnBean@ConditionalOnMissingBean 等注解所定义的条件。
  2. 执行条件判断:根据条件判断是否需要导入特定的自动配置类。如果满足条件,则将自动配置类的全限定名添加到结果列表中。
  3. 返回结果:最终返回一个包含了需要导入的自动配置类的数组,供 Spring Boot 自动配置机制使用。

通过 AutoConfigurationImportSelector,Spring Boot 实现了自动配置的机制,能够根据应用程序的需求,动态地选择性地导入自动配置类,以简化应用程序的配置和部署过程。

AutoConfigurationImportSelector 将所有需要导入到组件以全类名的方式返回,这些组件会被添加到容器,会给容器导入很多自动配置类(xxxAutoConfiguration),这就避免了我们手动编写配置注入功能的工作。

重点看下 AutoConfigurationImportSelector 这个类:

public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware,
		ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {
  ... ...
}

public interface DeferredImportSelector extends ImportSelector {
  ... ...
}

public interface ImportSelector {
  // 获取所有符合条件的类的全限定类名
  String[] selectImports(AnnotationMetadata importingClassMetadata);
}

AutoConfigurationImportSelector 类实现了 ImportSelector 接口,也就实现了这个接口中的 selectImports 方法,该方法主要用于获取所有符合条件的类的全限定类名,这些类需要被加载到 IoC 容器中。

简单看下 AutoConfigurationImportSelector 内部selectImports 的实现:

public String[] selectImports(AnnotationMetadata annotationMetadata) {
  	// 判断自动装配开关是否打开
		if (!isEnabled(annotationMetadata)) {
			return NO_IMPORTS;
		}
  	// 获取所有需要装配的bean
		AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata);
		return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
	}

在 Spring 中,ImportSelector 接口是用于编程式地选择并导入其他配置类的一种方式。AutoConfigurationImportSelector 就是一个典型的实现了 ImportSelector 接口的类,它根据一系列条件选择性地导入自动配置类,实现了自动配置的机制。

selectImports 方法是 AutoConfigurationImportSelector 接口的一个 实现方法,用于选择需要导入的配置类或者普通的 Java 类。 具体来说,selectImports 方法有以下作用:

  1. 选择需要导入的配置类:在 selectImports 方法中,可以编写逻辑来根据一定的条件选择需要导入的配置类。这些条件可以是任何自定义的逻辑,如类路径上的条件、环境属性、配置文件等。
  2. 返回导入的配置类selectImports 方法返回一个字符串数组,数组中包含了需要导入的配置类的全限定名。这些配置类将会被 Spring 容器自动导入,并纳入容器的管理。
  3. 灵活地实现自定义的导入逻辑:通过实现 selectImports 方法,可以实现灵活的自定义逻辑,根据应用程序的需求选择性地导入配置类。这样可以使得配置更加灵活、可扩展。

上面的代码,用到selectImports, 这里我们重点看下 getAutoConfigurationEntry 这个方法,

getAutoConfigurationEntry 主要负责加载自动配置类,核心调用链路如下:

在这里插入图片描述

我们来分析一下具体源码内容:

protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
  	// 判断自动装配开关是否打开,默认打开,spring.boot.enableautoconfiguration=true,可以在配置文件配置
		if (!isEnabled(annotationMetadata)) {
			return EMPTY_ENTRY;
		}
  	// 获取 @EnableAutoConfiguration 注解的 exclude 和 excludeName
		AnnotationAttributes attributes = getAttributes(annotationMetadata);
  	// 读取 spring.factories 文件的全类名,这里会读取所有 starter 下的该文件
		List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
		configurations = removeDuplicates(configurations);
		Set<String> exclusions = getExclusions(annotationMetadata, attributes);
		checkExcludedClasses(configurations, exclusions);
		configurations.removeAll(exclusions);
  	// 这里就是 @Conditional 条件注解生效的地方,过滤需要注入的 bean
		configurations = getConfigurationClassFilter().filter(configurations);
		fireAutoConfigurationImportEvents(configurations, exclusions);
		return new AutoConfigurationEntry(configurations, exclusions);
	}

getAutoConfigurationEntry 用于获取自动配置条目(AutoConfigurationEntry)的信息。

具体来说,getAutoConfigurationEntry 方法的作用是根据条件获取自动配置条目的信息,其中,自动配置条目包含了需要导入的自动配置类以及自动配置条件信息,最终生成需要导入的自动配置类的条目信息。

自动配置条件信息,如 @ConditionalOnClass@ConditionalOnBean 等注解所定义的条件,可以根据这些条件,判断需要导入哪些自动配置类。getAutoConfigurationEntry 方法就是用来获取这些自动配置条目的信息,以便后续的处理和导入。

在这里插入图片描述

getAutoConfigurationEntry 返回的自动配置条目,通常包括了自动配置类的全限定名,以及与条件判断相关的信息。

面试第一问:什么是 SpringBoot 自动装配?

在面试中,回答这个问题,一定要抓住关键点:

  • 自动装配底层实现:组合注解,每个注解的作用。
  • 核心的类 AutoConfigurationImportSelector#selectImports,会返回要导入容器的全类名,数组形式的。
  • 获取需要自动装配的配置类,读取 META-INF/spring.factories 文件。
  • 通过条件注解过滤读取的配置类,条件满足的类,才去加载 bean。

把这几个关键点说清楚,面试基本上没有什么问题。

面试第二问:如何控制一个Bean是否加载? 使用什么注解?

如果说 Spring 没有添加其他附加条件,这些配置中定义的 Bean ,都会被一股脑儿导入进 Spring 容器里,这样是非常消耗内存的。因此提供了很多条件注解,可以让我们控制某一个配置是否生效。

除了避免一次性全部加载全量的bean导致内存被无效消耗之外,Spring Boot 中的条件注解(Conditional Annotations)的存在,还有几个主要原因:

  1. 灵活配置:Spring Boot 需要在不同的环境下提供灵活的配置选项,以满足各种需求。条件注解允许根据运行时环境、类路径上的类是否存在、系统属性等条件来决定是否启用或禁用特定的配置。
  2. 依赖配置:条件注解,能根据应用程序的类路径和依赖关系,自动配置需要的bean。条件注解允许仅在满足依赖条件时配置,没有满足依赖条件场景,不进行bean配置。
  3. 模块化:Spring Boot 可以配置大量的功能模块 Feature Module,但不是每个应用都需要所有的功能模块 Feature Module。通过条件注解,可以根据应用的需求选择性地导入或排除特定的Feature Module,使应用更加轻量级和高效。

条件注解是 Spring Boot 中的一种重要机制,它提供了灵活的配置选项和自动配置功能,使得应用程序能够根据不同的条件进行定制和优化。

SpringBoot条件注解

Spring 为我们提供了条件化注解,可以让我们控制 bean 在某种条件下才加载,主要就是 @Conditional 注解,通过指定条件,然后根据条件结果执行。

Spring 还为我们提供了一些已有的条件可以让我们直接使用:

  1. @ConditionalOnClass:当类路径中存在指定的类时生效。
  2. @ConditionalOnMissingClass:当类路径中不存在指定的类时生效。
  3. @ConditionalOnBean:当容器中存在指定的 Bean 时生效。
  4. @ConditionalOnMissingBean:当容器中不存在指定的 Bean 时生效。
  5. @ConditionalOnProperty:当指定的配置属性存在且值符合条件时生效。
  6. @ConditionalOnResource:当类路径下存在指定资源文件时生效。
  7. @ConditionalOnWebApplication:当应用是 Web 应用时生效。
  8. @ConditionalOnNotWebApplication:当应用不是 Web 应用时生效。

这些条件注解可以用于配置类、自动配置类以及普通的 Bean 定义上,通过设置不同的条件,可以灵活地控制 Spring Boot 中的配置和自动配置行为。

@ConditionalOnClass

@ConditionalOnClass 用于指定当类路径中存在某个特定的类时,才会生效。

如果类路径中不存在指定的类,则该配置不生效。

这个注解通常用于自动配置类或者普通的配置类上,用于根据特定的类是否存在来决定是否应用该配置。

下面是一个@ConditionalOnClass 的简单示例:

@Configuration
@ConditionalOnClass(name = "com.crazymaker.circle.Foo")
public class MyConfiguration {

    @Bean
    public MyBean myBean() {
        return new MyBean();
    }
}

在这个示例中,MyConfiguration 是一个配置类,使用了 @ConditionalOnClass 注解。

当类路径中存在 com.crazymaker.circle.Foo 这个类时,MyConfiguration 配置类才会生效,从而将 MyBean Bean 注册到容器中。

如果类路径中不存在 com.crazymaker.circle.Foo 这个类,则 MyConfiguration 配置类不会生效,MyBean 也不会被注册到容器中。

@ConditionalOnMissingClass

@ConditionalOnMissingClass 用于指定当类路径中不存在某个特定的类时,才会生效。

如果类路径中存在指定的类,则该配置不生效。

下面是一个@ConditionalOnMissingClass 的简单示例:

javaCopy code@Configuration
@ConditionalOnMissingClass(name = "com.crazymaker.circle.Foo")
public class MyConfiguration {

    @Bean
    public MyBean myBean() {
        return new MyBean();
    }
}

在这个示例中,当类路径中不存在 com.crazymaker.circle.Foo 这个类时,MyConfiguration 配置类才会生效,从而将 MyBean Bean 注册到容器中。

如果类路径中存在 com.crazymaker.circle.Foo 这个类,则 MyConfiguration 配置类不会生效,MyBean 也不会被注册到容器中。

@ConditionalOnBean

@ConditionalOnBean 用于指定当容器中存在某个特定的 Bean 时,才会生效。如果容器中不存在指定的 Bean,则该配置不生效。

这个注解通常用于自动配置类或者普通的配置类上,用于根据容器中是否存在指定的 Bean 来决定是否应用该配置。

下面是一个简单的示例:

@Configuration
public class DependencyConfiguration {

    @Bean
    public MyDependency myDependency() {
        return new MyDependency();
    }

}



@Configuration
public class MyConfiguration {

    @Bean
    @ConditionalOnBean(MyDependency.class)
    public MyBean myBean(MyDependency myDependency) {
        return new MyBean(myDependency);
    }
}

在这个示例中,定义了两个 Bean:myDependencymyBean

myBean 方法上使用了 @ConditionalOnBean(MyDependency.class) 注解,

表示当容器中存在 MyDependency 类型的 Bean 时,myBean 方法才会生效,从而将 MyBean Bean 注册到容器中。

如果容器中不存在 MyDependency 类型的 Bean,则 myBean 方法不会生效,MyBean 也不会被注册到容器中。

@ConditionalOnProperty

@ConditionalOnProperty用于指定当指定的配置属性存在且值符合条件时,才会生效。如果指定的配置属性不存在或者值不符合条件,则该配置不生效。

这个注解通常用于自动配置类或者普通的配置类上,用于根据配置属性的存在和值来决定是否应用该配置。

下面是一个简单的示例:

@Configuration
@ConditionalOnProperty(name = "myapp.feature.enabled", havingValue = "true", matchIfMissing = true)
public class MyConfiguration {

    @Bean
    public MyBean myBean() {
        return new MyBean();
    }
}

在这个示例中,MyConfiguration 使用了 @ConditionalOnProperty 注解。

@ConditionalOnProperty 注解用到三个属性:

  • 一个配置属性 name为 myapp.feature.enabled
  • 一个配置属性 havingValue 为 true
  • 一个配置属性 matchIfMissing 为 true

这表示当应用程序的配置文件中,如果存在 myapp.feature.enabled=true 的配置属性时,MyConfiguration 配置类才会生效,从而将 MyBean Bean 注册到容器中。

如果应用配置文件中不存在 myapp.feature.enabled 这个属性,或者值不是 true,则 MyConfiguration 配置类不会生效,MyBean 也不会被注册到容器中。

此外,@ConditionalOnProperty还有一个matchIfMissing 属性,表示如果配置文件中不存在该属性,是否默认生效。

在这个示例中,matchIfMissing 设置为 true 表示如果配置文件中不存在该属性,也认为条件匹配,配置生效。

以上是三个重要的条件注解,其他的都是类似的,大家顾名思义就OK了。

如何自定义一个 SpringBoot starter?

Spring Boot 的自动装配机制使得模块化和复用化更加便捷和高效。

开发者可以根据项目的需求选择性地引入功能模块,同时也可以将常用的功能封装成 Starter,以供其他项目复用。

SpringBoot 这种模块化和复用化的设计理念使得 Spring Boot 在开发中得到了广泛的应用和认可。

SpringBoot Starter用于简化开发过程并提供各种功能的集成,Spring Boot 社区提供了许多常用的 Starter,常用的如下:

  1. spring-boot-starter-web:用于构建 Web 应用程序的 Starter,包括 Spring MVC、Embedded Tomcat、Jackson、Validation 等。
  2. spring-boot-starter-data-jpa:用于集成 Spring Data JPA 和 Hibernate 的 Starter,用于访问和操作关系型数据库。
  3. spring-boot-starter-data-mongodb:用于集成 Spring Data MongoDB 的 Starter,用于访问和操作 MongoDB 数据库。
  4. spring-boot-starter-security:用于集成 Spring Security 的 Starter,提供身份验证和授权功能。
  5. spring-boot-starter-test:用于测试 Spring Boot 应用程序的 Starter,包括 JUnit、Spring Test、Spring Boot Test 等。
  6. spring-boot-starter-actuator:用于监控和管理 Spring Boot 应用程序的 Starter,包括健康检查、指标、日志级别设置等。
  7. spring-boot-starter-log4j2:用于集成 Log4j2 日志框架的 Starter,用于记录应用程序日志。
  8. spring-boot-starter-mail:用于集成邮件发送功能的 Starter,包括 JavaMail 和 Spring Framework 的邮件支持。
  9. spring-boot-starter-cache:用于集成缓存支持的 Starter,包括 Spring 缓存抽象和常见的缓存实现,如 Ehcache、Redis、Caffeine 等。
  10. spring-boot-starter-actuator:用于添加生产就绪功能,如指标、健康检查、审计、HTTP追踪等。

这些 Starter 提供了各种功能的快速集成和配置,能够大大加速 Spring Boot 应用程序的开发过程,并且得益于 Spring Boot 的自动配置机制,使用这些 Starter 通常可以减少很多繁琐的配置工作。

如果 SpringBoot 官方的starter不够用,那么,如何自定义一个 starter ?

要自定义一个 Spring Boot Starter,需要遵循一些约定和最佳实践,以确保咱们的额Starter 能够被其他人方便地使用,并且与 Spring Boot 生态系统的其他部分无缝集成。

下面是创建一个自定义 Spring Boot Starter 的一般步骤:

  1. 定义 Starter 模块:创建一个 Maven 或 Gradle 项目,作为咱们的 Starter 模块。该模块应该包含要提供的功能、配置和依赖项。
  2. 创建自动配置类:编写一个自动配置类,该类负责配置和初始化你的 Starter 提供的功能。这个类通常需要用 @Configuration 和其他条件注解(如 @ConditionalOnClass@ConditionalOnBean 等)标记。
  3. 提供默认配置:通过 META-INF/spring.factories 文件中提供默认的配置类。
  4. 创建 Starter 类:创建一个类作为 Starter 的入口点。这个类通常会提供一些便捷的方法,以便用户能够轻松地在其项目中使用你的 Starter。
  5. 提供文档和示例:编写文档说明你的 Starter 的用法和配置方式,并提供示例代码。
  6. 发布到 Maven 仓库:将你的 Starter 打包,并发布到 Maven 中央仓库或其他公共或私有的 Maven 仓库中,以便其他用户可以方便地引用和使用。

1.新建 SpringBoot 项目,引入 SpringBoot 相关依赖

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
            <optional>true</optional>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
    </dependencies>

2.创建 @Configuration 配置类,在配置类里面声明要注入的 Bean,还可以结合 @Conditional 条件注解,按需加载

package com.example.mystarter;

import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.context.annotation.Configuration;

@Configuration
@ConditionalOnClass(MyStarterService.class)
public class MyStarterAutoConfiguration {

    // 自动配置的内容,例如注册 Bean、初始化等
}

MyStarterAutoConfiguration.java:自动配置类,用于配置和初始化 Starter 提供的功能。

这个类通常需要用 @Configuration 和其他条件注解(如 @ConditionalOnClass@ConditionalOnBean 等)标记。

如果有需求,可以 写一个 MyStarterProperties.java 用于定义 Starter 配置属性的 POJO 类。

package com.example.mystarter;

import org.springframework.boot.context.properties.ConfigurationProperties;

@ConfigurationProperties(prefix = "mystarter")
public class MyStarterProperties {

    // Starter 的配置属性
}

3.在项目的 resources 包下创建 META-INF/spring.factories 文件,并且配置类的全限定名

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
  com.example.mystarter.MyStarterAutoConfiguration

4.再其他工程引用自定义的 starter 即可

<dependency>
    <groupId>com.warm</groupId>
    <artifactId>custom-starter</artifactId>
    <version>0.0.1-SNAPSHOT</version>
</dependency>

下面是这一个简单starter的示例结构:

codemy-spring-boot-starter/
  ├── src/
  │   ├── main/
  │   │   ├── java/
  │   │   │   └── com/
  │   │   │       └── example/
  │   │   │           └── mystarter/
  │   │   │               ├── MyStarterAutoConfiguration.java
  │   │   │               └── MyStarterProperties.java
  │   │   └── resources/
  │   │       └── META-INF/
  │   │           └── spring.factories
  │   └── test/
  │       └── java/
  │           └── com/
  │               └── example/
  │                   └── mystarter/
  │                       └── MyStarterAutoConfigurationTest.java
  ├── pom.xml
  └── README.md

在这个示例中:

  • MyStarterAutoConfiguration 类是自动配置类,
  • MyStarterProperties 类是用于定义属性的 POJO 类。
  • spring.factories 文件包含默认配置类的声明。‘
  • 最后,README.md 文件提供了对 Starter 的说明和用法。

创建一个自定义的 Spring Boot Starter 的步骤如上,另外,建议在开始定义之前,先查阅 Spring Boot 官方文档,并参考一些现有的 Starter 项目以获取灵感和最佳实践。

Spring Boot 的自动配置和Spring run方法的关系

这里多提一嘴,Spring Boot 的自动配置与 Spring run 方法关系密切。

Spring run 方法是 Spring Boot 应用程序的入口。

Spring run 负责启动 Spring 应用程序并执行一些初始化操作。而自动配置则是 Spring Boot 提供的一种机制,用于根据类路径和依赖关系自动配置应用程序的功能。

Spring run 方法通常位于一个包含 @SpringBootApplication 注解的类中,如下所示:

javaCopy codeimport org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class MyApplication {

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

在这个示例中,MyApplication 类上标记了 @SpringBootApplication 注解,

该注解包含了 @EnableAutoConfiguration@ComponentScan@Configuration 注解。

其中,@EnableAutoConfiguration 是启用 Spring Boot 的自动配置机制的关键,它会自动扫描类路径上的各种配置类,并根据条件自动配置应用程序的功能。

因此,当调用 SpringApplication.run(MyApplication.class, args) 方法启动 Spring Boot 应用程序时,Spring Boot 将会自动扫描和加载类路径上的各种自动配置类,并根据条件来决定是否应用这些配置。

这使得开发者能够在不编写繁琐的配置代码的情况下,快速构建和部署功能强大的 Spring Boot 应用程序。

总之,Spring Boot 的自动配置机制通过 @EnableAutoConfiguration 注解和 run 方法的调用实现了紧密的集成,使得开发者能够轻松地构建和启动功能丰富的 Spring Boot 应用程序。

关于启动类 mian 方法中的 Spring run方法 方法的作用,以及它做了哪些事情。

1、推断应用的类型是普通的项目还是 Web 项目
2、查找并加载所有可用初始化器 , 设置到 initializers 属性中
3、找出所有的应用程序监听器,设置到 listeners 属性中
4、推断并设置 main 方法的定义类,找到运行的主类

通过一个张图,来带大家简单梳理一下这个过程, 下一次尼恩架构团队给大家 来一版惊天地、泣鬼神的Spring run方法 原理和源码解析 :

在这里插入图片描述

说在最后:有问题找老架构取经

Springboot起步依赖、自动装配相关的面试题,是非常重要的面试题。

如果能按照以上的内容,对答如流,如数家珍,基本上 面试官会被你 震惊到、吸引到。

最终,让面试官爱到 “不能自已、口水直流”。offer, 也就来了。

在面试之前,建议大家系统化的刷一波 5000页《尼恩Java面试宝典》V175,在刷题过程中,如果有啥问题,大家可以来 找 40岁老架构师尼恩交流。

另外,如果没有面试机会,可以找尼恩来帮扶、领路。

尼恩已经指导了大量的就业困难的小伙伴上岸,前段时间,帮助一个40岁+就业困难小伙伴拿到了一个年薪100W的offer,帮助小伙伴实现了 逆天改命

技术自由的实现路径:

实现你的 架构自由:

吃透8图1模板,人人可以做架构

10Wqps评论中台,如何架构?B站是这么做的!!!

阿里二面:千万级、亿级数据,如何性能优化? 教科书级 答案来了

峰值21WQps、亿级DAU,小游戏《羊了个羊》是怎么架构的?

100亿级订单怎么调度,来一个大厂的极品方案

2个大厂 100亿级 超大流量 红包 架构方案

… 更多架构文章,正在添加中

实现你的 响应式 自由:

响应式圣经:10W字,实现Spring响应式编程自由

这是老版本 《Flux、Mono、Reactor 实战(史上最全)

实现你的 spring cloud 自由:

Spring cloud Alibaba 学习圣经》 PDF

分库分表 Sharding-JDBC 底层原理、核心实战(史上最全)

一文搞定:SpringBoot、SLF4j、Log4j、Logback、Netty之间混乱关系(史上最全)

实现你的 linux 自由:

Linux命令大全:2W多字,一次实现Linux自由

实现你的 网络 自由:

TCP协议详解 (史上最全)

网络三张表:ARP表, MAC表, 路由表,实现你的网络自由!!

实现你的 分布式锁 自由:

Redis分布式锁(图解 - 秒懂 - 史上最全)

Zookeeper 分布式锁 - 图解 - 秒懂

实现你的 王者组件 自由:

队列之王: Disruptor 原理、架构、源码 一文穿透

缓存之王:Caffeine 源码、架构、原理(史上最全,10W字 超级长文)

缓存之王:Caffeine 的使用(史上最全)

Java Agent 探针、字节码增强 ByteBuddy(史上最全)

实现你的 面试题 自由:

4800页《尼恩Java面试宝典 》 40个专题

免费获取11个技术圣经PDF:

posted @ 2024-04-11 11:18  疯狂创客圈  阅读(548)  评论(0编辑  收藏  举报