Spring:IoC和DI原理

 

目录

一、IOC架构

二、核心容器

1、BeanFactory

1.1 BeanFactory

1.2 ListableBeanFactory

1.3 HierarchicalBeanFactory

1.4 AutowireCapableBeanFactory

1.5 ConfigurableBeanFactory

2、BeanDefinition

3、BeanDefinitionReader

4、ApplicationContext

4.1 资源加载:ResourceLoader

4.2 常用的实现类

5、WebApplicationContext

5.1 继承关系

5.2 和ServletContext的关系

5.3 初始化

四、Bean的生命周期

1、作用域

1.1 singleton

1.2 prototype

1.3 request

1.4 session

1.5 global session

1.6 自定义bean装配作用域

2、生命周期

2.1 Bean的创建

2.2 Bean的销毁

五、基于注解的IoC初始化

1、相关注解

1.1 类级别的注解

1.2 类内部的注解

2、流程

2.1 定位Bean扫描路径

2.2 读取注解的元数据

2.3 扫描指定包并解析为BeanDefinition

六、依赖注入

1、依赖注入的时机

2、Bean的创建

2.1 Bean实例化的策略:

2.2 默认无参构造器进行实例化

3、依赖注入

3.1 准备依赖注入

3.2 注入赋值

3.3 管理Bean依赖关系的方式

七、Bean加载过程综述

八、其他概念

1、Bean定义注册表

2、Bean单例缓存池


一、IOC架构

    Spring通过一个配置文件描述Bean及Bean之间的依赖关系,利用Java语言的反射功能实例化Bean并建立Bean之间的依赖关系。 Spring的IoC容器在完成这些底层工作的基础上,还提供了Bean实例缓存、生命周期管理、 Bean实例代理、事件发布、资源装载等高级服务。

    Spring启动时读取应用程序提供的Bean配置信息,并在Spring容器中生成一份相应的Bean配置注册表,然后根据这张注册表实例化Bean,装配好Bean之间的依赖关系,为上层应用提供准备就绪的运行环境。

二、核心容器

1、BeanFactory

    BeanFactory是Spring框架的基础设施,面向Spring本身。Spring中Bean的创建是典型的工厂模式,BeanFactory及其子接口和子接口的实现类构成了一系列的Bean工厂,即IoC容器。

    BeanFactory的继承关系:

这里写图片描述

    子接口和实现类详解:

1.1 BeanFactory

    BeanFactory是所有接口的父接口,定义了IoC容器的基本功能规范。

    主要方法:

  • getBean:用于根据名称、类型等信息,从容器中返回Bean;
  • getBeanProvider:根据类型获取Bean的ObjectProvider;
  • isSingleton:判断Bean是否是单例;
  • isPrototype:是否是原型;
  • isTypeMatch:判断给定名称的Bean是否匹配指定的类型;
  • getType:获取指定名称的Bean的类型。

1.2 ListableBeanFactory

    BeanFactory的子接口。接口定义了访问容器中Bean基本信息的若干方法,如查看Bean的个数、获取某一类型Bean的配置名、查看容器中是否包括某一Bean等方法。

    主要方法:

1、BeanDefinition相关的方法:

  • containsBeanDefinition:判断该BeanFactory是否包含指定名称的BeanDefinition;
  • getBeanDefinitionCount:获取BeanDefinition的数量;
  • getBeanDefinitionNames:获取BeanDefinition的名称全量;

2、获取Bean相关的方法:

  • getBeanNamesForType:根据类型、是否单例、饥饿加载还是懒加载等信息,查询BeanName的数组;
  • getBeansOfType:根据类型、是否单例、饥饿加载还是懒加载等信息,查询Bean的数组;

3、注解相关的方法:

  • getBeanNamesForAnnotation:根据注解类型查找BeanName的数组;
  • getBeansWithAnnotation:根据注解类型查找Bean的数组;
  • findAnnotationOnBean:根据beanName和注解类型,查找注解。

