摘要:本文学习了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配置
示例:
xml1 2 3
| <bean id="user" class="com.example.bean.User"> <constructor-arg ref="address"/> </bean>
|
2.1.2 注解配置
使用@Autowired注解:
java1 2 3 4 5 6 7 8
| @Component public class User { private Address address; @Autowired public User(Address address) { this.address = address; } }
|
使用@Inject注解:
java1 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配置
示例:
xml1 2 3
| <bean id="user" class="com.example.bean.User"> <property name="address" ref="address"/> </bean>
|
2.2.2 注解配置
使用@Autowired注解:
java1 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注解:
java1 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注解:
java1 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注解:
java1 2 3 4 5
| @Component public class User { @Autowired private Address address; }
|
使用@Inject注解:
java1 2 3 4 5
| @Component public class User { @Inject private Address address; }
|
使用@Resource注解:
java1 2 3 4 5
| @Component public class User { @Resource private Address address; }
|
3 循环依赖
循环依赖是指对象相互依赖的情况,这种情况意味着违反了软件设计的多个核心原则,是代码设计不合理的结果。
但是为了框架的灵活性,以及应对某些业务场景,Spring通过三级缓存机制屏蔽了大部分循环依赖问题。
3.1 抛出异常
3.1.1 构造器注入
无法屏蔽构造器注入存在的循环依赖,会在启动时抛出异常。
创建User实体类,依赖Address实体类:
java1 2 3 4 5 6 7 8
| @Component public class User { private Address address; @Autowired public User(Address address) { this.address = address; } }
|
创建Address实体类,依赖User实体类:
java1 2 3 4 5 6 7 8
| @Component public class Address { private User user; @Autowired public Address(User user) { this.user = user; } }
|
创建配置类:
java1 2 3 4
| @Configuration @ComponentScan(basePackages = "com.example") public class DemoConfig { }
|
创建启动类:
java1 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 = context.getBean(User.class); System.out.println(user); context.close(); } }
|
启动报错:
log1
| nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException
|
3.1.2 原型模式
无法屏蔽原型模式存在的循环依赖,会在启动时抛出异常。
创建User实体类,依赖Address实体类,设置作用域为原型模式:
java1 2 3 4 5 6
| @Component @Scope("prototype") public class User { @Autowired private Address address; }
|
创建Address实体类,依赖User实体类,设置作用域为原型模式:
java1 2 3 4 5 6
| @Component @Scope("prototype") public class Address { @Autowired private User user; }
|
创建配置类:
java1 2 3 4
| @Configuration @ComponentScan(basePackages = "com.example") public class DemoConfig { }
|
创建启动类:
java1 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 = context.getBean(User.class); System.out.println(user); context.close(); } }
|
启动报错:
log1
| nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException
|
3.1.3 构造创建
无法屏蔽使用@Bean注解存在的循环依赖,会在启动时抛出异常。
创建User实体类,依赖Address实体类:
java1 2 3 4 5 6
| public class User { private Address address; public User(Address address) { this.address = address; } }
|
创建Address实体类,依赖User实体类:
java1 2 3 4 5 6
| public class Address { private User user; public Address(User user) { this.user = user; } }
|
创建配置类,使用@Bean注解创建对象:
java1 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); } }
|
创建启动类:
java1 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 = context.getBean(User.class); System.out.println(user); context.close(); } }
|
启动报错:
log1
| nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException
|
3.1.4 显示依赖
无法屏蔽显示依赖存在的循环依赖,会在启动时抛出异常。
创建User实体类,依赖Address实体类:
java1 2 3 4 5
| @Component @DependsOn("address") public class User { private Address address; }
|
创建Address实体类,依赖User实体类:
java1 2 3 4 5
| @Component @DependsOn("user") public class Address { private User user; }
|
创建配置类,使用@Bean注解创建对象:
java1 2 3 4
| @Configuration @ComponentScan(basePackages = "com.example") public class DemoConfig { }
|
创建启动类:
java1 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 = context.getBean(User.class); System.out.println(user); context.close(); } }
|
启动报错:
log1
| org.springframework.beans.factory.BeanCreationException
|
3.2 屏蔽异常
3.2.1 方法注入和字段注入
可以屏蔽方法注入和字段注入存在的循环依赖问题。
创建User实体类,依赖Address实体类:
java1 2 3 4 5
| @Component public class User { @Autowired private Address address; }
|
创建Address实体类,依赖User实体类:
java1 2 3 4 5
| @Component public class Address { @Autowired private User user; }
|
创建配置类:
java1 2 3 4
| @Configuration @ComponentScan(basePackages = "com.example") public class DemoConfig { }
|
创建启动类:
java1 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 = context.getBean(User.class); System.out.println(user); context.close(); } }
|
启动不会报错。
3.2.2 延迟加载
使用@Lazy注解可以延迟加载对象,通过代理参数屏蔽循环依赖问题。
创建User实体类,依赖Address实体类:
java1 2 3 4 5 6
| public class User { private Address address; public User(Address address) { this.address = address; } }
|
创建Address实体类,依赖User实体类:
java1 2 3 4 5 6
| public class Address { private User user; public Address(User user) { this.user = user; } }
|
创建配置类,使用@Bean注解创建对象,使用@Lazy注解延迟加载对象:
java1 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); } }
|
创建启动类:
java1 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 = context.getBean(User.class); System.out.println(user); context.close(); } }
|
启动不会报错。
条