Spring3.X学习笔记-IoC容器概述

蔚落 2022-06-06 03:24 373阅读 0赞

2017即将接近尾声,每当年末的时候总要检查下今年还有什么事情没做,结果却发现今年好像也没做多少事情。于是就想着把之前一直想梳理的Spring知识,趁着年末好好整理下。本系列笔记基于”Spring3.x企业应用开发实战“一书,说来惭愧,书买了好多年了也没认真的看一遍,于是就有了这个想法,算是2017年的最后给自己的一份交代。

在开始之前,先简要介绍下Spring吧!

Spring是分层的Java SE/EE应用一站式的轻量级开源框架,由Rod Johnson创建,以IoC和AOP为内核,提供了展现层Spring MVC和持久层Spring JDBC以及业务层事务管理等众多企业级应用技术,并以海纳百川的胸怀整合了开源世界里众多的企业级应用技术,逐渐成为使用最多的Java EE企业应用开发框架。

1、IoC概述

1.1 IoC的概念

IoC(控制反转:Inverse of Control)是一个重要的面向对象编程理论,Spring核心模块实现了IoC的功能。Spring中的其他模块,像AOP、声明式事务等功能都是建立在IoC的基础之上,它将类和类之间的依赖从代码中脱离出来,用配置的方式进行依赖关系描述,由IoC容器负责依赖类之间的创建、拼接、管理、获取等工作。一般来说IoC的概念有两种表示方式,一个叫控制反转,一个叫依赖注入。由于控制反转并不好理解,业界也曾进行广泛的讨论,最终软件界的泰斗级人物Martin Folwer提出了DI(依赖注入:Dependency Injection)的概念用以代替IoC。

控制反转:对于软件来说,即某一接口具体实现类的选择控制权从调用类中移除,转交给第三方决定。

依赖注入:调用类对某一接口实现类的依赖关系由第三方(容器或协作类)注入,以移除调用类对某一接口实现类的依赖。

上面两个概念第一次接触的时候,你会发现都不太好理解,那是因为还不清楚IoC的实现机制,随着对IoC了解的深入,你会发现依赖注入的概念直接明了。

1.2 IoC的类型

从注入方法上看,主要可以划分为三种类型:构造函数注入、属性注入和接口注入。Spring支持构造函数注入和属性注入。

  • 构造函数注入: 在构造函数注入中,我们通过调用类的构造函数,将接口实现类通过构造函数变量传入。
  • 属性注入: 属性注入是指通过Setter方法完成调用类所需依赖的注入。
  • 接口注入: 将调用类所有依赖注入的方法抽取到一个接口中,调用类通过实现该接口提供相应的注入方法。

由于通过接口注入需要额外声明一个接口,增加了类的数目,而且它的效果和属性注入并无本质区别,所以不提倡采用这种方式。

2、IoC的底层实现原理

Spring的核心模块实现了IoC的功能,它通过配置文件或注解描述类和类之间的依赖关系,自动完成类的初始化和依赖注入的工作。让开发者们从底层实现类的实例化、依赖关系装配等工作中脱离出来,专注于更有意义的业务逻辑开发工作。这种“神奇”的力量归功于Java语言本身的类反射功能。

2.1 Java反射知识