1.3 HierarchicalBeanFactory

    HierarchicalBeanFactory是BeanFactory的子类。是一种父子级联IoC容器的接口,使得Spring的IoC容器可以建立父子层级关联的容器体系,子容器可以通过接口方法访问父容器,但父容器不能访问子容器的Bean。

    Spring使用父子级联容器实现了很多功能,比如在Spring MVC中,表现层Bean位于一个子容器中,而业务层和持久层的Bean位于父容器中。这样,表现层 Bean就可以引用业务层和持久层的Bean,而业务层和持久层的Bean则看不到展现层的Bean。

    主要方法:

  • getParentBeanFactory:返回父BeanFactory
  • containsLocalBean:是否包含指定名称的Bean,查询范围不包括父容器。

1.4 AutowireCapableBeanFactory

    AutowireCapableBeanFactory是BeanFactory的子接口,定义了将容器中的Bean按某种规则(如按名字匹配、按类型匹配等)进行自动装配的方法。

    主要方法:

  • createBean:根据给定类型、装配模式(懒加载、延迟加载等)等创建新的Bean;
  • autowireBean:对给定的Bean的属性进行自动装配;
  • configureBean:对给定的Bean的属性进行自动装配;
  • autowire:根据给定的类型、加载模式等实例化一个Bean;
  • autowireBeanProperties:装配Bean的属性;
  • destroyBean:删除Bean;
  • resolveNamedBean:解析唯一匹配给定类型的Bean实例;
  • resolveDependency:根据此工厂中定义的bean解析指定的依赖关系。

1.5 ConfigurableBeanFactory

    ConfigurableBeanFactory是HierarchicalBeanFactory的子接口。是一个重要的接口,增强了IoC容器的可定制性。它定义了设置类加载器、属性编辑器、容器初始化后置处理器等方法。

2、BeanDefinition

    BeanDefinition相当于Spring的类。Java通过类来创建实例,同理,Spring通过BeanDefinition来创建Bean。Spring管理Bean组件需要经过实例化的过程,这个实例化过程除了Java类所拥有的信息,还要解决作用域、实例化条件、组件依赖等一系列问题,Spring将这些信息封装在了BeanDefinition对象中。可以这样来理解:Bean是高级的实例,BeanDefinition是高级的class。

3、BeanDefinitionReader

    Bean的解析过程非常复杂,功能被划分的非常详细,因为这里需要被扩展的地方很多,必须保证足够的灵活性,以应对可能的变化。Bean的解析主要就是对Spring配置文件的解析。这个解析过程,主要是通过对BeanDefinitionReader来完成。

4、ApplicationContext

    ApplicationContext是在BeanFactory基础上定义的,实际上ApplicationContext是BeanFactory的一个超全集。ApplicationContext面向使用Spring框架的开发者,几乎所有的应用场合我们都直接使用ApplicationContext而非底层的BeanFactory。

    实际上,在BeanFactory的基础上,ApplicationContext还通过继承其他接口,扩展了一些功能:从属性文件从解析文本信息和将事件传递给所指定的监听器。

    ApplicationContext的继承关系:

4.1 资源加载:ResourceLoader

    ResourceLoader接口:

public interface ResourceLoader {

	/** 从class path 路径加载时的伪资源URL: "classpath:" 。web.xml配置中指定类路径下资源时便要加此前缀*/
	String CLASSPATH_URL_PREFIX = ResourceUtils.CLASSPATH_URL_PREFIX;

	/**返回指定path的资源*/
	Resource getResource(String location);

	/**需要直接访问ClassLoader的客户端可以这样做,即以与ResourceLoader统一的方式,而不是依赖线程上下文ClassLoader*/
	ClassLoader getClassLoader();
}

    应用示例:

ApplicationContext appCtx=FileSystemXMLApplication("applicationContext.xml");
Resource resource = appCtx.getResource("file_in_resources.properties");
//使用Resource对象获取文件
File file = resource.getFile();

