烽云

Spring Framework(更新中)(最近更新:2019-12-30)

Spring Framework概述

history, design philosophy, feedback, getting started.

Version 5.1.7.RELEASE

转载请注明出处

注意:
bean definition 为bean 的定义,在内容中没有翻译,因为bean definition 常常作为一个整体,如果单词加汉字会影响整体理解。

Spring使得可以很容易的创建Java企业应用。在一个企业环境中,他完全使用Java语言提供你需要的所有东西,并且也支持基于JVM的Groovy和Kotlin,同时可以根据应用需要灵活的创建多种多样的架构设计。从Spring Framework 5.1开始,Spring需要JDK8+(Java SE 8+),并为JDK11 LTS提供开箱即用的支持。

1. 我们说“Spring”的时候指的是什么

“Spring”这个词在不同的语境中有不同的意思,最初,它可以被用来指Spring Framework 项目本身。之后其他的一些Spring 项目基于Spring Framework 创建。通常,大家说“Spring”是指整个Spring家族。这个参考文档专注于这些的基础:Spring Framework 。
Spring Framework 划分为多个模块。应用可以选择需要的模块。这些模块的核心是core容器,包括配置模型和依赖注入的机制。除此之外,Spring为不同的应用架构提供了基础支持,包括消息,事务数据和持久化,Web。它也提供了基于Servlet的Spring MVC框架,并平行的提供Spring WebFlux响应式Web框架。

Spring的框架包可以基于JDK 9的模块路径("Jigsaw")构建。Spring Framework 5 的jar包使用"Automatic-Module-Name" 声明项,声明项根据固定的语法的模块名("spring.core", "spring.context" 等等) 而不是jar文件名(jar文件遵循同样的命名模式,只是单词连接使用“-”而不是“.",比如"spring-core" and "spring-context")。当然Spring的框架包依然可以正常使用JDK8和JDK9+基于classpath的方式

2.Spring 和 Spring Framework 的历史

Spring起始于2003的一个对于早期J2EE规范的回复.然而一些观点认为Java EE 和Spring是竞争关系,Spring实际上是Java EE的补充。Spring 编程设计并没有遵循Java EE平台规范,确切的说,它小心的选择了一下Java EE下的独立的规范:

  • Servlet API (JSR 340)

  • WebSocket API (JSR 356)

  • Concurrency Utilities (JSR 236)

  • JSON Binding API (JSR 367)

  • Bean Validation (JSR 303)

  • JPA (JSR 338)

  • JMS (JSR 914)

  • 以及在需要的情况下提供JTA/JCA的事务协作设置

    Spring Framework 也支持应用开发者使用依赖注入 (JSR 330)和通用注解(JSR250)规范来替换由Spring Framework提供的Spring定义的机制,
    从Spring Framework 5.0开始,Spring的最低版本是Java EE 7 (相应的其他规范比如Servlet 3.1+,JPA2.1+),同时提供了了集成在Java EE 8 (相应的其他规范比如Servlet4.0,JSON Binding API)上的新API的运行时支持。这使得Spring完全兼容比如Tomcat 8 和9,WebSphere 9 和 JBoss EAP 7.
    随着时间的过去,应用开发中的Java EE规则也已经进行了改进,在Java EE 和 Spring 的早期时候,应用的完成需要依赖于应用服务器。现在,因为有了 Spring Boot,应用只需要很小的改动就可以以一个Devops 和云友好的方式使用内置的 Servlet 容器。从Spring Framework 5开始,WebFlux甚至不需要直接使用Servlet API并且运行在没有Servlet 容器的服务器上
    Spring在不断地创新和发展。Spring Framework之外还有 Spring Boot, Spring Security, Spring Data, Spring Cloud, Spring Batch等等的一些项目。需要注意的是每个项目都有各自的源码资源,问题追踪和发布节奏,点击spring.io/projects 查看Spring项目完整列表

3.设计哲学

当你学习一个框架的时候,知道她遵循了什么原则比知道她做了什么更重要,下列是Spring Framework的指导原则:

  • 为各个级别提供选择。Spring让你尽量推迟设计选型。例如你可以不修改代码通过配置切换提供持久化的组件。对于很多其他的基础组件问题或者第三方API整合也是一样。
  • 适应各种角度。Spring拥抱灵活性,并不固执于事情应该怎么做。Spring从不同的角度支持广泛的应用需求。
  • 保持强壮的向后兼容性。Spring的发展被小心的管理使得版本间有很少的重大变化。Spring提供了一个精心选择过的JDK版本和第三方工具库范围,以方便依赖于Spring的应用和工具库。
  • 注重API设计。Spring团队花了很多想法和时间在使API直观并且在众多版本以及很长的时间都能适用。
  • 设置高标准的代码质量。Spring Framework很注重有一个有意义的,正确的,精准的javadoc。它是极少数可以声明干净代码结构且程序包之间没有循环依赖关系的项目之一。

4.反馈与建议

对于如何使用的问题,以及判断或调试的问题,我们推荐使用 StackOverflow,并且我们也有一个用来记录建议标签的问题页,如果您确定了一个SpringFramework中的问题,或者想要提供一个特性,请使用GitHub Issues
如果您有解决方案或建议的解决方案,可以在Github上提交拉取请求。 但是,请注意对于除最琐碎的问题以外的所有问题,我们希望在问题跟踪器中记录故障单,在该跟踪器中进行讨论并保留记录以备将来参考。
有关更多详细信息,请参阅贡献顶级项目页面上的准则。

5.准备开始

如果你刚开始使用Spring,你可以能希望从一个基于Spring Boot的应用开始使用Spring Framework.Spring Boot 提供了一个快捷和固定的方式去创建一个基于Spring的生产就绪状态的应用.它基于Spring Framework,相比配置更倾向于约定的旨在使你尽快起步和运行》
你可以使用start.spring.io来创建一个基本的项目或者跟着"Getting Started" guides其中的一个做,比如开始创建一个Restful Web Service.为了容易理解,这些引导文档更关注于引导如何创建,并且大部分都是基于Spring Boot.它们也包含了一些当你解决一个特定问题时可能考虑用到的Spring家族的其他项目.









Core 技术

IoC Container, Events, Resources, i18n, Validation, Data Binding, Type Conversion, SpEL, AOP.

Version 5.1.7.RELEASE

这部分的参考文档覆盖了Spring Framework所有的必不可少的技术

1.IoC容器

这一章是关于Spring的控制反转(IoC)容器的。

1.1 Spring IoC容器和Bean的介绍

这一章是关于Spring Framework对于控制反转(Ioc)规范的实现。IoC也被称为依赖注入(DI)。它是一个在构造之后或者工厂方法返回值后对象决定他们的依赖值通过构造器参数,工厂方法参数或者设置在对象实例上的参数的处理形式。容器在创建这些bean之后会注入到对象的依赖.这种处理方式从根本上是bean直接通过类的构造或者比如服务定位器模式的机制控制bean本身实例化或者bean依赖的定义的方式的反转(因此被称为依赖反转)

org.springframework.beans 和 org.springframework.context 包是Spring Framework IoC容器的基础。BeanFactory接口提供了一个可以管理任意对象类型的高级配置机制。ApplicationContext 是BeanFactory的一个子接口,它增加了:

  • 集成了Spring AOP 特性
  • 消息资源管理(针对国际化的用户)
  • 事件发布
  • 对于Web应用用户应用层特有的上下文WebApplicationContext

简单的说,BeanFactory 提供了配置框架和基础特性,ApplicationContext添加了一些针对企业的特性.ApplicationContext是BeanFactory的完整的超集所以在本章关于Spring IoC容器的讲解中只使用ApplicationContext.获得关于BeanFactory的更多信息,seeThe BeanFactory
在Spring中,主要构成应用的对象被Spring IoC容器所管理,被称为bean。一个bean是已经实例化,已经组合过的并且被Spring IoC管理的对象。实际上,一个bean只是应用中众多对象中简单的一个。Bean以及他们的依赖都是容器使用配置元数据反射得到的。

1.2 容器概述

org.springframework.context.ApplicationContext 接口即是指IoC容器,它负责实例化,配置,和组合bean.容器通过读取配置元数据获得去实例化,配置和组合什么对象的指引。配置元数据具体是指XML,Java注解或者Java代码。它可以让你描述那些组成应用的对象和他们之间的完整的互相依赖关系。

Spring提供了几个继承自ApplicationContext的接口,在独立的应用中,通常创建一个ClassPathXMLApplicationContext或者FileSystemXMLApplicationContext.虽然XML是定义配置元数据的传统格式,你也可以通过少量XML配置去声明开启额外的元数据格式使得容器使用Java注解或者代码作为元数据格式。

在大多数应用场景中,用户不必要显性的编写代码来实例化一个Spring容器的一些实例。例如,在Web应用场景中,通常需要在应用的web.xml文件中添加简单的大约8行的模板web标识符XML(see Convenient ApplicationContext Instantiation for Web Applications).如果你使用Spring Tool Suite(一个基于Eclipse的开发环境),你可以简单的通过点按几下鼠标或键盘来创建这些模板。

下图展示了Spring如何工作的一个高层的视角.首先你的应用中的类通过配置元数据组合,然后在ApplicationContext被创建和初始化后,你就有了一个完整配置过和可执行的系统或应用。

Figure 1. The Spring IoC container
Figure 1. The Spring IoC container

1.2.1 配置元数据

如前图所展示的一样,Spring IoC 容器 消费 消费这个词类似于生产者消费者模式中的消费者消费的概念了一份配置元数据,这个配置元数据描述了作为一个应用开发者的你告诉Spring 容器如果去实例化,配置和组合你应用中的对象。

配置元数据传统的方式是用简单和直观的XML格式,这一章大部分也使用XML格式来表现Spring IoC 容器的概念和特性。

基于XML的元数据并不是配置元数据可以接收的唯一格式,Spring IoC容器本身与元数据的编写方式是解耦的。现在,很多开发者为他们的Spring应用选择了使用基于Java注解的方式

获取更多关于为Spring 容器使用其他格式的信息,请看:

  • 基于注解配置:Spring 2.5提出了基于注解配置元数据的支持
  • 基于Java配置:从Spring3.0开始,很多Spring JavaConfig项目提出的特性变成了核心Spring Framework的一部分。因此你可以使用Java 在应用里类的外面定义bean的方式而不是XML文件的方式。要使用这些新的特性,请看@Configuration,@Bean,@Import,和@DependsOn注解

Spring 配置 包括了容器必须管理的至少一个,实际上超过一个的bean definition。基于XML配置元数据通过顶级元素下的元素配置这些bean。Java配置通常使用@Bean,直接在一个@Configuration类里面的方法上。

这些bean definition与组成你应用的实际对象相一致。通常,你定义service层对象,数据访问对象(DAOs,data access objects),比如Struts Action实例的展示对象,比如Hibernate SessionFactories的基础对象。JMS队列等等。通常,不为容器中的领域对象进行细粒度配置,因为通常是DAO和业务逻辑负责创建和使用领域对象。不过你可以使用让Spring集成AspectJ来配置已经在IoC容器之外创建的对象。请看Using AspetJ to dependency-inject domain objects with Spring.

下面例子展示了基于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
        https://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="..." class="...">
        <!-- 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>

id 属性标识每个bean definition的字符串。
class 属性定义了bean的类型,需要使用完全限定类名。

id 属性的值指向协作对象,引用写作对象的XML没有展示在例子中,请看Dependencies获取更多信息。

1.2.2实例化容器

提供给ApplicationContext构造器的定位路径是一些让容器通过多种形式的外部资源载入的资源字符串。比如一个本地文件系统路径,Java CLASSPATH路径等等

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

学习过Spring的IoC容器后,你可能希望知道更多关于Spring的Resource抽象概念(如Resources),Resource提供了一个便捷的方式去读取通过URI语法定义的InputStream。尤其是:如Application Contexts and Resource Paths中介绍的Resource路径被用来构建应用上下文。

下面例子展示了service层(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
        https://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
        https://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类和两个类型为JpaAccountDaoJpaItemDaod的数据访问对象(基于JPA ORM(Object-Relational Mapping,对象关系映射)规范)。property name元素指向JavaBean属性的名称(即在这个bean的类相对应的属性名称),ref元素指向另一个bean definition的名称(即id属性)。idref元素表示了协作对象间的依赖关系。获取更多关于配置一个对象依赖的信息,请看依赖

.

基于XML构成配置元数据

跨多XML文件获取bean definition很有用,通常,一个单独的XML配置文件代表了一个架构中逻辑层或者模块。

你可以使用应用上下文构造器从所有的这些XML分段来载入bean definition。这个构造器需要多个Resource 定位 ,就想本节开始展示的那样。或者可以使用一个或多个<import/>元素来从其他文件导入 bean definition。下面例子展示了如何这样做:

<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.xmlmessageSource.xmlthemeSource.xml文件中载入了外部的bean definition。所有的定位路径都关联到执行导入的定义文件。所以作为执行导入的文件services.xml必须处于同一目录下或者classpath路径下,而messageSource.xmlthemeSource.xml必须在导入文件位置下的resource里。你可以看到,路径头部的/被过滤了。然而,这些路径是相对路径,最好不要在头部使用/。根据Spring Schema,被导入文件的内容,包括顶级的<beans/>元素必须是正确的XML bean definition。

使用相对路径“../"在父目录里引用文件是可以做到,但是不推荐这样做。这样做会创建对当前应用程序外部文件的依赖。这种引用尤其不推荐使用在classpath:路径(例如,classpath:../services.xml),因为运行时解析处理会选择“最亲近的(nearest)”classpath然后查找其父级目录。classpath配置更改可能导致选择到一个与之前不同的错误的目录。

你可以使用完全限定资源路径代替相对路径:比如,file:C:/config/services.xml或者classpath:/config/services.xml。然而,你需要意识到你应用的配置和确定的绝对路径是耦合在一起的。比如通过使用在运行时针对JVM系统属性解析的 “${...}”占位符来保证为这些绝对路径保持间接联系通常会更好一些。

命名空间本身提供了import指令功能。Spring提供的一系列XML命名空间提供了除了简单的bean definition之外的更多配置功能---比如contextutil命名空间。

Groovy Bean Definition DSL

作为进一步外部配置元数据的例子,bean definition也能在从Grails框架引进的Groovy Bean Definition DSL 中表达。通常这样的配置存放在一个“.groovy”文件,使用下面例子中的结构:

beans {
    dataSource(BasicDataSource) {
        driverClassName = "org.hsqldb.jdbcDriver"
        url = "jdbc:hsqldb:mem:grailsDB"
        username = "sa"
        password = ""
        settings = [mynew:"setting"]
    }
    sessionFactory(SessionFactory) {
        dataSource = dataSource
    }
    myService(MyService) {
        nestedBean = { AnotherBean bean ->
            dataSource = dataSource
        }
    }
}

这种配置风格大致等同于XML bean definition,甚至支持Spring 的XML配置命名空间。它也允许通过一个importBean指令导入XML bean definition文件。

1.2.3 使用容器

ApplicationContext是一个维护各种bean和他们的依赖的高级工厂的接口。使用T getBean(String name, Class<T> requiredType)可以获得bean的实例。

ApplicationContext可以让你获取bean definition 也可以访问bean,就如下面例子展示的一样

// create and configure beans 创建和配置bean
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();

使用Groovy配置,引导程序看起来非常相似。有一个不同的可以识别Groovy的上下文实现类(也可以识别XML bean definition)。下面例子展示了Groovy配置:

ApplicationContext context = new GenericGroovyApplicationContext("services.groovy", "daos.groovy");

最灵活的方式是GenericApplicationContext组合使用reader委托---例如读取XML文件的XmlBeanDefinitionReader,如下例所示:

GenericApplicationContext context = new GenericApplicationContext();
new XmlBeanDefinitionReader(context).loadBeanDefinitions("services.xml", "daos.xml");
context.refresh();

你也可以使用读取Groovy文件的GroovyBeanDefinitionReader,如下例所示:

GenericApplicationContext context = new GenericApplicationContext();
new GroovyBeanDefinitionReader(context).loadBeanDefinitions("services.groovy", "daos.groovy");
context.refresh();

你可以在同一个ApplicationContext混合使用相匹配的reader委托从不同的数据源读取bean definition。

之后你可以使用getBean获取bean的实例。ApplicationContext接口有一些其他获取bean的方法,但是,理论上你应用代码应该永远不使用他们。确切的说,你的应用代码完全不应该有关于getbean()方法的调用,这样你的应用代码就完全不会有对于Spring API的依赖。例如,Spring与web框架的整合为各种web框架组件提供了依赖注入,比如controllers 和 JSF - 托管的bean,让你通过元数据声明依赖一个特定的bean(例如一个自动装配 注解)。

1.3 Bean 概述

一个Spring IoC 容器管理一个或多个bean。这些bean是通过你提供给容器的配置元数据创建的(例如,以XML<bean/>definition的格式。

在容器内,这些bean definition表示为BeanDefinition对象,包含了下面的元数据和其他信息:

  • 一个包限定的类名称:通常是被定义过的bean的具体实现类。
  • Bean 行为配置元素:用于声明Bean在容器中的行为(作用域,生命周期回调等)。
  • 为了bean的运行需要对他们bean的引用;这些引用也被称为协作者或者依赖。
  • 要在新创建的对象中设置的其他配置设置;例如,池的大小限制或要在管理连接池的bean中使用的连接数。

这个元数据转变为构成每个bean definition的一组数据,下表描述了这些属性:

Table 1. The bean definition

属性 在以下内容中讲解
Class (类) 实例化 Bean
Name(名) 命名 Beans
Scope(作用域) Bean 作用域
Constructor arguments(构造器参数) 依赖注入
Properties(属性) 依赖注入
Autowiring mode (自动装配模式) 自动装配 协作者
Lazy initialization mode(懒加载模式) Lazy-加载 Bean
Initialization method(加载方法) 加载回调
Destruction method(销毁方法) 销毁回调

除了bean definition(bean definition包括如何创建指定bean的信息),ApplicaitonContext实现 也允许由用户在容器外创建过的已经存在的对象。通过使用返回BeanFactory(DefaultListableBeanFactoryd的实现)的getBeanFactory()方法访问应用上下文的BeanFactory .DefaultListableBeanFactoryd提供通过registerSingleton(..)registerBeanDefinition(..)方法注册。不过标准的应用应该只使用通过常规的bean definition元数据定义过的bean。

Bean 元数据和手动提供的单例需要尽早注册,以便容器在自动装配或者其他introspection(内省) 步骤。虽然在某种程度上支持重写现有的元数据和现有的单例,在运行时注册新的bean(并发的实时访问工厂)不是官方支持的,可能引起并发访问异常或者bean容器中状态不一致,或者两者皆有。

1.3.1 命名bean

每个bean都有一个或多个标识符。这些标识符在掌握这些bean的容器内必须是独一无二的。一个bean通常只有一个标识符,然而如果确实需要多个,多出的会被视为alias(别名)。
在基于XML的配置元数据中,使用id属性,name属性或者两者一起来指示bean标识符。id属性可让您精确指定一个id。通常这些名字是含有字母和数字的('myBean','someService'等等),不过也可以包含特殊字符。如果想要引入这个bean的其他别名,你可以在name属性中指定,使用(,)(;)或者空格分隔多个别名。历史备注:Spring之前的版本,id属性被定义为xsd:ID类型,限制了可能的字符。从3.1开始,id属性被定义为xsd:string类型。请注意,尽管不再受XML解析器限制,bean的id仍由容器约束唯一性。
并不是必须为一个bean提供nameid。如果你没有显性的提供一个idname,容器会为bean生成一个唯一的名字。不过,如果你想使用名字引用bean(通过ref元素或者服务定位器样式查找)就必须定义一个名字。之所以可以不提供一个名字跟使用内部bean自动装配协作者有关。


Bean 命名约定

约定是当命名bean时使用标准Java约定实例化字段名。也就是bean名字开头使用小写字符,后面使用驼峰大小写形式。比如accountManager``accountService``userDao``loginController等等。

统一的命名bean会使你的配置更容易阅读和理解。并且,如果你使用Spring AOP,当通知到一系列用名字关联的bean的时候会很有用。


在扫描classpath内的组件时候,Spring会为没有命名的组件根据上面的约定生成bean名称 : 实际上是使用首字母小写的simple class name(class name 全名称是类似java.lang.String,simple class name 是指String,首字母缩写之后是string) 。不过如果有多个字符,并且第一二个字符都是大写,那么将会保留原始的大小写格式不做修改。这些规则就和java.beans.Introspector.decapitalize(Spring所使用的)定义的一样。

在Bean Definition 外给bean起一个别名

在bean definition内,一个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"/>

现在每个组件和主应用都可以通过一个唯一名字指向数据,并且和其他的定义不冲突(有效的创建一个namespace(命名空间)),实际上他们指向了同一个bean。


Java-configuration

如果你使用Java-configuration @Bean注解可以被用来提供别名。请看使用@Bean注解获取详情。

1.3.2 实例化Bean

bean definition 实际上是创建一个或者多个对象的菜谱。容器被请求时会查看一个命名过的bean的菜谱,然后利用bean definition里面的配置元数据去创建(或者获得)一个实际的对象。

如果使用基于XML的配置元数据,在<bean/>元素的class属性指定要实例化的对象的类型(或类)。class属性(在BeanDefinition对象中实际上是Class属性)通常是强制的(其他情况,请看使用实例工厂方法实例化Bean Definition继承)。你可以在下面两种方式中的一种使用Class属性。

  • 通常,用容器本身通过反射调用其构造函数直接创建Bean的办法来指定要构造的Bean类,这在某种程度上等同于使用new运算符的Java代码。
  • 在不太常见的情况下,指定一个包含用来调用去创建对象的static工厂方法的实际类,容器调用类的static工厂方法去创建bean。调用static工厂方法返回的对象类型可能是同样的类或者截然不同的其他类。

内部类名称
如果你想要为一个static嵌套类配置bean definition,你必须使用嵌套类的二进制名称。

例如,在com.example包中有一个类SomeThing,这个SomeThing类有一个static嵌套类,叫OtherThing,bean definition中class属性的值应该是com.exemple.SomeThing$OtherThing.

注意在名字中使用了$字符分隔了嵌套类名称和外部类名称。


使用构造器初始化

当你使用构造器的途径来创建一个bean的时候,所有的普通类都可以被Spring使用和兼容。也就是正在开发中的类并不需要集成任何特定的接口或者以特定方式编写代码。简单指定bean 的类就可以了。不过,考虑到你用于该特定类的IoC的类型,你可能需要一个默认(空)的构造器。
Spring IoC 容器可以管理你想要它管理的几乎所有的类。它并不限于管理真正的JavaBean。大多数Spring用户更倾向于仅有一个默认的(无参)构造器并且为容器内的属性创建的相应的setter和getter的实际的类。在你的容器内你可以有各种奇特的非bean样式(non-bean-style)的类。如果,举个例子,你需要使用一个绝对不遵守JavaBean规范的传统的连接池,Spring也可以管理它。
基于XML的配置元数据,你可以像下面这样指定你的bean的类:

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

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

获得更多关于给构造器提供参数的机制(如果必要的话)和关于在对象构造后设置对象实例属性的详情,请看依赖注入

使用静态工厂方法初始化

定义使用静态工厂方法创建的bean时,使用class属性指定包含static工厂方法的类,factory-method属性指定工厂方法的名字。你应该可以调用这个方法(带有可选参数的情况之后会有描述)然后返回一个活动(live)对象,之后该对象将被视为已经通过一个构造器创建。其中一种这样的bean definition的用法就是在传统的代码中调用static工厂。
下面的bean definition 指定了通过调用一个工厂方法创建的bean。这个定义不指定返回的类型(类),只有包含工厂方法的类。在这个例子中,createInstance()方法必须是静态方法,下面例子展示了如何指定一个工厂方法:

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

下面例子展示了一个将在bean definition之前工作的类。

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容器中配置通过一个实例静态工厂方法创建的bean。相比之下,FactoryBean(注意是大写的)指一个Spring特有的FactoryBean

1.4 依赖

一个典型的企业应用不会只包含一个对象(或者Spring的说法,bean)。即使是最简单的应用也有一些互相协作的类呈现给终端用户一个看起来有条理的应用。下一节讲解了你该如何从定义一定数量独立的bean definition到一个对象互相协作达成目标的完全实现的应用。

1.4.1 依赖注入

依赖注入(DI)是一个过程,通过该过程,对象仅通过构造函数参数,工厂方法的参数,以及在构造或创建对象实例后或 从工厂方法返回后在对象实例上设置的属性 来定义其依赖关系(即,与它们一起工作的其他对象)。容器会在创建bean时注入这些依赖。这种处理方式从根本上是bean直接通过类的构造或者服务定位器模式控制bean本身实例化或者bean依赖的定义的方式的反转(因此被称为依赖反转)。

使用 DI 原则,代码会更简洁,当为对象提供依赖时解耦更加有效。对象不查找他的依赖,也不知道依赖的位置和类。由此带来的结果是你的类变得更容易测试,特别是当一来是接口或者抽象基类的时候可以在单元测试使用stub(桩代码)或者mock实现。

DI 有两种实现形式:基于构造器依赖注入基于Setter依赖注入.

基于构造器依赖注入

基于构造器依赖注入是由容器调用带有一定数量参数的构造器,每个参数代表一个依赖。带有指定参数调用一个static工厂方法基本上是一样的,我们的讨论也以为构造器参数和static工厂方法参数是类似的。下面例子展示了一个只能通过构造器注入来依赖注入的类:

public class SimpleMovieLister {

    // the SimpleMovieLister has a dependency on a MovieFinder
    private MovieFinder movieFinder;

    // a constructor so that the Spring container can inject a MovieFinder
    public SimpleMovieLister(MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }

    // business logic that actually uses the injected MovieFinder is omitted...
}

注意这个类没有什么特别的地方,这只是一个 POJO (plain old java object),不依赖于容器特定的接口,基类或者注解。

构造器参数解析

构造函数参数解析匹配通过使用参数的类型进行。如果 bean definition 的构造参数没有潜在歧义的话,则在实例化时,bean definition中定义的构造器参数顺序就是这些参数提供给适当构造器的顺序。思考下面的这个类:

package x.y;

public class ThingOne {

    public ThingOne(ThingTwo thingTwo, ThingThree thingThree) {
        // ...
    }
}

假定 ThingTwoThingThree 这两个类之间没有继承关系,也没有潜在歧义存在。那么下面的配置运行正常。你也不需要指定构造器参数序号或者在 <constructor-arg/> 显性表述类型.

<beans>
    <bean id="beanOne" class="x.y.ThingOne">
        <constructor-arg ref="beanTwo"/>
        <constructor-arg ref="beanThree"/>
    </bean>

    <bean id="beanTwo" class="x.y.ThingTwo"/>

    <bean id="beanThree" class="x.y.ThingThree"/>
</beans>
posted @ 2019-12-19 15:47  n490808114  阅读(1425)  评论(0编辑  收藏  举报