Java语言允许通过程序化的方式间接对Class的对象实例操作,Class文件由类装载器装载后,在JVM中将形成一份描述CLass结构的元信息对象,通过该元信息对象可以获知Class的结构信息:如构造函数、属性和方法等。Java允许用户借由这个Class相关的元信息对象间接调用Class对象的功能,这就为使用程序化方式操作Class对象开辟了途径。下面先看一个例子:

  1. public class Car {
  2. private String brand;
  3. private String color;
  4. private int maxSpeed;
  5. public Car() {}
  6. public Car(String brand, String color, int maxSpeed) {
  7. this.brand = brand;
  8. this.color = color;
  9. this.maxSpeed = maxSpeed;
  10. }
  11. public void introduce() {
  12. System.out.println("brand:" + brand + ";color:" + color + ";maxSpeed:" + maxSpeed);
  13. }
  14. // 省略参数的getter/Setter方法
  15. }
  16. public class ReflectTest {
  17. public static Car initByDefaultConst() throws Throwable {
  18. // 通过类装载器获取Car类对象
  19. ClassLoader loader = Thread.currentThread().getContextClassLoader();
  20. Class<?> clazz = loader.loadClass("com.hhxs.bbt.web.Car");
  21. // 获取类的默认构造器对象并通过它实例化Car
  22. Constructor<?> cons = clazz.getDeclaredConstructor((Class[]) null);
  23. Car car = (Car) cons.newInstance();
  24. // 通过反射方法设置属性
  25. Method setBrand = clazz.getMethod("setBrand", String.class);
  26. setBrand.invoke(car, "红旗CA72");
  27. Method setColor = clazz.getMethod("setColor", String.class);
  28. setColor.invoke(car, "黑色");
  29. Method setMaxSpeed = clazz.getMethod("setMaxSpeed", int.class);
  30. setMaxSpeed.invoke(car, 200);
  31. return car;
  32. }
  33. public static void main(String[] args) throws Throwable {
  34. Car car1 = initByDefaultConst();
  35. car1.introduce();
  36. }
  37. }

通过查看运行结果,可以看到这和直接通过构造函数和方法调用类功能的效果是一致的,只不过前者是间接调用,后者是直接调用罢了。这说明我们完全可以通过编程方式调用Class的各项功能。如果我们将这些信息以一个配置文件的方式提供,就可以使用Java语言的反射功能编写一段通用代码对类似于Car的类进行实例化及功能调用操作了。有没有感觉这和上面提到的Spring框架的实现机制很相似,Spring正是基于Java语言自带的反射机制实现了IoC的功能。

下面简要介绍下上述例子用到得三个主要反射类,这些反射对象类在java.reflect包中定义:

  • Constructor: 类的构造函数反射类,通过Class#getConstructors()方法可以获得类的所有构造函数反射对象数组。Constructor的一个主要方法是newInstance(Object[]… initargs),通过该方法可以创建一个对象类的实例,相当new关键字。
  • Method: 类方法的反射类,通过Class#getDeclaredMethods()方法可以获取类的所有方法发射类对象数组Method[]。Method最主要的方法是invoke(Object obj, Objcet… args),obj表示操作的目标对象,args为方法入参。
  • Field: 类的成员变量反射类,通过Class#getDeclaredFields()方法可以获取类的成员变量反射对象数组。Filed类最主要的方法是set(Object obj, Object value),obj表示操作的目标对象,通过value为目标类对象的成员变量设置值。

此外,Java还为包提供了Package反射类,在JDK5.0中还未注解提供了AnnotatedElement反射类。总之,Java的反射体系保证了可以通过程序化的方式访问目标类中的所有元素,对于private或protected的成员变量和方法,只要JVM的安全机制允许,也可以通过反射进行调用setAccessible(boolean access)

3、三个核心接口

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

3.1 BeanFactory

Bean工厂(com.springframework.beans.factory.BeanFactory)是Spring框架最核心的接口,它提供了高级IoC的配置机制。BeanFactory使管理不同不同类型的Java对象成为可能,一般称BeanFactory为IoC容器。BeanFactory是类的通用工厂,它可以创建并管理各种类的对象,Spring称这些被创建和管理的Java对象为Bean。Bean最主要的方法就是getBean(String beanName),该方法从容器中返回特定名称的Bean。下面看一个小例子:

  1. // spring文件
  2. <?xml version="1.0" encoding="UTF-8" ?>
  3. <beans xmlns="http://www.springframework.org/schema/beans"
  4. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"
  5. xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
  6. <bean id="car" class="com.hhxs.bbt.web.Car"
  7. p:brand="红旗CA72"
  8. p:color="黑色"
  9. p:maxSpeed="200" />
  10. </beans>
  11. // JAVA代码
  12. public class BeanFactoryTest {
  13. public static void main(String[] args) throws Throwable{
  14. ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
  15. Resource res = resolver.getResource("classpath:spring/beans.xml");
  16. System.out.println(res.getURL());
  17. BeanFactory bf = new XmlBeanFactory(res);
  18. System.out.println("init BeanFactory.");
  19. Car car = bf.getBean("car",Car.class);
  20. System.out.println("car bean is ready for use!");
  21. car.introduce();
  22. }
  23. }

