IOC原理

探究Spring IOC控制反转 、DI 依赖注入

IoC 控制反转思想

可以用一句话来描述: 依赖即地狱 ------ IoC 控制反转思想是为了解决对象与对象之间、依赖地狱的问题

解释下这种依赖地狱, What is it ? 假设N个对象,Object_A 需调用 Object_B, Object_B又需调用Object_C, 而Object_C又必需调用Object_D ... Object_N 又必需调用Object_A, 一层层套娃式陷入了一个依赖地狱模式,形成这么一个依赖循环的闭合!

设计出这种牛掰的思想, 其目的是为了在开发中,对所有的对象,模块,进行高度化解耦,抽离出一个个单身的功能模块,把所有的对象与对象之间不再进行相互调用, 而是将它们注册到IOC容器, 即拿即用,每个模块只的职责只需负责自身对象中的成员变量函数方法等。Inversion of Control,将我们写好的对象交由IoC控制,是IoC控制调配Objects。 它是一种原理一种思想,协助我们完成控制程序执行流的反转. 譬如,在执行循环依赖闭合的开发时,因为没有初始化对象,编译是不会通过的 ! 交由IoC来完成这种闭合依赖的初始化对象。 而Dependency Injection依赖注入是解决Inversion of Control的设计模式,通过依赖注入, 减少了多个对象之间的耦合,因为它是由框架动态注入的。依赖流入有两种方式: Setter Injection 和 Constructor Injection.

image.png

概念态

概念形态的 bean ------ 指SpringMVC在类中所引用的注解,在绑定大量的xml文件来进行配置,通过class类中的注解来驱动开发。

譬如:Spring在xml文件或是class类中配置的


           ...

 
@Component 
class Student {
           ...
}

@Bean
public Person person(){
          ...
}

定义态

定义形态的 bean ------ 指我们手动在xml文件中配置的一个个 bean

譬如, 自己手动去配置一个bean,需要通过配置它的class、id、scope、autoware、property ... 等来完成一个手动的配置, spring 会根据自己的资源配置信息在底层隐式的封装, 通过bean所定义的信息来完成BeanDefinition在IOC容器中的注册


        

而一个个的 bean 在spring底层是隐式封装,其过程解析:

第一步: 先完成xml的配置或类的注解映射
通过ClassPathXmlApplicationContext(xml) 、AnnotationConfigApplicationContext( )实例化 new ApplicationContext()

第二步: 读取XML 、配置类的信息
Spring 是通过 BeanDefinitionDocumentReader 解析配置文件中的资源配置信息 , 它作用于解析文件后, 为完成第三步 ClassPathBeanDefinitionScanner 成功后调用生成BeanDefinition的接口完成注册作铺垫。

public interface BeanDefinitionDocumentReader {
    void registerBeanDefinitions(Document var1, XmlReaderContext var2) throws BeanDefinitionStoreException;
}

第三步: 扫描包下面的类,进行分析
Spring 利用 ClassPathBeanDefinitionScanner 扫描器分析,会对src/包下所有的类[ 符合条件的类 ] 逐个注册到 IOC 容器中,从成完成了BeanDefinitionRegistry 的注册

  protected Set doScan(String... basePackages) {
        Assert.notEmpty(basePackages, "At least one base package must be specified");
        Set beanDefinitions = new LinkedHashSet();
        String[] var3 = basePackages;
        int var4 = basePackages.length;
        ... 
}

第四步: 将Bean注册到IOC容器中, 以 Map 存储
BeanDefinition是封装Bean的定义信息, 它也决定一个Bean是怎么生产的. [ beanDefinitionMap装载 ]

  private final Map beanDefinitionMap = new ConcurrentHashMap(256);

spring底层用BeanDefinition中的beanClass来封装Bean, 如下方源码:

public class BeanDefinition extends NoOutputDefinition {
                @XmlTransient
                private Class beanClass;
   
                @XmlTransient
                private Object bean;
                ...
}

纯净态

