Spring框架概述
在传统JavaEE开发中,比如我们经常见到的Web项目,开发起来是非常繁琐的。Spring的出现使创建Java企业级应用程序变得容易。它提供了在企业环境中使用 Java 语言所需的一切,并且可以根据应用程序的需求灵活地创建多种体系结构。从 Spring Framework 5.0 开始,Spring 的使用需要的JDK版本为JDK 8(Java SE 8),并且已经为 JDK 9 提供了现成的支持。
Spring 支持广泛的应用场景。在大型企业中,应用程序通常存在很长时间,并且必须在升级周期不受开发人员控制的 JDK 和应用程序服务器上运行。其他服务器则可以作为单个 jar 运行,并且服务器可以嵌入云环境中。还有一些可能是不需要服务器的独立应用程序(例如批处理或集成工作负载)。
Spring 是开源的。它拥有一个庞大而活跃的社区,可以根据各种实际用例提供持续的反馈。这帮助 Spring 在很长一段时间内成功地 Developing 了。
Spring的含义
Spring在不同的上下文中表示不同的事物。但它通常是用来引用Spring Framework 项目本身,它也是一切的开始。随着时间的流逝,其他 Spring 项目已经构建在 Spring Framework 之上。通常,当人们说“Spring”时,它们表示整个项目系列。
Spring 框架分为多个模块,它也是这么多模块的一个集合,即Spring就是由这些模块集成的一个Java开发框架,这些模块为Spring这个框架提供了很多的功能,下图就是spring中的模块结构。
Spring Core
最下面Core Container中的Core是Spring中的核心模块,Spring其他所有的功能基本都需要依赖于该模块,主要提供 IoC 依赖注入功能的支持。
Spring Aspects
该模块为与 AspectJ 的集成提供支持。
Spring AOP
提供了面向切面的编程实现。
应用程序可以选择所需的模块。其中最核心的模块是其容器模块,包括配置模型和依赖注入机制。除此之外,Spring 框架为不同的应用程序体系结构提供了基础支持,包括消息传递,数据的事务性和持久性以及 Web。它还包括基于 Servlet 的 Spring MVC Web 框架,以及并行的 Spring WebFlux 反应式 Web 框架。
总而言之,Spring就相当于是一个工具,这个工具可以帮助我们更方便的构建JavaEE项目,它帮我们省去了很多的代码实现,以此来帮助我们更快速的开发JavaEE项目。
Spring核心
前面我们提到了Spring中最核心的模块就是其容器模块,下面在我们学习Spring 框架中最重要的控制反转(IoC)容器之前首先需要大概了解一下IoC这个概念。
IoC(Inverse of Control:控制反转)
IoC其实是一种设计思想,而不是一个具体的技术实现。它在Spring出现之前就已经存在了,并且也不是spring所特有的,这种思想叫做依赖倒置原则。
**什么是依赖倒置原则?**假设我们设计一辆汽车:先设计轮子,然后根据轮子大小设计底盘,接着根据底盘设计车身,最后根据车身设计好整个汽车。这里就出现了一个“依赖”关系:汽车依赖车身,车身依赖底盘,底盘依赖轮子。
这样的设计看起来没问题,但是可维护性却很低。假设设计完工之后,上司却突然说根据市场需求的变动,要我们把车子的轮子设计都改大一码。这下我们就蛋疼了:因为我们是根据轮子的尺寸设计的底盘,轮子的尺寸一改,底盘的设计就得修改;同样因为我们是根据底盘设计的车身,那么车身也得改,同理汽车设计也得改——整个设计几乎都得改!
我们现在换一种思路。我们先设计汽车的大概样子,然后根据汽车的样子来设计车身,根据车身来设计底盘,最后根据底盘来设计轮子。这时候,依赖关系就倒置过来了:轮子依赖底盘, 底盘依赖车身, 车身依赖汽车。
这时候,上司再说要改动轮子的设计,我们就只需要改动轮子的设计,而不需要动底盘,车身,汽车的设计了。
这就是依赖倒置原则——把原本的高层建筑依赖底层建筑(物理上)“倒置”过来,变成底层建筑依赖高层建筑(设计思想上),即在思想上我们先设计出框架再根据框架实现具体细节,而不是真正落实时由底层细节不断上推出整个框架跟盖楼是一样的。
高层建筑决定需要什么,底层去实现这样的需求,但是高层并不用管底层是怎么实现的。这样就不会出现前面的“牵一发动全身”的情况。
IoC 的思想就是将原本在程序中手动创建对象的控制权,交由 Spring 框架来管理。
为什么叫控制反转?
- 控制 :指的是对象创建(实例化、管理)的权力
- 反转 :控制权从程序设计者交给外部环境(Spring 框架、IoC 容器)
那么为什么要使用IoC呢?
因为在一个很多的项目中每个类之间的调用关系会非常复杂,如果其中一个出现问题,我们可能需要改动很多地方的代码,这种代码的耦合度很高,不利于维护。
而IoC的思想就是两方(调用者和被调用者)之间不互相依赖,由第三方容器(IoC容器)来管理相关资源。
好处有两个
- 对象之间的耦合度或者说依赖程度降低
- 资源变的容易管理;比如你用 Spring 容器提供的话很容易就可以实现一个单例
那么IoC思想是如何实现的呢?
IoC 容器是 Spring 用来实现 IoC 的载体, IoC 容器实际上就是个 Map(key,value),Map 中存放的是各种对象。IoC容器就像是一个工厂,实际上也实现了java的工厂设计模式,我们在进行开发中只需要在IOC容器中配置好我们未来需要的对象的配置文件/注解即可。当我们需要某一个对象时,我们只需要向IOC容器发送一个请求,IoC容器会帮我们创建好我们需要的对象并且发送给我们。
IoC的实现方式—DI
DI(Dependency Injection)依赖注入是实现IoC最常见的实现方式,通过DI我们可以实现通过IoC容器创建对象再把这个对象注入到需要它的地方。它的本质就是传递参数(但是这个传递的过程是被动的,即被传入),所谓依赖就是需要的参数(大部分情况是一个类),注入就是传递。比如甲方开放一个接口(或者一个方法(setter)),在它需要的时候,能够将乙方传递进来(注入)。结合IoC容器理解就是当A需要B时,不由A去直接创建B了,而是在A需要B时,由IoC容器去创建B然后IoC容器直接把B注入给A。
为什么会有DI呢?
因为在不使用IoC时,一个对象A需要另一个对象B即A依赖B时直接创建就可以了,但是使用IoC之后,A不能直接创建B了,因为控制反转了,控制权交给了一个第三方容器,第三方容器创建好B之后,直接注入给A,注入的意思是A是被动的,A被传入了B。在xml中用ref属性标注依赖的类
所以IoC的实现思路如下:
上文是依赖倒置原则,而IOC在Spring中具体的实现为资源不由使用资源的双方管理,而由不使用资源的第三方管理,这可以带来很多好处。第一,资源集中管理,实现资源的可配置和易管理。第二,降低了使用资源双方的依赖程度,也就是我们说的耦合度。
所谓的IoC控制反转,打个比方:我们在进行交易时,买家和卖家原来是直接面对面进行交易,一方因为某些原因不想继续交易,那么交易就会失败。控制反转之后就是交易的甲乙双方不相互依赖,交易活动的进行不依赖于甲乙任何一方,整个交易的进行由第三方负责管理,甲方要卖东西,就把东西交给第三方,乙方想买东西直接就从第三方买走了。
而DI依赖注入就是如果甲方需要一个东西,不需要自己去找乙方购买,而是给这个第三方说我需要XXX,那么第三方就会找乙方拿到这个XXX然后交给甲方,这个过程中甲方就不需要主动去购买,而是被动地等第三方把东西给他就好了。
IoC容器
IoC(控制反转)也称为依赖注入(DI)。在此过程中,对象仅通过构造函数参数,工厂方法参数来定义依赖,然后通过构造方法或工厂方法返回对象后在对象实例上设置的属性来定义其依赖项(即与对象实例一起使用的其他对象,这些对象都是其属性值) 。
所以容器在创建 bean 时会注入那些依赖到bean对象中。此过程从根本上讲是通过使用类的直接构造或诸如服务定位器模式之类的控件来控制其依赖项的实例化,从根本上反转了bean本身控制实例化或依赖的位置。
org.springframework.beans
和org.springframework.context
软件包是 Spring Framework 的 IoC 容器的基础。 BeanFactory接口提供了一种高级配置机制,能够 Management 任何类型的对象。 ApplicationContext是BeanFactory
的子接口。它增加了:
- 与 Spring 的 AOP 功能的轻松集成
- 消息资源处理(用于国际化)
- Event publication
- 特定于应用程序层的上下文,例如用于 Web 应用程序的
WebApplicationContext
。
简而言之,BeanFactory
提供了配置框架和基本功能,而ApplicationContext
添加了更多企业特定的功能。
在 Spring 中,构成应用程序主干并由 Spring IoC 容器 Management 的对象称为 bean。 Bean 是由 Spring IoC 容器实例化,组装和管理的对象。否则,bean 仅仅是我们的应用程序中许多普通对象中的一个。 Bean和其依赖(Bean的属性)之间的关系反映在容器使用的配置元数据中。
简而言之就是IoC容器在帮助我们创建bean的过程中(这一步也是反转了,不再是我们创建手动bean了,而是交给IoC容器创建了)会通过依赖注入的方式将这些bean所需的依赖也就是它们的属性附上值。 Di的意思就是从原来由bean的构造函数来实例化bean的过程改为由IoC容器来帮助bean来实例化,也就说这个过程中bean是被动的附上值的,而不是我们通过调用bean的构造函数或者set方法对其进行赋值了。
容器概述
org.springframework.context.ApplicationContext
接口(其实例就是一个IoC容器)代表了 Spring的IoC 容器,这个容器负责实例化,配置和装配Bean。容器通过读取配置元数据来获取要实例化,配置和组装哪些对象的指令。配置元数据以 XML,Java 注解或 Java 代码表示。配置元数据是用来表达组成应用程序的对象以及这些对象之间的丰富的相互依赖关系。
Spring 提供了ApplicationContext
接口的几种实现。在独立应用程序中,通常创建ClassPathXmlApplicationContext或FileSystemXmlApplicationContext的实例。尽管 XML 是定义配置元数据的传统格式,但是您可以通过提供少量 XML 配置来声明性地启用对其他元数据格式的支持,从而指示容器将 Java 注解或代码用作元数据格式。
从代码层次来看:Spring容器就是一个实现了ApplicationContext
接口的对象,
从功能上来看: Spring 容器是 Spring 框架的核心,是用来管理对象的。容器将创建对象,把它们连接在一起,配置它们,并管理他们的整个生命周期从创建到销毁。
下图显示了 Spring 的工作原理。Spring容器通过我们提交的pojo类以及配置元数据结合在一起,进而产生一个充分配置的可以使用的系统或应用程序也就是bean
这里说的配置元数据,实际上我们就是我们提供的XML配置文件,或者通过注解方式提供的一些配置信息
配置元数据
Spring IoC 容器使用一种形式的配置元数据表示应用程序开发人员告诉 Spring 容器实例化,配置和组装应用程序中的对象。
传统上,配置元数据以简单直观的 XML 格式提供,但现在我们使用更多的是基于注解式的配置。
Spring的配置元数据由至少一个(通常是一个以上)bean 定义组成。基于 XML 的配置元数据将这些 bean 配置为顶级<beans/>
元素内的<bean/>
元素。 Java 配置通常在@Configuration
类中使用@Bean
注解的方法。
以下示例显示了基于 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.xsd">
<bean id="..." class="..."> (1) (2)
<!-- collaborators and configuration for this bean go here -->
</bean>
<bean id="..." class="...">
<!-- collaborators and configuration for this bean go here -->
</bean>
<!-- more bean definitions go here -->
</beans>
-
(1)
id
属性是标识单个 bean 定义的字符串。 -
(2)
class
属性定义 bean 的类型并使用完全限定的类名。
实例化容器
提供给ApplicationContext
构造函数的位置路径是资源字符串,这些资源字符串使容器可以从各种外部资源(例如本地文件系统,Java CLASSPATH
等)加载配置元数据。
这里我们使用两个xml文件来定义service层的bean和dao层的bean。
ApplicationContext context = new ClassPathXmlApplicationContext("services.xml", "daos.xml");
以下示例显示了服务层对象(services.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.xsd">
<!-- services -->
<bean id="petStore" class="org.springframework.samples.jpetstore.services.PetStoreServiceImpl">
<property name="accountDao" ref="accountDao"/>
<property name="itemDao" ref="itemDao"/>
<!-- additional collaborators and configuration for this bean go here -->
</bean>
<!-- more bean definitions for services go here -->
</beans>
以下示例显示了数据访问对象daos.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.xsd">
<bean id="accountDao"
class="org.springframework.samples.jpetstore.dao.jpa.JpaAccountDao">
<!-- additional collaborators and configuration for this bean go here -->
</bean>
<bean id="itemDao" class="org.springframework.samples.jpetstore.dao.jpa.JpaItemDao">
<!-- additional collaborators and configuration for this bean go here -->
</bean>
<!-- more bean definitions for data access objects go here -->
</beans>
在上面的示例中,服务层由PetStoreServiceImpl
类和两个类型为JpaAccountDao
和JpaItemDao
的数据访问对象组成。 property name
元素引用当前JavaBean属性的名称,而ref
元素引用另一个 bean 定义的id。
构成基于 XML 的配置元数据
使bean 定义跨越多个 XML 文件可能很有用。通常,每个单独的 XML 配置文件都代表体系结构中的逻辑层或模块。
我们可以使用ApplicationContext的构造函数从很多xml中加载 bean 定义,因为该构造函数具有多个资源字符串位置(这种表示这些xml文件中的bean都会加载进这一个IoC容器)。或者使用一个或多个<import/>
元素从另一个文件中加载 bean 定义。如下:
<beans>
<import resource="services.xml"/>
<import resource="resources/messageSource.xml"/>
<import resource="/resources/themeSource.xml"/>
<bean id="bean1" class="..."/>
<bean id="bean2" class="..."/>
</beans>
在前面的示例中,从三个文件services.xml
,messageSource.xml
和themeSource.xml
加载了外部 bean 定义。且所有位置路径都相对于进行导入的定义文件,因此services.xml
必须与进行导入的文件位于同一目录或 Classpath 位置,而messageSource.xml
和themeSource.xml
必须位于导入文件位置下方的resources
位置。但是鉴于这些路径是相对的,最好不要使用任何斜线。
可以但不建议使用相对的“ …/”路径引用父目录中的文件。这样做会创建对当前应用程序外部文件的依赖关系。特别是,不建议对classpath:
URL(例如classpath:../services.xml
)使用此引用,在 URL 中,运行时解析过程选择“最近”Classpath 根,然后查看其父目录。Classpath 配置的更改可能导致选择其他错误的目录。
我们可以使用标准资源位置而不是相对路径:例如file:C:/config/services.xml
或classpath:/config/services.xml
。但是这里我们正在将应用程序的配置耦合到特定的绝对位置。通常,最好为这样的绝对位置保留一个间接寻址,例如通过在运行时针对 JVM 系统属性解析的“ ${…}”占位符。
以上是使用xml文件实例化容器,但现在我们大多使用SpringBoot进行项目的构建,那么我们是不需要写xml文件的,因为SpringBoot启动时会自动创建一个容器,这个容器会自动扫描启动类所在包的所有配置类并加载所有扫描到的bean(如果我们没用配置ComponentScan的扫描路径)。
容器的使用
ApplicationContext
是高级工厂的接口,该工厂能够维护不同 bean 及其依赖关系的注册表,其底层是一个Map,key就是Bean的名字,value就是这个Bean实例。通过使用方法T getBean(String name, Class<T> requiredType)
检索 bean 的实例。如以下所示:
// create and configure beans
ApplicationContext context = new ClassPathXmlApplicationContext("services.xml", "daos.xml");
// retrieve configured instance
PetStoreService service = context.getBean("petStore", PetStoreService.class);
// use configured instance
List<String> userList = service.getUsernameList();
虽然我们可以使用getBean
来检索 bean 的实例,且ApplicationContext
接口还有其他几种检索 bean 的方法,但是实际上我们的代码根本不应该调用getBean()
方法,因为Spring 与 各种框架(比如Web)的集成为各种框架的组件(例如控制器和 JSFManagement 的 Bean)提供了依赖注入,以此可以直接通过元数据(例如自动装配)声明对特定 Bean 的依赖。,而不需要我们访问这些bean来进行进一步操作。
Bean总览
Spring IoC 容器管理一个或多个 bean。这些 bean 是使用我们提供给容器的配置元数据创建的(例如,以 XML <bean/>
定义的形式)。
在容器本身内,这些 bean 定义表示为BeanDefinition
对象,其中包含(除其他信息外)以下元数据:
- 包限定的类名:通常定义了 Bean 的实际实现类。
- Bean 行为配置元素,用于声明 Bean 在容器中的行为(作用域,生命周期回调等)。
- 引用其他进行其工作所需的 bean。这些引用也称为协作者或依赖项。
- 要在新创建的对象中设置的其他配置设置,例如,池的大小限制在 Management 连接池的 bean 中使用的连接数。
该元数据转换为构成每个 bean 定义的一组属性。下表描述了这些属性:
Property | Explained in… |
---|---|
Class | Instantiating Beans |
Name | Naming Beans |
Scope | Bean Scopes |
Constructor arguments | Dependency Injection |
Properties | Dependency Injection |
Autowiring mode | Autowiring Collaborators |
Lazy initialization mode | Lazy-initialized Beans |
Initialization method | Initialization Callbacks |
Destruction method | Destruction Callbacks |
除了包含有关如何创建特定 bean 的bean 定义之外,ApplicationContext
的实现还允许注册在容器外部(由用户)创建的现有对象。这是通过getBeanFactory()
方法访问 ApplicationContext 的 BeanFactory 来完成的,该方法返回 BeanFactory DefaultListableBeanFactory
的实现。 DefaultListableBeanFactory
通过registerSingleton(..)
和registerBeanDefinition(..)
方法支持此注册。但是,典型的应用程序只能与通过常规元数据定义的 bean 一起使用。
命名 bean
每个 bean 具有一个或多个标识符。这些标识符在承载 Bean 的容器内必须唯一。一个 bean 通常只有一个标识符。但是如果需要多个,则可以将多余的标识符视为别名。
在基于 XML 的配置元数据中,可以使用id
属性和name
属性或同时使用这两者来指定 bean 标识符。 id
属性可让您精确指定一个 ID。按照惯例,这些名称是字母数字(“ myBean”,“ someService”等),但它们也可以包含特殊字符。如果要为 bean 引入其他别名,还可以在name
属性中指定它们,并用逗号(,
),分号(;
)或空格分隔。如果未明确为bean提供name
或id
,则容器将为该 bean 生成一个唯一的名称。但是如果要按名称引用该 bean,则需要通过使用ref
元素(xml);或者使用注解@Autowired等。
Bean命名约定
约定是在命名 bean 时将标准 Java 约定用于实例字段名称。也就是说bean 名称以小写字母开头,并从那里用驼峰式大小写。这样的名称的示例包括accountManager
,accountService
,userDao
,loginController
等。
通过在 Classpath 中进行组件扫描时,Spring 会按照前面描述的规则为未命名的组件生成 Bean 名称。 从本质上讲,如果类名为简单类名则会将其初始字符转换为小写。但是在特殊情况下,如果有多个字符并且第一个和第二个字符均为大写字母,则会保留原始大小写。这些规则与java.beans.Introspector.decapitalize
定义的规则相同。
Bean定义别名
在 bean 定义本身中,可以使用一个id
属性和多个name
属性为 一个bean 提供多个名称。这些名称是同一个 bean 的等效别名。
但是在实际定义 bean 的地方指定所有别名并不够。有时需要为在别处定义的 bean 引入别名。在大型系统中通常是这种情况,在大型系统中,配置在每个子系统之间分配,每个子系统都有自己的对象定义集。在基于 XML 的配置元数据中,可以使用<alias/>
元素来完成此操作。以下示例显示了如何执行此操作:
<alias name="fromName" alias="toName"/>
在这种情况下,使用该别名定义后,名为fromName
的 bean(在同一容器中)也可以称为toName
。
例如,子系统 A 的配置元数据可以通过名称subsystemA-dataSource
引用数据源。子系统 B 的配置元数据可以通过名称subsystemB-dataSource
引用数据源。组成使用这两个子系统的主应用程序时,主应用程序使用myApp-dataSource
的名称引用数据源。要使所有三个名称都引用相同的对象,可以将以下别名定义添加到配置元数据中:
<alias name="myApp-dataSource" alias="subsystemA-dataSource"/>
<alias name="myApp-dataSource" alias="subsystemB-dataSource"/>
现在,每个组件和主应用程序都可以通过唯一的名称引用数据源,并且可以保证不与任何其他定义冲突(有效地创建名称空间),但是它们引用的是同一 bean。
但是我们现在的项目很少使用xml,而是使用 Java直接配置,这里就可以使用@Bean
注解提供别名。
实例化 bean
Bean定义的本质是创建对象的方法。当被询问时,容器将查看这个bean的定义,并使用该 bean 定义封装的配置元数据来创建(或获取)实际对象。那么如何使用这些配置元数据呢?
我们想要实例化bean,首先需要知道这个bean的类型,这就可以由bean定义中的配置元数据获取。
如果使用基于 XML 的配置元数据,则可以在<bean/>
元素的class
属性中指定要实例化的对象的类型(或类)。通常此class
属性(在容器内部是BeanDefinition
实例上的Class
属性)。可以通过以下两种方式之一使用Class
属性:
- 通常在容器本身通过反射调用其构造函数直接创建 Bean 的情况下:指定要构造的 Bean 类,这在某种程度上等效于new对象。
- 指定包含被创建对象(bean)的
static
工厂方法的类,容器将在类上调用static
工厂方法以创建 Bean。从static
工厂方法调用返回的对象类型可以是同一类,也可以是完全不同的另一类。
Spring实例化一个bean有三种方式:
- 构造方法
- 通过静态工厂方法
- 通过实例工厂方法
无一例外,这三种实例化bean的方式都需要明确的定义这个要实例化的bean的类型或者可以创建这个bean的类的类型,即谁能创建这个bean就需要在定义中明确给出。
使用构造函数实例化
当通过构造方法创建一个 bean 时,所有普通类都可以被 Spring 使用并与之兼容。也就是说,正在开发的类不需要实现任何特定的接口或以特定的方式进行编码。只需指定 bean的类型就足够了。但是用于特定 bean 的 IoC 的类型,我们可能需要一个默认(空)构造函数。
Spring IoC 容器几乎可以管理我们想要它管理的任何类。它不仅限于 Managementtrue 的 JavaBean。大多数 Spring 用户更喜欢实际的 JavaBean,它仅具有默认(无参数)构造函数,并具有根据容器中的属性适当的 setter 和 getter。您还可以在容器中具有更多奇特的非 bean 样式类。例如,如果您需要使用绝对不符合 JavaBean 规范的旧式连接池,则 Spring 也可以对其进行 Management。
使用基于 XML 的配置元数据,您可以如下指定 bean 类:
<bean id="exampleBean" class="examples.ExampleBean"/>
<bean name="anotherExample" class="examples.ExampleBeanTwo"/>
有关向构造函数提供参数(如果需要)并在构造对象之后设置对象实例属性的机制下面会介绍。
使用静态工厂方法实例化
定义使用静态工厂方法创建的 bean 时,请使用class
属性来指定包含静态工厂方法的类,并使用名为factory-method
的属性来指定工厂方法本身的名称。您应该能够调用此方法(带有可选参数)并返回一个对象,该对象随后将被视为已通过构造函数创建。这种 bean 定义的一种用法是在旧代码中调用static
工厂。
以下 bean 定义指定通过调用工厂方法来创建 bean。该定义不指定返回对象的类型(类),而仅指定包含工厂方法的类。在此的示例createInstance()
方法必须是静态方法。以下示例显示如何指定工厂方法:
<bean id="clientService" class="examples.ClientService" factory-method="createInstance"/>
以下示例显示了可与前面的 bean 定义一起使用的类:
public class ClientService {
private static ClientService clientService = new ClientService();
private ClientService() {}
public static ClientService createInstance() {
return clientService;
}
}
有关为工厂方法提供(可选)参数并在从工厂返回对象后设置对象实例属性的机制下面也会介绍
使用实例工厂方法实例化
与通过静态工厂方法实例化类似,使用实例工厂方法实例化会从容器中调用现有 bean 的非静态方法以创建新 bean。要使用此机制,请将class
属性留空,并在factory-bean
属性中,在当前容器(或父容器或祖先容器)中指定包含将要创建对象的实例方法所在 bean 的名称。使用factory-method
属性设置工厂方法本身的名称。以下示例显示了如何配置此类 Bean:
<!-- the factory bean, which contains a method called createInstance() -->
<bean id="serviceLocator" class="examples.DefaultServiceLocator">
<!-- inject any dependencies required by this locator bean -->
</bean>
<!-- the bean to be created via the factory bean -->
<bean id="clientService"
factory-bean="serviceLocator"
factory-method="createClientServiceInstance"/>
以下示例显示了相应的 Java 类:
public class DefaultServiceLocator {
private static ClientService clientService = new ClientServiceImpl();
public ClientService createClientServiceInstance() {
return clientService;
}
}
一个工厂类也可以包含一个以上的工厂方法,如以下示例所示:
<bean id="serviceLocator" class="examples.DefaultServiceLocator">
<!-- inject any dependencies required by this locator bean -->
</bean>
<bean id="clientService"
factory-bean="serviceLocator"
factory-method="createClientServiceInstance"/>
<bean id="accountService"
factory-bean="serviceLocator"
factory-method="createAccountServiceInstance"/>
以下示例显示了相应的 Java 类:
public class DefaultServiceLocator {
private static ClientService clientService = new ClientServiceImpl();
private static AccountService accountService = new AccountServiceImpl();
public ClientService createClientServiceInstance() {
return clientService;
}
public AccountService createAccountServiceInstance() {
return accountService;
}
}
这种方法表明,工厂 Bean 本身可以通过依赖注入(DI)进行管理和配置。
在 Spring 文档中,“ factory bean”是指在 Spring 容器中配置并通过instance或static工厂方法创建的 Bean。相反,FactoryBean
(注意大小写)是指特定于 Spring 的FactoryBean。
深入源码理解实例化bean
下面我们通过源码来验证我们从官方文档中得到的结论。我们再从代码的角度进行一波分析,这里我们直接定位到org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#createBeanInstance
这个方法中(直接在项目中搜索这个方法的源码即可),然后我们通过debug下面这段代码:
// 这里我们通过xml配置实例化一个容器
ClassPathXmlApplicationContext cac = new ClassPathXmlApplicationContext("classpath:application.xml");
MyServiceImpl test = (MyServiceImpl) cac.getBean("myServiceImpl");
直接main方法运行,然后在org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#createBeanInstance
这个方法的入口打一个断点,如图:
实例化bean的方法
接下来我们对这个方法进行分析,代码如下:
protected BeanWrapper createBeanInstance(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) {
// 1.下面调用的这个方法主要用于获取这个bean的class属性,确保beanDefinition中beanClass属性已经完成解析
// 我们通过xml从<bean>标签中解析出来的class属性在刚开始创建bean的时候只是个字符串,需要加载到beanDefinition中的beanClass属性中
Class<?> beanClass = resolveBeanClass(mbd, beanName);
// 省略异常判断代码.....
// 2.通过beanDefinition中的supplier实例化这个bean
Supplier<?> instanceSupplier = mbd.getInstanceSupplier();
if (instanceSupplier != null) {
return obtainFromSupplier(instanceSupplier, beanName);
}
// 3.通过FactoryMethod实例化这个bean
if (mbd.getFactoryMethodName() != null) {
return instantiateUsingFactoryMethod(beanName, mbd, args);
}
// 4.下面这段代码都是在通过构造函数实例化这个Bean,分两种情况,一种是通过默认的无参构造,一种是通过推断出来的构造函数
boolean resolved = false;
boolean autowireNecessary = false;
if (args == null) {
synchronized (mbd.constructorArgumentLock) {
if (mbd.resolvedConstructorOrFactoryMethod != null) {
resolved = true;
autowireNecessary = mbd.constructorArgumentsResolved;
}
}
}
if (resolved) {
if (autowireNecessary) {
return autowireConstructor(beanName, mbd, null, null);
}
else {
return instantiateBean(beanName, mbd);
}
}
// Candidate constructors for autowiring?
Constructor<?>[] ctors = determineConstructorsFromBeanPostProcessors(beanClass, beanName);
if (ctors != null || mbd.getResolvedAutowireMode() == AUTOWIRE_CONSTRUCTOR ||
mbd.hasConstructorArgumentValues() || !ObjectUtils.isEmpty(args)) {
return autowireConstructor(beanName, mbd, ctors, args);
}
// Preferred constructors for default construction?
ctors = mbd.getPreferredConstructors();
if (ctors != null) {
return autowireConstructor(beanName, mbd, ctors, null);
}
// No special handling: simply use no-arg constructor.
return instantiateBean(beanName, mbd);
}
我们主要关注进行实例化的几个方法:
- 通过
BeanDefinition
中的instanceSupplier
直接获取一个实例化的对象。关于这个instanceSupplier
属性,我们可以在org.springframework.context.support.GenericApplicationContext
这个类中找到以下两个方法
经过断点测试,发现这种情况下,在实例化对象时会进入上面的supplier方法。下面是测试代码:
public static void main(String[] args) {
// AnnotationConfigApplicationContext是GenericApplicationContext的一个子类
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext();
ac.registerBean("service", Service.class,Service::new);
ac.refresh();
System.out.println(ac.getBean("service"));
}
可以发现进入了这个方法进行实例化
这个方法是Spring提供的一种方便外部扩展的手段,让开发者能够更加灵活的实例化一个bean,或者说我们可以导入容器外部创建的bean,这点在上面我们提到过。
分类讨论创建bean的过程
接下来我们通过不同的创建bean的手段,来分别验证对象的实例化方法
- 通过
@compent
,@Service
等注解的方式
测试代码:
public class Main {
public static void main(String[] args) {
// 通过配置类扫描
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(Config.class);
System.out.println(ac.getBean(Service.class));
}
}
@Component
public class Service {
}
观察debug:
可以发现,代码执行到最后一行,同时我们看代码上面的注释可以知道,当没有进行特殊的处理的时候,默认会使用无参构造函数进行对象的实例化
-
通过普通XML的方式(同
@compent
注解,这里就不赘诉了) -
通过
@Configuration
注解的方式
测试代码:
public class Main {
public static void main(String[] args) {
// 通过配置类扫描
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(Config.class);
// 这里将测试对象换为config即可,同时记得将条件断点更改为beanName.equlas("config")
System.out.println(ac.getBean(config.class));
}
}
同样,断点也进入最后一行
- 通过
@Bean
的方式
测试代码:
@Configuration
@ComponentScan("com.dmz.official")
public class Config {
@Bean
public Service service(){
return new Service();
}
}
public class Main {
public static void main(String[] args) {
AnnotationConfigApplicationContext ac =
new AnnotationConfigApplicationContext(Config.class);
System.out.println(ac.getBean("service"));
}
}
断点结果:
可以发现,通过@Bean
方法创建对象时,Spring底层是通过factoryMethod
的方法进行实例化对象的。Spring会在我们需要实例化的这个对象对应的BeanDefinition
中记录factoryBeanName
是什么(在上面的例子中factoryBeanName就是config),同时会记录这个factoryBean中创建对象的factoryMethodName
是什么,最后通过factoryBeanName
获取一个Bean然后反射调用factoryMethod
实例化一个对象。
这里我们需要注意几个概念:
- 这里所说的通过静态工厂方式通过
factoryBeanName
获取一个Bean,注意,这个Bean不是一个FactoryBean
。也就是说不是一个实现了org.springframework.beans.factory.FactoryBean
接口的Bean。至于什么是FactoryBean
我们在后面的文章会认真分析 - 提到了一个概念
BeanDefinition
,它就是Spring对自己所管理的Bean的一个抽象。不懂可以暂且跳过,后面有文章会讲到。
- 通过静态工厂方法的方式
测试代码:
public static void main(String[] args) {
ClassPathXmlApplicationContext cc =
new ClassPathXmlApplicationContext("application.xml");
System.out.println(cc.getBean("service"));
}
12345
<?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.xsd">
<!-- <bean id="myServiceImpl" class="com.dmz.official.service.Service"/>-->
<!-- the factory bean, which contains a method called get() -->
<bean id="myFactoryBean" class="com.dmz.official.service.MyFactoryBean">
<!-- inject any dependencies required by this locator bean -->
</bean>
<!-- 测试实例工厂方法创建对象-->
<bean id="clientService"
factory-bean="myFactoryBean"
factory-method="get"/>
<!--测试静态工厂方法创建对象-->
<bean id="service"
class="com.dmz.official.service.MyFactoryBean"
factory-method="staticGet"/>
</beans>
断点如下:
可以发现,这种情况也进入了instantiateUsingFactoryMethod
方法中。通过静态工厂方法这种方式特殊之处在于,包含这个静态方法的类,不需要实例化,不需要被Spring管理。Spring的调用逻辑大概是:
- 通过
<bean>
标签中的class属性得到一个Class对象 - 通过Class对象获取到对应的方法名称的Method对象
- 最后反射调用
Method.invoke(null,args)
因为是静态方法,方法在执行时,不需要一个对象。
- 通过实例工厂方法的方式
测试代码(配置文件不变):
public static void main(String[] args) {
ClassPathXmlApplicationContext cc =
new ClassPathXmlApplicationContext("application.xml");
System.out.println(cc.getBean("clientService"));
}
12345
断点如下:
还是执行的这个方法。这个方法的执行过程我断点跟踪了以后,发现跟@Bean
方式执行的流程是一样的。这里也不再赘述了。
到这里,这段代码我们算结合官网大致过了一遍。其实还遗留了以下几个问题:
- Spring是如何推断构造函数的?我们在上面验证的都是无参的构造函数,并且只提供了一个构造函数
- Spring是如何推断方法的?不管是静态工厂方法,还是实例工厂方法的方式,我们都只在类中提供了一个跟配置匹配的方法名,假设我们对方法进行了重载呢?
要说清楚这两个问题需要比较深入的研究代码,同时进行测试。我们在官网学习过程中,暂时不去强求这类问题。这里提出来是为了在源码学习过程中,我们可以带一定目的性去阅读。
实例化总结:
-
对象实例化,只是得到一个对象,还不是一个完全的Spring中的Bean,我们实例化后的这个对象还没有完成依赖注入,没有走完一系列的声明周期,这里需要大家注意
-
Spring官网上指明了在Spring中实例化一个对象有三种方式(但之前还提到我们可以注册外部对象):
- 构造函数
- 实例工厂方法
- 静态工厂方法
-
总结结论如下:
Spring通过解析我们的配置元数据,以及我们提供的类对象得到一个Beanfinition对象。通过这个对象可以实例化出一个java bean对象。主要流程如图:
到这里关于Spring的概述和IoC容器以及Bean的相关文档我们就学习完毕,下篇文章我们将会学习关于DI依赖注入的详细文档说明。
感谢耐心看到这里的同学,觉得文章对您有帮助的话希望同学们不要吝啬您手中的赞,动动您智慧的小手,您的认可就是我创作的动力!
之后还会勤更自己的学习笔记,感兴趣的朋友点点关注哦。