注意: 通过BeanFactory启动IoC容器时,并不会初始化配置文件中定义的Bean,初始化动作发生在第一次调用时。对于单实例的Bean来说,BeanFactory会缓存Bean实例,所以第二次使用getBean()获取Bean时将直接从IoC容器的缓存中获取Bean实例。Spring在DefaultSingletonBeanRegistry类中提供了一个用于缓存单实例Bean的缓存器,它是一个用HashMap实现的缓存器,单实例的Bean以beanName为键保存在这个HashMap中。

3.2 ApplicationContext

应用上下文(com.springframework.context.ApplicationContext)建立在BeanFactory基础之上,提供了更多面向应用的功能,它提供了国际化支持和框架事件体系,更易于创建实际应用。一般称ApplicationContext为应用上下文或者Spring容器。

ApplicationContext的主要实现类是ClassPathXmlApplicationContext和FileSystemXmlApplicationContext,前者默认从类路径中加载配置文件,后者默认从文件系统中装载配置文件。

  1. ApplicationContext ctx = new ClassPathXmlApplicationContext("spring/beans.xml");
  2. //或者
  3. ApplicationContext ctx = new FileSystemXmlApplicationContext("spring/beans.xml");

在获取ApplicationContext实例后,就可以像BeanFactory一样调用getBean(beanName)返回Bean了。需要注意的是ApplicationContext在初始化应用上下文时就实例化所有单实例的Bean。因此,相比BeanFactory,初始化时间也会相对较长些,不过之后的调用就不在有“第一次惩罚”的问题。

3.3 WebApplicationContext

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

Spring与Wen应用的上下文融合

WebApplicationContext的初始化方式和BeanFactory、ApplicationContext有所区别,因为WebApplicationContext需要ServletContext实例,也就是说它必须在拥有Web容器的前提下才能完成启动的工作。有过Web开发经验的读者都知道可以在web.xml中配置自启动的Servlet(spring3.0及以后版本中已经删除)或定义Web容器监听器(ServletContextListener),借助这两者中的任何一个都可以完成启动Spring Web应用上下文的工作。

通过web容器监听器启动:web.xml

  1. <context-param>
  2. <param-name>contextConfigLocation</param-name>
  3. <param-value>
  4. classpath:/spring/spring-context.xml
  5. </param-value>
  6. </context-param>
  7. <!-- spring容器启动监听器 -->
  8. <listener>
  9. <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
  10. </listener>

4、Bean的生命周期

4.1 BeanFactory中Bean的生命周期

我们知道Web容器中的Servlet拥有明确的生命周期,Spring容器中的Bean也拥有相似的生命周期。我们可以从两个层面定义Bean的生命周期:第一个层面是Bean的作用范围;第二个层面是实例化Bean时所经历的一系列阶段。

