Spring5中文文档【2】Spring核心之IOC容器和Bean简介

1. IOC容器和Bean简介

本系列基于最新5.3.10版本,大部分内容copy于官方文档…
官方文档地址

1.1 IOC是什么?

IOC是Inversion of Control的缩写,翻译过来就是控制反转,不是什么技术,而是一种设计思想。

早在1988,Ralph E. Johnson与BrianFoote在文章Designing Reusable Classes中有一段话,成为控制反转(Inversion of Control)的来由。

One important characteristic of a framework is that the methods defined by the user to tailor the framework will often be called from within the framework itself, rather than from the user's application code. The framework often plays the role of the main program in coordinating and sequencing application activity. This inversion of control gives frameworks the power to serve as extensible skeletons. The methods supplied by the user tailor the generic algorithms defined in the framework for a particular application.【框架/framework的一个重要特征是,为定制框架而由用户定义的方法,常常由框架自己调用,而非由用户的应用程序代码(来调用)。在协调和理顺应用程序活动上,框架通常扮演主程序的角色。这种控制的反转给予框架这样的威力——作为一个可扩展的骨架。由用户提供的方法,将框架中定义的一般性算法加以定制给特定的应用。】

理解好Ioc的关键是要明确“谁控制谁,控制什么,为何是反转(有反转就应该有正转了),哪些方面反转了”,那我们来深入分析一下:

  • 谁控制谁,控制什么:传统Java SE程序设计,我们直接在对象内部通过new进行创建对象,是程序主动去创建依赖对象;而IoC是有专门一个容器来创建这些对象,即由Ioc容器来控制对 象的创建;谁控制谁?当然是IoC 容器控制了对象;控制什么?那就是主要控制了外部资源获取(不只是对象包括比如文件等)。
  • 为何是反转,哪些方面反转了:有反转就有正转,传统应用程序是由我们自己在对象中主动控制去直接获取依赖对象,也就是正转;而反转则是由容器来帮忙创建及注入依赖对象;为何是反转?因为由容器帮我们查找及注入依赖对象,对象只是被动的接受依赖对象,所以是反转;哪些方面反转了?依赖对象的获取被反转了。

用图例说明一下,传统程序设计如图2-1,都是主动去创建相关对象然后再组合起来:
 

当有了IoC/DI的容器后,在客户端类中不再主动去创建这些对象了,如图2-2所示:

 

1.2 DI是什么

DI是Dependency Injection的缩写,即“依赖注入”。

在Spring官网中看到一句话,翻译过来就是:IoC也称为依赖注入(DI)
 

1.3 IoC容器和Bean简介

IoC也称为依赖注入 (DI)。这是一个过程,其中对象仅通过构造函数数、工厂方法返回后在对象实例上设置的属性来定义它们的依赖项(即它们使用的其他对象) ,然后容器在创建 bean 时注入这些依赖项。这个过程基本上是 bean 本身的逆过程(因此得名,控制反转),通过使用类的直接构造或服务定位器模式之类的机制来控制其依赖项的实例化和位置。

org.springframework.beansorg.springframework.context包是Spring框架的IoC容器的基础。 BeanFactory 接口提供了一种能够管理任何类型对象的高级配置机制。 ApplicationContext 是BeanFactory的子接口。主要扩展以下功能点:

  • 更容易与 Spring 的 AOP 特性集成
  • 消息资源处理(用于国际化)
  • 活动发布
  • 应用层特定的上下文,例如WebApplicationContext 在 Web 应用程序中使用的 。

简而言之,BeanFactory提供了配置框架的基本功能,并且ApplicationContext添加了更多企业特定的功能。该ApplicationContext是对一个完整的BeanFactory,在Spring的IoC容器的本章会进行讲解使用。

在Spring 中,由 Spring IoC 容器管理的对象称为 bean。bean 是由 Spring IoC 容器实例化、组装和管理的对象。bean 只是应用程序中众多对象之一。Bean 以及它们之间的依赖关系反映在容器使用的配置元数据中。

1.4 容器概述

org.springframework.context.ApplicationContext接口代表 Spring IoC 容器,负责实例化、配置和组装 bean。容器通过读取配置元数据来获取有关要实例化、配置和组装哪些对象的指令。配置元数据以 XML、Java 注解或 Java 代码等方式实现。

