Spring之IoC(控制反转)和DI(依赖注入)

1.IoC的概念

IoC:通过容器去控制业务对象之间的依赖关系。控制权由应用代码中转到了外部容器,控制权的转移就是反转。控制权转移的意义是降低了类之间的耦合度。

Spring中将IoC容器管理的对象称为Bean,这个和JavaBean并没有什么关系,就跟Java和JavaScript一样。

Spring IoC容器

为了实现IoC功能,Spring提供了两个类

BeanFactory:Bean工厂,借助于配置文件能够实现对JavaBean的配置和管理,用于向使用者提供Bean的实例。

ApplicationContext:ApplicationContext构建在BeanFactory基础之上,提供了更多的实用功能。

BeanFactory的初始化和ApplicationContext的初始化有一个很大的区别:ApplicationContext初始化时会实例化所有单实例(注意是单实例)的bean,后面调用getBean方法的时候,就可以直接从缓存中进行读取;而BeanFactory初始化时不会实例化Bean,直到第一次访问某个Bean时才会进行实例化。因此初始化ApplicationContext比BeanFactory慢,但后面调用Bean实例对象的时候则ApplicationContext比BeanFactory快。

2.IoC底层原理(源码后面慢慢分析)

(1)xml配置文件
(2)dom4j解析xml
(3)工厂模式
(4)反射

3.IoC入门案例

(1)基本的jar包
1.png

(2)创建Bean,在类里添加方法

public class User {
	public void add() {
		System.out.println("666666");
	}
}

(3)在xml里配置类

在xml文件引入schema约束

<?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.xsd">
   <!-- 配置IoC -->
   <bean id="user" class="com.codeliu.entity.User"></bean>
</beans>

(4)写代码测试对象创建

    @Test
	public void testIoC() {
		// 加载spring配置文件,创建对象
		@SuppressWarnings("resource")
		ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
		// 得到创建的对象,参数为配置文件中bean标签的id
		User user = (User)context.getBean("user");
		System.out.println(user);
		user.add();
	}

4.实例化Bean的三种方式

(1)使用类的无参数构造函数创建(常用)

这种方式记得得有无参构造方法

<!-- 使用类的无参构造函数实例化 -->
<bean id="user1" class="com.codeliu.entity.User"></bean>

(2)使用静态工厂创建

public class StaticFactory {
	public static User getUser() {
		return new User();
	}
}
<!-- 使用静态工厂方法实例化User -->
<bean id="staticFactory" class="com.codeliu.bean.StaticFactory" factory-method="getUser"></bean>
    @Test
	public void testStaticFactory() {
		// 加载spring配置文件,创建对象
		@SuppressWarnings("resource")
		ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
		// 得到创建的对象,参数为配置文件中bean标签的id
		User user = (User)context.getBean("staticFactory");
		System.out.println(user);
	}

(3)使用实例工厂创建

public class Factory {
	public User getUser() {
		return new User();
	}
}
<!-- 使用实例工厂实例化User -->
<!-- 首先实例化bena3 -->
<bean id="factory" class="com.codeliu.bean.Factory"></bean>
<bean id="user2" factory-bean="factory" factory-method="getUser"></bean>
    @Test
	public void testFactory() {
		// 加载spring配置文件,创建对象
		@SuppressWarnings("resource")
		ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
		// 得到创建的对象,参数为配置文件中bean标签的id
		User user = (User)context.getBean("user2");
		System.out.println(user);
	}

5.bean标签常用属性

(1)id
唯一,必须以字母开头,不能包含一些特殊字符。Spring根据class属性创建对应类的实例后,会以id为键key,实例对象为值value放入一个Map中,当调用getBean方法时,则会根据id的值从Map中把实例取出来。

(2)class
Bean类的全路径,不能是接口

(3)name
可以出现特殊符号,当没有设置id的时候,name也可以作为id

(4)scope

Bean的作用范围

  • singleton(常用):默认值,单例的,每次调用getBean方法创建的是同一个对象
<bean id="user1" class="com.codeliu.entity.User" scope="singleton"></bean>

进行测试

User user = (User)context.getBean("user1");
User user2 = (User)context.getBean("user1");
// true
System.out.println(user == user2);

可以看到,创建一个实例后,这个唯一实例会被缓存起来,下一次要请求使用就直接返回缓存中的实例。

  • prototype(常用):多例的,每次调用getBean方法创建的是不同的对象
<bean id="user1" class="com.codeliu.entity.User" scope="prototype"></bean>

进行测试

User user = (User)context.getBean("user1");
User user2 = (User)context.getBean("user1");
// false
System.out.println(user == user2);

每次请求都会创建一个新的实例,这样就会出现频繁的创建和销毁对象,造成很大的开销,因此,如果不是必要,不设置成prototype。

  • request:web项目中,Spring创建一个Bean的对象,将对象存入request域中

  • session:web项目中,Spring创建一个Bean的对象,将对象存入session域中

  • globalSession:web项目中,应用在Porlet环境,如果没有Porlet环境,那么globalSession相当于session。

6.属性注入

创建对象的时候,向类里面的属性设置值。

三种方式实现属性注入
(1)使用set方法(常用)
(2)使用带参的构造函数
(3)使用接口

spring只支持前两张方式的注入

(1)使用带参的构造函数
比如我下面的类

public class PropertyDemo1 {
	private String name;
	public PropertyDemo1(String name) {
		this.name = name;
	}
	public void add() {
		System.out.println("PropertyDemo1" + name);
	}
}

里面有一个带参数的构造方法,我们可以通过xml配置进行赋值

   <!-- 使用带参的构造方法为Bean中的属性设值 -->
   <bean id="property1" class="com.codeliu.entity.PropertyDemo1">
   		<!-- name属性表示Bean类中属性的名字, value表示要设置的值-->
   		<constructor-arg name="name" value="CodeTiger"></constructor-arg>
   </bean>

测试一下

    @Test
	public void testProperty1() {
		// 加载spring配置文件,创建对象
		@SuppressWarnings("resource")
		ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
		// 得到创建的对象,参数为配置文件中bean标签的id
		PropertyDemo1 p1 = (PropertyDemo1)context.getBean("property1");
		p1.add();
	}

(2)使用set方法(重点)
我们定义下面这样一个类

public class PropertyDemo2 {
	private String book;
	public PropertyDemo2() {}
	
	public void setBook(String book) {
		this.book = book;
	}

	public void add() {
		System.out.println(book);
	}
}

有一个属性book并带有相应的set方法

<!-- 使用set方法为Bean中的属性设值 -->
   <bean id="property2" class="com.codeliu.entity.PropertyDemo2">
   		<!-- 使用property标签 -->
   		<property name="book" value="Think in java"></property>
   </bean>

进行测试

    @Test
	public void testProperty2() {
		// 加载spring配置文件,创建对象
		@SuppressWarnings("resource")
		ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
		// 得到创建的对象,参数为配置文件中bean标签的id
		PropertyDemo2 p2 = (PropertyDemo2)context.getBean("property2");
		p2.add();
	}

我们这里只是使用了一个String类型的属性,但实际中,我们应该会用其他类作为一个类的属性,获取一些更复杂的类型比如Map、List等,这时候该怎么注入呢?

我们写一个service类,一个dao类,然后使用service类去调用dao类的方法,这样service类中肯定会有一个dao类的实例对象,看看这是应该怎么赋值呢?

public class UserDao {
	public void add() {
		System.out.println("dao.......");
	}
}
public class UserService {
	private UserDao dao;
	public void setDao(UserDao dao) {
		this.dao = dao;
	}
	public void add() {
		System.out.println("service.....");
		dao.add();
	}
}

在service中,其实还是和上面set方法赋值一样的原理,只是配置文件变了,来看看怎么配置

<!-- 注入对象类型的属性 -->
   <!-- 先实例化UserDao -->
   <bean id="userDao" class="com.codeliu.dao.UserDao"></bean>
   <bean id="userService" class="com.codeliu.service.UserService">
   		<!-- name表示 UserService类中属性的名称  ref表示配置UserDao的bean标签的id值-->
   		<property name="dao" ref="userDao"></property>
   </bean>

只是这次是赋值一个对象,所以我们得先实例化dao类,才能给service类的dao属性赋值。注意property 没有使用value属性,而是使用了ref属性。

    @Test
	/**
	 * 通过set方法注入对象类型的属性
	 */
	public void testProperty3() {
		// 加载Spring配置文件,创建对象
		@SuppressWarnings("resource")
		ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
		// 得到创建的对象,参数为配置文件中bean标签的id
		UserService service = (UserService)context.getBean("userService");
		service.add();
	}

7.p名称空间注入

首先在xml文件的最外层bean标签中加上这么一句

xmlns:p="http://www.springframework.org/schema/p"

这样才可以使用p名称空间。

<bean id="property2" class="com.codeliu.entity.PropertyDemo2" p:book="Think in java"></bean>
<!-- 上下两句等价 -->
<bean id="property2" class="com.codeliu.entity.PropertyDemo2"> -->
   	<!-- 使用property标签 -->
   	<property name="book" value="Think in java"></property>
</bean>

上面的配置文件中,上下两句等价。

那如果我要注入一个对象类型的属性呢?那就得这么写

   <bean id="userDao" class="com.codeliu.dao.UserDao"></bean>
   <bean id="userService" class="com.codeliu.service.UserService">
   		<!-- name表示 UserService类中属性的名称  ref表示配置UserDao的bean标签的id值-->
   		<property name="dao" ref="userDao"></property>
   </bean>
   <!-- 和 上面的等价,注意p:dao-ref,ref必须加上,表示后面的是引用的另一个bean-->
   <bean id="userService2" class="com.codeliu.service.UserService" p:dao-ref="userDao"></bean>

注意p后面的,必须带上-Ref表示是引用另一个bean

这时候又有问题,如果我们的属性名称叫xxRef,那怎么办?写成

p:daoRef-ref="userDao"

这样会出错的。所以p命名空间也不能乱用。

8.一些复杂类型的注入

(1)数组类型

(2)List类型

(3)Map类型

(4)java.util.Properties类型

首先在User类中添加上面四种类型的属性,并添加相应的set方法。直接看配置

   <bean id="user1" class="com.codeliu.entity.User">
   		<!-- 为数组类型的属性赋值 -->
   		<property name="arr">
   			<list>
   				<value>xu</value>
   				<value>liu</value>
   				<value>li</value>
   				<value>guo</value>
   			</list>
   		</property>
   		<!-- 为List类型的属性赋值 -->
   		<property name="list">
   			<list>
   				<value>1</value>
   				<value>2</value>
   				<value>3</value>
   				<value>4</value>
   			</list>
   		</property>
   		<!-- 为Map类型的属性赋值 -->
   		<property name="map">
   			<map>
   				<entry key="1" value="liu"></entry>
   				<entry key="2" value="xu"></entry>
   				<entry key="3" value="guo"></entry>
   			</map>
   		</property>
   		<!-- 为Properities类型的属性赋值 -->
   		<property name="properities">
   			<props>
   				<prop key="1">liu</prop>
   				<prop key="2">xu</prop>
   			</props>
   		</property>
   </bean>

9.Bean之间的关系

Spring允许在配置Bean时为Bean指定继承依赖两种关系。

(1)继承

有时我们可能两个类之间大多数的属性都相同,这是如果对每个Bean都重复编写注入信息就很繁琐了,这时我们可以通过bean标签的parent属性重用已有的Bean元素的配置信息。