BeanFactory中Bean的生命周期

  1. 当调用者通过getBean(beanName)向容器请求某一个Bean时,如果容器注册了org.springframework.beans.factory.config.InstantiationAwareBeanPostProcessor接口,在实例化Bean之前,将调用接口的postProcessBeforeInstantiation()方法;
  2. 根据配置情况调用Bean构造函数或工厂方法实例化Bean;
  3. 如果容器注册了InstantiationAwareBeanPostProcessor接口,在实例化Bean之后,调用该接口的postProcessAfterInstantiationn()方法,可以在这里对已经实例化的对象进行处理。
  4. 如果Bean配置了属性信息,容器在这一步着手将配置值设置到Bean对应的属性中,不过在设置每个属性值之前先调用InstantiationAwareBeanPostProcessor接口的postProcessPropertyValues()方法;
  5. 调用Bean的属性设置方法设置属性值;
  6. 如果Bean实现了org.springframework.beans.factory.BeanNameAware接口,将调用setBeanName()接口方法,将配置文件中该Bean对应的名称设置到Bean中;
  7. 如果Bean实现了org.springframework.bean.factory.BeanFactoryAware接口,将调用setBeanFactory()接口方法,将BeanFactory容器实例设置到Bean中;
  8. 如果BeanFactory装配了org.springframework.beans.factory.config.BeanPostProcessor后处理器,将调用BeanPostProcessor的Object postProcessBeforeInstantiation(Object bean, Stringn beanName)接口方法对Bean进行加工操作。其中入参bean是当前正在处理的Bean,而beanName是当前Bean的配置名,返回的对象为加工处理后的Bean。BeanPostProcessor在Spring框架中占有重要的地位,为容器提供duiBean进行后续架构处理的切入点,Spring容器所提供的各种“神奇功能”(如AOP,动态代理等)都通过BeanPostProcessor实施
  9. 如果Bean实现了InitializingBean的接口,将调用该接口的afterPropertiesSet()方法;
  10. 如果在通过init-method属性定义了初始化方法,将执行这个方法;
  11. BeanPostProcessor后处理定义了两个方法:其一时postProcessBeforeInstantiation()在第8步调用;其二是Object postProcessAfterInstantiationn(Object bean, String beanName)方法,这个方法在此时调用,容器中再次获得对Bean进行加工处理的机会。
  12. 如果在中指定Bean的作用范围是scope=”prototype”,将Bean返回给调用者,调用者负责Bean后续生命的管理,Spring不再管理这个Bean的生命周期。如果作用范围设置为scope=“singleton”,则将Bean放入到Spring IoC容器的缓存池中,并将Bean引用返回给调用者,Spring继续对这些Bean进行后续的生命管理。
  13. 对于scope=“singleton”的Bean,当容器关闭时,将触发Spring对Bean的后续生命周期的管理工作,首先如果Bean实现了DisposableBean接口,则将调用接口的afterPropertiesSet()方法,可以在此编写释放资源、记录日志等操作。
  14. 对于scope=“singleton”的Bean,如果通过的destroy-method属性指定了Bean的销毁方法,Spring将执行Bean的这个方法,完成Bean资源的释放等操作。