Spring 提供了ApplicationContext接口的几种实现。在独立应用程序中,通常创建ClassPathXmlApplicationContextFileSystemXmlApplicationContext的实例。虽然 XML 一直是定义配置元数据的传统格式,但可以使用 Java注解或代码作为元数据格式。

在大多数应用场景中,不需要用户显式的来实例化一个或多个 Spring IoC 容器实例。例如,在 Web 应用程序场景中,应用程序文件中的简单八行(左右) Web XML描述符 web.xml通常就足够了。如果您使用 Spring Tools for Eclipse(一个 Eclipse 驱动的开发环境),您可以通过点击几下鼠标或击键轻松创建这个样板XML配置。

下图显示了 Spring 如何工作的流程图。您的应用程序类与配置元数据相结合,因此,在ApplicationContext创建和初始化之后,您就有了一个完全配置且可执行的系统或应用程序。

 

1.4.1 配置元数据

Spring IoC 容器使用多种形式的配置元数据。配置元数据表示应用程序开发人员告诉 Spring 容器如何实例化、配置和组装应用程序中的对象,配置元数据传统上以简单直观的 XML 格式提供(以前都是XML,但是现在基本都是基于注解)。

基于XML 的元数据并不是唯一允许的配置元数据形式。Spring IoC 容器本身与实际写入此配置元数据的格式完全分离。

还支持以下方式

  • 基于注解的配置:Spring 2.5 引入了对基于注解的配置元数据的支持。
  • 基于 Java 的配置:从 Spring 3.0 开始,Spring JavaConfig 项目提供的许多特性成为核心 Spring Framework 的一部分。因此,您可以使用 Java 而不是 XML 文件来定义应用程序类外部的 bean。要使用这些新功能,请参阅 @Configuration, @Bean, @Import,和@DependsOn注解。

Spring 配置包含多个关于容器管理的 bean 定义。基于 XML 的配置元数据将这些 bean 配置为<beans/>标签中的元素。Java 配置通常使用@Bean在@Configuration类中使用。

这些bean 构成应用程序的实际对象。通常,您定义服务层对象(service)、数据访问对象 (DAO)、表示对象(controller)、基础设施对象(例如 Hibernate SessionFactories、JMSQueues等)。通常,不会在容器中配置细粒度的域对象,因为创建和加载域对象通常是 DAO 和业务逻辑的责任。但是,可以使用 Spring 与 AspectJ 的集成来配置在 IoC 容器控制之外创建的对象。

1.4.2 实例化一个容器

提供给ApplicationContext构造函数的一个或多个位置路径,这将允许容器从各种外部资源(例如本地文件系统、Java 等)加载配置元数据。

例如

ApplicationContext context = new ClassPathXmlApplicationContext("services.xml", "daos.xml");

1.4.3 使用容器

ApplicationContext是一个高级工厂接口,能够维护不同 bean 及其依赖项的注册表。通过使用以下方法,查询bean 的实例。

 T getBean(String name, Class<T> requiredType)

1.5 Bean概述

Spring IoC 容器管理一个或多个 bean。这些 bean 是使用您提供给容器的配置元数据创建的(例如,以 XML<bean/>定义的形式 )。

在容器本身内,这些 bean 定义表示为BeanDefinition 对象,其中包含(除其他信息外)以下元数据:

  • 包限定的类名:通常是定义的 bean 的实际实现类。
  • Bean 行为配置元素,它说明 Bean 在容器中的行为方式(范围、生命周期回调等)。
  • 对 bean 执行其工作所需的其他 bean 的引用。这些引用也称为协作者或依赖项。
  • 在新创建的对象中设置的其他配置设置——例如,池的大小限制或在管理连接池的 bean 中使用的连接数。

此元数据转换为组成每个 bean 定义的一组属性。下表描述了这些属性:

属性 说明
Class 实例化 Bean 的类
Name Bean 的名称
Scope 作用域
Constructor arguments 构造函数参数
Properties 属性
Autowiring mode 自动装配模式
Lazy initialization mode 延迟初始化模式
Initialization method 初始化方法
Destruction method 销毁方法

除了包含有关如何创建特定 bean 的信息的 bean 定义之外,ApplicationContext实现还允许注册在容器外部(由用户创建)创建的现有对象。

