1 什么是控制反转
在传统的程序开发中,需要调用对象时,通常由调用者来创建被调用者的示例,即对象是由调用者主动new出来的。
但在Spring框架中创建对象的工作不再由调用者来完成,而是交给IoC容器来创建,再推送给调用者,整个流程完成反转,所以是控制反转。
2 配置文件
- 通过配置 bean 标签来完成对象的管理
- id:对象名
- class:对象的模板类(所有交给IoC容器来管理的类必须有无参构造函数,因为Spring底层是通过反射机制来创建对象的,而反射机制调用的是无参构造)
- 对象的成员变量通过 property 标签赋值
- name:成员变量名
- vlaue:成员变量值(基本数据类型,String可以直接赋值;如果是其他引用,不能通过 value 赋值)
- ref:将 IoC 中的另一个bean付给当前的成员变量(DI 依赖注入)
<?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.southwind.entity.Student">
<property name="age" value="22"></property>
<property name="id" value="1"></property>
<property name="name" value="张三"></property>
<property name="addr" ref="address"></property>
</bean>
<bean id="address" class="com.southwind.entity.address">
<property name="name" value="科技路"></property>
<property name="id" value="1"></property>
</bean>
</beans>
3 如何使用 IoC?
- 创建 Maven 工程,pom.xml 添加依赖
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.22</version>
</dependency>
</dependencies>
- 创建实体类 Student
package com.southwind.entity;
import lombok.Data;
@Data
public class Student {
private long id;
private String name;
private int age;
}
- 传统的开发方式:手动 new Student
package com.southwind.test;
import com.southwind.entity.Student;
public class Test {
public static void main(String args[]){
Student student = new Student();
student.setId(1L);
student.setName("张三");
student.setAge(22);
System.out.println(student);
}
}
- 通过 IoC容器 创建对象,在配置文件中添加需要管理的对象,XML 格式的配置文件,文件名可以自定义
- 定义一个 spring.xml 配置文件(创建一个 XML Configuration File 文件):
<?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.southwind.entity.Student">
<property name="age" value="22"></property>
<property name="id" value="1"></property>
<property name="name" value="张三"></property>
</bean>
</beans>
- IoC的基本使用:
package com.southwind.test;
import com.southwind.entity.Student;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Test {
public static void main(String args[]){
// 加载配置文件
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
Student student = (Student) applicationContext.getBean("student");
System.out.println(student);
}
}
4 IoC 底层原理
- 读取配置文件,解析XML
- 通过反射机制实例化配置文件中所配置所有的bean
4.1 IoC底层原理的简单演示
(1)根据加载配置文件的代码可知
// 加载配置文件
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
Student student = (Student) applicationContext.getBean("student");
System.out.println(student);
/*
ApplicationContext是一个接口,有一个方法:getBean(String)
ClassPathXmlApplicationContext是实现它的类
调用反射机制,通过无参构造函数创建。
*/
(2)手动编写 ApplicationContext 和 ClassPathXmlApplicationContext
① ApplicationContext
package com.southwind.ioc;
public interface ApplicationContext {
public Object getBean(String id);
}
② ClassPathXmlApplicationContext
package com.southwind.ioc;
import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Locale;
import java.util.Map;
public class ClassPathXmlApplicationContext implements ApplicationContext {
private Map<String, Object> ioc = new HashMap<>();
public ClassPathXmlApplicationContext(String path){
try{
SAXReader reader = new SAXReader();
Document document = reader.read("src/main/resources/"+path);
Element root = document.getRootElement();
Iterator<Element> iterator = root.elementIterator();
while (iterator.hasNext()){
Element element = iterator.next();
String id = element.attributeValue("id");
String className = element.attributeValue("class");
// 通过反射机制创建对象
Class clazz = Class.forName(className);
// 获取无参构造函数
Constructor constructor = clazz.getConstructor();
Object object = constructor.newInstance();
// 给目标对象赋值
Iterator<Element> beanIter = element.elementIterator();
while(beanIter.hasNext()){
Element property = beanIter.next();
String name = property.attributeValue("name");
String valueStr = property.attributeValue("value");
String ref = property.attributeValue("ref");
if(ref == null){
String methodName = "set"+name.substring(0,1).toUpperCase()+name.substring(1,name.length());
Field field = clazz.getDeclaredField(name);
Method method = clazz.getDeclaredMethod(methodName,field.getType());
// 根据成员变量的数据类型将 value 进行转换
Object value = null;
if(field.getType().getName() == "long"){
value = Long.parseLong(valueStr);
}
if(field.getType().getName() == "java.lang.String"){
value = valueStr;
}
if(field.getType().getName() == "int"){
value = Integer.parseInt(valueStr);
}
method.invoke(object, value);
}
}
ioc.put(id,object);
}
}catch (Exception e){
e.printStackTrace();
}
}
@Override
public Object getBean(String id) {
return ioc.get(id);
}
}
③ 运行结果
Student(id=1, name=张三, age=22, addr=null)
// addr的赋值也是类似,以后再补充
5 获取IoC容器创建的对象
5.1 通过 id 获取对象
Student student = (Student) applicationContext.getBean("student");
5.2 通过 运行时类 获取对象
Student student = (Student) applicationContext.getBean(Student.class);
这种方式存在一个问题,配置文件中一个数据类型的对象只能由一个实例,否则会抛出异常,因为没有唯一的bean。
6 创建对象的方式
6.1 无参构造
在实体类中创建对应的有参构造函数。
@NoArgsConstructor 无参构造
<bean id="student" class="com.southwind.entity.Student">
<property name="age" value="22"></property>
<property name="id" value="1"></property>
<property name="name" value="张三"></property>
<property name="addr" ref="address"></property>
</bean>
<bean id="address" class="com.southwind.entity.address">
<property name="name" value="科技路"></property>
<property name="id" value="1"></property>
</bean>
6.2 有参构造
在实体类中创建对应的有参构造函数。
@AllArgsConstructor 有参构造
<!-- 写法一 -->
<bean id="student2" class="com.southwind.entity.Student">
<constructor-arg name="id" value="3"></constructor-arg>
<constructor-arg name="name" value="小米"></constructor-arg>
<constructor-arg name="age" value="18"></constructor-arg>
<constructor-arg name="addr" ref="address2"></constructor-arg>
</bean>
<bean id="address2" class="com.southwind.entity.address">
<constructor-arg name="id" value="2"></constructor-arg>
<constructor-arg name="name" value="可新路"></constructor-arg>
</bean>
<!-- 写法二 -->
<!-- name 省略,但要按顺序 -->
<bean id="student2" class="com.southwind.entity.Student">
<constructor-arg value="3"></constructor-arg>
<constructor-arg value="小米"></constructor-arg>
<constructor-arg value="18"></constructor-arg>
<constructor-arg ref="address2"></constructor-arg>
</bean>
<bean id="address2" class="com.southwind.entity.address">
<constructor-arg value="2"></constructor-arg>
<constructor-arg value="可新路"></constructor-arg>
</bean>
<!-- 写法三 -->
<!-- index代替name -->
<bean id="student2" class="com.southwind.entity.Student">
<constructor-arg index="0" value="3"></constructor-arg>
<constructor-arg index="1" value="小米"></constructor-arg>
<constructor-arg index="2" value="18"></constructor-arg>
<constructor-arg index="3" ref="address2"></constructor-arg>
</bean>
<bean id="address2" class="com.southwind.entity.address">
<constructor-arg index="0" value="2"></constructor-arg>
<constructor-arg index="1" value="可新路"></constructor-arg>
</bean>
注解:
(1)@AllArgsConstructor 有参构造
(2)@NoArgsConstructor 无参构造