什么是控制反转(IOC)/依赖注入(DI)
-
什么控制被反转了?
控制反转(Inversion of Control) 是一种设计思想,我们对对象的控制权被反转了。我们将对象的控制交给了Spring的容器。
-
什么是Spring的容器?
简单的说,Spring容器就是一个超级大工厂,负责创建、管理所有的Java对象,这些Java对象被称为Bean。Spring容器管理着容器中Bean之间的依赖关系,Spring使用一种被称为“依赖注入”的方式来管理Bean之间的依赖关系。
-
什么又是依赖注入呢?
依赖注入(DI) 就是控制反转的一种实现形式,可以作为控制反转的一种实现方式。Spring容器也是通过这种形式管理Bean的。依赖注入就是将实例变量传入到一个对象中去。
-
Spring容器如何做到替你管理这些bean的呢?
工厂模式。
-
Spring框架费了九牛二虎之力创建一个Spring容器去帮我们管理这些Bean又有什么好处呢? 其实和工厂模式的好处差不多:
- 解除硬编码耦合,利于项目升级和维护;
- 简化了对象的管理,使我们更加专注于业务操作
BeanFactory与ApplicationContext
BeanFactory和ApplicationContext之间的关系
-
BeanFactory和ApplicationContext是Spring的两大核心接口,而其中ApplicationContext是BeanFactory的子接口。它们都可以当做Spring的容器,Spring容器是生成Bean实例的工厂,并管理容器中的Bean。在基于Spring的Java EE应用中,所有的组件都被当成Bean处理,包括数据源,Hibernate的SessionFactory、事务管理器等。
-
生活中我们一般会把生产产品的地方称为工厂,而在这里bean对象的地方官方取名为BeanFactory,直译Bean工厂(com.springframework.beans.factory.BeanFactory),我们一般称BeanFactory为IoC容器,而称ApplicationContext为应用上下文。
-
Spring的核心是容器,而容器并不唯一,框架本身就提供了很多个容器的实现,大概分为两种类型:
- 一种是不常用的BeanFactory,这是最简单的容器,只能提供基本的DI功能;
- 一种就是继承了BeanFactory后派生而来的ApplicationContext(应用上下文),它能提供更多企业级的服务。
- 默认初始化所有的Singleton,也可以通过配置取消预初始化。
- 继承MessageSource,因此支持国际化。
- 资源访问,比如访问URL和文件。
- 事件机制。
- 同时加载多个配置文件。
- 以声明式方式启动并创建Spring容器。
-
BeanFactory和ApplicationContext都支持BeanPostProcessor、BeanFactoryPostProcessor的使用,但两者之间的区别是:BeanFactory需要手动注册,而ApplicationContext则是自动注册。
BeanFactory介绍
Spring容器最基本的接口就是BeanFactory。BeanFactory负责配置、创建、管理Bean,BeanFactory 是 Spring 的“心脏”。它就是 Spring IoC 容器的真面目。Spring 使用 BeanFactory 来实例化、配置和管理 Bean。
spring Ioc容器的实现,从根源上是beanfactory,但真正可以作为一个可以独立使用的ioc容器还是DefaultListableBeanFactory,因此可以这么说,DefaultListableBeanFactory 是整个spring ioc的始祖。
ApplicationContext介绍
如果说BeanFactory是Sping的心脏,那么ApplicationContext就是完整的身躯了。
ApplicationContext常用实现类:
- AnnotationConfigApplicationContext:从一个或多个基于java的配置类中加载上下文定义,适用于java注解的方式。
- ClassPathXmlApplicationContext:从类路径下的一个或多个xml配置文件中加载上下文定义,适用于xml配置的方式。
- FileSystemXmlApplicationContext:从文件系统下的一个或多个xml配置文件中加载上下文定义,也就是说系统盘符中加载xml配置文件。
- AnnotationConfigWebApplicationContext:专门为web应用准备的,适用于注解方式。
- XmlWebApplicationContext:从web应用下的一个或多个xml配置文件加载上下文定义,适用于xml配置方式。
Bean实例化时间&依赖注入时间
在Spring里,创建被调用者实例的工作不再由调用者来完成。因此成为控制反转(ioc)。创建被调用者实例的工作由Spring容器来完成,然后注入调用者。因此也称为依赖注入,那么bean什么时候被实例化?什么时候发生依赖注入?
bean什么时候被实例化
-
如果使用BeanFactory作为Spring Bean的工厂类,则所有的bean都是在第一次使用该Bean(调用getBean())的时候实例化,这样,我们就不能发现一些存在的Spring的配置问题。如果Bean的某一个属性没有注入,BeanFacotry加载后,直至第一次使用调用getBean方法才会抛出异常。
-
如果使用ApplicationContext作为Spring Bean的工厂类,则又分为以下几种情况:
- 如果bean的scope是单例模式(Singleton)的,并且lazy-init为false,则 ApplicationContext启动的时候就实例化该Bean,并且将实例化的Bean放在一个map结构的缓存中,下次再使用该Bean的时候,直接从这个缓存中取
- 如果bean的scope是singleton的,并且lazy-init为true或使用 @Lazy 注解将其设置为懒加载模式,即让容器在解析注册Bean定义时进行预实例化,触发依赖注入。
- 如果bean的scope是prototype的,则该Bean的实例化是在第一次使用该Bean的时候进行实例化
注:由于ApplicationContext会预先初始化所有的Singleton Bean,于是在系统创建前期会有较大的系统开销,但一旦ApplicationContext初始化完成,程序后面获取Singleton Bean实例时候将有较好的性能。也可以为bean设置lazy-init属性为true或者使用@Lazy注解,即Spring容器将不会预先初始化该bean。
依赖注入发生的时间
当Spring IoC容器完成了Bean定义资源的定位、载入和解析注册,IoC容器就可以管理Bean定义的相关数据了,但是此时IoC容器还没有对所管理的Bean进行依赖注入(DI),依赖注入在以下两种情况下发生:
- 用户第一次调用getBean()方法时,IoC容器触发依赖注入。
- 当用户在配置文件中将<bean>元素配置了 lazy-init=false(默认是false,所以可以不用设置)或 @Lazy(false) 时,即让容器在解析注册Bean定义时进行预实例化,触发依赖注入。
大家观察Bean实例化与依赖注入在时间上是不是有些许雷同,这里就罗列出来提供参考。
依赖注入方式
- 接口注入 接口注入模式因为具备侵入性,它要求组件必须与特定的接口相关联,因此并不被看好,实际使用有限。
public class ClassA {
private InterfaceB clzB;
public void doSomething() {
Ojbect obj = Class.forName(Config.BImplementation).newInstance();
clzB = (InterfaceB)obj;
clzB.doIt();
}
……
}
解释一下上述的代码部分,ClassA依赖于InterfaceB的实现,我们如何获得InterfaceB的实现实例呢?传统的方法是在代码中创建 InterfaceB实现类的实例,并将赋予clzB.这样一来,ClassA在编译期即依赖于InterfaceB的实现。为了将调用者与实现者在编译期分离,于是有了上面的代码。我们根据预先在配置文件中设定的实现类的类名(Config.BImplementation),动态加载实现类,并通过InterfaceB强制转型后为ClassA所用,这就是接口注入的一个最原始的雏形。 setter方法注入
setter注入模式在实际开发中有非常广泛的应用,setter方法更加直观,我们来看一下spring的配置文件:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.1.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.1.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.1.xsd">
<!-- 使用spring管理对象的创建,还有对象的依赖关系 -->
<bean id="userDao4Mysql" class="com.tgb.spring.dao.UserDao4MysqlImpl"/>
<bean id="userDao4Oracle" class="com.tgb.spring.dao.UserDao4OracleImpl"/>
<bean id="userManager" class="com.tgb.spring.manager.UserManagerImpl">
<!-- (1)userManager使用了userDao,Ioc是自动创建相应的UserDao实现,都是由容器管理-->
<!-- (2)在UserManager中提供构造函数,让spring将UserDao实现注入(DI)过来 -->
<!-- (3)让spring管理我们对象的创建和依赖关系,必须将依赖关系配置到spring的核心配置文件中 -->
<property name="userDao" ref="userDao4Oracle"></property>
</bean>
</beans>
- setter方法注入
import com.tgb.spring.dao.UserDao;
public class UserManagerImpl implements UserManager{
private UserDao userDao;
//使用设值方式赋值
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
@Override
public void addUser(String userName, String password) {
userDao.addUser(userName, password);
}
}
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.1.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.1.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.1.xsd">
<!-- 使用spring管理对象的创建,还有对象的依赖关系 -->
<bean id="userDao4Mysql" class="com.tgb.spring.dao.UserDao4MysqlImpl"/>
<bean id="userDao4Oracle" class="com.tgb.spring.dao.UserDao4OracleImpl"/>
<bean id="userManager" class="com.tgb.spring.manager.UserManagerImpl">
<!-- (1)userManager使用了userDao,Ioc是自动创建相应的UserDao实现,都是由容器管理-->
<!-- (2)在UserManager中提供构造函数,让spring将UserDao实现注入(DI)过来 -->
<!-- (3)让spring管理我们对象的创建和依赖关系,必须将依赖关系配置到spring的核心配置文件中 -->
<property name="userDao" ref="userDao4Oracle"></property>
</bean>
</beans>
- 构造方法注入
import com.tgb.spring.dao.UserDao;
public class UserManagerImpl implements UserManager{
private UserDao userDao;
//使用构造方式赋值
public UserManagerImpl(UserDao userDao) {
this.userDao = userDao;
}
@Override
public void addUser(String userName, String password) {
userDao.addUser(userName, password);
}
}
有的文章还提到工厂方法注入等方式,这里就不做说明了
装配
提到依赖注入,就不能不说装配。依赖注入的本质就是装配,装配是依赖注入的具体行为。
Spring具有非常大的灵活性,它提供了三种主要的装配机制:
-
在XMl中进行显示配置:通过xml文件将配置加载到IOC容器中
-
在Java中进行显示配置:通过java注解的方式将配置加载到IOC容器
//同xml一样描述bean以及bean之间的依赖关系
@Configuration
public class ManConfig {
@Bean
public Man man() {
return new Man(car());
}
@Bean
public Car car() {
return new QQCar();
}
}
- 隐式的bean发现机制和自动装配
- 组件扫描(@ComponentScan&@Configuration):Spring会自动发现应用上下文中所创建的bean。
- 自动装配(@Autowired&@Resource(@Qualifier)&@Value):Spring自动满足bean之间的依赖。
隐式的bean发现机制和自动装配
/**
* 这是一个游戏光盘的实现
*/
//这个简单的注解表明该类回作为组件类,并告知Spring要为这个创建bean。
@Component
public class GameDisc implements Disc{
@Override
public void play() {
System.out.println("我是xxx游戏光盘。");
}
}
不过,组件扫描默认是不启用的。我们还需要显示配置一下Spring,从而命令它去寻找@Component注解的类,并为其创建bean。
@Configuration
@ComponentScan
public class DiscConfig {
}
我们在DiscConfig上加了一个@ComponentScan注解表示在Spring中开启了组件扫描,默认扫描与配置类相同的包,就可以扫描到这个GameDisc的Bean了。这就是Spring的自动装配机制。
Bean的生命周期
这里我们讲的是 ApplicationContext 中Bean的生命周期。而实际上 BeanFactory 也是差不多的,只不过处理器需要手动注册。
bean的生命周期:
书中把bean的整个生命周期可能会调用的方法分为4类,分别是:
-
Bean自身的方法。比如构造方法,getter、setter方法,其他的自定义方法
-
Bean级的生命周期方法。要调用这些方法需要由Bean来实现Bean级的生命周期方法接口:比如BeanNameAware,BeanFactoryAware,InitializingBean,DisposableBean,这些接口中的方法是自动调用的,作用范围只是针对实现了前面所说的4个接口的类型的Bean
-
容器级的生命周期接口方法。图中带有☆的方法就是容器级的方法,容器中所有的bean都要被容器级的生命周期方法处理,而且这个级别的接口是单独实现的,独立于Bean之外。上图中的容器及生命周期接口为:InstantiationAwareBeanPostProcessor 和BeanPostProcessor
-
工厂后处理器接口方法:包括AspectJWeavingEnabler,CustomeAutowireConfigurer,ConfigurationClassPostProcessor等方法,在应用上下文装配文件后立即调用
本文结合了多位大佬的博文,别问为什么不写了,累了~~,希望能解开各位的一些疑惑
推荐文章: