1.IoC的概念
IoC:通过容器去控制业务对象之间的依赖关系。控制权由应用代码中转到了外部容器,控制权的转移就是反转。控制权转移的意义是降低了类之间的耦合度。
Spring中将IoC容器管理的对象称为Bean,这个和JavaBean并没有什么关系,就跟Java和JavaScript一样。
为了实现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包
(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