<!-- Bean之间的继承。。。不是指类之间的继承,只是配置信息的复用 -->
   <bean id="class1" class="com.codeliu.Class1">
   		<property name="name" value="liu"></property>
   		<property name="age" value="22"></property>
   		<property name="address" value="NJUPT"></property>
   </bean>
   <bean id="class2" class="com.codeliu.Class2" parent="class1">
   		<property name="name" value="xu"></property>
   </bean>

这里的继承指的是配置信息的复用,和传统的Java类的继承没有半毛钱关系。

(2)依赖

IoC能保证在实例化一个Bean时,它所依赖的其他Bean已经实例化完毕。但有时候我们有这样的需求,我们想要类A比类B先实例化,如果类A是作为类B的属性,这还好办,但关键是A不是B的属性啊,这时我们可以通过bean标签的depends-on属性进行指定前置依赖的Bean,即使没有关联关系。

<!-- 设置两个Bean的依赖关系,userDao要先于userService实例化 -->
<bean id="userDao" class="com.codeliu.dao.UserDao"></bean>
<bean id="userService" class="com.codeliu.service.UserService" depends-on="userDao"></bean>

10.自动装配

Spring IoC容器可以自动装配相互协作Bean之间的关联关系。可以通过设置bean标签的autowire属性进行设置,该属性有5种取值分别如下:

(1)no:不使用自动装配,默认值。必须通过ref进行指定依赖。

(2)byName:根据属性名自动匹配。如果某个Bean设置了此选项,那么IoC将根据名字查找与属性完全一致的Bean,并将其与属性自动匹配。如某个Bean设置成byName,该Bean中有一个属性叫dao(同时要提供set方法),那么IoC就会查找名为dao的Bean,用它来装配dao属性。

(3)byType:如果容器中存在一个与指定属性类型相同的Bean,那么将该属性自动装配。如果存在多个该类型的Bean,则抛出异常,并指出不能使用byType方式进行装配。如果没有找到相同类型的,则什么事都不会发生,属性也不会被设置。如果你希望在没找到时发出提示信息,可以设置dependency-check属性的值为objects,这样在找不到的时候就会抛出异常。

(4)constructor:与byType类似,不同之处在于它应用于构造器参数,如果属性在容器中没有找到与构造器参数类型一致的Bean,则抛出异常。

(5)autodetect:通过Bean的自省机制(introspection)来决定是否使用constructor还是byType方式进行自动装配。如果发现默认的构造器,将使用byType方式。


下面摘抄网上的一段话作为文章的结尾

IOC是一种叫做“控制反转”的设计思想。

1、较浅的层次——从名字上解析
“控制”就是指对 对象的创建、维护、销毁等生命周期的控制,这个过程一般是由我们的程序去主动控制的,如使用new关键字去创建一个对象(创建),在使用过程中保持引用(维护),在失去全部引用后由GC去回收对象(销毁)。
“反转”就是指对 对象的创建、维护、销毁等生命周期的控制由程序控制改为由IOC容器控制,需要某个对象时就直接通过名字去IOC容器中获取。

2、更深的层次——提到DI,依赖注入,是IOC的一种重要实现
一个对象的创建往往会涉及到其他对象的创建,比如一个对象A的成员变量持有着另一个对象B的引用,这就是依赖,A依赖于B。IOC机制既然负责了对象的创建,那么这个依赖关系也就必须由IOC容器负责起来。负责的方式就是DI——依赖注入,通过将依赖关系写入配置文件,然后在创建有依赖关系的对象时,由IOC容器注入依赖的对象,如在创建A时,检查到有依赖关系,IOC容器就把A依赖的对象B创建后注入到A中(组装,通过反射机制实现),然后把A返回给对象请求者,完成工作。

3、IOC的意义何在?
IOC并没有实现更多的功能,但它的存在使我们不需要很多代码、不需要考虑对象间复杂的耦合关系就能从IOC容器中获取合适的对象,而且提供了对 对象的可靠的管理,极大地降低了开发的复杂性。

原文来自https://blog.csdn.net/zhangliangzi/article/details/51550912

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值