一、控制反转 - IOC
由用户管理Bean转变为容器管理Bean,这个就叫控制反转 - Inversion of Control (IOC)。
优点:
松耦合,重用性高。
举例:
文件上传的功能,新增一个接口IUploadFile
,它的实现类UploadFileToNas
,用于将文件上传到Nas服务器。此时可以这样创建对象:
// IOC创建对象
@Resource
private IUploadFile uploadFile1;
// new创建对象
private IUploadFile uploadFile2 = new UploadFileToNas();
随着业务的变动,需要将文件上传到FTP服务器,此时新增实现类UploadFileToFtp
。IOC创建对象的方法,只需要将UploadFileToNas
不注入到IOC容器即可,但是new创建对象的方法,需要将所有使用到的地方都做修改。
二、依赖注入 - DI
控制反转是设计思想,依赖注入是实现方式
三、创建bean的三种方式
- xml配置
- java配置:
@Configuration
+@bean
- 注解配置:
@Component
,@Controller
,@Service
,@Repository
四、依赖注入的三种方式
- setter注入
- 构造器注入
- 属性注入
为什么推荐使用构造器实现依赖注入?
-
依赖不可变:
给依赖属性加上final关键字。 -
完全初始化的状态:
构造器注入,IOC容器会在实例化bean的时候通过调用合适的构造方法并传递相应的参数来完成注入操作;
属性注入,IOC容器会在创建bean实例后通过反射机制注入属性;
setter注入,IOC容器会在创建bean实例后通过调用setter方法注入属性;
由此可以看出,构造器注入的方式,实例化得到的bean是最完整的bean。
// 正例:使用构造器注入,服务正常启动
@Service
public class OrderService {
private final OrderRepository orderRepository;
public OrderService(OrderRepository orderRepository){
this.orderRepository = orderRepository;
this.orderRepository.add();
}
}
// 反例:使用属性注入,服务启动失败,空指针异常
@Service
public class OrderService {
@Autowired
private OrderRepository orderRepository;
public OrderService(){
orderRepository.add(); // 服务启动时,orderRepository为null,报空指针异常
}
}
-
依赖不为空:
和第二点的原因一样。setter注入和属性注入都是在实例化bean之后做的操作,那么在bean实例化之后,依赖注入之前这段时间内,依赖属性都是null。 -
避免循环依赖:
构造器注入可以避免循环依赖。但是在spring boot项目中,任何一种注入方式都会报错,因为spring boot默认的bean创建机制是单例模式,当出现循环依赖时,由于一个bean正在被创建而另一个bean也需要依赖它,就会造成死锁和无限递归等问题。解决方案:①使用@Scope("prototype")
或者xml配置,将bean的作用域(scope)设置为原型(prototype);②使用@Lazy
;③使用@DependsOn("")
;④最好还是在编码时避免循环依赖。
@Autowired
、@Resource
、@Inject
注解注入有何区别?
@Autowired
:默认根据类型注入。如果有多个类型相同的bean,可以使用@Qualifier
注解实现根据名称注入;或者使用@Primary
注解实现优先注入功能。也可以将required属性设置为false,以允许服务在没有匹配到bean的情况下继续运行。@Resource
:默认根据名称注入。可以使用name属性指定bean的名称。可以使用type属性,以根据类型注入。@Inject
:默认根据类型注入。如果有多个类型相同的bean,可以使用@Named
注解实现根据名称注入。