第一个环节是先实例化:

实例化后, 它就是一个纯净态的bean. 在实例化后它会生成bean的生命周期, Spring 底层实例化一个bean时用3种方式:

1 实例化方式一--------反射

@Component
public class Car{
            @Autowired
            private  Tank  tank;
            private  String  name;
            public void  setTank(Tank tank) {
                   this.tank = tank; 
            }
}

上方的@Component注释会将Car类读取到AbstractBeanDefinition中的beanClass中去,通过这一属性反射。@Component和@ComponentScan有区别,@Component只是将属性反射,并未把方法注册到IOC容器 [ 多个类配置@Component, 利用@ComponentScan扫描包下的路径含有@Bean、@Component 的 annotations,都会将它们注册到工厂方法 ] 。

下方AbstractBeanDefinition源码中的beanClass

public abstract class AbstractBeanDefinition extends BeanMetadataAttributeAccessor implements BeanDefinition, Cloneable {
          ...
          private volatile Object beanClass;
          ... 
}

2 实例化方式二--------工厂方法 @bean
@bean是基于方法的

@ComponentScan(value = "com.cloud.car")
public class MainConfig{
        @Bean
        public void  Car car() {
                   car.owner=("amy")
                   return car;
        }
}

在xml中另一种工厂方法factory-method="" , 它也是工厂方法@bean注解的另一个体现方式


             ...

@ComponentScan作用于扫描已定义的包类的对象, 此处扫描@Component实例化的bean; 两种工厂方式: 类上配置@ComponentScan@bean注释和xml配置factory-method="",都是将定义的bean读到factoryMethodName这一属性上去。下方AbstractBeanDefinition源码中的factoryMethodName用于工厂方法读取工厂方法bean

public abstract class AbstractBeanDefinition extends BeanMetadataAttributeAccessor implements BeanDefinition, Cloneable {
             private String factoryMethodName;
             ...
}

额外补充: 因为大部分开发者经常会混淆 @Component @Bean 注释驱动,会认为两个注释都是同一种用途的不同注释,其实他们大有不同!! 下方文献摘自技术WebSite。

In Spring, both annotations are quite different.

Difference between @Component and @Bean annotations

@Component used to auto-detect and auto-configure beans using classpath scanning. There’s an implicit one-to-one mapping between the annotated class and the bean (i.e. one bean per class). it will identify the classes annotated with @Component annotation (within the given package) and create the beans of such classes and register them in the ApplicationContext; @Component is a class level annotation and its purpose it to make the class as spring managed component and auto detectable bean for classpath scanning feature.

@Component is a class level annotation and its purpose it to make the class as spring managed component and auto detectable bean for classpath scanning feature.

if you want to know more about @Component and other stereo type annotations, it is recommended to look at this article.

@Bean is used to explicitly declare and register a bean (as a configuration bean) in Spring IOC container that is returned from a method. @Bean is a method level annotation and it is used within a class that is annotated with @Configuration.

Simply, @Bean annotation is used to register the bean returned by a method as a spring configuration bean in IOC Container. Another big difference is that @Component is a class level annotation where as @Bean is a method level annotation and ,by default, name of the method serves as the bean name.

To declare a bean, simply annotate a method with the @Bean annotation. When JavaConfig encounters such a method, it will execute that method and register the return value as a bean within a ApplicationContext. By default, the bean name will be the same as the method name.The following is a simple example of a @Bean method declaration.

3 实例化方式三 ------- 工厂类 FactoryBean

FactoryBean 是基于类的,以实现接口方法 getObject() 返回这个bean ,以下代码狸猫换太子, getObject() 是返回的是Tank对象

public  class  Car   implement   FactoryBean{
     @Autowired
     private   Tank   tank;
     private   String   name;
     public  void setTank(Tank tank){this.tank = tank;}
     public  void getTank { return tank;}
     public  Car (String  name){ this.name = name }
     public  String  toString { ... }
     ...
     @Override
     public Object  getObject() throws Exception{
                 return  new Tank();
     }
     @Override
     public Object  getObject() throws Exception{
                 return  null ;
     }
}
 