Bean 元数据和手动提供的单例实例需要尽早注册,以便容器在自动装配和其他内省步骤中正确推理它们。虽然在某种程度上支持覆盖现有元数据和现有单例实例,但官方不支持在运行时注册新 bean(与实时访问工厂同时),并可能导致并发访问异常、bean 容器中的状态不一致等问题。

1.5.1 命名 Bean

每个bean 都有一个或多个标识符。这些标识符在容器中必须是唯一的。一个 bean 通常只有一个标识符。但是,如果它需要多个,则可以将多余的视为别名。

在基于XML 的配置元数据中,您可以使用id属性、name属性来指定 bean 标识符。该id属性允许您指定一个 ID。通常,这些名称是字母数字(‘myBean’、‘someService’ 等),但它们也可以包含特殊字符。如果要为 bean 引入其他别名,也可以在name 属性中指定它们,用逗号、分号 或空格分隔。

如果您没有明确提供name或id,则容器会为该 bean 生成一个唯一的名称。但是,如果您想通过名称引用该 bean,通过使用ref元素或服务定位器样式查找,您必须提供名称。

Bean 命名约定

  • 约定是在命名 bean 时对实例字段名称使用标准 Java 约定。也就是说,bean 名称以小写字母开头,并从那里开始使用驼峰式大小写。比如accountManager、 accountService、userDao、loginController等等。
  • 始终如一的命名 bean 使您的配置更易于阅读和理解。此外,如果您使用 Spring AOP,则在将建议应用于一组按名称相关的 bean 时会很有帮助。

通过类路径中的组件扫描,Spring 为未命名的组件生成 bean 名称,遵循前面描述的规则:本质上,采用简单的类名并将其初始字符转换为小写。但是,在有多个字符且第一个和第二个字符都是大写的(不寻常的)特殊情况下,原始大小写被保留。

1.5.2 Bean 取别名

在bean 定义本身中,您可以通过使用id属性指定的最多一个名称和属性中任意数量的其他名称的组合,为 bean 提供多个名称name。这些名称可以是同一个 bean 的等效别名。

然而,在实际定义 bean 的地方指定所有别名并不总是足够的。有时需要为在别处定义的 bean 引入别名。这在大型系统中很常见,其中配置在每个子系统之间拆分,每个子系统都有自己的一组对象定义。在基于 XML 的配置元数据中,您可以使用<alias/>元素来完成此操作。以下示例显示了如何执行此操作:

<alias name="fromName" alias="toName"/>

在这种情况下,命名的 bean(在同一容器中)fromName也可以在使用此别名定义后称为toName。

例如,子系统 A 的配置元数据可能引用名为 subsystemA-dataSource的数据源。子系统 B 的配置元数据可以通过名称subsystemB-dataSource来引用数据源。在组合使用这两个子系统的主应用程序时,主应用程序通过 myApp-dataSource名称引用 DataSource。要让所有三个名称都引用同一个对象,您可以将以下别名定义添加到配置元数据中:

<alias name="myApp-dataSource" alias="subsystemA-dataSource"/>
<alias name="myApp-dataSource" alias="subsystemB-dataSource"/>

现在,每个组件和主应用程序都可以通过一个唯一的名称来引用 dataSource,并且保证不会与任何其他定义发生冲突(有效地创建一个命名空间),但它们引用的是同一个 bean。

1.5.3 实例化 Bean

bean 定义本质上是创建一个或多个对象的方法。当被询问时,容器会查看命名 bean 的配方,并使用该 bean 定义封装的配置元数据来创建(或获取)实际对象。

如果使用基于 XML 的配置元数据,则指定要在元素的class属性中实例化的对象的类型(或类)<bean/>。这个 class属性(在内部,它是Class一个BeanDefinition 实例的属性)通常是强制性的。(对于例外情况,请参阅 使用实例工厂方法和Bean 定义继承进行实例化。)您可以通过以下Class两种方式之一使用该属性:

  • 通常,在容器本身通过反射调用其构造函数直接创建 bean 的情况下,指定要构造的 bean 类,有点等同于带有new运算符的Java 代码。
  • 指定包含static被调用以创建对象的工厂方法的实际类,在不太常见的情况下,容器调用 static类上的工厂方法来创建 bean。调用static工厂方法返回的对象类型可能是同一个类,也可能完全是另一个类

如果要为嵌套类配置 bean 定义,可以使用嵌套类的二进制名称或源名称。

