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容器创建对象:
- 首先我们需要导入对应的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 标签来完成对象的管理。
- id:对象名。
- class:对象的模板类。(所有交由IoC容器管理的类都必须含有无参构造函数,因为Spring底层是通过反射机制来创建对象的,调用的是无参构造函数)
- 对象的成员变量通过 property 标签来完成赋值。
- name:成员变量名。
- value:成员变量的值。(基本数据类型、String类型可以直接赋值,其他引用类型不能通过value直接赋值)
- 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的创建。

浙公网安备 33010602011771号