IoC(控制反转)

什么是控制反转?

在传统程序开发中,需要调用对象时,通常需要创建对象的实例,也就是需要通过new去实例化对象。

但是在Spring框架中,创建对象的工作不再需要调用者来完成,而是交由IoC容器来创建对象,再交由调用者使用,整个流程完成反转,所以叫做控制反转。

 

代码示例

首先我们创建一个Student类。

package com.exambner.ioc;

public class Student {

    private long id;

    private String name;

    private int age;

    public long getId() {
        return id;
    }

    public void setId(long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "Student{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

传统的实例化bean的方式,需要手动去new对象。

public static void main(String[] args) {
        Student student = new Student();
        student.setId(1L);
        student.setName("小明");
        student.setAge(18);
        System.out.println(student);
    }
Student{id=1, name='小明', age=18}

通过Ioc容器创建对象:

  1. 首先我们需要导入对应的jar包。
<dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.3.22</version>
        </dependency>
</dependencies>

  2. 然后创建配置文件spring.xml。配置文件一般我们放在resources文件夹下。

 

<?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">
    
    <bean id="student" class="com.exambner.ioc.Student"></bean>
</beans>

  3. 获取bean的实例。通过bean的id获取。

public static void main(String[] args) {
        // 加载配置文件
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
        Student student = (Student) applicationContext.getBean("student");
        System.out.println(student);
    }

这时我们就得到了一个bean的实例对象。

Student{id=0, name='null', age=0}

  4. 给bean赋值。

<bean id="student" class="com.exambner.ioc.Student">
        <property name="id" value="1"></property>
        <property name="name" value="小明"></property>
        <property name="age" value="18"></property>
</bean>

这时我们再次运行就可以看到赋值的结果了。

Student{id=1, name='小明', age=18}

 

配置文件:

  • 通过配置 bean 标签来完成对象的管理。
  1. id:对象名。
  2. class:对象的模板类。(所有交由IoC容器管理的类都必须含有无参构造函数,因为Spring底层是通过反射机制来创建对象的,调用的是无参构造函数)
  • 对象的成员变量通过 property 标签来完成赋值。
  1. name:成员变量名。
  2. value:成员变量的值。(基本数据类型、String类型可以直接赋值,其他引用类型不能通过value直接赋值)
  3. ref:将IoC容器中的另一个 bean 赋值给当前变量。这里使用到了DI(依赖注入)

ref的使用:

首先我们创建一个Address类

package com.exambner.ioc;

public class Address {
    private long id;

    private String address;

    public long getId() {
        return id;
    }

    public void setId(long id) {
        this.id = id;
    }

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }
}

然后在Student中添加一个student字段,类型为该Address。

package com.exambner.ioc;

public class Student {

    private long id;

    private String name;

    private int age;

    private Address address;

    public long getId() {
        return id;
    }

    public void setId(long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public Address getAddress() {
        return address;
    }

    public void setAddress(Address address) {
        this.address = address;
    }

    @Override
    public String toString() {
        return "Student{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", age=" + age +
                ", address=" + address +
                '}';
    }
}

然后通过Ioc去创建bean。

<bean id="student" class="com.exambner.ioc.Student">
        <property name="id" value="1"></property>
        <property name="name" value="小明"></property>
        <property name="age" value="18"></property>
        <property name="address" ref="address"></property>
    </bean>

    <bean id="address" class="com.exambner.ioc.Address">
        <property name="id" value="1"></property>
        <property name="address" value="南京"></property>
    </bean>

这个时候因为address字段是Address类型,是IoC中的另外一个bean,所以我们不能直接通过value来进行赋值。所以我们需要使用ref来将id为address的bean赋值给address字段。

 

Ioc底层原理

1、读取配置文件,解析XML。

2、通过反射机制实例化配置文件中所配置的所有的bean。

 

通过运行时类获取bean

public static void main(String[] args) {
        // 加载配置文件
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
        Student student = applicationContext.getBean(Student.class);
        System.out.println(student);
    }
Student{id=1, name='小明', age=18, address=Address{id=1, address='南京'}}

使用运行时类获取bean时,要保证xml配置文件中相同数据类型的bean只能有一个,否则会抛出异常,因为没有唯一的bean。

<bean id="student" class="com.exambner.ioc.Student">
        <property name="id" value="1"></property>
        <property name="name" value="小明"></property>
        <property name="age" value="18"></property>
        <property name="address" ref="address"></property>
    </bean>
    <bean id="student1" class="com.exambner.ioc.Student">
        <property name="id" value="1"></property>
        <property name="name" value="小明"></property>
        <property name="age" value="18"></property>
        <property name="address" ref="address"></property>
    </bean>

比如这里我们添加两个Student的bean,分别定义id为student和student1。这个时候去根据运行时类匹配时,就会发现有两个Student的bean,程序无法判定我们所需要的是哪一个bean,就会提示异常。

Exception in thread "main" org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'com.exambner.ioc.Student' available: expected single matching bean but found 2: student,student1
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveNamedBean(DefaultListableBeanFactory.java:1273)
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveBean(DefaultListableBeanFactory.java:494)
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:349)
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:342)
    at org.springframework.context.support.AbstractApplicationContext.getBean(AbstractApplicationContext.java:1172)
    at com.exambner.ioc.Text.main(Text.java:16)

 

通过有参构造函数创建bean

首先我们的实体类中需要一个有参构造函数:

package com.exambner.ioc;

public class Student {

    private long id;

    private String name;

    private int age;

    private Address address;
    // 有参构造方法
    public Student(long id, String name, int age, Address address) {
        this.id = id;
        this.name = name;
        this.age = age;
        this.address = address;
    }