4.2 常用的实现类

    常用的ApplicationContext接口实现类包括:

  • FileSystemXmlApplicationContext:该容器从XML文件中加载已被定义的bean,要求给构造器提供XML文件的完整路径;
  • ClassPathXmlApplicationContext:该容器从XML文件中加载已被定义的 bean,不需要提供XML文件的路径,只需正确配置 CLASSPATH环境变量,容器会从CLASSPATH中搜索bean配置文件;
  • WebXmlApplicationContext:该容器会在一个web应用程序的范围内加载在XML文件中已被定义的bean。

5、WebApplicationContext

5.1 继承关系

    WebApplicationContext是ApplicationContext的子接口,是专门为Web服务准备的。WebApplicationContext的继承关系图:

5.2 和ServletContext的关系

    WebApplicationContext是专门为Web应用准备的,它允许从相对于Web根目录的路径中装载配置文件完成初始化工作。从WebApplicationContext中可以获得ServletContext的引用,而整个Web应用上下文对象也将作为属性放置到ServletContext中,以便Web应用环境可以访问Spring应用上下文。从ServletContext中获取WebApplicationContext的方式:

        WebApplicationContext context = (WebApplicationContext) servletContext.
                getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);

    WebApplicationContext和ServletContext的关系示意图:

5.3 初始化

    WebApplicationContext的初始化需要ServletContext实例,必须在拥有Web容器的前提下才能完成启动的工作。可以在web.xml中配置自启动的Servlet或定义Web容器监听器(ServletContextListener),借助这两者中的任何一个就可以完成启动Spring Web应用上下文的工作。

    Spring提供了用于启动WebApplicationContext的Servlet和Web容器监听器:

  • org.springframework.web.context.ContextLoaderServlet
  • org.springframework.web.context.ContextLoaderListener

    由于WebApplicationContext需要使用日志功能,比如日志框架使用Log4J,用户可以将Log4J的配置文件放置到类路径WEB-INF/classes下,这时Log4J引擎即可顺利启动。如果Log4J配置文件放置在其他位置,还需要在web.xml指定Log4J配置文件位置。

四、Bean的生命周期

1、作用域

    Bean的作用域使用scope来配置:

<bean id="role" class="spring.chapter2.maryGame.Role" scope="singleton"/>

1.1 singleton

    singleton表示单例。当一个bean的作用域设置为singleton, 那么Spring IOC容器中只会存在一个共享的bean实例,并且所有对bean的请求,只要id与该bean定义相匹配,则只会返回bean的同一实例。这个单一实例会被存储到单例缓存(singleton cache)中。

     这里的singleton作用域和GOF设计模式中的单例是完全不同的,单例设计模式表示一个ClassLoader中该class只有一个实例,而这里的singleton则表示一个容器中该BeanDefinition只对应一个bean。

1.2 prototype

    prototype表示原型。prototype作用域部署的bean,每一次请求都会产生一个新的bean实例。

    Spring不能对一个prototype作用域的bean的整个生命周期负责,容器在初始化、配置、装配完一个prototype实例后,将它交给客户端,随后就对该prototype实例不闻不问了。清除prototype作用域的对象并释放任何prototype bean所持有的资源,都是客户端代码的职责。

1.3 request

    专用于Web应用程序上下文的一种作用域。

    request作用域表示该针对每一次HTTP请求都会产生一个新的bean,同时该bean仅在当前HTTP request内有效。使用的时候首先要在初始化web的web.xml中配置ContextListener。

<listener>
  <listener-class>org.springframework.web.context.request.RequestContextListener</listener-class>
</listener>

1.4 session

    专用于Web应用程序上下文的一种作用域。

    session作用域表示该针对每一次HTTP请求都会产生一个新的bean,同时该bean仅在当前HTTP session内有效。和request配置实例的前提一样,配置好web启动文件即可。

1.5 global session

    专用于Web应用程序上下文的一种作用域。

    global session作用域类似于标准的HTTP Session作用域,不过它仅仅在基于portlet的web应用中才有意义。Portlet规范定义了全局Session的概念,它被所有构成某个portlet web应用的各种不同的portlet所共享。在global session作用域中定义的bean被限定于全局portlet Session的生命周期范围内。如果在web中使用global session作用域来标识bean,那么web会自动当成session类型来使用。和request配置实例的前提一样,配置好web启动文件即可。

