ToB企服应用市场:ToB评测及商务社交产业平台

标题: Spring6框架中依赖注入的多种方式(推荐构造器注入) [打印本页]

作者: 拉不拉稀肚拉稀    时间: 2024-5-26 14:41
标题: Spring6框架中依赖注入的多种方式(推荐构造器注入)
你好,这里是codetrend专栏“Spring6全攻略”。
一个典型的企业应用程序不是由单个对象(或在Spring术语中称为bean)组成的。
纵然是最简单的应用程序也有一些对象一起工作,呈现给终极用户看到的内容形成一个连贯的应用程序。
要实现多个bean的连贯工作,这里就要使用到Spring的核心技术:依赖注入(DI)。
依赖注入(DI)是一种过程,对象通过构造函数参数、工厂方法的参数或在对象实例构建后设置的属性来定义它们的依赖关系(即与其一起工作的其他对象)。
容器在创建bean时注入这些依赖关系。这个过程根本上是bean本身不再通过直接构造类或使用Service Locator模式控制其依赖项的实例化或位置,因此被称为控制反转(Inversion of Control)。
遵循DI原则的代码更加清晰,对象提供其依赖关系时解耦更有效。
该对象不会查找其依赖项,也不知道依赖项的位置或种别。
因此类变得更易于测试,特殊是当依赖项是接口或抽象基类时,可以在单元测试中使用存根或模拟实现。
依赖注入有两种主要变体:基于构造函数的依赖注入和基于Setter的依赖注入。
基于构造函数的依赖注入

基于构造函数的依赖注入是Spring6中的一种依赖注入策略,主要用于确保在对象创建时其必需依赖已经得到初始化。
在构造函数注入中,对象的依赖关系明确地通过构造函数的参数通报给对象。
这意味着在实例化一个类时,Spring IoC容器会分析构造函数署名中的参数类型,然后从容器中查找并提供相匹配的bean作为依赖注入的目标对象。
下面的代码是一个完整的示例,展示了基于构造函数的依赖注入:
  1. import lombok.extern.slf4j.Slf4j;
  2. import org.springframework.context.ApplicationContext;
  3. import org.springframework.context.annotation.AnnotationConfigApplicationContext;
  4. import org.springframework.context.annotation.Bean;
  5. import org.springframework.context.annotation.Configuration;
  6. import java.util.Arrays;
  7. import java.util.List;
  8. /**
  9. * 基于构造函数的依赖注入
  10. * @author nine
  11. * @since 1.0
  12. */
  13. public class ConstructorDIDemo {
  14.     public static void main(String[] args) {
  15.         // 创建一个基于 Java Config 的应用上下文
  16.         ApplicationContext context = new AnnotationConfigApplicationContext(ConstructorAppConfig.class);
  17.         // 从上下文中获取名bean,其类型为PetStoreService
  18.         SimpleMovieLister bean = context.getBean(SimpleMovieLister.class);
  19.         // 调用获取的bean的方法
  20.         bean.listMovies();
  21.     }
  22. }
  23. /**
  24. * App配置
  25. */
  26. @Configuration
  27. class ConstructorAppConfig{
  28.     @Bean
  29.     public MovieFinder movieFinder() {
  30.         return new MovieFinder();
  31.     }
  32.     @Bean
  33.     public SimpleMovieLister simpleMovieLister(MovieFinder movieFinder) {
  34.         return new SimpleMovieLister(movieFinder);
  35.     }
  36. }
  37. /**
  38. * 服务代码
  39. */
  40. @Slf4j
  41. class SimpleMovieLister {
  42.     private final MovieFinder movieFinder;
  43.     public SimpleMovieLister(MovieFinder movieFinder) {
  44.         this.movieFinder = movieFinder;
  45.     }
  46.     public void listMovies() {
  47.         log.info("电影列表打印中");
  48.         movieFinder.findMovies().forEach(log::info);
  49.     }
  50. }
  51. @Slf4j
  52. class MovieFinder {
  53.     public List<String> findMovies() {
  54.         return Arrays.asList("电影1", "电影2", "电影3");
  55.     }
  56. }
复制代码
在Spring配置文件或Java配置类中,容器会根据构造函数参数类型找到符合条件的bean,并主动调用带有适当参数的构造函数来实例化SimpleMovieLister。这种方式的优势在于:
Spring6推荐优先使用构造函数注入,尤其是对于必需的、不可缺失的依赖。而对于可选依赖或易于变动的配置属性,则更得当使用setter方法注入。
基于Setter的依赖注入

基于Setter方法的依赖注入是Spring6框架中另一种常用的依赖注入策略。
它答应在对象实例化之后通过调用setter方法来设置依赖关系。
这种方法答应对象在构造完成后继承接受依赖注入,这在依赖不是必须的情况下特殊有用,因为对象可以先创建一个默认状态,然后再通过setter方法补充注入依赖。
把构造函数注入修改为如下代码,这是一个完整的示例,展示了基于Setter的依赖注入:
  1. import lombok.extern.slf4j.Slf4j;
  2. import org.springframework.beans.factory.annotation.Autowired;
  3. import org.springframework.context.ApplicationContext;
  4. import org.springframework.context.annotation.AnnotationConfigApplicationContext;
  5. import org.springframework.context.annotation.Bean;
  6. import org.springframework.context.annotation.Configuration;
  7. /**
  8. * 基于Setter的依赖注入
  9. * @author nine
  10. * @since 1.0
  11. */
  12. public class SetterDIDemo {
  13.     public static void main(String[] args) {
  14.         // 创建一个基于 Java Config 的应用上下文
  15.         ApplicationContext context = new AnnotationConfigApplicationContext(SetterAppConfig.class);
  16.         // 从上下文中获取名bean,其类型为PetStoreService
  17.         SimpleMovieListerSet bean = context.getBean(SimpleMovieListerSet.class);
  18.         // 调用获取的bean的方法
  19.         bean.listMovies();
  20.     }
  21. }
  22. /**
  23. * App配置
  24. */
  25. @Configuration
  26. class SetterAppConfig{
  27.     @Bean
  28.     public MovieFinder movieFinder() {
  29.         return new MovieFinder();
  30.     }
  31.     @Bean
  32.     public SimpleMovieListerSet simpleMovieLister() {
  33.         return new SimpleMovieListerSet();
  34.     }
  35. }
  36. @Slf4j
  37. class SimpleMovieListerSet {
  38.     private MovieFinder movieFinder;
  39.     @Autowired
  40.     public void setMovieFinder(MovieFinder movieFinder) {
  41.         this.movieFinder = movieFinder;
  42.     }
  43.     public void listMovies() {
  44.         log.info("电影列表打印中");
  45.         movieFinder.findMovies().forEach(log::info);
  46.     }
  47. }
复制代码
在这种情况下,Spring容器会在创建完SimpleMovieListerSet实例后,查找类型匹配的MovieFinder bean,并调用setMovieFinder()方法将其注入。
setter注入的优点包括:
然而,相比于构造函数注入,setter注入的一个潜在缺点是大概导致对象在未完全初始化时就被使用,增加了代码明白和维护的难度,以及大概引入运行时错误的风险。
其它依赖注入方式

属性注入是指直接在类的成员变量上使用@Autowired或@Inject注解来声明依赖。Spring容器会在bean初始化时主动为这些字段赋值。例如:
  1. public class UserService {
  2.     @Autowired
  3.     private UserRepository userRepository;
  4.     // ...
  5. }
复制代码
方法注入答应在非构造函数的方法中注入依赖。这包括像Spring Test框架中测试方法的参数注入,以及在方法级别处理依赖,如Spring的@PostConstruct、@PreDestroy生命周期回调方法。例如:
  1. @Component
  2. public class MyService {
  3.     private SomeDependency someDependency;
  4.     @Autowired
  5.     public void init(SomeDependency someDependency) {
  6.         this.someDependency = someDependency;
  7.     }
  8.     // ...
  9. }
复制代码
使用@Configuration、@Bean等注解编写Java配置类,以声明式的方式来定义bean及其依赖关系。例如:
  1. @Configuration
  2. public class AppConfig {
  3.     @Bean
  4.     public UserService userService(UserRepository userRepository) {
  5.         return new UserService(userRepository);
  6.     }
  7.     @Bean
  8.     public UserRepository userRepository() {
  9.         return new UserRepositoryImpl();
  10.     }
  11. }
复制代码
Spring同时支持JSR-330规范中的注解,如@javax.inject.Inject,可以用它代替Spring的@Autowired来实现依赖注入。
Dependency Resolution Process 依赖注入剖析过程

Spring框架中的依赖注入剖析过程主要包括以下几个步骤:
配置元数据加载
Bean定义注册
依赖剖析
依赖注入
Bean生命周期管理
整个过程表现了控制反转(IoC)的原则,Spring容器饰演了协调者角色,负责创建、装配和管理应用程序中的所有对象,使得对象之间相互解耦,进步了代码的可测试性和可维护性。
整个过程都包罗在 BeanFactory 中,这里的代码示例就是这行代码 ApplicationContext context = new AnnotationConfigApplicationContext(SetterAppConfig.class);。
  1. // 构造函数分为3个步骤
  2. public AnnotationConfigApplicationContext(Class<?>... componentClasses) {
  3.     this();
  4.     register(componentClasses);
  5.     refresh();
  6. }
  7. //在this()初始化Spring相关的工具库,一个reader和一个scanner
  8. public AnnotationConfigApplicationContext() {
  9.     StartupStep createAnnotatedBeanDefReader = getApplicationStartup().start("spring.context.annotated-bean-reader.create");
  10.     this.reader = new AnnotatedBeanDefinitionReader(this);
  11.     createAnnotatedBeanDefReader.end();
  12.     this.scanner = new ClassPathBeanDefinitionScanner(this);
  13. }
  14. // register(componentClasses); 是代码的核心,注册配置类里面的相关信息,主要调用了私有方法doRegisterBean
复制代码
doRegisterBean的核心代码如下:
[code]// 1. 加载配置元数据// 此方法负责将给定的类转换为AnnotatedGenericBeanDefinition,从而提取类上的元数据信息private  void doRegisterBean(Class beanClass, @Nullable String name,        @Nullable Class




欢迎光临 ToB企服应用市场:ToB评测及商务社交产业平台 (https://dis.qidao123.com/) Powered by Discuz! X3.4