    public long getId() {
        return id;
    }

    public void setId(long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public Address getAddress() {
        return address;
    }

    public void setAddress(Address address) {
        this.address = address;
    }

    @Override
    public String toString() {
        return "Student{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", age=" + age +
                ", address=" + address +
                '}';
    }
}

在配置文件中我们使用 <construcror-tag> 标签来通过有参构造函数创建bean:

<bean id="student" class="com.exambner.ioc.Student">
        <constructor-arg name="id" value="1"></constructor-arg>
        <constructor-arg name="name" value="小明"></constructor-arg>
        <constructor-arg name="age" value="18"></constructor-arg>
        <constructor-arg name="address" ref="address"></constructor-arg>
    </bean>
Student{id=1, name='小明', age=18, address=Address{id=1, address='南京'}}

这里的话我们也可以省略name属性,因为 constructor-arg 默认是根据索引位置来对应我们的属性,但是要保证索引顺序一致。

<bean id="student" class="com.exambner.ioc.Student">
        <constructor-arg  value="1"></constructor-arg>
        <constructor-arg  value="小明"></constructor-arg>
        <constructor-arg value="18"></constructor-arg>
        <constructor-arg ref="address"></constructor-arg>
</bean>

如果索引顺序与构造函数参数顺序不一致就会发生异常。

<bean id="student" class="com.exambner.ioc.Student">
    <constructor-arg  value="1"></constructor-arg>
    <constructor-arg value="18"></constructor-arg>
    <constructor-arg  value="小明"></constructor-arg>
    <constructor-arg ref="address"></constructor-arg>
</bean>
Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'student' defined in class path resource [spring.xml]: Unsatisfied dependency expressed through constructor parameter 2: Could not convert argument value of type [java.lang.String] to required type [int]: Failed to convert value of type 'java.lang.String' to required type 'int'; nested exception is java.lang.NumberFormatException: For input string: "小明"
Exception in thread "main" org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'student' defined in class path resource [spring.xml]: Unsatisfied dependency expressed through constructor parameter 2: Could not convert argument value of type [java.lang.String] to required type [int]: Failed to convert value of type 'java.lang.String' to required type 'int'; nested exception is java.lang.NumberFormatException: For input string: "小明"

这里就是因为构造函数索引3位置的属性是age,是int类型,而配置文件赋值时索引3的位置传递的是字符串,发生了类型转换异常。

我们可以修改通过指定index属性的方法来解决这个问题。

<bean id="student" class="com.exambner.ioc.Student">
        <constructor-arg index="0" value="1"></constructor-arg>
        <constructor-arg index="2" value="18"></constructor-arg>
        <constructor-arg index="1" value="小明"></constructor-arg>
        <constructor-arg index="3" ref="address"></constructor-arg>
    </bean>

 

向bean中注入集合

package com.exambner.ioc;

import java.util.List;

public class Student {

    private long id;

    private String name;

    private int age;

    private List<Address> addressList;

    public long getId() {
        return id;
    }

    public void setId(long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public List<Address> getAddressList() {
        return addressList;
    }

    public void setAddressList(List<Address> addressList) {
        this.addressList = addressList;
    }

    @Override
    public String toString() {
        return "Student{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", age=" + age +
                ", addressList=" + addressList +
                '}';
    }
}

一些情况下我们类中的字段会使用到List集合来接收数据,这时就需要使用到 list 标签来注入bean了。

<bean id="student" class="com.exambner.ioc.Student">
        <property name="id" value="1"></property>
        <property name="name" value="小明"></property>
        <property name="age" value="18"></property>
        <property name="addressList">
            <list>
                <ref bean="address"></ref>
                <ref bean="address1"></ref>
            </list>
        </property>
    </bean>

 

scope(作用域)。

Spring管理的bean是根据scope来生成的,表示bean的作用域。共4种:

  • singleton:单例模式。表示通过Ioc容器创建的bean是唯一的。
  • prototype:原型模式。表示通过IoC容器创建的bean是不同的。
  • request:请求。表示在一次HTTP请求中有效。
  • session:表示在一次用户会话中有效。

request和session只用于Web项目中,一般情况下只会使用singleton和prototype。

<bean name="student2" class="com.exambner.ioc.Student" scope="singleton">
        <property name="id" value="1"></property>
        <property name="name" value="小明"></property>
        <property name="age" value="18"></property>
</bean>

这里我们定义一个作用域为单例模式的bean。

<bean name="student2" class="com.exambner.ioc.Student" scope="singleton">
        <property name="id" value="1"></property>
        <property name="name" value="小明"></property>
        <property name="age" value="18"></property>
</bean>
public static void main(String[] args) {
        // 加载配置文件
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
        Student student = (Student) applicationContext.getBean("student2");
        Student student2 = (Student) applicationContext.getBean("student2");
        System.out.println(student == student2);
    }

这里我们实例化两个对象,通过==判断他们是否是同一个对象。

true

这就是单例模式。

然后我们使用原型模式创建一个bean,再次输出结果。

<bean name="student2" class="com.exambner.ioc.Student" scope="prototype">
        <property name="id" value="1"></property>
        <property name="name" value="小明"></property>
        <property name="age" value="18"></property>
</bean>
false

这时他们就不再相等。因为使用了原型模式。在我们每次去getBean的时候都会在堆空间中创建一个新的对象。

 

使用单例模式时,bean是在解析配置文件时调用的无参构造函数来创建bean对象。

使用原型模式时,bean是在调用getBean方法时才去调用无参构造函数来完成bean的创建。

 

posted @ 2023-01-04 03:27  Amireux-126  阅读(37)  评论(0)    收藏  举报