例如,如果您在com.example包中调用了一个SomeThing类,并且SomeThing该类有一个static名为的嵌套类OtherThing,则它们之间可以用美元符号 ( $) 或点( .)分隔。因此classbean 定义中的属性值将是com.example.SomeThing$OtherThing或者com.example.SomeThing.OtherThing

(1)使用构造函数实例化

当您通过构造函数方法创建 bean 时,所有普通类都可以被 Spring 使用并与 Spring 兼容。也就是说,正在开发的类不需要实现任何特定的接口或以特定的方式进行编码。只需指定 bean 类就足够了。但是,根据您对该特定 bean 使用的 IoC 类型,您可能需要一个默认(空)构造函数。

Spring IoC 容器几乎可以管理您希望它管理的任何类。它不仅限于管理真正的 JavaBean。大多数 Spring 用户更喜欢实际的 JavaBeans,它只有一个默认(无参数)构造函数和适当的 setter 和 getter,它们以容器中的属性为模型。您还可以在您的容器中拥有更多异国情调的非 bean 风格的类。例如,如果您需要使用绝对不符合 JavaBean 规范的遗留连接池,Spring 也可以管理它。

使用基于 XML 的配置元数据,您可以按如下方式指定 bean 类:

<bean id="exampleBean" class="examples.ExampleBean"/>

<bean name="anotherExample" class="examples.ExampleBeanTwo"/>

(2)使用静态工厂方法实例化

在定义使用静态工厂方法创建的 bean 时,使用class 属性来指定包含static工厂方法的类和命名factory-method为指定工厂方法本身名称的属性。您应该能够调用此方法(带有可选参数,如下所述)并返回一个活动对象,随后将其视为通过构造函数创建的。

以下bean 定义指定通过调用工厂方法来创建 bean。定义中没有指定返回对象的类型(类),只指定包含工厂方法的类。在这个例子中,createInstance() 方法必须是静态方法。以下示例显示了如何指定工厂方法:

<bean id="clientService"
    class="examples.ClientService"
    factory-method="createInstance"/>

(3)使用实例工厂方法实例化
与通过静态工厂方法实例化类似,使用实例工厂方法实例化从容器中调用现有 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"/>

在Spring 文档中,“工厂 bean”是指在 Spring 容器中配置并通过实例或 静态工厂方法创建对象的 bean 。相比之下, FactoryBean(注意大写)是指特定于 Spring 的 FactoryBean实现类。

(4) 确定 Bean 的运行时类型

确定特定 bean 的运行时类型并非易事。bean 元数据定义中的指定类只是一个初始类引用,可能与声明的工厂方法相结合,或者是FactoryBean可能导致 bean 的不同运行时类型的类,或者在实例的情况下根本没有设置 -级别工厂方法(通过指定factory-bean名称解析)。此外,AOP 代理可以使用基于接口的代理包装 bean 实例,并限制暴露目标 bean 的实际类型(仅其实现的接口)。

找出特定 bean 的实际运行时类型的推荐方法是BeanFactory.getType调用指定的 bean 名称。这考虑了上述所有情况,并返回BeanFactory.getBean调用将返回的相同 bean 名称的对象类型。

案例演示

1. 环境搭建

1、 创建一个maven工程,引入SpringFramework相关依赖;

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.example</groupId>
    <artifactId>spring-framewor-demo</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <org.springframework.version>5.3.6</org.springframework.version>
        <commons-logging.version>1.2</commons-logging.version>
        <junit.version>4.12</junit.version>
    </properties>
    <dependencies>
        <!-- Spring核心依赖 -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-core</artifactId>
            <version>${org.springframework.version}</version>
        </dependency>
        <!-- Spring beans包-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-beans</artifactId>
            <version>${org.springframework.version}</version>
        </dependency>
        <!-- Spring 容器包 -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>${org.springframework.version}</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.20</version>
            <scope>compile</scope>
        </dependency>
    </dependencies>
</project>

1、 resources目录下创建beans.xml,用于定义Bean;

<?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">
       
</beans>

1、 创建一个普通类Animal,定义属性及一个普通方法;

@Data
@ToString
@AllArgsConstructor
@NoArgsConstructor
public class Animal {
   
     

    String name;

    Integer age;

    public void eat(String name) {
   
     
        System.out.println("Animal eating " + name);
    }
}

2. 测试定义Bean元数据的三种方式

