zoukankan      html  css  js  c++  java
  • 控制反转(Ioc)

    控制反转(Ioc)

    Ioc不是一种实实在在的技术,只是一种设计思想。

    面向对象编程中,对象之间不可避免且必要地存在着耦合,但过度耦合会导致代码难以维护。

    一般来说,当一个对象需要获取另一个对象,他就需要在自身的代码中显式地实例化一个该对象,比如

     Object object = new Object();  

    对象实例化的时机由调用者在其自身的代码中决定,调用者掌握着控制权。

    Ioc就是把原本由调用者掌握的控制权交给Ioc容器,由Ioc容器根据配置文件(xml)来代为实例化对象,以达到松耦合的目的。

    很通俗的例子,由自己调用者买食材做食物亲自new对象)变成到食堂Ioc容器)购买。

    至于为什么能解耦,需要先了解Ioc容器。

    Ioc容器

    Ioc容器:具有依赖注入功能的容器,IOC容器负责实例化、定位、配置应用程序中的对象及建立这些对象间的依赖

    应用程序无需直接在代码中new相关的对象,应用程序由IOC容器进行组装

    Spring IoC 容器的设计主要是基于以下两个接口:

    • BeanFactory
    • ApplicationContext

    其中 ApplicationContext 是 BeanFactory 的子接口之一,换句话说:BeanFactory 是 Spring IoC 容器所定义的最底层接口,而 ApplicationContext 是其最高级接口之一,并对 BeanFactory 功能做了许多的扩展,所以在绝大部分的工作场景下,都会使用 ApplicationContext 作为 Spring IoC 容器。

    ApplicationContext 继承关系

    延续上一个例子,定义一个Food接口和若干食物bean(Rice,Buger,Noodle...)

    注:面向接口编程是实现Ioc的重要基础之一。

    1 public interface Food {
    2 
    3 }
    4 
    5 public class Rice implements Food {
    6     String name;
    7 }
    8 ...

    人每天都吃饭,抽象出一个Person类,包含food属性和eat方法

     1 public class Person {
     2     Food food;
     3     public Person(){
     4         //紧耦合
     5         this.food=new Rice();
     6     }
     7     public void eat(){
     8         
     9     }
    10 }

    此时,Person和Food之间的关系可以表示为

    不难看出,此时Person和实现Food接口的Rice类是紧耦合的。

    Person类应该描述一个能吃任何食物的人,但由于紧耦合,它只能描述一个只吃rice的人,想要这个人能吃buger和noodle,必须在其代码中做出修改,但仍然只能吃特定的食物。这显然有违我们创建这个类的初衷。

    利用Ioc容器作为管理对象实例的工具,可以达到松耦合的目的,下图很直观地反映了这一点。

    有了Ioc容器,不必再在Person类中显式地实例化一个具体的食物类

     1 public class Person {
     2     Food food;
     3 
     4     public Person(Food food) {
     5         //松耦合
     6         this.food = food;
     7     }
     8 
     9     public void eat() {
    10 
    11     }
    12 }

    Person的构造方法仅传进来了一个Food类型的对象的引用,而Food只是一个接口,整段代码中完全没有出现任何一种具体实现了这个接口的类。

    在这种情况下,Person怎么才能知道food的具体类型呢?

    依赖注入(DI)

    Spring IoC 的容器的初始化和依赖注入

    虽然 Spring IoC 容器的生成十分的复杂,但是大体了解一下 Spring IoC 初始化的过程还是必要的。这对于理解 Spring 的一系列行为是很有帮助的。

    注意:Bean 的定义和初始化在 Spring IoC 容器是两大步骤,它是先定义,然后初始化和依赖注入的。

    • Bean 的定义分为 3 步:
      1.Resource 定位
      Spring IoC 容器先根据开发者的配置,进行资源的定位,在 Spring 的开发中,通过 XML 或者注解都是十分常见的方式,定位的内容是由开发者提供的。
      2.BeanDefinition 的载入
      这个时候只是将 Resource 定位到的信息,保存到 Bean 定义(BeanDefinition)中,此时并不会创建 Bean 的实例
      3.BeanDefinition 的注册
      这个过程就是将 BeanDefinition 的信息发布到 Spring IoC 容器中
      注意:此时仍然没有对应的 Bean 的实例。

    bean被定义了,但并没有被实例化。

    对于单例模式的bean,真正的实例化将发生在Ioc容器被创建时,即

     ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");  

    Ioc容器被创建的同时,其中的单例对象将被实例化,分配资源。

    对于多例模式的bean,其实例化则要等到使用Ioc容器获取bean时,即

      Object object = (Object)context.getBean("<name>",<class>);  

    注:单例和多例模式由scope配置选项决定)

    此外,还有一个配置选项lazy-init,即懒加载。其含义就是是否初始化 Spring Bean。在没有任何配置的情况下,它的默认值为 default,实际值为 false,也就是 Spring IoC 默认会自动初始化 Bean。如果将其设置为 true,那么只有当我们使用 Spring IoC 容器的 getBean 方法获取它时,它才会进行 Bean 的初始化,完成依赖注入。

    依赖的形式有三种

    1)构造方法注入

    2)setter注入

    3)接口注入(几乎不用)

    回到之前的例子,Person类中没有实例化任何bean,而是等待Ioc容器为其注入。

    构造方法注入

    首先为Rice类和Person类定义构造方法。

     1 public interface Food {
     2 
     3 }
     4 
     5 public class Rice implements Food {
     6     String name;
     7     public Rice(String name) {
     8         this.name=name;
     9     }
    10 }
    11 ... 
    12 
    13 public class Person {
    14     Food food;
    15     public Person(Food food){
    16        //松耦合
    17         this.food=food;
    18     }
    19     public void eat(){
    20         
    21     }
    22 }

    此时,需要为Rice注入name,为Person注入Rice。

    在配置文件applicationContext.xml中

     1     <!-- 定义rice -->
     2     <bean id="rice" class="com.spring.di.Rice">
     3         <!-- 通过构造方法注入常量 -->
     4         <constructor-arg name="name" value="rice"/>
     5     </bean>
     6 
     7     <!-- 定义person -->
     8     <bean id="person" class="com.spring.di.Person">
     9         <!-- 通过构造方法注入bean -->
    10         <constructor-arg name="food" ref="rice"/>
    11     </bean>

    setter注入

    为两个类设置setter

     1 public interface Food {
     2 
     3 }
     4 
     5 public class Rice implements Food {
     6     String name;
     7     public void setName(String name) {
     8         this.name = name;
     9     }
    10 }
    11 ...
    12 
    13 public class Person {
    14     Food food;
    15     public void setFood(Food food) {
    16         this.food = food;
    17     }
    18 }

    xml

     1     <!-- 定义rice -->
     2     <bean id="rice" class="com.spring.di.Rice">
     3         <!-- 通过setter注入常量 -->
     4         <property name="name" value="rice"/>
     5     </bean>
     6 
     7     <!-- 定义person -->
     8     <bean id="person" class="com.spring.di.Person">
     9         <!-- 通过setter注入bean -->
    10         <property name="food" ref="rice"/>
    11     </bean>

    至此已经完成了bean的定义

    使用Ioc容器

    1         ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
    2         Person person = (Person)context.getBean("person",Person.class);
    3         person.eat();

    终于吃上饭了。

    这时候如果想吃面,只需在xml配置文件中定义一个noodle的bean,并将其注入到Person类中。

    这就实现了Person与Food之间的松耦合,从而不修改Java代码,Person就可以吃到任何种类的食物。

    参考:《Spring实战 第4版》

           Spring AOP 简介

  • 相关阅读:
    C陷阱与缺陷学习笔记
    C陷阱与缺陷学习笔记
    Linux Socket编程(不限Linux)
    Linux Socket编程(不限Linux)
    Windows API 教程(九) 网络编程
    Windows API 教程(九) 网络编程
    sockaddr与sockaddr_in的关系
    winsocket入门学习
    09-C语言数组
    08-C语言循环
  • 原文地址:https://www.cnblogs.com/CofJus/p/12881649.html
Copyright © 2011-2022 走看看