1.6 自定义bean装配作用域

    spring支持用户自定义作用域,甚至可以重新定义已有的作用域,只要不覆盖singleton和prototype即可。spring的作用域由接口org.springframework.beans.factory.config.Scope来定义,自定义自己的作用域只要实现该接口。

2、生命周期

    Spring只帮我们管理单例模式Bean的完整生命周期,对于prototype的bean,Spring在创建好交给使用者之后则不会再管理后续的生命周期。

2.1 Bean的创建

    Bean创建的主要流程:

  1. 实例化Bean;
  2. 填充属性;
  3. 设置Bean的基本配置:BeanName、BeanFactory、ApplicationContext;
  4. 调用InitializingBean的afterPropertiesSet方法;
  5. 实例化Bean:用构造器或调用定制的初始化方法。Bean配置中,可以通过@Bean的属性initMethod指定初始化的名称;
  6. 实例化Bean的属性:用构造器或调用定制的初始化方法;
  7. 调用初始化前处理器:BeanPostProcess.postProcessBeforeInitialization;
  8. 调用定制的初始化方法。Bean配置中,可以通过@Bean的属性initMethod指定初始化的名称;
  9. 调用初始化后处理器:BeanPostProcess.postProcessAfterInitialization;
  10. 调用@PostConstruct注解标识的方法。

2.2 Bean的销毁

  1. 调用DispostbleBean的destroy方法;
  2. 调用定制的销毁方法。

五、基于注解的IoC初始化

1、相关注解

1.1 类级别的注解

   类级别的注解包括:Spring的 @Component、@Repository、@Controller、@Service,以及Java EE 6的@ManagedBean、@Named。

    Spring IoC容器根据注解的过滤规则扫描读取注解Bean定义类,并将其注册到Spring IoC容器中。

1.2 类内部的注解

    类内部的注解包括:@Autowire、@Value、@Resource,以及EJB和WebService相关注解等,都是添加到类内部的字段或方法上的。

    Spring IoC通过Bean后置注解处理解析器分析Bean内部的注解。

2、流程

2.1 定位Bean扫描路径

    Spring中管理注解的Bean定义的容器有两个:AnnotationConfigApplicationContext和AnnotationConfigWebApplicationContext。这两个是专门用来处理Spring注解方式配置的容器,直接依赖于将注解作为容器配置信息来源的IoC容器。其中,后者是前者的web版本,两者用法和对注解的处理方式基本没有差别。这里以AnnotationConfigApplicationContext为例。

    Spring对注解的处理方式有两种:

  1. 直接将注解Bean注册到容器中:可以在初始化容器时注册;也可以在容器创建后手动调用注册方法向容器注册,然后通过手动刷新容器,使得容器对注册的注解Bean进行处理;
  2. 通过扫描指定的包及其子包下的所有类处理:在初始化注解容器时指定要自动扫描的路径,如果容器创建以后向给定路径动态添加了注解Bean,则需要手动调用容器扫描的方法手动刷新容器,使容器对所注册的注解Bean进行处理。

2.2 读取注解的元数据

    AnnotationConfigApplicationContext通过调用注解Bean定义读取器注册注解Bean。

    AnnotatedBeanDefinitionReader的register()方法向容器注册指定的注解Bean定义类的基本步骤:

  1. 解析作用域:使用元数据解析器解析注解Bean中关于作用域的配置:原型还是单例;
  2. 处理通用注解:使用AnnotationConfigUtils.processCommonDefinitionAnnotations()方法处理注解Bean定义类中通用的注解:@Lazy(懒加载)、@Primary(依赖注入时优先选择)、@DependsOn(当前Bean依赖另一个Bean)、@Role、@Description、@Qualifier(指定注入Bean的名称);
  3. 代理Bean定义类数据:使用AnnotationConfigUtils.applyScopedProxyMode()方法根据作用域代理类型创建代理对象,作用域代理类型包括NO(默认值)、INTERFACES(JDK动态代理)、TARGET_CLASS(CGLib)。作用域代理通过@Scope注解的proxyMode设置;
  4. 注册Bean:通过BeanDefinitionReaderUtils向容器中注册Bean,即放入到BeanDefinitionRegistry的Map中。