1、 XML配置文件,配置元数据传统上以简单直观的XML格式提供,目前基本很少用注入属性时还可以使用p名称空间注入,可以简化基于xml配置方式;

    <!--配置一个Bean-->
    <!--该id属性是标识单个bean定义的字符串-->
    <!--该class属性定义Bean的类型,并使用完全限定的类名-->
    <bean id="animal" class="org.pearl.spring.demo.pojo.Animal">
        <!--配置属性-->
        <property name="name" value="Dog"></property>
        <property name="age" value="5"></property>
    </bean>
   <!--注入属性时还可以使用 p 名称空间注入,可以简化基于 xml 配置方式-->
    <bean id="animal222" class="org.pearl.spring.demo.pojo.Animal" p:age="18" p:name="使用P注入"/> 

使用p 名称空间注入时,需要在声明xmlns约束

xmlns:p="http://www.springframework.org/schema/p"

1、 基于注解的配置,Spring2.5引入了对基于注解的配置元数据的支持简单演示,后续会详解;

// 直接在类上使用注解
@Component
public class Animal {
   
     

    String name;

    Integer age;

    public  void eat(String name) {
   
     
        System.out.println("Animal eating " + name);
    }
}

1、 基于Java代码配置,可以使用Java而不是XML文件来定义应用程序类外部的bean简单演示,后续会详解;

@Configuration
public class AnimalConfig {
   
     
	
	// JAVA代码结合@Bean返回一个对象
    @Bean
    public Animal animal() {
   
     
        return new Animal("AnimalConfig",18);
    }
}

3. 创建容器并使用

1、 创建ClassPathXmlApplicationContext容器并获取Bean;

        // 1. 获取XML中的Bean
        ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
        Animal animal = context.getBean("animal", Animal.class);
        System.out.println(animal.toString());

1、 创建AnnotationConfigApplicationContext容器并获取Bean;

        // 通过AnnotationConfigApplicationContext实例化Spring容器,
        // 这种通用的ApplicationContext实现方式不仅可以接受@Configuration标识类作为输入,
        // 还可以接受普通@Component类和使用JSR-330元数据注解的类。
        // 当@Configuration类作为输入时,@Configuration类和该类中所有@Bean的方法都会被注册为Bean。
        AnnotationConfigApplicationContext annotationConfigApplicationContext = new AnnotationConfigApplicationContext(AnimalConfig.class);
        Animal animal111 = annotationConfigApplicationContext.getBean("animal", Animal.class);
        System.out.println(animal111);

4. 测试命名 Bean

1、 XML中设置别名,然后通过别名获取Bean;

    <bean id="animal" class="org.pearl.spring.demo.pojo.Animal">
        <!--配置属性-->
        <property name="name" value="Dog"></property>
        <property name="age" value="5"></property>
    </bean>
    <alias name="animal" alias="animalAlias"/>

 

5. 实例化Bean测试

1、 使用构造函数实例化,默认的实例化方式,只需要一个默认(无参数)构造函数和属性的setter和getter就可以;

<!--注入属性时还可以使用 p 名称空间注入,可以简化基于 xml 配置方式-->
    <bean id="animal222" class="org.pearl.spring.demo.pojo.Animal" p:age="18" p:name="使用P注入"/>

1、 使用静态工厂方法实例化,提供一个工厂类及一个创建对象的方法即可,然后在XML中声明工厂类及指定factory-method方法即可;

public class PearlBeanFactory {
   
     
    // 创建Bean实例的静态工厂方法
    public static Animal createBean() {
   
     
        return new Animal();
    }
}

    <!--静态工厂方式创建Bean-->
    <bean id="animalByBeanFactory" class="org.pearl.spring.demo.bean.PearlBeanFactory" factory-method="createBean"/>

1、 使用实例工厂方法实例化,工厂类创建非静态方法,然后在xml中声明即可;

    // 创建Bean的方法
    public Animal createBean111() {
   
     
        return new Animal();
    }

    <!-- 配置实例工厂 -->
    <bean id="beanFactory" class="org.pearl.spring.demo.bean.PearlBeanFactory"/>
    <!-- factory-bean属性指定一个实例工厂,factory-method属性确定使用工厂中的哪个方法 -->
    <bean id="animal333" factory-bean="beanFactory" factory-method="createBean111"/>

版权声明:本文不是「本站」原创文章,版权归原作者所有 | 原文地址: