你好,这里是codetrend专栏“Spring6全攻略”。
一个典型的企业应用程序不是由单个对象(或在Spring术语中称为bean)组成的。
纵然是最简单的应用程序也有一些对象一起工作,呈现给终极用户看到的内容形成一个连贯的应用程序。
要实现多个bean的连贯工作,这里就要使用到Spring的核心技术:依赖注入(DI)。
依赖注入(DI)是一种过程,对象通过构造函数参数、工厂方法的参数或在对象实例构建后设置的属性来定义它们的依赖关系(即与其一起工作的其他对象)。
容器在创建bean时注入这些依赖关系。这个过程根本上是bean本身不再通过直接构造类或使用Service Locator模式控制其依赖项的实例化或位置,因此被称为控制反转(Inversion of Control)。
遵循DI原则的代码更加清晰,对象提供其依赖关系时解耦更有效。
该对象不会查找其依赖项,也不知道依赖项的位置或种别。
因此类变得更易于测试,特殊是当依赖项是接口或抽象基类时,可以在单元测试中使用存根或模拟实现。
依赖注入有两种主要变体:基于构造函数的依赖注入和基于Setter的依赖注入。
基于构造函数的依赖注入
基于构造函数的依赖注入是Spring6中的一种依赖注入策略,主要用于确保在对象创建时其必需依赖已经得到初始化。
在构造函数注入中,对象的依赖关系明确地通过构造函数的参数通报给对象。
这意味着在实例化一个类时,Spring IoC容器会分析构造函数署名中的参数类型,然后从容器中查找并提供相匹配的bean作为依赖注入的目标对象。
下面的代码是一个完整的示例,展示了基于构造函数的依赖注入:- import lombok.extern.slf4j.Slf4j;
- import org.springframework.context.ApplicationContext;
- import org.springframework.context.annotation.AnnotationConfigApplicationContext;
- import org.springframework.context.annotation.Bean;
- import org.springframework.context.annotation.Configuration;
- import java.util.Arrays;
- import java.util.List;
- /**
- * 基于构造函数的依赖注入
- * @author nine
- * @since 1.0
- */
- public class ConstructorDIDemo {
- public static void main(String[] args) {
- // 创建一个基于 Java Config 的应用上下文
- ApplicationContext context = new AnnotationConfigApplicationContext(ConstructorAppConfig.class);
- // 从上下文中获取名bean,其类型为PetStoreService
- SimpleMovieLister bean = context.getBean(SimpleMovieLister.class);
- // 调用获取的bean的方法
- bean.listMovies();
- }
- }
- /**
- * App配置
- */
- @Configuration
- class ConstructorAppConfig{
- @Bean
- public MovieFinder movieFinder() {
- return new MovieFinder();
- }
- @Bean
- public SimpleMovieLister simpleMovieLister(MovieFinder movieFinder) {
- return new SimpleMovieLister(movieFinder);
- }
- }
- /**
- * 服务代码
- */
- @Slf4j
- class SimpleMovieLister {
- private final MovieFinder movieFinder;
- public SimpleMovieLister(MovieFinder movieFinder) {
- this.movieFinder = movieFinder;
- }
- public void listMovies() {
- log.info("电影列表打印中");
- movieFinder.findMovies().forEach(log::info);
- }
- }
- @Slf4j
- class MovieFinder {
- public List<String> findMovies() {
- return Arrays.asList("电影1", "电影2", "电影3");
- }
- }
复制代码 在Spring配置文件或Java配置类中,容器会根据构造函数参数类型找到符合条件的bean,并主动调用带有适当参数的构造函数来实例化SimpleMovieLister。这种方式的优势在于:
- 确保对象实例化时就有所有的必需依赖项,增强了对象状态的完整性。
- 由于构造函数私有的强制性依赖无法为null,进步了代码健壮性。
- 有利于实现不可变对象,也就是在属性上面加了final修饰符,提拔多线程环境下对象的安全性。
- 使得依赖关系清晰可见,利于阅读和明白代码。
Spring6推荐优先使用构造函数注入,尤其是对于必需的、不可缺失的依赖。而对于可选依赖或易于变动的配置属性,则更得当使用setter方法注入。
基于Setter的依赖注入
基于Setter方法的依赖注入是Spring6框架中另一种常用的依赖注入策略。
它答应在对象实例化之后通过调用setter方法来设置依赖关系。
这种方法答应对象在构造完成后继承接受依赖注入,这在依赖不是必须的情况下特殊有用,因为对象可以先创建一个默认状态,然后再通过setter方法补充注入依赖。
把构造函数注入修改为如下代码,这是一个完整的示例,展示了基于Setter的依赖注入:- import lombok.extern.slf4j.Slf4j;
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.context.ApplicationContext;
- import org.springframework.context.annotation.AnnotationConfigApplicationContext;
- import org.springframework.context.annotation.Bean;
- import org.springframework.context.annotation.Configuration;
- /**
- * 基于Setter的依赖注入
- * @author nine
- * @since 1.0
- */
- public class SetterDIDemo {
- public static void main(String[] args) {
- // 创建一个基于 Java Config 的应用上下文
- ApplicationContext context = new AnnotationConfigApplicationContext(SetterAppConfig.class);
- // 从上下文中获取名bean,其类型为PetStoreService
- SimpleMovieListerSet bean = context.getBean(SimpleMovieListerSet.class);
- // 调用获取的bean的方法
- bean.listMovies();
- }
- }
- /**
- * App配置
- */
- @Configuration
- class SetterAppConfig{
- @Bean
- public MovieFinder movieFinder() {
- return new MovieFinder();
- }
- @Bean
- public SimpleMovieListerSet simpleMovieLister() {
- return new SimpleMovieListerSet();
- }
- }
- @Slf4j
- class SimpleMovieListerSet {
- private MovieFinder movieFinder;
- @Autowired
- public void setMovieFinder(MovieFinder movieFinder) {
- this.movieFinder = movieFinder;
- }
- public void listMovies() {
- log.info("电影列表打印中");
- movieFinder.findMovies().forEach(log::info);
- }
- }
复制代码 在这种情况下,Spring容器会在创建完SimpleMovieListerSet实例后,查找类型匹配的MovieFinder bean,并调用setMovieFinder()方法将其注入。
setter注入的优点包括:
- 可以延迟注入可选依赖,答应类在没有所有依赖的情况下也能创建实例。
- 更容易适应配置变革,因为可以在运行时重新配置或更换已注入的依赖项。
- 有时间对于第三方类库或不能更改源代码的情况,如果只能通过setter暴露依赖,则setter注入大概是唯一可行的DI方式。
然而,相比于构造函数注入,setter注入的一个潜在缺点是大概导致对象在未完全初始化时就被使用,增加了代码明白和维护的难度,以及大概引入运行时错误的风险。
其它依赖注入方式
属性注入是指直接在类的成员变量上使用@Autowired或@Inject注解来声明依赖。Spring容器会在bean初始化时主动为这些字段赋值。例如:- public class UserService {
- @Autowired
- private UserRepository userRepository;
- // ...
- }
复制代码 方法注入答应在非构造函数的方法中注入依赖。这包括像Spring Test框架中测试方法的参数注入,以及在方法级别处理依赖,如Spring的@PostConstruct、@PreDestroy生命周期回调方法。例如:- @Component
- public class MyService {
- private SomeDependency someDependency;
- @Autowired
- public void init(SomeDependency someDependency) {
- this.someDependency = someDependency;
- }
- // ...
- }
复制代码
- 注解驱动的配置(Annotation-based Configuration)
使用@Configuration、@Bean等注解编写Java配置类,以声明式的方式来定义bean及其依赖关系。例如:- @Configuration
- public class AppConfig {
- @Bean
- public UserService userService(UserRepository userRepository) {
- return new UserService(userRepository);
- }
- @Bean
- public UserRepository userRepository() {
- return new UserRepositoryImpl();
- }
- }
复制代码
- JSR-330注解(Java Dependency Injection)
Spring同时支持JSR-330规范中的注解,如@javax.inject.Inject,可以用它代替Spring的@Autowired来实现依赖注入。
Dependency Resolution Process 依赖注入剖析过程
Spring框架中的依赖注入剖析过程主要包括以下几个步骤:
配置元数据加载:
- 应用程序启动时,Spring IoC容器首先读取和剖析配置元数据,这些元数据可以来自于XML配置文件、Java配置类(通过@Configuration注解)或组件类上的注解(如@Component、@Service、@Repository和@Controller等)。
Bean定义注册:
- 容器根据配置元数据创建Bean Definition对象,这些对象包罗了如何创建Bean的全部信息,如Bean的类型(类)、构造器参数、属性值、依赖关系和其他生命周期回调方法等。
依赖剖析:
- 当Spring容器创建一个Bean时,它会检察Bean Definition中关于依赖的描述。如果是构造器注入,容器会辨认并获取构造器参数所需的Bean,通过调用构造器来注入依赖。
- 如果是Setter注入,容器会在Bean实例化后遍历其setter方法,找到那些带有@Autowired或其他相干注解的setter方法,然后查找并注入相应的依赖Bean。
- 如果字段注入,容器则会直接找到类中带有@Autowired等注解的字段,为它们注入符合的Bean。
依赖注入:
- 容器根据Bean定义中定义的依赖关系,从IoC容器中查找或创建需要注入的Bean,并将这些依赖注入到目标Bean中。
- 注入过程中,容器会办理依赖的循环引用题目,保证依赖链的完整性,并可以处理多种作用域的Bean之间的依赖关系。
Bean生命周期管理:
- 容器除了注入依赖外,还会实行Bean生命周期的相干回调方法,如@PostConstruct和@PreDestroy等,以确保Bean在初始化和销毁时能正确实行相应操作。
整个过程表现了控制反转(IoC)的原则,Spring容器饰演了协调者角色,负责创建、装配和管理应用程序中的所有对象,使得对象之间相互解耦,进步了代码的可测试性和可维护性。
整个过程都包罗在 BeanFactory 中,这里的代码示例就是这行代码 ApplicationContext context = new AnnotationConfigApplicationContext(SetterAppConfig.class);。- // 构造函数分为3个步骤
- public AnnotationConfigApplicationContext(Class<?>... componentClasses) {
- this();
- register(componentClasses);
- refresh();
- }
- //在this()初始化Spring相关的工具库,一个reader和一个scanner
- public AnnotationConfigApplicationContext() {
- StartupStep createAnnotatedBeanDefReader = getApplicationStartup().start("spring.context.annotated-bean-reader.create");
- this.reader = new AnnotatedBeanDefinitionReader(this);
- createAnnotatedBeanDefReader.end();
- this.scanner = new ClassPathBeanDefinitionScanner(this);
- }
- // register(componentClasses); 是代码的核心,注册配置类里面的相关信息,主要调用了私有方法doRegisterBean
复制代码 doRegisterBean的核心代码如下:
[code]// 1. 加载配置元数据// 此方法负责将给定的类转换为AnnotatedGenericBeanDefinition,从而提取类上的元数据信息private void doRegisterBean(Class beanClass, @Nullable String name, @Nullable Class |