public static void main(String[]  args){
        // 加载Spring上下文
        AnnotionCofigApplicationContext   context = new  AnnotionCofigApplicationContext(MainConfig.class);

        Tank  tank= (Tank)context.getBean("tank");
        system.out.println(car);
}
       
public static void main(String[]  args){
        // 加载Spring上下文, 通常是使用AnnotionCofigApplicationContext
        AnnotionCofigApplicationContext   context = new  AnnotionCofigApplicationContext(MainConfig.class);
        Car   car= (car)context.getBean("car");
        //  BeanFactory的方式[需要额外手动配置,因为此方式和上方加载Spring上下文不同, 加载上下文是把类中的整个所有类中属性和实现方法全部扫描拿到bean ]
        // BeanFactory占用内存更小,  这种方式适合于设备受限的内嵌应用
        DefaultListableBeanFactory  beanFactory = new  DefaultListableBeanFactory();
       beanFactory.getBean("car") ; //会报错,需要额外配置其它属性
} 

从上面我写的这段主运行程序中,区分BeanFactory 和 FactoryBean的区别:

beanFactory是一个子函数具体实现方法[它占的内存更小,需要手动去创建它,适合在资源受限的设置上内嵌开发 ],而FactoryBean是一个类 [它占的内存更大 ];
AnnotionCofigApplicationContext [它占的内存是最大的!在new AnnotionCofigApplicationContext ( XXX.class)是加载整个类,扫描整个 spring 上下文,整个类包含了各种方法 ] AnnotionCofigApplicationContext 包含BeanFactory的所有具体实现方法. 看下方的结构树图 :

image.png

AbstractApplicationContext中的getBean(), 拿到bean实现:

public abstract class AbstractApplicationContext extends DefaultResourceLoader implements ConfigurableApplicationContext, DisposableBean {
         ...
         public Object getBean(String name) throws BeansException {
                    this.assertBeanFactoryActive();
                    return this.getBeanFactory().getBean(name);
        }

        public  T getBean(String name, Class requiredType) throws BeansException {
                    this.assertBeanFactoryActive();
                    return this.getBeanFactory().getBean(name, requiredType);
        }

        public  T getBean(Class requiredType) throws BeansException {
                  this.assertBeanFactoryActive();
                  return this.getBeanFactory().getBean(requiredType);
        }

         public Object getBean(String name, Object... args) throws BeansException {
                  this.assertBeanFactoryActive();
                  return this.getBeanFactory().getBean(name, args);
         }

        public  T getBean(Class requiredType, Object... args) throws BeansException {
                  this.assertBeanFactoryActive();
                  return this.getBeanFactory().getBean(requiredType, args);
        }
        ...
}

DefaultListableBeanFactory类中的getBean()实现:
[AnnotationConfigApplicationContext实现了此方法, 获取getBean()拿到bean ]

