Spring 的 IOC 容器管理对象示例

IOC 控制反转

IOC—Inversion of Control,即 “控制反转”,不是什么技术,而是一种设计思想。

在 Java 开发中,IOC 意味着将你设计好的对象交给容器控制,而不是传统的在你的对象内部直接控制。

所谓 IOC,对于 Spring 框架来说,就是由 Spring 来负责控制对象的生命周期和对象间的关系。

Spring 所倡导的开发方式就是如此,所有的类都会在 Spring 容器中登记。所有的类的创建、销毁都由 Spring 来控制,也就是说控制对象生存周期的不再是引用它的对象,而是 Spring。对于某个具体的对象而言,以前是它控制其他对象,现在是所有对象都被 Spring 控制,所以这叫控制反转。

理解 IOC

如何理解好 IOC 呢?理解好 IOC 的关键是要明确:

  • 谁控制谁,控制什么?

    传统 Java SE 程序设计,我们直接在对象内部通过 new 进行创建对象,是程序主动去创建依赖对象;而 IOC 是有专门一个容器来创建这些对象,即由 IOC 容器来控制对 象的创建;谁控制谁?当然是 IOC 容器控制了对象;控制什么?那就是主要控制了外部资源获取(不只是对象包括比如文件等)。

  • 为何是反转,哪些方面反转了?

    有反转就有正转,传统应用程序是由我们自己在对象中主动控制去直接获取依赖对象,也就是正转;而反转则是由容器来帮忙创建及注入依赖对象;为何是反转?因为由容器帮我们查找及注入依赖对象,对象只是被动的接受依赖对象,所以是反转;哪些方面反转了?依赖对象的获取被反转了。

对象示例

public class HelloWorld {
    private String userName;

    public void setUserName(String userName) {
        this.userName = userName;
    }
    public String getUserName() {
        return userName;
    }
    public void sayHello(){
        System.out.println("Hello,"+userName);
    }
}

传统方式加载对象

HelloWorld helloWorld=new HelloWorld();
helloWorld.setUserName("周杰伦");
helloWorld.sayHello();

IOC 容器加载对象

新建 IOC 配置文件

在 resources 目录下新建一个 IOC 的配置文件 applicationContext.xml,等下需要通过这个配置文件去创建 IOC 容器。

<?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="helloWorld" class="com.jueee.bean.HelloWorld">
        <property name="userName" value="林俊杰"></property>
    </bean>
</beans>

IOC 普通方式加载

  • BeanFactory:表示 Spring IOC 容器,专门生产 bean 对象的工厂,负责配置,创建和管理 bean
  • bean:被 Spring IOC 容器管理的对象都是 bean,可以理解为 Spring 下皆为 bean
//1.从classpath路径去寻找配置文件,加载我们的配置
Resource resources= new ClassPathResource("applicationContext.xml");
//2.加载配置文件之后,创建IOC容器
BeanFactory factory=new XmlBeanFactory(resources);
//3.从Spring IOC容器中获取指定名称的对象
HelloWorld helloWorld= (HelloWorld) factory.getBean("helloWorld");
//--------------------IOC结束了---------------------
helloWorld.sayHello();

IOC 注解方式加载

  • 先在测试类头上加个 @ContextConfiguration,意思是找到 Spring 容器,classpath 就是 resource 目录
  • @Autowired:表示自动按照类型去 Spring 容器中找到对应的 Bean 对象,然后自动注入
@SpringBootTest
@ContextConfiguration("classpath:applicationContext.xml")
public class HelloWorldTest2 {

    @Autowired
    private HelloWorld helloWorld;

    @Test
    public void testHelloIOCNB(){
        helloWorld.sayHello();
    }
}

IOC 加载工作原理

IOC 的工作原理,使用到的技术有两个,反射和内省。

如下示例所示:

String className = "com.jueee.bean.HelloWorld";
//--------------------模拟IOC开始了-------------------
//1.使用反射创建对象
Class clzz = Class.forName(className);
Constructor con = clzz.getConstructor();
con.setAccessible(true);//设置构造器可访问性为true
Object obj = con.newInstance();

