抱歉,您的浏览器无法访问本站
本页面需要浏览器支持(启用)JavaScript
了解详情 >

摘要:本文学习了Spring依赖注入的概念和方式,以及循环依赖的解决原理。

环境

Windows 10 企业版 LTSC 21H2
Java 1.8
Tomcat 8.5.50
Maven 3.6.3
Spring 5.2.25.RELEASE

1 概念

依赖注入(Dependency Injection,DI)是控制反转(IoC)的具体实现方式,它将对象所依赖的外部资源(通常是其他对象)通过外部手段注入到对象内部,而不是让对象自己创建或查找这些依赖。

主要优势:

  • 降低对象间的耦合度。
  • 提高代码的可测试性。
  • 增强系统的可维护性。
  • 便于管理对象的生命周期。

2 注入方式

2.1 构造器注入

构造器注入是在创建对象时通过构造函数注入依赖的方式。这是Spring官方推荐的注入方式,特别适合于必需的依赖。

2.1.1 XML配置

示例:

xml
1
2
3
<bean id="user" class="com.example.bean.User">
<constructor-arg ref="address"/>
</bean>

2.1.2 注解配置

使用@Autowired注解:

java
1
2
3
4
5
6
7
8
@Component
public class User {
private Address address;
@Autowired
public User(Address address) {
this.address = address;
}
}

使用@Inject注解:

java
1
2
3
4
5
6
7
8
@Component
public class User {
private Address address;
@Inject
public User(Address address) {
this.address = address;
}
}

2.2 方法注入

方法注入是通过调用对象的Set方法注入依赖的方式。这种方式适合于可选的依赖或可以在运行时更改的依赖。

2.2.1 XML配置

示例:

xml
1
2
3
<bean id="user" class="com.example.bean.User">
<property name="address" ref="address"/>
</bean>

2.2.2 注解配置

使用@Autowired注解:

java
1
2
3
4
5
6
7
8
9
10
11
@Component
public class User {
private Address address;
public Address getAddress() {
return address;
}
@Autowired
public void setAddress(Address address) {
this.address = address;
}
}

使用@Inject注解:

java
1
2
3
4
5
6
7
8
9
10
11
@Component
public class User {
private Address address;
public Address getAddress() {
return address;
}
@Inject
public void setAddress(Address address) {
this.address = address;
}
}

使用@Resource注解:

java
1
2
3
4
5
6
7
8
9
10
11
@Component
public class User {
private Address address;
public Address getAddress() {
return address;
}
@Resource
public void setAddress(Address address) {
this.address = address;
}
}

2.3 字段注入

字段注入是直接在类的字段上使用注解注入依赖的方式。这种方式最简洁,但在某些情况下可能不安全。

使用@Autowired注解:

java
1
2
3
4
5
@Component
public class User {
@Autowired
private Address address;
}

使用@Inject注解:

java
1
2
3
4
5
@Component
public class User {
@Inject
private Address address;
}

使用@Resource注解:

java
1
2
3
4
5
@Component
public class User {
@Resource
private Address address;
}

3 循环依赖

循环依赖是指对象相互依赖的情况,这种情况意味着违反了软件设计的多个核心原则,是代码设计不合理的结果。

但是为了框架的灵活性,以及应对某些业务场景,Spring通过三级缓存机制屏蔽了大部分循环依赖问题。

3.1 抛出异常

3.1.1 构造器注入

无法屏蔽构造器注入存在的循环依赖,会在启动时抛出异常。

创建User实体类,依赖Address实体类:

java
1
2
3
4
5
6
7
8
@Component
public class User {
private Address address;
@Autowired
public User(Address address) {
this.address = address;
}
}

创建Address实体类,依赖User实体类:

java
1
2
3
4
5
6
7
8
@Component
public class Address {
private User user;
@Autowired
public Address(User user) {
this.user = user;
}
}

创建配置类:

java
1
2
3
4
@Configuration
@ComponentScan(basePackages = "com.example")
public class DemoConfig {
}

创建启动类:

java
1
2
3
4
5
6
7
8
9
10
11
12
public class DemoApplication {
public static void main(String[] args) {
// 加载配置类
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(DemoConfig.class);
// 获取User对象
User user = context.getBean(User.class);
// 使用User对象
System.out.println(user);
// 关闭容器
context.close();
}
}

启动报错:

log
1
nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException

3.1.2 原型模式

无法屏蔽原型模式存在的循环依赖,会在启动时抛出异常。

创建User实体类,依赖Address实体类,设置作用域为原型模式:

java
1
2
3
4
5
6
@Component
@Scope("prototype")
public class User {
@Autowired
private Address address;
}

创建Address实体类,依赖User实体类,设置作用域为原型模式:

java
1
2
3
4
5
6
@Component
@Scope("prototype")
public class Address {
@Autowired
private User user;
}

创建配置类:

java
1
2
3
4
@Configuration
@ComponentScan(basePackages = "com.example")
public class DemoConfig {
}

创建启动类:

java
1
2
3
4
5
6
7
8
9
10
11
12
public class DemoApplication {
public static void main(String[] args) {
// 加载配置类
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(DemoConfig.class);
// 获取User对象
User user = context.getBean(User.class);
// 使用User对象
System.out.println(user);
// 关闭容器
context.close();
}
}

启动报错:

log
1
nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException

3.1.3 构造创建

无法屏蔽使用@Bean注解存在的循环依赖,会在启动时抛出异常。

创建User实体类,依赖Address实体类:

java
1
2
3
4
5
6
public class User {
private Address address;
public User(Address address) {
this.address = address;
}
}

创建Address实体类,依赖User实体类:

java
1
2
3
4
5
6
public class Address {
private User user;
public Address(User user) {
this.user = user;
}
}

创建配置类,使用@Bean注解创建对象:

java
1
2
3
4
5
6
7
8
9
10
11
@Configuration
public class DemoConfig {
@Bean
public User user(Address address) {
return new User(address);
}
@Bean
public Address address(User user) {
return new Address(user);
}
}

创建启动类:

java
1
2
3
4
5
6
7
8
9
10
11
12
public class DemoApplication {
public static void main(String[] args) {
// 加载配置类
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(DemoConfig.class);
// 获取User对象
User user = context.getBean(User.class);
// 使用User对象
System.out.println(user);
// 关闭容器
context.close();
}
}

启动报错:

log
1
nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException

3.1.4 显示依赖

无法屏蔽显示依赖存在的循环依赖,会在启动时抛出异常。

创建User实体类,依赖Address实体类:

java
1
2
3
4
5
@Component
@DependsOn("address")
public class User {
private Address address;
}

创建Address实体类,依赖User实体类:

java
1
2
3
4
5
@Component
@DependsOn("user")
public class Address {
private User user;
}

创建配置类,使用@Bean注解创建对象:

java
1
2
3
4
@Configuration
@ComponentScan(basePackages = "com.example")
public class DemoConfig {
}

创建启动类:

java
1
2
3
4
5
6
7
8
9
10
11
12
public class DemoApplication {
public static void main(String[] args) {
// 加载配置类
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(DemoConfig.class);
// 获取User对象
User user = context.getBean(User.class);
// 使用User对象
System.out.println(user);
// 关闭容器
context.close();
}
}

启动报错:

log
1
org.springframework.beans.factory.BeanCreationException

3.2 屏蔽异常

3.2.1 方法注入和字段注入

可以屏蔽方法注入和字段注入存在的循环依赖问题。

创建User实体类,依赖Address实体类:

java
1
2
3
4
5
@Component
public class User {
@Autowired
private Address address;
}

创建Address实体类,依赖User实体类:

java
1
2
3
4
5
@Component
public class Address {
@Autowired
private User user;
}

创建配置类:

java
1
2
3
4
@Configuration
@ComponentScan(basePackages = "com.example")
public class DemoConfig {
}

创建启动类:

java
1
2
3
4
5
6
7
8
9
10
11
12
public class DemoApplication {
public static void main(String[] args) {
// 加载配置类
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(DemoConfig.class);
// 获取User对象
User user = context.getBean(User.class);
// 使用User对象
System.out.println(user);
// 关闭容器
context.close();
}
}

启动不会报错。

3.2.2 延迟加载

使用@Lazy注解可以延迟加载对象,通过代理参数屏蔽循环依赖问题。

创建User实体类,依赖Address实体类:

java
1
2
3
4
5
6
public class User {
private Address address;
public User(Address address) {
this.address = address;
}
}

创建Address实体类,依赖User实体类:

java
1
2
3
4
5
6
public class Address {
private User user;
public Address(User user) {
this.user = user;
}
}

创建配置类,使用@Bean注解创建对象,使用@Lazy注解延迟加载对象:

java
1
2
3
4
5
6
7
8
9
10
11
12
@Configuration
@ComponentScan(basePackages = "com.example")
public class DemoConfig {
@Bean
public User user(@Lazy Address address) {
return new User(address);
}
@Bean
public Address address(@Lazy User user) {
return new Address(user);
}
}

创建启动类:

java
1
2
3
4
5
6
7
8
9
10
11
12
public class DemoApplication {
public static void main(String[] args) {
// 加载配置类
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(DemoConfig.class);
// 获取User对象
User user = context.getBean(User.class);
// 使用User对象
System.out.println(user);
// 关闭容器
context.close();
}
}

启动不会报错。

评论