public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFactory implements ConfigurableListableBeanFactory, BeanDefinitionRegistry, Serializable {
        ...
        public  T getBean(Class requiredType) throws BeansException {
                  return this.getBean(requiredType, (Object[])null);
        }

        public  T getBean(Class requiredType, Object... args) throws BeansException {
                    NamedBeanHolder namedBean = this.resolveNamedBean(requiredType, args);
                    if (namedBean != null) {
                                return namedBean.getBeanInstance();
                    } else {
                   BeanFactory parent = this.getParentBeanFactory();
                   if (parent != null) {
                              return parent.getBean(requiredType, args);
                   } else {
                              throw new NoSuchBeanDefinitionException(requiredType);
                  }
       }
      ...
}

AnnotationConfigApplicationContext类中super(beanFactory),它直接到上面的DefaultListableBeanFactory类从而获取getBean()拿到bean。

public class AnnotationConfigApplicationContext extends GenericApplicationContext implements AnnotationConfigRegistry {
       ...
       public AnnotationConfigApplicationContext(DefaultListableBeanFactory beanFactory) {
                 super(beanFactory);
                 this.reader = new AnnotatedBeanDefinitionReader(this);
                 this.scanner = new ClassPathBeanDefinitionScanner(this);
        }
       ...
}
第二个环节是@Autowired @Value属性注入:

利用三级缓存解决循环依赖 [ 另一篇文章单独写了关于此问题的解决 ]。

第三个环节是初始化生命周期的回调以及aware方法:

初始化生命周期的回调的三种方法: @PostConstruct & afterPropertiesSet() & init-method = " "

The container calls afterPropertiesSet() for the former and destroy() for the latter to let the bean perform certain actions upon initialization and destruction of your beans. @PostConstruct and @PreDestroy annotations are generally considered best practice for receiving lifecycle callbacks in a modern Spring application.

Spring 初始化Bean & 销毁化Bean [ 示范性三种方式 ]:
示范一:以afterPropertiesSet() + destroy() 方式


       
                        
       

// In a managed bean, @PostConstruct is called after the regular Java object constructor.  
// Why need to use @PostConstruct to initialize by bean, instead of the regular constructor itself?
// Because when the constructor is called, the bean is not yet initialized - i.e. no dependencies are injected. In the @PostConstruct method the bean is fully initialized and you can use the dependencies.

package com.amy.customer.services;
public class CustomerService implements InitializingBean, DisposableBean{
    String message;
    
    public String getMessage() {
      return message;
    }

    public void setMessage(String message) {
      this.message = message;
    }
    
    public void afterPropertiesSet() throws Exception {
      System.out.println("Init method after properties are set : " + message);
    }
    
    public void destroy() throws Exception {
      System.out.println("Spring Container is destroy! Customer clean up");
    }
    
}

Run it

package com.amy.common;

import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.amy.customer.services.CustomerService;

public class App{
      public static void main( String[] args ){
              ConfigurableApplicationContext context =  new ClassPathXmlApplicationContext(new String[] {"Spring-Customer.xml"});
    
              CustomerService cust = (CustomerService)context.getBean("customerService");
        
              System.out.println(cust);
        
              context.close();
      }
}
// Output
Init method after properties are set : im property message
com.amy.customer.services.CustomerService@47393f
...
INFO: Destroying singletons in org.springframework.beans.factory.
support.DefaultListableBeanFactory@77158a: 
defining beans [customerService]; root of factory hierarchy
Spring Container is destroy! Customer clean up

示范二: 以@ PostConstruct初始化注释 和 @ PreDestroy销毁化注释的方式
// Book.java
package com.concretepage;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
public class Book {
         private String bookName;
         public Book(String bookName) {
                    this.bookName = bookName;
         }

         @PostConstruct
         public void springPostConstruct() {
                     System.out.println("---Do initialization task---");
                     if(bookName != null) {
                               System.out.println("bookName property initialized with the value:"+ bookName);
                     }
         }  

        public String getBookName() {
                      return bookName;
        }
    
           @PreDestroy
           public void springPreDestroy() {
                   System.out.println("---Release resources or perform destruction task---");
           }
} 
// AppConfig.java
package com.concretepage;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class AppConfig {
        @Bean   
      public Book book(){
              return new Book("Ramayana");
      }
} 
// Main Class
package com.concretepage;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class SpringDemo {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
        ctx.register(AppConfig.class);
        ctx.refresh();
        Book book = ctx.getBean(Book.class);
        System.out.println("Book name:" + book.getBookName());
        ctx.close();
    }
} 
// Output
---Do initialization task---
bookName property initialized with the value:Ramayana
Book name:Ramayana
---Release resources or perform destruction task--- 

示范三: 以xml 映射的方式,通过绑定 init-method=" " destroy-method=" "对bean进行初始化回调和销毁化回调。

