springboot 自动装配和bean注入原理及实现
装配:创建bean,并加入IOC容器。
注入:创建bean之间的依赖关系。
1、类自动装配
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 在开发中得到了广泛的应用和认可。
1.1 自动装配原理(那些bean会被自动装配)
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 ,
但是,在加载到时候,会同时加载这两个文件。
比如自己可以配置需要加入的bean并在类上使用@AutoConfiguration注解。包名和配置示例如下
可以通过条件注解的方式排除某些bean加载到容器中,节省启动时间和内存
1.2 自动装配的实现
1.2.1 自动装配实现
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。
1.2.2 向容器注入bean
主要方式见上面的自动装配,下来举例几个
2.2.1 @Component + @ComponentScan
SpringBoot启动类的@SpringBootApplication注解中就标注了 @ComponentScan注解,这个注解默认扫描当前包及其子包下的标注有@Component、@Controller、@Service、@Repository等的类加载到容器中。
2.2.2 @Import注解
2.2.3 @Configuration + @Bean
只需要在配置类上标注上@Configuration声明配置类,在某个方法上标注@Bean并返回一个需要注入的对象即可
@Configuration
public class MyBeanConfiguration {@Bean(name = "myBean")public MyBean initMyBean() {return new MyBean();}
}-------------------------------------------------@SpringBootApplication
@ComponentScan(basePackages = {"com.example.demo.*", "com.alibaba"}) // 需指明路径。
public class emptyDemoApplication {public static void main(String[] args) {ConfigurableApplicationContext context = SpringApplication.run(emptyDemoApplication.class, args);Object myBean = context.getBean("myBean");System.out.println(myBean); // MyBean(filedA=null, fieldB=null)}
2.1.4 手动注入实现接口BeanDefinitionRegistryPostProcessor
我们知道SpringBean的生命周期中有很多前后置方法,整体上可以概括为普通类对象转化为beanDefinition再转化为spring中的bean这么三个阶段。
而Spring会在启动的AbstratApplicationContxt类中的refresh方法中执行
invokeBeanFactoryPostProcessors,这个方法中会回调所有实现
BeanDefinitionRegistryPostProcessor接口的钩子方法。
可以简单理解成beanDefinition加载完毕之后,会对beanDefinition进行后置处理。所以理论上实现BeanDefinitionRegistryPostProcessor接口就可以手动将bean注入到容器中。
下面举例以手动注入HashMap为例:
public class TestBeanProcessor {public static void main(String[] args) {AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();UptownBeanProcessor beanDefinitionRegistryPostProcessor = new UptownBeanProcessor();applicationContext.addBeanFactoryPostProcessor(beanDefinitionRegistryPostProcessor);applicationContext.refresh();Object bean = applicationContext.getBean("test_map");System.out.println(bean);}
}
class UptownBeanProcessor implements BeanDefinitionRegistryPostProcessor {@Overridepublic void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.rootBeanDefinition(HashMap.class).getBeanDefinition();registry.registerBeanDefinition("test_map", beanDefinition);}@Overridepublic void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {}
}
1.3 自动装配的核心注解
Spring Boot 实现自动装配的几个核心注解包括:
- @SpringBootApplication:这是一个组合注解,相当于同时使用了 @Configuration、@EnableAutoConfiguration 和 - @ComponentScan 注解。它标记了主应用程序类,并告诉 Spring Boot 开始组件扫描、自动配置和装配。
- @EnableAutoConfiguration:该注解用于启用 Spring Boot 的自动配置功能。它会根据应用程序的依赖关系和当前环境,自动注册所需的 bean。
- @ComponentScan:该注解用于启用组件扫描,以便 Spring Boot 可以自动发现和注册标有 @Component、@Service、@Repository 和 @Controller 注解的类。
- @ConditionalOnClass 和 @ConditionalOnMissingClass:这两个条件化注解用于根据类路径上是否存在特定的类来决定是否注册 bean。@ConditionalOnClass 在类路径上存在指定类时生效,而 @ConditionalOnMissingClass 在类路径上不存在指定类时生效。
- @ConditionalOnBean 和 @ConditionalOnMissingBean:这两个条件化注解用于根据是否存在特定的 bean 来决定是否注册 bean。@ConditionalOnBean 在容器中存在指定的 bean 时生效,而 @ConditionalOnMissingBean 在容器中不存在指定的 bean 时生效。
- @ConditionalOnProperty:该条件化注解用于根据配置属性的值来决定是否注册 bean。它可以根据配置文件中的属性值来决定是否启用或禁用特定的 bean。
这些注解结合在一起,为 Spring Boot 提供了自动装配的能力。
从源码层面来说,Spring Boot 自动装配的核心实现,主要就是依赖 @SpringbootApplication 这个注解,它又是个组合注解,由3部分组成:
- @SpringBootConfiguration,标注是一个配置类
- @EnableAutoConfiguration,开启自动装配
@AutoConfigurationPackage,指定默认的包规则,将主程序类所在包及其子包下的组件扫描到容器里
@Import(AutoConfigurationImportSelector.class),导入AutoConfigurationImportSelector,并通过 selectImports 方法读取 META-INF/spring.factories 文件中配置的全类名,并按照条件过滤,注入需要的 bean - @ComponentScan,配置扫描路径,用来加载 bean
1.3.1 条件注解
SpringBoot条件注解
Spring 为我们提供了条件化注解,可以让我们控制 bean 在某种条件下才加载,主要就是 @Conditional 注解,通过指定条件,然后根据条件结果执行。
Spring 还为我们提供了一些已有的条件可以让我们直接使用:
@ConditionalOnClass:当类路径中存在指定的类时生效。
@ConditionalOnMissingClass:当类路径中不存在指定的类时生效。
@ConditionalOnBean:当容器中存在指定的 Bean 时生效。
@ConditionalOnMissingBean:当容器中不存在指定的 Bean 时生效。
@ConditionalOnProperty:当指定的配置属性存在且值符合条件时生效。
@ConditionalOnResource:当类路径下存在指定资源文件时生效。
@ConditionalOnWebApplication:当应用是 Web 应用时生效。
@ConditionalOnNotWebApplication:当应用不是 Web 应用时生效。
1.4 如何自定义一个 SpringBoot starter?
Spring Boot 的自动装配机制使得模块化和复用化更加便捷和高效。
开发者可以根据项目的需求选择性地引入功能模块,同时也可以将常用的功能封装成 Starter,以供其他项目复用。
SpringBoot 这种模块化和复用化的设计理念使得 Spring Boot 在开发中得到了广泛的应用和认可。
1.4.1 社区常用starter
SpringBoot Starter用于简化开发过程并提供各种功能的集成,Spring Boot 社区提供了许多常用的 Starter,常用的如下:
- spring-boot-starter-web:用于构建 Web 应用程序的 Starter,包括 Spring MVC、Embedded Tomcat、Jackson、Validation 等。
- spring-boot-starter-data-jpa:用于集成 Spring Data JPA 和 Hibernate 的 Starter,用于访问和操作关系型数据库。
- spring-boot-starter-data-mongodb:用于集成 Spring Data MongoDB 的 Starter,用于访问和操作 MongoDB 数据库。
- spring-boot-starter-security:用于集成 Spring Security 的 Starter,提供身份验证和授权功能。
- spring-boot-starter-test:用于测试 Spring Boot 应用程序的 Starter,包括 JUnit、Spring Test、Spring Boot Test 等。
- spring-boot-starter-actuator:用于监控和管理 Spring Boot 应用程序的 Starter,包括健康检查、指标、日志级别设置等。
- spring-boot-starter-log4j2:用于集成 Log4j2 日志框架的 Starter,用于记录应用程序日志。
- spring-boot-starter-mail:用于集成邮件发送功能的 Starter,包括 JavaMail 和 Spring Framework 的邮件支持。
- spring-boot-starter-cache:用于集成缓存支持的 Starter,包括 Spring 缓存抽象和常见的缓存实现,如 Ehcache、Redis、Caffeine 等。
- spring-boot-starter-actuator:用于添加生产就绪功能,如指标、健康检查、审计、HTTP追踪等。
1.4.2 自定义starter
基本的结构图
自定义步骤如下,一般是一个新建的module
1、新建 SpringBoot 项目,引入 SpringBoot 相关依赖
2、创建 @Configuration 配置类,在配置类里面声明要注入的 Bean,还可以结合 @Conditional 条件注解,按需加载
@Configuration
@ConditionalOnClass(MyStarterService.class)
public class MyStarterAutoConfiguration {// 自动配置的内容,例如注册 Bean、初始化等
}
3、在项目的 resources 包下创建 META-INF/spring/ 文件,并且配置类的全限定名
4、其他项目或者模块引入即可
<dependency><groupId>com.warm</groupId><artifactId>custom-starter</artifactId><version>0.0.1-SNAPSHOT</version>
</dependency>
2、bean的注入实现
就是将一个对象注入到另一个对象中,以便它们可以相互协作创建依赖关系,提高了代码的可维护性和可读性
2.1 注入原理
2.2 注入的几种实现方式
2.2.1 通过属性(字段)注入
注解@Autowired或@Resource
字段注入是一种不太常用的注入方式,它使用@Autowired或者@Resource注解直接将需要注入的Bean注入到类的字段中。下面是一个示例:
@Autowiredprivate ApplicationContext applicationContext;
-
优点
代码少,简洁明了。
新增依赖十分方便,不需要修改原有代码 -
缺点
容易出现空指针异常。
空指针异常不是必现的,与bean的实例化顺序有关。有时,把依赖的bean改个名字就会报空指针异常。
会出现循环依赖的隐患。
2.2.2 通过setter方法注入
Setter方法注入是另一种常用的注入方式。开发者可以在Bean类中定义Setter方法,并使用@Autowired注解将需要注入的Bean作为参数传入。Spring容器会自动调用这些Setter方法,并将Bean注入到它们中。下面是一个示例:
private AdminUserApi adminUserApi;//@Autowired是用在成员变量的Setter函数上。@Autowiredpublic void setAdminUserApi(AdminUserApi adminUserApi) {this.adminUserApi = adminUserApi;}
但是我的通过setter方法注入为null,我通过setter方法+对象传递
if (handler instanceof AbstractSliderDesensitizationHandler) {((AbstractSliderDesensitizationHandler<?>) handler).setAdminUserApi(adminUserApi);}
2.2.3 通过构造函数+final注入
是Springboot最为推荐的一种使用方式。
构造函数注入是最常用的注入方式之一。开发者可以在Bean类的构造函数中声明需要注入的Bean,并在应用程序启动时,Spring容器会自动将这些Bean注入到构造函数中。下面是一个简单的示例:
@Component
@SuppressWarnings("rawtypes")
public class StringDesensitizeSerializer extends StdSerializer<String> implements ContextualSerializer {private final ApplicationContext applicationContext;private final AdminUserApi adminUserApi;//@Autowiredpublic StringDesensitizeSerializer(ApplicationContext applicationContext, AdminUserApi adminUserApi) {super(String.class);this.applicationContext = applicationContext;this.adminUserApi = adminUserApi;}
结果注入成功,不是null
重点
- 不能提供无参构造方法,否则Springboot默认会加载无参的构造方法,Bean实例对象会为null
- Springboot官方建议使用final来修饰成员变量,然后通过构造方法来进行注入。原因:final修饰的成员变量是不能够被修改的;不加final虽然也能注入Bean,但是若被其他人修改为null,可能会导致不必要的问题,所以最好是加final。
若手工写构造方法觉得麻烦,也可以使用lombok中的 @RequiredArgsConstructor
@RequiredArgsConstructor
public class StringDesensitizeSerializer extends StdSerializer<String> implements ContextualSerializer {private final ApplicationContext applicationContext;private final AdminUserApi adminUserApi;}
2.2.4 通过Qualifier注解
该 注解可以指定注入的实现类(某接口多个 实现类)
@Service
public class MyService {private final MyRepository repository;@Autowiredpublic MyService(@Qualifier("myRepositoryImpl") MyRepository repository) {this.repository = repository;}
}
3、名词介绍
- 定义:Bean 是在 Spring 容器中被实例化、管理和维护的对象。一个 Bean 可以是任何普通的 Java 对象,例如 POJO、Service、Respository、Controller 等等。将一个类声明为 Bean 的方式可以是在类级别上使用 ‘@Component’ 注解或其派生注解(‘@Service’、‘@Repository’、‘@Controller’等),也可以是通过配置文件进行显式的声明。
-
实例化:Spring 容器负责实例化 Bean。当应用程序启动时,Spring 容器会根据配置信息或注解扫描的结果,找到并实例化所有被标记为 Bean 的类,并将它们加入容器中。实例化的过程由 Spring 的 IoC 容器负责。
-
管理:一旦 Bean 被实例化,Spring 容器将负责管理 Bean 的生命周期和依赖关系。它会根据配置文件或注解的信息,自动解决 Bean 之间的依赖关系,确保在需要的时候正确的注入依赖。Spring 容器还会负责销毁不再需要的 Bean。
-
依赖注入:依赖注入是 Spring 框架的一个重要特性,它允许通过自动或显式配置的方式将 Bean 的依赖项注入到其它 Bean 中。依赖注入可以通过构造函数注入、Setter 方法注入或字段注入的方式实现,其中最常见的是使用 ‘@Autowired’注解进行注入。
-
作用域:Spring 框架提供了多种作用域(scope)来管理 Bean 的生命周期。常见的作用域包括单例(Singleton)、原型(Prototype)、会话(Session)、请求(Request)等。默认情况下,Bean 是单例的,即每个容器中只存在一个实例。但可以根据需要配置其它作用域。
@Component // 默认为单例 -
自动装配:Spring Boot 支持自动装配(Auto - wiring),它能够根据类型或名称自动解析和注入依赖关系。通过在需要注入的字段、构造函数或 Setter 方法上使用 ‘@Autowired’ 注解,Spring 容器会自动查找并注入对应的 Bean。
@Component
public class MyService {
@Autowired
private MyBean myBean;// 使用myBean的代码…
}
总的来说,Bean 是 Spring 框架中被实例化、管理和维护的对象。通过在类上使用 ‘@Component’ 注解或其派生注解,将一个类声明为 Bean,并将其交给 Spring 容器处理。Spring 容器负责实例化、管理和维护 Bean 的生命周期和依赖关系。通过依赖注入和自动装配,应用程序可以方便的使用和管理 Bean。