//2.使用内省机制获取所有的属性名称
BeanInfo beanInfo = Introspector.getBeanInfo(clzz, Object.class);
PropertyDescriptor[] pds = beanInfo.getPropertyDescriptors();

for (PropertyDescriptor pd : pds) {
    String propertyName = pd.getName();
    if ("userName".equals(propertyName)) {
        pd.getWriteMethod().invoke(obj, "陈奕迅");
    }
}
HelloWorld helloWorld = (HelloWorld) obj;
//--------------------模拟IOC结束了---------------------
helloWorld.sayHello();

IOC 的容器类型

BeanFactory

//--------------------IOC开始了-------------------
//1.从classpath路径去寻找配置文件,加载我们的配置
Resource resources= new ClassPathResource("applicationContext.xml");
//2.加载配置文件之后,创建IOC容器
BeanFactory factory=new XmlBeanFactory(resources);
//3.从Spring IOC容器中获取指定名称的对象
HelloWorld helloWorld= (HelloWorld) factory.getBean(HelloWorld.class);
//--------------------IOC结束了---------------------
helloWorld.sayHello();

ApplicationContext

ApplicationContext 这个其实是 BeanFactory 的一个子接口。

//--------------------IOC开始了-------------------
ApplicationContext ctx=new ClassPathXmlApplicationContext("applicationContext.xml");
System.out.println("上面的代码已经创建Bean对象了,下面的获取Bean,获取已有的Bean");
HelloWorld helloWorld= ctx.getBean("helloWorld",HelloWorld.class);
//--------------------IOC结束了---------------------
helloWorld.sayHello();

加载 Bean 的方式

  1. 根据 Bean 对象在容器中的 id 来获取。当有两个 id 重复时,就会报错。

    HelloWorld helloWorld= (HelloWorld) factory.getBean("helloWorld");
  2. 根据类型获取 Bean。当两个 bean 的 id 不一样,class 一样的时候,还是会报错,报类找到的 Bean 不是唯一的

    HelloWorld helloWorld= (HelloWorld) factory.getBean(HelloWorld.class);
  3. 根据 id + 类型来获取 Bean,推荐方式。

    HelloWorld helloWorld= ctx.getBean("helloWorld",HelloWorld.class);

Bean 的作用域

  1. singltton: 单例,在 IOC 容器中的 Bean 实例,都是唯一的

    <bean id="helloWorld1" class="com.jueee.bean.HelloWorld" scope="singleton"></bean>
  2. prototype: 多例,在 IOC 容器中的 Bean,每次都返回一个新的对象

    <bean id="helloWorld2" class="com.jueee.bean.HelloWorld" scope="prototype"></bean>

配置文件的 import 导入

我们的 applicationContext.xml 里面写的是 Bean,当项目里面多个需要控制反转的配置,如果都写在一个 xml 文件里,太大,也太乱。

所以我们可以分开写每个包里面写个自己的 xml,然后 applicationContext.xml 直接 import 导入就可以了。

<!--导入其他的配置文件-->
<import resource="classpath:HelloWorld.xml"></import>

Bean 的初始化和销毁

  1. 构建 Bean

    public class MyDataSource {
        public void open(){
            System.out.println(this + " 初始化");
        }
    
        public void dowork(){
            System.out.println(this + " 工作");
        }
    
        public void close(){
            System.out.println(this + " 销毁");
        }
    }
  2. 配置 xml

    <bean id="myDataSource" class="com.jueee.bean.MyDataSource"  init-method="open" destroy-method="close"></bean>
  3. 普通模式

    MyDataSource myDataSource=new MyDataSource();
    myDataSource.open();
    myDataSource.dowork();
    myDataSource.close();

    输出:

    com.jueee.bean.MyDataSource@4ae33a11 初始化
    com.jueee.bean.MyDataSource@4ae33a11 工作
    com.jueee.bean.MyDataSource@4ae33a11 销毁
  4. IOC 模式

    @SpringBootTest
    @ContextConfiguration("classpath:applicationContext4.xml")
    public class MyDataSourceTest {
        
        @Autowired
        private MyDataSource myDataSource;
    
        //IOC容器的方式
        @Test
        public void test2(){
            myDataSource.dowork();
        }
    }

    输出(自动进行创建和销毁):

    image-20201231164806006