If you do not want to use the JSR-250 annotations but you still want to remove coupling, consider init-method and destroy-method bean definition metadata. In the case of XML-based configuration metadata.
若不用@注释的方式解除耦合性,可通过xml配置元数据的方式定义bean的初始化回调和摧毁化回调。



        
              
             

package com.amy.customer.services;
public class CustomerService{
          String message;
    
          public String getMessage() {
                    return message;
          }

          public void setMessage(String message) {
                      this.message = message;
          }
    
          public void initIt() throws Exception {
                    System.out.println("Init method after properties are set : " + message);
          }

          public void cleanUp() throws Exception {
                    System.out.println("Spring Container is destroy! Customer clean up");
          } 
}

Run it

package com.amy.common;

import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.mkyong.customer.services.CustomerService;

public class App {
    public static void main( String[] args ) {
            ConfigurableApplicationContext context = new ClassPathXmlApplicationContext(new String[] {"Spring-Customer.xml"});

            CustomerService cust = (CustomerService)context.getBean("customerService");

            System.out.println(cust);

            context.close();
    }
}
//  Output
Init method after properties are set : i'm property message
com.mkyong.customer.services.CustomerService@47393f
...
INFO: Destroying singletons in org.springframework.beans.factory.
support.DefaultListableBeanFactory@77158a: 
defining beans [customerService]; root of factory hierarchy
Spring Container is destroy! Customer clean up

Aware

The ApplicationContextAware Interface:

//The code overrides the setApplicationContext() method to lookup another bean with the id user using the injected ApplicationContext.

public class ApplicationContextAwareImpl implements ApplicationContextAware {
            @Override
            public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
                      User user = (User) applicationContext.getBean("user");
                      System.out.println("User Id: " + user.getUserId() + " User Name :" + user.getName());
            }
}
//The preceding code is of a bean that implements ApplicationContextAware. 


The BeanFactoryAware Interface :

public class BeanFactoryAwareImpl implements BeanFactoryAware {
            @Override
            public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
                        System.out.println(beanFactory.getBean("user"));
            }
}

The BeanNameAware Interface:

public class BeanFactoryAwareImpl implements BeanFactoryAware {
            @Override
            public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
                             System.out.println(beanFactory.getBean("user"));
           }
}

The MessageSourceAware Interface:

// If you want to access i18n resources bundles for different locales into your java source code,
// then that java class must implement MessageSourceAware interface. 

// String getMessage(String code, Object[] args, String default, Locale loc)
// String getMessage(String code, Object[] args, Locale loc)
// String getMessage(MessageSourceResolvable resolvable, Locale locale)

@Controller
public class EmployeeController implements MessageSourceAware{
            private MessageSource messageSource;
  
            public void setMessageSource(MessageSource messageSource) {
                        this.messageSource = messageSource;
            }
 
            public void readLocaleSpecificMessage(){
                      String englishMessage = messageSource.getMessage("first.name", null, Locale.US);
  
                      System.out.println("First name label in English : " + englishMessage);
  
                      String chineseMessage = messageSource.getMessage("first.name", null, Locale.SIMPLIFIED_CHINESE);
          
                      System.out.println("First name label in Chinese : " + chineseMessage);
             }
}

#messages_en_US.properties
first.name=FirstName in English
 
#messages_zh_CN.properties
first.name=FirstName in Chinese

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.howtodoinjava.demo.controller.EmployeeController;
 
public class TestSpringContext {
        @SuppressWarnings("resource")
        public static void main(String[] args){
                    ApplicationContext context = new ClassPathXmlApplicationContext( new String[] { "/spring-servlet.xml" });
 
                      EmployeeController controller = (EmployeeController) context.getBean(EmployeeController.class);
         
                    controller.readLocaleSpecificMessage();
        }
}
 
Output:
First name label in English : FirstName in English
First name label in Chinese : FirstName in Chinese


关于Aware接口就不一一罗列, 可以看下方的图片,Aware的子接口
...

image.png

成熟态

image.png

你可能感兴趣的:(IOC原理)