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

1.IoC的概念

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

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

Spring之IoC(控制反转)和DI(依赖注入)_第1张图片
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包

Spring之IoC(控制反转)和DI(依赖注入)_第2张图片
1.png

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

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

(3)在xml里配置类

在xml文件引入schema约束



   
   

(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)使用类的无参数构造函数创建(常用)

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



(2)使用静态工厂创建

public class StaticFactory {
    public static User getUser() {
        return new User();
    }
}


    @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();
    }
}




    @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方法创建的是同一个对象

进行测试

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

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

  • prototype(常用):多例的,每次调用getBean方法创建的是不同的对象

进行测试

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配置进行赋值

   
   
        
        
   

测试一下

    @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方法


   
        
        
   

进行测试

    @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方法赋值一样的原理,只是配置文件变了,来看看怎么配置


   
   
   
        
        
   

只是这次是赋值一个对象,所以我们得先实例化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名称空间。



 -->
    
    

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

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

   
   
        
        
   
   
   

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

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

p:daoRef-ref="userDao"

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

8.一些复杂类型的注入

(1)数组类型

(2)List类型

(3)Map类型

(4)java.util.Properties类型

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

   
        
        
            
                xu
                liu
                li
                guo
            
        
        
        
            
                1
                2
                3
                4
            
        
        
        
            
                
                
                
            
        
        
        
            
                liu
                xu
            
        
   

9.Bean之间的关系

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

(1)继承

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


   
        
        
        
   
   
        
   

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

(2)依赖

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

你可能感兴趣的:(Spring之IoC(控制反转)和DI(依赖注入))