IOC控制反转概要
一、IOC和DI
说明:IOC和DI是同一个概念的不同角度描述。
IOC是控制反转,由spring来负责控制对象Bean的生命周期和对象间的关系。
“控制”:Ioc容器控制对象及外部资源获取。
“反转”:由容器帮我们查找及注入依赖对象,对象只是被动的接受依赖对象。
DI是依赖注入。由容器动态的将某个依赖关系注入到组件中。被注入的对象依赖IOC容器配置依赖对象。在系统运行中,动态的向某个对象提供它所需要的其他对象。
DI是依赖注入,动态的向某个对象提供它所需要的其他对象。另一个同一概念,不同描述的IOC控制反转。让Ioc容器控制对象及外部资源获取,并且由IOC容器帮忙查找和注入依赖对象。
1.1、spring的Bean
bean是一个由Spring IoC容器实例化、组装和管理的对象。
- bean是对象,一个或者多个不限定
- bean由Spring中一个叫IoC的东西管理
- 我们的应用程序由一个个bean构成
二、DI的依赖注入方式
DI注入实现方式:XML【构造器注入、setter方法注入、FactoryBean工厂类注入、factory-method属性注入(静态和动态)】和注解。
2.1、xml–setter方法注入
2.1.1 注入案例1
Dao层-------一般类
public class SetterDao {
public String operator1(){
return "调用了operator1";
}
}
Dao层-----接口+实现类
public interface SetterDaoInterface {
public String operator2();
}
public class SetterDaoInterfaceImpl implements SetterDaoInterface {
@Override
public String operator2() {
return "调用了operator2";
}
}
service层-----接口+实现类
public interface SetterService1 {
public void way();
}
public class SetterService1Impl implements SetterService1 {
/**
*一般类声明
*/
private SetterDao setterDao;
/**
* 接口声明
*/
private SetterDaoInterface setterDaoInterface1;
public void setSetterDao(SetterDao setterDao) {
this.setterDao = setterDao;
}
public void setSetterDaoInterface(SetterDaoInterface setterDaoInterface) {
this.setterDaoInterface1 = setterDaoInterface;
}
@Override
public void way() {
System.out.println( setterDao.operator1());
System.out.println( setterDaoInterface1.operator2());
}
}
service层—测试
@Service
public class DITestService {
@Autowired
SetterService1 setterService2; ---当接口只有一个实现子类,那么setterService2没限制
@PostConstruct
public void setterServiceWay1(){
setterService2.way();
}
}
从上面看SetterDao类、SetterDaoInterfaceImpl子类、SetterService1Impl类都没有注入到Spring的IOC容器,并且SetterService1Impl类依赖SetterDao类和SetterDaoInterfaceImpl子类。接下来采用setter方法注入------在applicationContext.xml中配置。
<!--setter方式依赖注入-->
<bean id="setterDao_1" class="com.wwy.DI.DISetter.SetterDao"></bean>
<bean id="setterDaoInterface_1" class="com.wwy.DI.DISetter.SetterDaoInterfaceImpl"></bean>
<bean id="setterService" class="com.wwy.DI.DISetter.SetterService1Impl">
<property name="setterDao" ref="setterDao_1"></property>
<property name="setterDaoInterface" ref="setterDaoInterface_1"></property>
</bean>
-----说明:
<bean id=" " class=" "></bean>
spring的依赖注入的特性,将对象注入到spring容器中管理。
ref="setterDao_1"
对应的已经在xml中已经注入到spring容器中,所以将这个Bean当做参数传入setterService类中。
<property name="setterDao" ref="setterDao_1"></property>
配合SetterService1Impl 实现类的
public void setSetterDao(SetterDao setterDao) {
this.setterDao = setterDao;
}
方法,是将IOC容器的Bean传入到对应的类中。
2.1.2 注入案例2
按照上面的分析,如果一个Bean已经在spring容器中【不是通过xml方式实现】,也是可以传入任何类中。
Dao层—一般类
@Component
public class SetterDao {
public String operator1(){
return "调用了operator1";
}
}
service层—接口+实现类
public interface SetterService1 {
public void way();
}
public class SetterService1Impl implements SetterService1 {
@Autowired
private SetterDao setterDao;
/**
* 接口声明
*/
private SetterDaoInterface setterDaoInterface1;
public void setSetterDaoInterface(SetterDaoInterface setterDaoInterface) {
this.setterDaoInterface1 = setterDaoInterface;
}
@Override
public void way() {
System.out.println( setterDao.operator1());
System.out.println( setterDaoInterface1.operator2());
}
}
上面的例子,SetterDao 类是通过@Component注解注入到IOC容器,SetterService1Impl 类、SetterDaoInterface实现类都没注入。同样的原理,利用setter方式注入。
<!--setter方式依赖注入-->
<bean id="setterDaoInterface_1" class="com.wwy.DI.DISetter.SetterDaoInterfaceImpl"></bean>
<bean id="setterService" class="com.wwy.DI.DISetter.SetterService1Impl">
<property name="setterDaoInterface" ref="setterDaoInterface_1"></property>
</bean>
2.1.3 实际应用场景解读
在spring+mybatis+mysql中,如果需要操作sql,那么在IOC容器中需要sqlSession实例【即sqlSessionFactory对象】,有了这个对象就可以执行sql任务。
同样,sqlSessionFactory又需要必须属性dataSource【“配置jdbc数据源”信息----理解setter依赖注入思想】,另外其还有一个通用属性configLocation(用来指定mybatis的xml配置文件路径)。
applicationContext.xml中<import resource="spring-mybatis.xml" />
spring-mybatis.xml文件内容如下
<?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:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
<bean id="configProperties" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="locations">
<list>
<value>classpath:jdbc.properties</value>
</list>
</property>
</bean>
<!-- 配置 jdbc 数据源 -->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<!-- 使用properties来配置 -->
<property name="driverClassName">
<value>${jdbc.driverClassName}</value>
</property>
<property name="url">
<value>${jdbc.url}</value>
</property>
<property name="username">
<value>${jdbc.username}</value>
</property>
<property name="password">
<value>${jdbc.password}</value>
</property>
</bean>
<!-- 配置mybatis SqlSessionFactoryBean -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="configLocation" value="classpath:mybatis-config.xml"/>
<property name="mapperLocations" value="classpath:mapper/*.xml"/>
</bean>
<!-- 配置mybatis mapper接口 -->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.wwy.Dao"/>
<!--<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>-->
</bean>
</beans>
—查看SqlSessionFactoryBean源码发现,dataSource、configLocation就是采用的Setter方式注入。
2.2、xml–构造器注入
其他的类信息同“2.1.1 注入案例1”小章节信息。
public class SetterService2Impl implements SetterService1 {
private SetterDaoInterface setterDaoInterface1;
public SetterService2Impl(SetterDaoInterface setterDaoInterface1){
this.setterDaoInterface1 =setterDaoInterface1;
}
@Override
public void way() {
System.out.println(setterDaoInterface1.operator2()+" SetterService2Impl");
}
}
思想同setter方式类似,xml中配置细节如下:
<!-- 构造方法注入-->
<bean id="setterDaoInterface_1" class="com.wwy.DI.DISetter.SetterDaoInterfaceImpl">
<bean id="setterService2" class="com.wwy.DI.DIConstructor.SetterService2Impl">
<constructor-arg ref="setterDaoInterface_1"></constructor-arg>
</bean>
<constructor-arg ref="setterDaoInterface_1"></constructor-arg>
配合
public SetterService2Impl(SetterDaoInterface setterDaoInterface1){
this.setterDaoInterface1 =setterDaoInterface1;
}
就可以将将IOC容器中已经存在的Bean对象,按照构造器函数方式传入到SetterService2Impl类中。另外,<bean id= 方式
是将SetterService2Impl类注入到IOC容器。
2.3、xml–FactoryBean工厂类注入
FactoryBean:从容器中获取Bean的时候,返回的并不是类的一个实例,而是工厂Bean中getObject方法返回的对象。
FactoryBean是spring中工厂模式的实现,可以通过配置创建不同的对象。
通常可用在基于不同配置产生不同对象场景中,如数据库连接(线上、测试环境连接参数不一样)。
a)getObject(),返回生成的对象;【同一个Bean id多次获取,也只是第一次初始化的对象】
b)getObjectType(),返回对象类型;
c)isSingleton() ,是否是单例,true:是,false:不是。
public class StudentFB {
private String id;
private String name;
StudentFB(){}
StudentFB(String id,String name){
this.id = id;
this.name = name;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
实现FactoryBean接口的子类
public class StudentFactoryBean implements FactoryBean<StudentFB> {
private String info;
//返回生成的对象
@Override
public StudentFB getObject() throws Exception {
return new StudentFB(info.split("_")[0],info.split("_")[1]);
}
//返回对象类型
@Override
public Class<?> getObjectType() {
return StudentFB.class;
}
//是否是单例,true:是 false:否
@Override
public boolean isSingleton() {
return true;
}
public String getInfo() {
return info;
}
public void setInfo(String info) {
this.info = info;
}
}
xml的配置:DIFactoryBean.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans
xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.1.xsd">
<bean id="studentV1" class="com.wwy.DI.DIFactoryBean.StudentFactoryBean">
<property name="info" value="001_wwy"></property>
</bean>
<bean id="studentV2" class="com.wwy.DI.DIFactoryBean.StudentFactoryBean">
<property name="info" value="002_wwy"></property>
</bean>
</beans>
测试的例子
public class FactoryBeanMain {
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext(
"classpath:DIFactoryBean.xml");
StudentFB s1 = ctx.getBean("studentV1", StudentFB.class);
StudentFB s2 = ctx.getBean("studentV1", StudentFB.class);
System.out.println(s1 == s2); //同一个对象
StudentFB s3 = ctx.getBean("studentV2", StudentFB.class);
System.out.println(s1 == s3); //不同对象
}
}
2.4、xml–FactoryMethod注入
public class PersonFM {
private String id;
PersonFM(String id){
this.id = id;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
}
创建静态和动态–FactoryMethod类
public class PersonFactoryMethod {
//静态创建类
public static PersonFM getStaticPer(String id){
return new PersonFM(id);
}
//动态创建类
public PersonFM getDynaPer(String id){
return new PersonFM(id);
}
}
xml的配置:DIFactoryMethod.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans>
<!--静态获取对象-->
<bean id="staticPer" class="com.wwy.DI.DIFactoryMethod.PersonFactoryMethod" factory-method="getStaticPer">
<constructor-arg value="1"/> <!--传入getStaticPer方法的参数-->
</bean>
<!--生成对象的工厂-->
<bean id="personFactory" class="com.wwy.DI.DIFactoryMethod.PersonFactoryMethod"/>
<!--动态获取对象-->
<bean id="dynamicPer" factory-bean="personFactory" factory-method="getDynaPer">
<constructor-arg value="11"/> <!--传入getDynaPer方法的参数-->
</bean>
</beans>
测试的类
public class FactoryMethodMain {
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext(
"classpath:DIFactoryMethod.xml");
PersonFM s1 = ctx.getBean("staticPer", PersonFM.class);
PersonFM s2 = ctx.getBean("dynamicPer", PersonFM.class);
System.out.println(s2);
}
}
2.4、注解–基于注解注入
@Component
可以使用此注解描述 Spring 中的 Bean,但它是一个泛化的概念,仅仅表示一个组件(Bean),并且可以作用在任何层次。使用时只需将该注解标注在相应类上即可。
@Repository
用于将数据访问层(DAO层)的类标识为 Spring 中的 Bean,其功能与 @Component 相同。
@Service
通常作用在业务层(Service 层),用于将业务层的类标识为 Spring 中的 Bean,其功能与 @Component 相同。
@Controller
通常作用在控制层(如 Struts2 的 Action),用于将控制层的类标识为 Spring 中的 Bean,其功能与 @Component 相同。
@Autowired
用于对 Bean 的属性变量、属性的 Set 方法及构造函数进行标注,配合对应的注解处理器完成 Bean 的自动配置工作。默认按照 Bean 的类型进行装配。
@Resource
其作用与 Autowired 一样。其区别在于 @Autowired 默认按照 Bean 类型装配,而 @Resource 默认按照 Bean 实例名称进行装配。
@Resource 中有两个重要属性:name 和 type
Spring 将 name 属性解析为 Bean 实例名称,type 属性解析为 Bean 实例类型。
如果指定 name 属性,则按实例名称进行装配;
如果指定 type 属性,则按 Bean 类型进行装配。
如果都不指定,则先按 Bean 实例名称装配,如果不能匹配,则再按照 Bean 类型进行装配;如果都无法匹配,则抛出 NoSuchBeanDefinitionException 异常。
@Qualifier
与 @Autowired 注解配合使用,会将默认的按 Bean 类型装配修改为按 Bean 的实例名称装配,Bean 的实例名称由 @Qualifier 注解的参数指定。
三、几种依赖注入方式对比
3.1、xml–setter方法注入
优点
注入属性过多时,更轻便;
在对象需要被注入时才被注入依赖,不需要在初始化时注入;
方便在类实例化后重新对该属性进行配置或注入。
缺点
Setter过多导致代码冗余,维护麻烦;
会把循环依赖隐藏;
依赖不是不可变的【不能使用final修饰】。
3.2、xml–构造器方法注入
优点
依赖不可变:使用final关键字来修饰依赖字段。
依赖不为空:允许构造函数可以保证一些重要属性在Bean实例化时就设置好。
单一职责原则:当使用构造函数注入时,如果参数过多可能会促使你主动对类进行拆分。
完全初始化的状态:保证返回客户端的代码是完全初始化的状态。
更好的封装类变量:不需要为每个属性指定Setter方法,避免外部错误的调用。
更利于单元测试:其它两种方式注入,进行单元测试时需要初始化整个Spring的环境。
避免循环依赖:若存在循环依赖则启动会抛异常–【后续研究“循环依赖”】。
缺点
当注入对象特别多时,构造器显的繁琐,可读性和可维护性较差;
灵活性不强,对于不可选属性,参数值提供Null。
构造器不利于类的扩展和继承,子类需要引用父类复杂的构造函数。
3.3、注解–注解方法注入
优点
简单、便于添加新的依赖;
减少大量冗余代码;
新增依赖时不需要修改过多代码。
缺点
单一职责侵入:添加依赖过于简单,可能添加几十个依赖;
无法声明不可变的字段:字段不能使用final修饰;
隐藏了依赖关系:
与依赖注入容器紧耦合: