Spring Boot中的依赖注入和自动注入
前言
以下内容为本人学习Spring Boot的依赖注入和自动注入
与ChatGpt提问后对其回答进行部分修改
(有的错误实在是离谱 = =)、格式调整等操作后的答案,可能对于其中部分细节(是错是对,能力有限有的看不出来 = =),并未做深入探究
,大家感兴趣的话可以自行验证。
详细讲讲依赖注入
依赖注入
(Dependency Injection
,简称DI
)的概念最早由Martin Fowler
在2004
年提出。依赖注入是一种软件设计模式
,旨在解耦和组织对象之间的依赖关系
,以提高代码的可维护性和可测试性
。
在传统的编程模式中,一个对象通常负责自己创建和管理所依赖的其他对象
。这种紧密耦合的设计模式会导致代码的脆弱性
,因为对象需要直接知道它所依赖的对象的实现细节
,并且对于变化会很敏感
(使用过传统编程模式的应该深有体会)。此外,这也会增加代码的复杂性
,使得对象之间的关系变得难以理解和维护
。
依赖注入的目标是通过将依赖对象的创建和管理责任从对象本身转移到外部容器中
,以实现松耦合和可维护性
。在依赖注入中,对象声明它所需要的依赖关系
,而不需要关心如何创建或获取
这些依赖对象。
依赖注入的核心思想是控制反转
(Inversion of Control,简称IoC
)。在传统的编程模式中,对象自己控制创建和管理
它所依赖的对象,而在依赖注入中,这种控制被反转了。对象不再自己创建依赖对象
,而是通过外部容器(通常是一个IoC容器)来创建和注入依赖对象
。
依赖注入可以通过以下几种方式实现
:
-
构造函数注入(Constructor Injection)
:对象通过构造函数
接收依赖对象作为参数
。在创建对象时,依赖对象会被传递给构造函数
进行注入。这种方式通常提供了更好的不变性和一致性
。 -
Setter方法注入(Setter Injection)
:对象通过Setter方法设置依赖对象
。在对象创建后,通过调用相应的Setter方法来注入依赖对象
。这种方式可以提供更灵活的注入方式,允许依赖对象在对象创建后被替换
。 -
接口注入(Interface Injection)
:对象通过实现特定接口,从而暴露用于注入依赖对象的方法
。容器在创建对象后,会调用接口方法并注入依赖对象
。
依赖注入的优点
包括:
-
松耦合
:依赖对象与被依赖对象之间的耦合度降低,对象的职责更加单一,易于理解和维护。 -
可测试性
:依赖注入使得测试变得更加容易。可以通过注入模拟对象(Mock Objects)来进行单元测试和模块测试
。 -
可重用性
:通过依赖注入,依赖对象可以被多个对象共享和重用
,避免了重复创建和管理相同的依赖对象。 -
可扩展性
:依赖注入使得应用程序的组件之间的关系更加灵活
,可以轻松添加、替换和升级依赖对象
。
现代的开发框架和容器(如Spring
框架、Spring Boot
和Google Guice
)提供了依赖注入
的实现。它们通过注解、配置文件或其他方式
,实现了依赖注入的自动化和便利性,使得开发者可以专注于业务逻辑的实现而不必过多关注依赖对象的创建和管理。
给出依赖的三种注入的方式的示例
当谈到依赖注入的三种主要方式时(构造函数注入、Setter方法注入和接口注入
),我将为你提供每种方式的示例代码。
构造函数注入(Constructor Injection)
的示例:
public class UserService {
private UserRepository userRepository;
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
// ...
}
在上面的示例中,UserService
类通过构造函数接收一个UserRepository
依赖对象,并将其赋值给成员变量userRepository
。这种方式可以在创建UserService
对象时,将UserRepository
的实例传递给构造函数来进行注入。
Setter方法注入(Setter Injection)
的示例:
public class UserService {
private UserRepository userRepository;
public void setUserRepository(UserRepository userRepository) {
this.userRepository = userRepository;
}
// ...
}
在上述示例中,UserService
类通过Setter方法 setUserRepository()
接收一个UserRepository
依赖对象,并将其赋值给成员变量userRepository
。这样,在创建UserService
对象后,可以调用setUserRepository()
方法来注入UserRepository
的实例。
接口注入(Interface Injection)
的示例:
public interface DiscountService {
void setDiscountProvider(DiscountProvider discountProvider);
}
public class DiscountServiceImpl implements DiscountService {
private DiscountProvider discountProvider;
@Override
public void setDiscountProvider(DiscountProvider discountProvider) {
this.discountProvider = discountProvider;
}
// ...
}
在上述示例中,DiscountService
是一个接口,定义了一个setDiscountProvider()
方法,用于接收DiscountProvider
依赖对象。DiscountServiceImpl
类实现了该接口,并通过实现方法setDiscountProvider()
来注入DiscountProvider
的实例。
这些示例展示了依赖注入的不同方式,通过构造函数、Setter方法或接口方法来接收依赖对象,并将其赋值给类的成员变量或属性。在实际应用中,通常会结合使用这些方式,根据具体的需求和场景选择适合的注入方式。
从概念上讲讲自动注入
自动注入(Autowired)
是依赖注入(Dependency Injection,DI)
的一种实现
方式,最早由Spring框架
引入并提出。
在传统的依赖注入方式
中,开发人员需要显式地在代码中声明依赖关系并手动进行依赖对象的注入
。这种方式需要编写大量的重复代码,并且容易出错。
为了简化依赖注入的过程
,Spring框架引入了自动注入的概念
。自动注入允许开发人员使用特定的注解来标记依赖对象
,而无需显式地进行依赖注入的代码编写
。框架会自动查找和注入匹配
的依赖对象,减少了手动注入的工作量,提高了开发效率。
自动注入的概念最早在Spring框架的早期版本
中被提出。通过在目标类的字段、构造函数或Setter方法上添加@Autowired注解
,Spring容器会在创建对象
时自动查找匹配的依赖对象
,并将其注入
到相应的位置。自动注入是根据类型进行匹配的
,如果存在多个匹配的依赖对象
,可以使用@Qualifier注解或通过名称
来指定具体的依赖对象。
自动注入带来了以下好处:
-
简化开发
:自动注入减少了手动注入的代码量,使代码更简洁和易于维护。 -
减少配置
:开发人员无需手动配置每个依赖对象的注入,框架会自动完成依赖的查找和注入。 -
提高可测试性
:通过自动注入,可以更容易地进行单元测试,可以轻松地替换依赖对象进行测试。
Spring框架的自动注入成为了它的核心特性之一
,并且受到了广泛的应用和认可
。后续的框架和技术也借鉴了Spring的自动注入概念,提供了类似的功能来简化依赖注入的过程。
Spring Boot中注解@Autowired,自动注入注解默认使用哪种注入方式
@Autowired注解
可以使用以下几种依赖注入的方式:
-
按照类型进行注入(byType)
:这是@Autowired注解的默认方式
,它会根据属性的类型在容器中查找对应的Bean
,如果找到唯一的一个Bean
,则进行注入
。如果没有找到或者找到多个
相同类型的Bean,则会抛出异常
。这种方式可以用在字段、构造方法或者setter方法上。 -
按照名称进行注入(byName)
:这种方式需要配合@Qualifier
注解来指定Bean的名称
,它会根据属性的名称在容器中查找对应的Bean,如果找到则进行注入
。如果没有找到,则会抛出异常。这种方式可以用在字段、构造方法或者setter方法上。 -
按照构造方法进行注入(constructor)
:这种方式是通过Bean对象的构造方法参数类型和容器中的Bean类型进行匹配,如果找到唯一的一个相同类型的Bean,则通过构造方法进行注入。如果没有找到或者找到多个相同类型的Bean,则会抛出异常。这种方式只能用在构造方法上
。
一般来说,按照类型进行注入
是最常用和方便
的方式,但是如果你需要更精确地
指定要注入的Bean,或者你想保证依赖对象不为空
,那么你可以使用按照名称或者按照构造方法进行注入
。
给我一些上面自动注入的简单示例
好的,我将给你一些简单的示例。
按照类型
进行注入的示例(大部分用的都是这种
):
@Component
public class FooService {
//在字段上使用@Autowired注解,按照类型进行注入
@Autowired
private FooFormatter fooFormatter;
}
@Component
public class FooFormatter {
public String format() {
return "foo";
}
}
按照名称
进行注入的示例:
@Component
public class BarService {
//在字段上使用@Autowired和@Qualifier注解,按照名称进行注入
@Autowired
@Qualifier("barFormatter")
private BarFormatter barFormatter;
}
@Component("barFormatter")
public class BarFormatter {
public String format() {
return "bar";
}
}
按照构造方法
进行注入的示例:
@Component
public class BazService {
private BazFormatter bazFormatter;
//在构造方法上使用@Autowired注解,按照类型进行注入
@Autowired
public BazService(BazFormatter bazFormatter) {
this.bazFormatter = bazFormatter;
}
}
@Component
public class BazFormatter {
public String format() {
return "baz";
}
}
依赖注入和自动注入的关联以及不同点
依赖注入(Dependency Injection,简称DI)和自动注入(Autowired)
是相关但不完全相同
的概念。下面我将解释它们之间的关联和不同点:
关联
:
-
自动注入是依赖注入的一种实现方式
。自动注入是指在容器启动或对象创建时,容器自动将所需的依赖对象注入到目标对象中,而无需手动显式地进行依赖注入
。 -
在
Spring框架及其衍生的Spring Boot
中,使用@Autowired
注解实现自动注入
。通过在目标对象的字段、构造函数或Setter方法上使用@Autowired注解
,Spring容器会根据类型或名称自动查找并注入相应的依赖对象。
不同点:
-
控制权
:依赖注入
是一种设计模式
,强调将依赖对象的创建和管理责任转移到外部容器
中。在依赖注入中,对象声明自己需要的依赖,但并不负责创建或获取依赖对象
。相反,自动注入
是依赖注入的一种具体实现方式
,它是由容器在对象创建时自动完成依赖对象
的注入。 -
显式性
:依赖注入
通常需要在代码中显式地
声明依赖关系,即通过构造函数、Setter方法或接口方法
来接收依赖对象。这样可以清晰地表达对象之间的依赖关系。而自动注入
则不需要显式地声明依赖关系
,容器会自动根据配置或默认规则完成注入
。 -
灵活性:依赖注入提供了
更大的灵活性
,允许对象在创建后替换依赖对象
,或者在不同的环境中使用不同的依赖对象。通过手动注入,可以更容易地进行单元测试和模块替换。相比之下,自动注入的灵活性相对较低
,依赖对象的选择通常由容器的配置或规则
确定。
总结来说,依赖注入是一种设计模式
,强调通过外部容器将依赖对象注入到目标对象
中,以提高代码的可维护性和可测试性。
自动注入是依赖注入的一种具体实现方式
,它通过使用@Autowired注解
或其他类似的机制,在对象创建时自动完成依赖对象的注入
。自动注入减少了手动注入的代码量,但相对来说可能牺牲了一些灵活性。
在Spring Boot
中,可以使用@Autowired注解
实现自动注入,从而简化依赖注入的过程。