引言:学习过Spring框架的人一定都会听过Spring的IoC(控制反转) 、DI(依赖注入)这两个概念,对于初学Spring的人来说,总觉得IoC 、DI这两个概念是模糊不清的,是很难理解的,下面这篇文章就是从原理到实现方式各个角度分析了IOC和DI的区别,谨以此作为交流学习。
一、概念解释
IOC(控制反转):是对组件对象控制权的转移,从代码本身转移到了外部容器,通过容器来实现对组件的装配和管理。而Spring是IOC的有一个容器。也就是说,将对象的管理(创建,维护、销毁)交给Spring管理,但在使用对象的时候将对象反转给使用方
在Java SE部分,我们通常的做法是在对象内部new一个新的对象,是程序主动去创建依赖对象,这样做会导致类与类之间的高耦合,很难测试,且程序一旦修改,改动面很大。Ioc意味着将你设计好的对象交给容器控制,而不是传统的在你的对象内部直接控制。降低了程序间的耦合性,方便测试,利于功能复用,使得系统的结构体系更加灵活。
依赖注入(DI):即在运行期由容器将依赖关系注入到组件之中,就是在运行期,由Spring根据配置文件,将其他对象的引用通过组件提供的setter方法进行设定。
spring创建对象A时,会将对象A所依赖的对象B也创建出来,并自动注入到对象A中。依赖注入的目的并非为软件系统带来更多功能,而是为了提升组件重用的频率,并为系统搭建一个灵活、可扩展的平台。
IoC和DI有什么关系呢?
其实它们是同一个概念的不同角度描述,由于控制反转概念比较含糊(可能只是理解为容器控制对象这一个层面,很难让人想到谁来维护对象关系),所以2004年大师级人物Martin Fowler又给出了一个新的名字:“依赖注入”,相对IoC 而言,“依赖注入”明确描述了“被注入对象依赖IoC容器配置依赖对象”
以下是具体区分:
控制反转:创建对象实例的控制权从代码控制剥离到IOC容器控制,实际就是你在xml文件控制,侧重于原理。
依赖注入:创建对象实例时,为这个对象注入属性值或其它对象实例,侧重于实现。
依赖注入和控制反转是同一概念,是对同一件事情的不同描述,它们描述的角度不同。依赖注入是从应用程序的角度在描述:应用程序依赖容器创建并注入它所需要的外部资源;而控制反转是从容器的角度在描述:容器控制应用程序,由容器反向的向应用程序注入应用程序所需要的外部资源(对象、文件等)。
二、具体解释IOC和DI的关系和区别
要理解IOC,先要理解Spring的工作原理,以下为Spring的使用步骤:
1、向相应的软件引入Spring核心jar包
2、创建Spring全局配置文件
3、创建类
4、将第三步创建好的类交给Spring管理,在第二步创建的Spring全局配置文件中添加相关配置信息
5、创建主函数使用类。步骤:利用ClassPathXmlApplicationContext 获取Spring实例,利用getBean()方法获取第三步创建好的类的实例,调用里面方法实现。
Spring实例过程理解:
Spring容器支持两种格式的配置文件,分别为Properties文件格式和xml文件格式,而在实际的开发当中,最常使用的是xml文件格式,下面将以xml文件格式的配置方式进行说明。XML配置文件的根元素是<beans>,其可以包含多个子元素<bean>,每个子元素定义一个Bean,并描述了Bean该如何被装配到Spring容器中。
<bean>标签元素中的属性如下:
bean标签:管理bean
id属性:(必填)Bean的唯一标识符,Spring对Bean的配置、管理都通过该属性来完成;
name属性:作用和id属性一致,名字可以包含特殊的字符
class属性:指定了Bean的具体实现类,必须使用类的全路径名
scope属性:bean的范围。设定Bean实例的作用域,其属性有singleton(单例)[在同一个jvm中只存在一个实例]、 prototype(原型)[多例]、request、session、和global Session,默认值为singleton
constructor-arg:<bean>元素的子元素,可以使用此元素传入构造参数进行实例化,该元素的index属性指定构造参数 的序号(从0开始)。
property:<bean>元素的子元素,通过调用Bean实例中的setter方法完成属性赋值,从而完成依赖注入;
ref:property、constructor-arg等元素的子元素,该元素中的bean属性用于指定对Bean工厂中某个Bean实例的引用;
value:property、constructor-arg等元素的子元素,用来直接指定一个常量值;
list:用于封装List或数组类型的依赖注入;
set:用于封装Set或数组类型的依赖注入;
map:用于封装Map或数组类型的依赖注入;
entry:map元素的子元素,用于设定一个键值对,其key属性指定字符串类型的键值,ref或value子元素指定其值。
了解了Bean标签,再来了解下Bean的实例化过程。
在Spring中,Bean的实例化一般分为三种:无参构造、静态工厂、普通工厂
1、无参构造
配置文件:
<!--无参构造函数-->
<bean id="user" class="com.tulun.bean6.User"/>
注意: 使用该方式实现bean的实例化,必须保证实例化的bean具有无参构造函数
不显性写构造函数则会生成默认的无参构造函数
指定了构造函数,需要指定一个无参构造函数
2、静态工厂
静态工厂类实现
public class StaticFactory {
public static User getBean() {
return new User();
}
}
spring配置文件:
<!--
静态工厂实例化bean
class属性:指定的是静态工厂类的全路径
factory-method属性:创建user对象的方法名
-->
<bean id="user1" class="com.tulun.factory6.StaticFactory" factory-method="getBean"/>
3、普通工厂
普通工厂实现
public class CommonFactory {
public User getBean() {
return new User();
}
}
spring的配置文件:
<!--普通工厂实例化bean-->
<bean id="factoty" class="com.tulun.factory6.CommonFactory"/>
<bean id="user2" factory-bean="factoty" factory-method="getBean"/>
要理解DI,首先需要理解Spring中依赖注入的过程:
首先,先介绍一下在Java中的依赖注入方式(3种):
1、通过set方法
2、通过有参构造
3、通过接口方式
Spring依赖注入的方式(2种):
1、通过有参构造
public class User {
private String name;
//有参构造函数
public User(String name) {
this.name = name;
}
}
spring配置
<!--依赖注入方式1:有参构造-->
<bean id="user3" class="com.tulun.bean6.User">
<!--name属性注入-->
<constructor-arg name="name" value="zhansan"/>
</bean>
2、通过set方法
public class User {
private String name;
//set方法
public void setName(String name) {
this.name = name;
}
}
spring配置:
<!--依赖注入方式:set方法-->
<bean id="user4" class="com.tulun.bean6.User">
<!--name属性注入-->
<property name="name" value="lisi"/>
</bean>
说到这里,介绍一下Spring配置中,类型注入的几种方式(4种):
第一种:
<!--引用类型注入-->
<bean id="factory" class="com.tulun.factory6.CommonFactory"/>
<bean id="user5" class="com.tulun.bean6.User">
<property name="factory" ref="factory"/>
</bean>
第二种:
<!--注入list类型属性-->
<bean id="user6" class="com.tulun.bean6.User">
<property name="li">
<list>
<value>1000</value>
<value>2000</value>
<value>1000</value>
</list>
</property>
</bean>
第三种:
<!--注入数组类型属性-->
<bean id="user7" class="com.tulun.bean6.User">
<property name="in">
<array>
<value>356</value>
</array>
</property>
</bean>
第四种:
<!--注入map类型属性-->
<bean id="user8" class="com.tulun.bean6.User">
<property name="mp">
<map>
<entry key="A" value="aa"/>
<entry key="B" value="bb"/>
</map>
</property>
</bean>
注意,通常情况下,注入普通类型,用到的是value
注入引用类型,用到的是ref
三、Spring以注解的方式进行Bean的实例化和属性注入
Bean的实例化:
1、全局配置文件中增加约束条件
<?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:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd">
<!--通过扫描方式,扫描指定包路径下的所有注解-->
<context:component-scan base-package="com.tulun"/>
</beans>
2、对类添加注解 @Component
@Component(value = "user")
public class User {}
3、直接调用spring容器获取对象
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("springconfig1.xml");
User user = (User) context.getBean("user");
System.out.println(user);
注意:
@Component注解是bean的实例化提供注解方式(四种)
@Component衍生出三个注解
@Controller web层
@Service 业务层
@Repository dao层
其余三个注解作用和 @Component一样的,只是为了编写业务流程代码的规范性和可读性、区分性更强一点才衍生出来的。
依赖注入:
依赖注入的注解是@Autowired也可以使用@Resource
例子:使用注解:@Autowired
public class UserController {
@Autowired
private UserService userService;
public void add() {
//调用service提供add方法
userService.add();
System.out.println("UserController.add...");
}
}
@Autowired和@Resource的区别
用途:做bean的注入时使用
历史:@Autowired 属于Spring的注解
org.springframework.beans.factory.annotation.Autowired
@Resource 不属于Spring的注解,JDK1.6支持的注解
javax.annotation.Resource
共同点:
装配bean. 写在字段上,或写在setter方法
不同点:
@Autowired 默认按类型装配
依赖对象必须存在,如果要允许null值,可以设置它的required属性为false @Autowired(required=false)
也可以使用名称装配,配合@Qualifier注解
public class TestServiceImpl {
@Autowired
@Qualifier("userDao")
private UserDao userDao;
}
@Resource 默认按名称进行装配,通过name属性进行指定
public class TestServiceImpl {
// 下面两种@Resource只要使用一种即可
@Resource(name="userDao")
private UserDao userDao; // 用于字段上
@Resource(name="userDao")
public void setUserDao(UserDao userDao) { // 用于属性的setter方法上
this.userDao = userDao;
}
}
总结:大白话解释,@Autowired自动注解,举个例子吧,一个类,俩个实现类,Autowired就不知道注入哪一个实现类,而Resource有name属性,可以区分。