Bean生命周期实例:

  1. public class Car implements BeanFactoryAware, BeanNameAware, InitializingBean, DisposableBean {
  2. private String brand;
  3. private String color;
  4. private int maxSpeed;
  5. private BeanFactory beanFactory;
  6. private String beanName;
  7. public Car() {
  8. System.out.println("调用Car()构造函数。");
  9. }
  10. public Car(String brand, String color, int maxSpeed) {
  11. this.brand = brand;
  12. this.color = color;
  13. this.maxSpeed = maxSpeed;
  14. }
  15. public void introduce() {
  16. System.out.println("brand:" + brand + ";color:" + color + ";maxSpeed:" + maxSpeed);
  17. }
  18. public String getBrand() {
  19. return brand;
  20. }
  21. public void setBrand(String brand) {
  22. System.out.println("调用setBrand()设置属性。");
  23. this.brand = brand;
  24. }
  25. public String getColor() {
  26. return color;
  27. }
  28. public void setColor(String color) {
  29. this.color = color;
  30. }
  31. public int getMaxSpeed() {
  32. return maxSpeed;
  33. }
  34. public void setMaxSpeed(int maxSpeed) {
  35. this.maxSpeed = maxSpeed;
  36. }
  37. // 5 DisposableBean方法
  38. @Override
  39. public void destroy() throws Exception {
  40. System.out.println("调用DisposaleBean.destroy()。");
  41. }
  42. // 4 IntializingBean接口方法
  43. @Override
  44. public void afterPropertiesSet() throws Exception {
  45. System.out.println("调用IntializingBean.afterPropertiesSet()。");
  46. }
  47. // 3 BeanNameAware接口方法
  48. @Override
  49. public void setBeanName(String name) {
  50. System.out.println("调用BeanNameAware.setBeanName()。");
  51. this.beanName = beanName;
  52. }
  53. // 2 BeanFactoryAware接口方法
  54. @Override
  55. public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
  56. System.out.println("调用BeanFactoryAware.setBeanFactory()。");
  57. this.beanFactory = beanFactory;
  58. }
  59. // 6 通过<bean>的init-method属性指定的初始化方法
  60. public void myInit() {
  61. System.out.println("调用init-method所指定的myInit(),将maxSpeed设置为240。");
  62. this.maxSpeed = 240;
  63. }
  64. // 7 通过<bean>的destroy-method属性指定的销毁方法
  65. public void myDestroy() {
  66. System.out.println("调用destroy-method所指定的myDestroy()。");
  67. }
  68. }
  69. public class MyInstantiationAwareBeanPostProcessor extends InstantiationAwareBeanPostProcessorAdapter {
  70. // 1 在实例化Bean前进行调用
  71. public Object postProcessBeforeInitialization(Class beanClass, String beanName) throws BeansException {
  72. // 1-1 仅对容器中Car Bean进行处理
  73. if ("car".equals(beanName)) {
  74. System.out.println("InstantiationAware BeanPostProcessor.postProcessBeforeInstantiation");
  75. }
  76. return null;
  77. }
  78. // 2 在实例化Bean后调用
  79. public boolean postProcessAfterInstantiation(Object bean, String beanName) {
  80. // 2-1 仅对容器中Car Bean进行处理
  81. if ("car".equals(beanName)) {
  82. System.out.println("InstantiationAware BeanPostProcessor.postProcessAfterInstantiation");
  83. }
  84. return true;
  85. }
  86. // 3 在设置某个属性时调用
  87. public PropertyValues postProcessPropertyValues(PropertyValues pvs, PropertyDescriptor[] pds, Object bean,
  88. String beanName) throws BeansException {
  89. // 3-1 仅对容器中Car Bean进行处理,还可以通过pdst入参进行过滤,仅对car的某个特定属性时进行处理
  90. if("car".equals(beanName)) {
  91. System.out.println("InstantiationAware AwareBeanPostProcessor.postProcessPropertyValues");
  92. }
  93. return pvs;
  94. }
  95. }
  96. public class MyBeanPostProcessor implements BeanPostProcessor {
  97. @Override
  98. public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
  99. if(beanName.equals("car")) {
  100. Car car = (Car)bean;
  101. if(car.getColor() == null) {
  102. System.out.println("调用BeanPostProcessor.postProcessBeforeInitialization(),color为空,设置为默认黑色。");
  103. car.setColor("黑色");
  104. }
  105. }
  106. return bean;
  107. }
  108. @Override
  109. public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
  110. if(beanName.equals("car")) {
  111. Car car = (Car)bean;
  112. if(car.getMaxSpeed() >= 200) {
  113. System.out.println("调用BeanPostProcessor.postProcess AfterInitialization(), 将maxSpeed调整为200。");
  114. car.setMaxSpeed(200);
  115. }
  116. }
  117. return bean;
  118. }
  119. }
  120. **beans.xml**
  121. <?xml version="1.0" encoding="UTF-8" ?>
  122. <beans xmlns="http://www.springframework.org/schema/beans"
  123. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"
  124. xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
  125. <bean id="car" class="com.hhxs.bbt.web.Car"
  126. init-method="myInit"
  127. destroy-method="myDestroy"
  128. p:brand="红旗CA72"
  129. p:maxSpeed="200"
  130. scope="singleton" />
  131. </beans>
  132. public class BeanLifeCycle {
  133. private static void lifeCycleInBeanFactory() {
  134. Resource res = new ClassPathResource("spring/beans.xml");
  135. BeanFactory bf = new XmlBeanFactory(res);
  136. // 向容器中注册后处理器
  137. ((ConfigurableBeanFactory)bf).addBeanPostProcessor(new MyBeanPostProcessor());
  138. ((ConfigurableBeanFactory)bf).addBeanPostProcessor(new MyInstantiationAwareBeanPostProcessor());
  139. // 第一次从容器中获取Car,将触发容器实例化该Bean,这将引发Bean生命周期方法的调用。
  140. Car car1 = (Car)bf.getBean("car");
  141. car1.introduce();
  142. car1.setColor("红色");
  143. car1.introduce();
  144. // 第二次从容器中获取Car,直接从缓存池中获取
  145. Car car2 = (Car)bf.getBean("car");
  146. // 查看car1和car2是否指向同一引用
  147. System.out.println("car1==car2:" + (car1==car2));
  148. // 关闭容器
  149. ((XmlBeanFactory)bf).destroySingletons();
  150. }
  151. public static void main(String[] args) {
  152. lifeCycleInBeanFactory();
  153. }
  154. }

运行上述代码,我们在控制台上得到以下输出信息,仔细观察,将发现它验证了我们前面所介绍的Bean生命周期过程。

  1. 调用Car()构造函数。
  2. InstantiationAware BeanPostProcessor.postProcessAfterInstantiation
  3. InstantiationAware AwareBeanPostProcessor.postProcessPropertyValues
  4. 调用setBrand()设置属性。
  5. 调用BeanNameAware.setBeanName()。
  6. 调用BeanFactoryAware.setBeanFactory()。
  7. 调用BeanPostProcessor.postProcessBeforeInitialization(),color为空,设置为默认黑色。
  8. 调用IntializingBean.afterPropertiesSet()。
  9. 调用init-method所指定的myInit(),将maxSpeed设置为240
  10. 调用BeanPostProcessor.postProcess AfterInitialization(), maxSpeed调整为200
  11. brand:红旗CA72;color:黑色;maxSpeed:200
  12. brand:红旗CA72;color:红色;maxSpeed:200
  13. car1==car2:true
  14. 调用DisposaleBean.destroy()。
  15. 调用destroy-method所指定的myDestroy()。

4.2 ApplicationContext中Bean的生命周期

Bean在应用上下文中的生命周期和BeanFactory中生命周期类似,不同的是,如果Bean实现org.springframework.context.ApplicationContextAware接口,会增加一个调用该接口方法setApplicationContext()的步骤。

ApplicationContext中Bean的生命周期

此外,如果配置文件中声明了工作后处理器接口BeanFactoryPostProcessor的实现类,则应用上下文在装载配置文件之后初始化Bean实例之前将调用这些BeanFactoryPostProcessor对配置信息进行加工处理。工厂后处理器是容器级的,仅在应用上下文初始化时调用一次,其目的是完成一些配置文件的加工处理工作。

ApplicationContext和BeanFactory另一最大的不同之处在于:前者会利用Java反射机制自动识别出配置文件中定义的BeanPostProcessor、InstantiationAwareBeanPostProcessor和BeanFactoryPostProcessor,并自动将它们注册到应用上下文中;而后者需要在代码中拿你给通过手工调用addBeanPostProcessor()方法进行注册。这也是为什么在应用开发时,我们普遍使用ApplicationContext而很少使用BeanFactory的原因之一。


————本文结束感谢您的阅读————

发表评论

表情:
评论列表 (有 0 条评论,373人围观)

还没有评论,来说两句吧...

相关阅读

    相关 Spring3.X学习笔记-IoC容器概述

    > 2017即将接近尾声,每当年末的时候总要检查下今年还有什么事情没做,结果却发现今年好像也没做多少事情。于是就想着把之前一直想梳理的Spring知识,趁着年末好好整理下。本系