Spring IOC学习

一、什么是IOC

  • IOC(控制反转)又叫做DI(依赖注入),它描述了对象的定义和依赖的一个过程,也就是说,依赖的对象通过构造参数、工厂方法参数或属性注入,当对象实例后依赖的对象才被创建,当创建bean后容器注入这些依赖对象。与原来在类中使用其他类时相比(new Object()),这个过程是反向的,即对象与对象间的依赖不再是主动的去new 一个对象,而是交由Bean容器在对象实例化的时候进行注入。

 

二、为什么要用IOC

IOC是Spring的重要部分,那么为什么要使用IOC呢?下面是使用IOC与不使用的比较:

a. 原始的对象间的关系

image

 

b.基于IOC容器的对象间的关系

image

 

从上面的图中对比可以看出,原始的对象间的关系存在着耦合过高的问题,在大型软件系统中其耦合程度更是极其复杂,IOC容器将复杂的系统分解成相互合作的对象,降低了解决问题的复杂度,并且由于Spring默认是单例模式,使得对象可以被灵活地扩展和重用,极大降低了系统的开销。通过IOC容器,实现了具有依赖关系的对象间的解耦

 

三、IOC的实现原理:

IOC中最基本的技术就是反射,JAVA反射机制就是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能调用它的任意方法和属性;这种动态获取信息以及动态调用对象方法的功能称为JAVA语言的反射机制;这里简单讲述一下

为什么要使用反射:

程序运行前需要先编译,编译过程中将代码中需要的类加载到JVM中,运行的时候进行内存分配(类的实例化),相当于加载的类已经是固定的,如果使用静态编译的话,增加某个类的创建需要重新编译整个软件,而使用反射机制(动态编译)则不需要,可以在运行的过程中动态的获取。

a. 无反射技术的工厂模式:

//构造工厂类
//也就是说以后如果我们在添加其他的实例的时候只需要修改工厂类就行了
class Factory{
     public static fruit getInstance(String fruitName){
         fruit f=null;
         if("Apple".equals(fruitName)){
             f=new Apple();
         }
         if("Orange".equals(fruitName)){
             f=new Orange();
         }
         return f;
     }

b. 基于反射技术的工厂模式:

class Factory{
    public static fruit getInstance(String ClassName){
        fruit f=null;
        try{
            f=(fruit)Class.forName(ClassName).newInstance();
        }catch (Exception e) {
            e.printStackTrace();
        }
        return f;
    }
}

由上面的代码示例可以看出,反射技术极大提升了代码的灵活性,由于无法知道需要创建的Bean类型,反射技术可以在运行时动态的调用构造方法进行类的动态创建,IOC实现的工厂模式即是使用反射技术,能否在运行时动态创建也是衡量一门语言是否是动态语言的标准。之一(可以查下动态语言和静态语言的区别)。

 

反射机制的作用:

  • 在运行的时候能够判断任意对象所属的类
  • 在运行时获取类的对象
  • 在运行时访问java的属性、方法和构造方法等;

 

反射技术的优缺点:

  • 优点:能够动态的创建对象和编译,具有极强的灵活性。
  • 缺点:性能相对较差,使用反射基本是一种解释性操作。

 

四、Spring IOC的重要内容

4.1 依赖注入的实现(DI)

4.1.1 注入方式

  • 基于构造函数的注入

TextEditor.java

public class TextEditor {
   private SpellChecker spellChecker;
   public TextEditor(SpellChecker spellChecker) {
      System.out.println("Inside TextEditor constructor." );
      this.spellChecker = spellChecker;
   }
   public void spellCheck() {
      spellChecker.checkSpelling();
   }
}

SpellChecker.java

public class SpellChecker {
   public SpellChecker(){
      System.out.println("Inside SpellChecker constructor." );
   }
   public void checkSpelling() {
      System.out.println("Inside checkSpelling." );
   } 
}

MainApp.java

public class MainApp {
   public static void main(String[] args) {
      ApplicationContext context = 
             new ClassPathXmlApplicationContext("Beans.xml");
      TextEditor te = (TextEditor) context.getBean("textEditor");
      te.spellCheck();
   }
}

xml配置文件

<?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-3.0.xsd">

   <!-- Definition for textEditor bean -->
   <bean id="textEditor" class="com.tutorialspoint.TextEditor">
      <constructor-arg ref="spellChecker"/>
   </bean>

   <!-- Definition for spellChecker bean -->
   <bean id="spellChecker" class="com.tutorialspoint.SpellChecker">
   </bean>

</beans>

 

  • 基于setter方法的注入(由于注入是基于java反射机制实现的,即使没有 setter 声明的方法,也可以进行注入)

TextEditor.java

public class TextEditor{
   private SpellChecker spellChecker;
   // a setter method to inject the dependency.
   public void setSpellChecker(SpellChecker spellChecker) {
      System.out.println("Inside setSpellChecker." );
      this.spellChecker = spellChecker;
   }
   // a getter method to return spellChecker
   public SpellChecker getSpellChecker() {
      return spellChecker;
   }
   public void spellCheck() {
      spellChecker.checkSpelling();
   }
}

SpellChecker.java

public class SpellChecker {
   public SpellChecker(){
      System.out.println("Inside SpellChecker constructor." );
   }
   public void checkSpelling() {
      System.out.println("Inside checkSpelling." );
   }  
}

xml配置文件

<?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-3.0.xsd">

   <!-- Definition for textEditor bean -->
   <bean id="textEditor" class="com.tutorialspoint.TextEditor">
      <property name="spellChecker" ref="spellChecker"/>
   </bean>

   <!-- Definition for spellChecker bean -->
   <bean id="spellChecker" class="com.tutorialspoint.SpellChecker">
   </bean>

</beans>

4.1.2 注入配置

  • 基于XML的注入配置(如上面的配置)
  • 基于注解的注入配置(通过@Autowired进行注入)

 

4.2 Spring IOC的初始化(Bean的初始化)

  • 参考:https://www.jianshu.com/p/70886997c46b

  • 主要分为三个步骤(容器的初始化是通过refresh()实现)

    • 定位:通过Resource定位BeanDefinition,BeanDefinition定义了Bean的元信息、依赖关系等,即寻找Bean的过程。
    • 载入BeanDefinition的信息已经定位到了,第二步就是把定义的BeanDefinitionIoc容器中转化成一个Spring内部标示的数据结构的过程。
    • 注册:将抽象好的BeanDefinition统一注册到IoC容器中,IoC容器是通过ConcurrentHashMap来维护BeanDefinition信息的,key为beanName,value为BeanDefinition。

   

五、IOC的优缺点

优点

  • 实现了对象之间的解耦,基于单例模式可以有效减少系统资源的消耗。

缺点

  • 基于反射实现,性能会稍微差一些,单例模式引入了线程安全问题。
  • 生成对象的步骤变得复杂了,需要投入学习成本和精力。

   

六、IOC的源码解析

ClassPathXmlApplicationContext

BeanFactory和ApplicationContext的区别

  • ApplicationContext接口继承自BeanFactory接口,同时继承了MessageSource和ResourceLoader等其他接口,相比BeanFactory,ApplicationContext提供了更多的扩展功能,如能够实现国际化访问、事务传播及AOP等服务。
  • BeanFactory是懒加载(延迟加载),BeanFactory加载后,需要第一次调用getBean()方法才会实例化,而ApplicaitonContext实现的是饿汉加载,在容器初始化的时候,会实例化所有的bean。
  • ApplicationContext可以及时检查Bean是否完成注入,BeanFactory需要调用getBean()的时候才会抛出异常。

   

参考