2.3 扫描指定包并解析为BeanDefinition

    当创建注解处理容器时,如果传入的初始参数是注解Bean定义类所在的包,注解容器将扫描给定的包及其子包,将扫描到的注解Bean定义载入并进行注册。

1、扫描给定的包及其子包

    AnnotationConfigApplicationContext通过调用类路径Bean定义扫描器ClassPathBeanDefinitionScanner扫描给定包及其子包下的所有类。

    主要流程:

  1. 组件扫描:根据包名拼接所有class文件的全限定名的正则表达式,classpath*:包名/**/*.class,同时根据注解类型过滤,找出全部候选组件,得到Set<BeanDefinition>;
  2. 设置每个BeanDefinition的基础属性和通用注解,基础包括:beanName、作用域、自动依赖注入等;
  3. 代理Bean定义类数据;
  4. 注册Bean:向容器中注册扫描到的Bean。

六、依赖注入

1、依赖注入的时机

    依赖注入发生在以下两种情况:

  1. 延迟加载:用户第一次调用getBean()方法时,IoC容器触发依赖注入;
  2. 饥饿加载:当用户在配置文件中将<bean>元素配置了lazy-init=false属性时,即让容器在解析注册Bean定义时进行预实例化,触发依赖注入。

2、Bean的创建

    Bean的创建在AbstractAutowireCapableBeanFactory的createBean()方法中进行创建。如果Bean配置了初始化前处理器和初始化后处理器,则直接返回一个需要创建的Bean的代理对象;

2.1 Bean实例化的策略:

  1. 如果RootBeanDefinition包含工厂方法,则使用工厂方法实例化;
  2. 如果配置了自动装配属性,则使用容器的自动装配进行实例化,自动装配根据参数类型匹配Bean的构造方法;
  3. 没有配置自动装配属性,则使用默认的无参构造器进行实例化,使用默认的无参构造器需要使用相应的初始化策略(JDK的反射机制,或者CGLib)。

2.2 默认无参构造器进行实例化

    如果Bean的方法被覆盖了,就使用CGLib进行实例化,否则使用JDK的反射机制,调用构造器.newInstance()进行实例化。

    CGLib是一个常用的字节码生成器的类库,它提供了一系列API实现Java字节码的生成和转换功能。

3、依赖注入

3.1 准备依赖注入

    对Bean的依赖注入主要分成两个步骤:

  1. 调用createBeanInstance()方法生成Bean所包含的Java对象实例;
  2. 调用populateBean()方法对Bean属性的依赖注入进行处理。

    属性的依赖注入的过程分为两种情况:

  1. 属性值类型不需要强制转换时,不需要解析属性值,直接进行依赖注入;
  2. 属性值类型需要强制转换时,如归其他对象的引用,首先需要解析属性值,然后对解析后的属性值进行依赖注入。

    属性值类型需要强制转换的情况,例如xml中Bean的配置是另一个Bean实例对象的引用,这时就需要容器先根据属性值解析出所引用的的对象,然后才能将该引用对象注入到目标实例对象的属性上。

1、对引用类型进行解析

  1. 如果引用类型在父类容器中,则从父类容器中获取指定的引用类型;
  2. 如果引用类型不在父类容器中,则从当前容器中获取引用;
  3. 如果当前容器中指定的Bean没有被实例化,则递归触发Bean的初始化和依赖注入。

2、对内部类进行解析

    调用内部类的解析方法进行解析。

3、对数组类型进行解析

  1. 获取数组类型;
  2. 获取数组元素类型;
  3. 使用反射获取指定类型的对象;
  4. 如果没有获取到数组类型和数组元素类型,则将数组类型设置为Object数组。

4、对集合类型进行解析

  1. 创建指定类型的数组,用于存放和返回解析后的数组;
  2. 递归解析集合的每个元素,并将结果放入到上面的数组中;
  3. 将上面的数组中的结果存入到指定的集合中。

3.2 注入赋值

    BeanWrapperImpl类负责对容器中完成初始化的Bean实例对象进行属性的依赖注入,即把Bean对象设置到它所依赖的另一个Bean的属性上。

    Spring IoC容器将属性值注入到Bean实例对象的方式:

  1. 对于集合类型的属性,将属性值解析为目标类型的集合后直接赋值给属性;
  2. 对于非集合类型的属性,大量使用JDK的反射机制,通过属性的getter()方法获取指定属性注入前的值,同时调用setter()方法为属性设置注入后的值。

3.3 管理Bean依赖关系的方式

    Spring IoC容器提供了两种管理Bean依赖关系的方式:

  1. 显示管理:通过BeanDefinition的属性值和构造方法实现Bean依赖关系的管理。例如在XML中定义的Bean中,定义属性的值为其他的Bean实例;
  2. autowiring:自动装配,不需要对Bean属性的依赖关系做显式的声明,只需要配置好autowiring属性,IoC容器会自动反射查找属性的类型和名称,然后给予属性的类型或者名称来自动匹配容器中的Bean,完成自动注入。例如使用注解定义的Bean。

    autowiring自动装配的实现过程:

  1. 对Bean属性调用getBean()方法,完成依赖Bean的初始化和依赖注入;
  2. 将依赖Bean的属性引用设置到被依赖的Bean属性上;
  3. 将依赖Bean的名称和被依赖Bean的名称存储到IoC容器的集合上。存储的形式是Map<String, Set<String>>,key是依赖Bean的名称,Set<String>是被依赖Bean的名称的集合。

七、Bean加载过程综述

1、加载配置信息

    ResourceLoader从存储介质中加载Spring配置信息,并使用Resource表示这个配置文件的资源;

2、解析并存储BeanDefinition

    BeanDefinitionReader读取Resource所指向的配置文件资源,然后解析配置文件。配置文件中每一个解析成一个BeanDefinition对象,并保存到BeanDefinitionRegistry中;

3、用BeanFactoryPostProcessor处理BeanDefinition

    容器扫描BeanDefinitionRegistry中的BeanDefinition,使用Java的反射机制自动识别出Bean工厂后处理后器(实现BeanFactoryPostProcessor接口)的Bean,然后调用这些Bean工厂后处理器对BeanDefinitionRegistry中的BeanDefinition进行加工处理。主要完成以下两项工作:

  1. 对使用到占位符的元素标签进行解析,得到最终的配置值,这意味对一些半成品式的BeanDefinition对象进行加工处理并得到成品的BeanDefinition对象;
  2. 对BeanDefinitionRegistry中的BeanDefinition进行扫描,通过Java反射机制找出所有属性编辑器的Bean(实现java.beans.PropertyEditor接口的Bean),并自动将它们注册到Spring容器的属性编辑器注册表中(PropertyEditorRegistry);

4、 实例化Bean

    Spring容器从BeanDefinitionRegistry中取出加工后的BeanDefinition,并调用InstantiationStrategy着手进行Bean实例化的工作。

5、设置Bean的属性

    在实例化Bean时,Spring容器使用BeanWrapper对Bean进行封装,BeanWrapper提供了很多以Java反射机制操作Bean的方法,它将结合该Bean的BeanDefinition以及容器中属性编辑器,完成Bean属性的设置工作。

6、用BeanPostProcessor处理Bean

    利用容器中注册的Bean后处理器(实现BeanPostProcessor接口的Bean)对已经完成属性设置工作的Bean进行后续加工,直接装配出一个准备就绪的Bean。

八、其他概念

1、Bean定义注册表

    Spring通过BeanDefinitionRegistry的实例来存储Bean定义信息BeanDefinition的实例,以Map的形式保存。

2、Bean单例缓存池

    Spring在DefaultSingletonBeanRegistry类中提供了一个用于缓存单实例Bean的缓存器,它是一个用HashMap实现的缓存器,单实例的Bean以beanName为键保存在这个HashMap中。Bean直接的依赖关系,则通过Map<String, Set<String>>的形式保存,里面的字符串都是Bean的名称。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值