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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,560评论 4 361
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,104评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,297评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,869评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,275评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,563评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,833评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,543评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,245评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,512评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,011评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,359评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,006评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,062评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,825评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,590评论 2 273
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,501评论 2 268

推荐阅读更多精彩内容