首页 > 其他分享 >Spring 5 中文解析之核心篇-IoC容器(上)

Spring 5 中文解析之核心篇-IoC容器(上)

时间:2022-12-19 23:35:40浏览次数:75  
标签:容器 依赖 定义 Spring bean 解析 IoC 属性

概述

​Spring​​​使得开发人员更加容易的创建企业级应用程序,它提供了在企业Java生态技术栈中的任何技术体系,同时也提供了​​Groovy​​​和​​Kotlin​​​对JVM的支持,并且可以根据应用程序的需要灵活地创建多种体系结构,从​​Spring Framework 5.1​​​开始​​Spring​​需要JDK1.8+,同时也提供了对JDK 11 LTS开箱即用的支持。​​Java SE 8 update 60​​作为Java 8最低的补丁版本被建议使用,但是一般推荐使用最近发布的补丁版本。

​Spring​​支持广泛的应用场景。在大型企业中,应用程序可能长时间的存在,而且需要运行在JDk上,同时应用程序的升级维护生命周期不受开发人员的控制。其他可能作为单独的jar运行或者嵌入式运行,也可能运行在云环境中。也可能是不需要服务器的一个独立的应用程序(例如:批处理、集成负载)。

​Groovy​​​和​​Kotlin​​​和​​Java​​一样可以编译成JVM支持的字节码格式,运行在JVM中。

​Spring​​​是一个开源的框架。​​Spring​​​拥有一个非常强大和活跃的社区,它提供了一个范围广泛的实际应用案例的持续反馈。这有助于​​Spring​​长时间的成功发展。

What We Mean by “Spring”

​Spring​​​术语在不同上下文意义是不同。在这里​​Spring​​​就是指​​SrpingFramework​​​项目本身。然而随着时间的推移,其他的​​Spring​​​项目被构建在​​SrpingFramework​​​基础上。大多数情况下,我们所说的​​Spring​​​是包括整个项目体系。这篇文档主要聚焦在​​Spring Framework​​框架本身。

​SpringFramework​​​分为多个模块。应用程序根据需要选择合适的模块。核心容器的模块是核心包括:配置模型、依赖注入机制。除此之外,​​SpringFramework​​​提供一些基础的为不同应用架构、消息、事务数据和持久化、web提供支持。它也包括了基于Servlet基础的SpringMVC的web框架和支持​​WebFlux​​的reactive编程模型的web框架。

关于模块需要注意的:​​Spring​​的这些框架jar包允许被部署到JDK9的模块路径。为了在支持​​Jigsaw​​​的应用程序中使用,​​Spring Framework 5​​​ jar附带了​​自动模块名称​​​清单条目,这些清单条目定义了与jar无关的稳定语言级别的模块名称(​​spring.core​​​,​​spring.context​​​等)名称(这些jar遵循相同的命名模式,用​​-​​​代替​​.​​​,例如​​spring-core​​​和​​spring-context​​)。当然,Spring的框架jar可以在JDK 8和9的类路径上正常工作。

History of Spring and the Spring Framework

介于早期的​​J2EE​​​规范过于复杂,在2003年​​Spring​​​诞生了,尽管有些人认为Java EE和​​Spring​​​竞争,但事实上​​Spring​​​是Java EE的补充。​​Spring​​编程模型不包含Java EE平台规范。相反,它精心选择各个规范进行集成

​SpringFramework​​​同时也支持依赖注入(​​JSR 330​​​)和通用注解规范 (​​JSR 250​​) ,应用程序开发人员可以选择使用这些规范来代替Spring框架提供的特定于Spring的机制。

从​​SpringFramework5.0​​​后,​​Spring​​的最低要求 Java EE 7(​​Servlet 3.1+​​​, ​​JPA 2.1+​​)同时提供了开箱即用的集成Java EE 8最新的API (​​JSON Binding API​​​),这样使得​​Spring​​​能够更好的兼容例如:​​Tomcat 8​​​ 、 ​​Tomcat 9​​​, ​​WebSphere 9​​​, ​​JBoss EAP 7​​服务容器。

随着时间的推移,​​J2EE​​​在应用开发中的作用已经发生改变。在J2EE和​​Spring​​​的早期,应用程序需要部署到应用服务器上。如今,在​​SpringBoot​​​的帮助下,这些应用可以通过​​devops​​​和友好的方式被创建,同时通过​​Servlet​​​容器的嵌入和一些很小的改变。从​​SpringFramework5.0​​​开始​​WebFlux​​​的应用程序甚至不需要直接使用​​Servlet​​​的API并且能够直接运行在非​​Servlet​​容器服务器上。

​Spring​​​不断的创新和发展。除了​​Spring Framework​​​,还有其他项目,例如​​Spring Boot​​​,​​Spring Security​​​,​​Spring Data​​​,​​Spring Cloud​​​,​​Spring Batch​​​等。请务必记住,每个项目都有自己的源代码存储库,问题跟踪程序和发布节奏。请查看​​spring.io/projects​​​ 罗列了完整的​​Spring​​项目清单。

Design Philosophy(哲学)

当你在学习框架的时候,最重要的是不仅仅知道它能做什么,而且要遵循什么原则。以下是​​Spring​​框架的指导原则。

  • Spring提供在各个级别的选择。让开发者尽可能的推迟对设计的抉择。例如:你可以在不更改代码的情况下使用配置来切换数据的持久化方案。对于其他许多基础架构问题以及与第三方API的集成也是一样可以通过配置来调整。
  • 容纳不同的观点。​​Spring​​​支持灵活性,对于事情应该如何完成没有任何意见。它支持具有不同视角的广泛应用程序需求(译者:意思是​​Spring​​​设计相当灵活、对应怎样去完成逻辑处理​​Spring​​不会干涉)
  • Spring保存了强的向后兼容性。Spring的演进被精心的管理以确保在不同版本间的变更尽量小。Spring非常小心的选择JDK和第三方库的版本,以方便维护依赖于Spring的应用程序和库。
  • Spring的API精心设计。Spring团队花费了大量的时间和精力来设计API,并在许多版本和很多年中都适用的API。
  • 高标准的代码质量要求。Spring框架非常强调有意义的、当前的和准确的​​javadoc​​。它是极少数可以代码结构整洁且程序包之间没有循环依赖关系的项目之一。

Spring核心

The IoC Container

1.1 Spring IoC容器和bean

这章节主要包括了​​SpringFramework​​​对​​IoC​​​容器的控制反转的原理。​​IoC​​​也被称作依赖注入(​​DI​​​)。这是一个对象仅通过构造函数参数、工厂方法的参数或对象实例构造或从工厂方法返回后在对象实例上设置的属性来定义其依赖项(即与之一起工作的其他对象)的过程。此过程从根本上讲是通过使用类的直接构造或诸如服务定位器模式之类的机制来控制其依赖项的实例化或位置的bean的逆过程(因此称为​​Control Inversion​​)。(译者:大白话的意思就是为实例对象注入依赖的实例,注入方式包括构造函数注入、对象属性注入、setXXX方法注入等。)

​org.springframework.beans​​​和​​org.springframework.context​​​包是​​SpringFramework​​​容器的基础。​​BeanFactory​​​接口提供一个高级的配置机制能力,可以管理任何类型的对象。​​ApplicationContext​​​是​​BeanFactory​​的子接口。增加了如下能力:

  • 更容易的集成​​Spring​​​的​​AOP​​特性
  • 消息资源处理(国际化)
  • 事件发布器
  • 应用层特定的上下文,例如:对应web应用上下文​​WebApplicationContext​

简要来说,​​BeanFactory​​​提供了配置框架和基本的功能,​​ApplicationContext​​​提供了更多的企业级功能。​​ApplicationContext​​​是一个完整的​​BeanFactory​​​的超级,在这章节中只是用来描述为​​Spring​​​的容器。更多的​​ApplicationContext​​​替换​​BeanFactory​​​的使用查看​​BeanFactory​​。

在​​Spring​​中,构成你的应用程序的骨架和被容器所管理的对象被称为beanbean是被​​Spring​​容器所实例化、包装、和管理的对象。bean也是在我们的应用程序中许多对象中之一。beanbean之间的依赖关系是通过容器的配置元数据反映的。

参考示例代码: ​​com.liyong.ioccontainer.IoCContainer​

1.2 Spring IoC容器概述

​org.springframework.context.ApplicationContext​​​接口代表​​Spring​​​的​​IoC​​​容器,同时有责任对这些bean的实例化、配置和组装。容器通过获取配置元数据知道哪些对象需要进行实例化、配置和组装。这些配置元数据通过​​xml​​​、java的注解或者java的配置(​​Java Config​​)。它允许你去表达组成应用程序的对象以及这些对象之间丰富的相互依赖关系。

在​​Spring​​​中提供了一些关于​​ApplicationContext​​​的实现。在一个独立的应用程序中,一般通过​​ClassPathXmlApplicationContext​​​ 或 ​​FileSystemXmlApplicationContext​​​去创建容器实例。而​​XML​​作为传统的定义配置元数据的方式,同时你可以引导容器去使用Java的注解或者Java Config作为元数据格式,通过提供少量的XML配置去声明激活这些附加元数据配置格式。

在大多数应用场景下,显示的实例化一个或多个​​Spring IoC​​​容器实例是没有必要的。例如:在Web应用场景中,在应用程序​​web.xml​​文件中简单的xml配置描述通常就足够。如果使用Spring Tools for Eclipse工具,我们将非常容易的创建样板配置。

下面通过图片来展示​​Spring​​​是怎样工作的,我们的应用程序类(业务类)与配置元数据结合在一起,在​​ApplicationContext​​被创建和实例化后,我们将拥有一个完整的配置和可执行的应用系统。

Spring 5 中文解析之核心篇-IoC容器(上)_spring

1.2.1 配置元数据

上图所示,​​Spring​​​容器获取配置元数据。那么这些元数据是怎样被表示或者是怎样配置的呢,作为一个开发人员,在你的应用程序中去告诉​​Spring​​容器怎样去实例化、配置和组装这些对象。

​XML​​格式作为一种传统的配置元数据方式,在这个章节中大部分内容使用这些关键概念和特性。

基于​​XML​​​的配置元数据不是唯一被允许使用的配置方式。​​Spring​​容器本身是和这些配置元数据是解偶的。现在,大多数的开发人员使用Java base configuration作为​​Spring​​应用的配置方式。

关于更多的Spring容器的元数据配置方式以下罗列出来:

  • 基于注解的配置方式:从Spring 2.5开始引入对注解元数据配置的支持
  • 基于Java-base configuration方式:从Spring 3.0开始,​​Spring​​的JavaConfig项目成为​​SpringFramework​​的核心部分,许多的特性被提供。因此,我们可以使用Java Config的方式定义外部bean而不是XML文件。我们可以使用这些新特性注解 例如: ​​@Configuration​​​、 ​​@Bean​​​、​​@Import​​​ 和​​@DependsOn​​。

​Spring​​​配置组成至少通常超过一个bean的定义被容器管理。基于​​XML​​的配置元数据将这些bean配置为顶级元素内的元素。Java Config通常使用​​@Bean​​​注解在方法上同时所在的类被​​@Configuration​​标注。

这些bean的定义对应到真实的bean对象实例同时我们的应用程序由这些对象实例所构建。比如:我们定义的service层数据存储层(dao),**展示层 ** 比如:Struts的​​Action​​实例、Hibernate的​​SessionFactories​​实例、JMS的​​Queues​​​等等。典型地,不需要在容器中配置细粒度的域对象,因为通常由DAO和业务逻辑负责创建和加载域对象。但是,你可以使用​​Spring​​​与​​AspectJ​​的集成来配置在IoC容器控制范围之外创建的对象。

以下使用基于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>
</beans>

参考示例代码:​​com.liyong.ioccontainer.XmlIocContainer​

  • id属性表示一个bean的唯一id
  • class属性表示这个bean的全路径类型名称例如:​​com.liyong.ioccontainer.ConfigruationService​
1.2.2 容器实例化

​ApplicationContext​​构造函数提供了一个或多个字符串路径参数,让容器去加载外部资源的配置元数据,例如:本地文件系统或者Java 类路径等等。

ApplicationContext context = new ClassPathXmlApplicationContext("services.xml", "daos.xml");//加载services.xml和daos.xml配置元数据

在我们学习了关于​​Spring IoC​​​容器后,我们可能想知道更多关于​​Resource​​​的抽象,​​Resource​​​提供了一个便利的机制去通过URI语法定义的位置读取一个输入流。特别地,​​Resource​​​被用于构建应用程序的上下文。​​Application上下文和Resource路径​​。

以下给出​​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>

以下显示数据获取层​​dao.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​​​类和数据存储层​​JpaAccountDao​​​和​​JpaItemDao​​对象组成(以JPA的对象关系映射为标准)。属性名称元素引用JavaBean的属性名称,并且​​ref​​​元素引用其他bean定义的名称。id和ref之间的联系表达了协作对象的依赖性。更多的关于对象依赖的配置查看​​Dependencies​​。

  • 组成基于XML的配置元数据
    配置元数据可以跨多个XML配置文件。通常情况下,每个独立的XML配置文件代表项目架构中的逻辑层或者模块。
    你可以使用​​ApplicationContext​​构造函数从这些配置的​​XML​​片段中加载bean的定义。这个构造函数可以使用多个​​Resource​​来定位资源,在上面的例子中可以查看。或者,使用一个或多个标签元素从其他的XML配置文件中加载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>

在上面的例子中,加载外部的这些bean从3个文件中被加载分别是:​​services.xml​​​、​​messageSource.xml​​​和​​themeSource.xml​​​。所有被导入文件路径是相对应导入文件位置。(译者:大白话就是相对于当前导入文件的路径),因此​​services.xml​​​必须在导入文件相同的目录或者类路径下,而​​messageSource.xml​​​和​​themeSource.xml​​​必须在导入文件下的​​resources​​​路径。正如你所看到​​/​​被忽略。但是,鉴于这些路径是相对的,最好不要使用斜线。

一种可能的使用方式使用​​../​​​去引用父目录文件,但是不是推荐的。这样做会导致当前应用对外部文件的依赖。特别是,这种引用方式在使用​​classpath:​​​和​​URL​​​组合是不被推荐(例如:​​classpath:../services.xml​​​),运行时解析过程在其中选择​​最近的​​类路径根,然后查看其父目录。类路径配置的更改可能导致选择其他错误的目录。

我们应该总是使用全限定资源路径去替换相对路径:例如:​​file:C:/config/services.xml​​​或者​​classpath:/config/services.xml​​​。但是请注意,你正在将应用程序的配置耦合到特定的绝对位置。通常最好对这样的绝对位置保留间接使用,例如通过在运行时针对JVM系统属性解析的​​${…}​​占位符。

名称空间本身提供了import指令特性。除了普通bean定义之外,​​Spring​​​提供的​​XML​​​名称空间选择中还提供了其他配置功能-例如:​​context​​​和​​util​​命名空间

  • Groovy的bean定义
    为外部配置元数据例举更进一步的例子,​​bean​​的定义能够使用​​Spring​​的Groovy语言定义来表达(可以从​​Grails​​框架去了解)。典型地,这个配置使用​​.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​​定义方式并且支持SpringXML命名空间配置。这种方式允许我们通过​​importBeans​​指令去导入XML的​​bean​​定义。

参考示例代码:​​com.liyong.ioccontainer.ImportXmlIocContainer​

1.2.3 容器使用

​ApplicationContext​​​是能够维护不同bean及其依赖项的注册表的高级工厂的接口。通过使用​​T getBean(String name, Class<T> requiredType)​​方法可以找回我们的bean实例对象。

​ApplicationContext​​允许我们去读取bean的定义和获取bean实例对象,举例子:

// 创建容器和加载bean定义
ApplicationContext context = new ClassPathXmlApplicationContext("services.xml", "daos.xml");

// 获取bean实例
PetStoreService service = context.getBean("petStore", PetStoreService.class);

// use configured instance
List<String> userList = service.getUsernameList();

使用Groovy配置,引导非常的类似。但是它是不同的Context的实现。举例子:

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

最灵活的变体是​​GenericApplicationContext​​结合了reader的代理-例如:通过​​XmlBeanDefinitionReader​​读取XML文件中的配置信息。举例子:

GenericApplicationContext context = new GenericApplicationContext();
//加载xml中配置元数据
new XmlBeanDefinitionReader(context).loadBeanDefinitions("services.xml", "daos.xml");
context.refresh();

我们也可以使用​​GroovyBeanDefinitionReader​​加载Groovy配置文件。举例子:

GenericApplicationContext context = new GenericApplicationContext();
//加载groovy配置元数据
new GroovyBeanDefinitionReader(context).loadBeanDefinitions("services.groovy", "daos.groovy");
context.refresh();

我们也可以在相同的​​ApplicationContext​​中混合或者匹配一个reader的代理,去读取不同配置源的bean定义配置信息。

我们也可以使用​​getBean()​​​方法去获取我们的bean实例对象。​​ApplicationContext​​​接口同时提供了一些其他的方法获取bean实例,但是,理想情况下,我们的代码不要直接使用它们。确实,我们的应用程序代码不应该直接调用​​getBean()​​​方法,这样就不会对​​Spring​​​的API进行耦合(译者:个人观点,在目前来看​​Spring​​​已经是容器管理的规范和标准了,使用API会对应用程序耦合这个两说,个人觉得是没有问题的,除非你还想换掉底层容器)。例如:​​Spring​​​集成的Web框架(一般指​​SpringMVC​​​)提供了变体的Web框架组件的依赖注入​​controllers​​(Controller) 和 ​​JSF-managed​​​ 的bean对象,同时允许我们通过元数据声明一个指定bean的依赖(例如注解注入:​​@Autowired​​​、​​@Resource​​)。

1.3 Bean的概述

​Spring IoC​​容器管理一个或多个bean对象。这些bean是通过我们提供的容器和配置元数据所创建的(例如:通过在XML文件中定义的)。

在​​Spring IoC​​​容器自身内部,这些bean的定义是被​​BeanDefinition​​对象所描述,包括了一些元数据信息:

  • 包限定类名:典型地,bean被定义的实际实现类
  • bean的行为配置元素,bean在容器中应该是怎样的行为状态(​​scope​​​, ​​lifecycle​​​ ​​callbacks​​,等等)
  • 引用bean完成工作所需要的其他bean。这些引用bean被称为协作者或者依赖项。
  • 在新创建的对象中去配置其他配置的设置 例如:在一个bean中使用连接数或池大小现在来管理一个连接池。

这个配置元数据转移到属性配置集合中构建每一个bean的定义。下面这个表格描述了这些属性:

属性

解释

Class(配置Class信息)

​Instantiating Beans​

Name(bean名称)

​Naming Beans​

Scope(bean的范围)

​Bean Scopes​

Constructor arguments(构造参数)

​Dependency Injection​

Properties(bean的属性)

​Dependency Injection​

Autowiring mode(自定注入模式)

​Autowiring Collaborators​

Lazy initialization mode(是否为懒加载)

​Lazy-initialized Beans​

Initialization method(bean初始化回调方法)

​Initialization Callbacks​

Destruction method(bean销毁回调方法)

​Destruction Callbacks​

此外bean的定义也包括了怎样去创建一个指定bean的信息,​​ApplicationContext​​的实现允许在容器外部创建的bean注册到容器中。这个是通过获取​​ApplicationContext​​​的​​BeanFactory​​​通过​​getAutowireCapableBeanFactory()​​​方法,返回一个​​BeanFactory​​​的​​DefaultListableBeanFactory​​​实现。​​DefaultListableBeanFactory​​​通过​​registerSingleton(..)​​​和​​registerBeanDefinition(..)​​方法支持去注册bean信息。但是,典型的应用程序只能与通过常规bean定义元数据定义的bean一起使用。(译者:一般不对Spring进行拓展是不会使用到)

bean元数据和手动提供的单例bean实例需要尽可能早地被注册,以便容器在自动装配和其他内省步骤期间正确地推断它们。覆盖存在的元数据和单例实例在某些角度是被支持的,新对象在运行时被注册官方是不支持的并且可能导致并发获取异常、容器中的bean状态不一致,或者两种情况都有。

参考示例代码:​​com.liyong.ioccontainer.starter.RegisterBenDefination​

1.3.1 Bean的命名

​Spring IoC​​容器中的每个bean都有一个或多个唯一的标识。这些标识在同一个容器中必须是唯一的。一个bean通常只有一个标识。但是,如果bean需要多个标识可以考虑使用别名的方式。

在基于XML的元数据配置中,我们可以使用​​id​​​属性、​​name​​​属性或者两者都使用。​​id​​​属性可以精确的指定唯一的​​id​​​。按照惯例,这些名称可以是数字字母,但是也可以包含特定的字符。如果我们想为​​bean​​​引入其他的别名,可以指定​​name​​​属性通过​​,​​​ 、​​:​​​ 、或者​​空格​​​来分割多个别名。需要注意的是,在​​Spring3.1​​​之前的版本中,​​id​​​属性被定义作为​​xsd:ID​​​类型,约束可能的字符。在​​Spring3.1​​​后,被定义为​​xsd:string​​类型。值得注意:Bean ID唯一性仍由容器强制执行,尽管不再由XML解析器执行。

Spring没有强制要求我们提供一个bean的​​id​​​或者​​name​​​属性。如果我们没有显示的提供​​id​​​或者​​name​​属性,Spring容器会为bean生成一个唯一的名称。但是,如果我们想通过bean名称去引用可以通过使用​​ref​​元素或者是使用服务定位查找,必须提供一个bean的名称。如果不提供bean名称的动机可能是与使用​​内部bean​​​和​​自动装配协作者​​有关。

Bean命名约定

当给bean的属性命名的时候,使用Java中的标准命名规范。也就是,以小写字母开始驼峰的命名方式。例如:​​accountManager​​​, ​​accountService​​​, ​​userDao​​​, ​​loginController​

统一的命名约束能够使我们更容易的去阅读和理解。如果我们使用Spring AOP,当应用通知到通过名称所关联的bean集合是有用的。

通过在类路径中进行组件扫描,​​Spring​​会按照前面描述的规则为未命名的组件生成Bean名称:从本质上讲,采用简单的类名称并将其初始首字符转换为小写。但是,在(不寻常的)特殊情况下,如果有多个字符并且第一个和第二个字符均为大写字母,则会保留原始大小写。这些规则与java.beans.Introspector.decapitalize(Spring在此使用)定义的规则相同。

  • 在Bean定义之外别名Bean
    在bean定义本身中,通过使用​​​id​​​属性指定的最多一个名称和name属性中任意数量的其他名称的组合,可以为bean提供多个名称。这些名称和相同bean的别名是等效,在一些情况是非常有用的,例如通过使用特定于该组件本身的Bean名称,让应用程序中的每个组件都引用一个公共依赖项。
    指定实际定义bean的所有别名并不总是足够的,有时需要为在别处定义的bean引入别名。在大型系统中通常是这种情况,在大型系统中,配置在每个子系统之间分配,每个子系统都有自己的对象定义集。在基于XML配置元数据中,我们可以使用标签元素来完成。举例子:
<alias name="fromName" alias="toName"/>

在这个例子中,一个bean(相同容器中)的名称​​fromName​​​被定义一个别名​​toName​​。

例如:​​subsystemA​​​系统通过配置元数据​​subsystemA-dataSource​​​引用一个​​DataSource​​​数据源。​​subsystemB​​​系统通过配置元数据​​subsystemB-dataSource​​​引用一个​​DataSource​​​数据源。当主应用程序组合使用这两个系统,主应用程序通过​​myApp-dataSource​​​名称引用​​DataSource​​ 。总共有三个名称引用相同的对象,我们可以增加别名定义配置元数据:

<!--myApp-dataSource定义一个别名subsystemA-dataSource-->
<alias name="myApp-dataSource" alias="subsystemA-dataSource"/>
<!--myApp-dataSource定义一个别名subsystemB-dataSource-->
<alias name="myApp-dataSource" alias="subsystemB-dataSource"/>

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

Java-Configuration

如果使用​​JavaConfig​​​方式配置bean,​​@Bean​​​注解也提供了别名的支持。查看 ​​@Bean​​ 使用详情

参考示例代码:

XML配置方式:​​com.liyong.ioccontainer.starter.XmlAliasIocContainer​

JavaConfig方式:​​com.liyong.ioccontainer.starter.AnnotationAliaseIoCContainer​

1.3.2 bean的实例化

bean定义实际上是创建一个或多个bean对象的模版。容器通过被命名的bean查找模板,当从容器中请求对象时通过bean定义的元数据封装去创建(或获取存在的)一个真实的对象。

如果使用基于​​XML​​​的配置元数据,我们通过在标签元素指定​​Class​​​属性来进行实例的初始化(在​​BeanDefinition​​​实例中指定​​Class​​​属性)。这个​​Class​​​属性通常是必填的。(更多查看 ​​实例工厂方法​​​ 和 ​​继承Bean定义​​​)我们可以使用两种​​Class​​属性的一种方式:

  • 典型地,在容器本身直接的创建​​bean​​​的地方,通过​​class​​​的构造函数反射地构建​​bean​​​实例,类似在​​java​​​代码中​​new​​操作。
  • 指定真实类去包含静态的工厂方法,调用静态方法创建这个对象,在不太常见的情况下,容器在类上调用静态工厂方法来创建Bean。对象类型从静态工厂方法的调用被返回,可能返回相同的类或其他的类。

内部类名字

如果想配置一个静态的嵌入的类定义,我们必须使用嵌入类的二进制名。

例如:如果有一个类叫做​​SomeThing​​​在​​com.example​​​包中,并且这个​​SomeThing​​​类有一个静态的嵌入类叫​​OtherThing​​​,这个​​class​​属性的值在bean定义中是​​com.example.SomeThing$OtherThing​​。

参考示例代码:​​com.liyong.ioccontainer.starter.InnerIoCContainer​

  • 构造函数初始化
    当我们通过构造函数方法创建bean时,所有常规的类是可以被Spring使用或兼容的。也就是说开发的类不需要实现特定的接口或者特定的编码风格。一个简单的指定bean就可以满足需求。然而,这取决于你指定bean依赖的IoC类型,你需要一个默认的构造函数。(也就是说类需要一个默认的构造函数)
    实际上Spring IoC容器能够管理你想管理的任何类。它不会限制管理的是否是真正的JavaBean。大多数​​Spring​​用户更喜欢真实的JavaBean,它们仅具有默认(无参数)构造函数,并根据容器中​​bean​​​的属性建模适当的​​setter​​​和​​getters​​​方法。在我们的容器中也可以有更多的外来的非​​bean​​格式的类。例如:你去使用一个绝对的不符合JavaBean规范的遗留的连接池,​​Spring​​​也可以管理好它。
    通过基于XML配置的元数据,你可以指定bean的类如下:
<bean id="exampleBean" class="examples.ExampleBean"/>

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

更多的关于提供多个参数的构造函数机制和在对象属性设置对象实例后被构造,可查看​​依赖注入​

  • 静态工厂方法实例化
    当通过静态工厂方法定义一个​​​bean​​​时,使用​​class​​​属性去指定这个类包含这个​​static​​​工厂方法并用factory-method属性去指定工厂方法的名称。你应该去调用这个方法并且返回一个存活对象,随后将其视为已通过构造函数创建。这种bean定义的一个用途是在遗留代码中调用静态工厂。
    下面的​​​bean​​​定义指定通过调用工厂方法去创建​​bean​​​。这个定义没有指定返回对象的类型,这个类仅仅包含了工厂方法。在这个例子中,​​createInstance()​​方法必须是一个静态的方法。下面的例子展示了怎样去指定工厂方法。
<bean id="clientService"
class="examples.ClientService"
factory-method="createInstance"/>

有关向工厂方法提供(可选)参数和在从工厂返回对象后设置对象实例属性的机制的详细信息,​​查看依赖和配置详情​

参考代码:​​com.liyong.ioccontainer.starter.XmlStaticFactoryMethodIocContainer​

  • 实例工厂方法实例化
    类似通过静态工厂方法实例化,初始化一个实例工厂方法调用一个存在bean的非静态方法,从bean容器中创建一个新bean实例。为了使用这个机制,​​​class​​​属性设置为空并且在​​factory-bean​​​属性中设置一个在当前容器bean的名称,这个实例方法被调用创建一个对象实例。通过设置​​factory-method​​属性指定工厂方法名称。
<!-- the factory bean, which contains a method called createInstance() -->
<bean id="serviceLocator" class="examples.DefaultServiceLocator">
<!-- inject any dependencies required by this locator bean -->
</bean>

<!-- factory-bean:指定bean名称 factory-method:指定创建bean的方法 -->
<bean id="clientService"
factory-bean="serviceLocator"
factory-method="createClientServiceInstance"/>

下面是使用示例的类定义

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

上面例子对应类定义:

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​​实现类。

参考代码:​​com.liyong.ioccontainer.starter.XmlFactoryMethodIocContainer​

  • 确定​​bean​​​的运行时类型
    ​​​bean​​​的运行时类型确定是不容易的。在bean的元数据定义中指定类仅仅是初始类的引用,潜在地,结合声明的工厂方法或者存在一个​​FactoryBean​​​类可能导致bean的运行时类型不同,或者在实例级别工厂方法没有被设置(通过指定​​factory-bean​​​名称替换)。此外,AOP代理可以用一个基于接口的代理包装一个bean实例,该代理只公开有限的目标bean的实际类型。(译者:由于存在代理导致运行时真实类型在运行期间才能确定)
    推荐的方式查找关于特定​​​bean​​​的运行时类型是去调用​​BeanFactory.getType​​​方法并指定​​bean​​​的名称。考虑了以上所有情况并通过指定相同​​bean​​​名称调用​​BeanFactory.getBean​​返回对象类型。
1.4 依赖关系

一个典型的企业应用不是由一个简单的对象(在​​Spring​​中叫bean)组成。即使是最简单的应用程序,也有一些对象协同工作,以呈现最终用户视为一致的应用程序。(译者:相当于所有的bean一起协同工作对于用户是无感知的)。下一部分将说明如何从定义多个独立的Bean对象协作去实现应用程序的目标。

1.4.1 依赖注入

依赖注入是从工厂方法构造或返回的实例并通过设置对象实例的构造参数、工厂方法参数或者属性去定义它的依赖关系(与它一起工作的对象)的过程。当创建​​bean​​​的时候容器注入这些依赖。从根本上讲,此过程是通过使用类的直接构造或服务定位器模式来控制​​bean​​​自身依赖关系的实例化或位置的​​bean​​本身的逆过程(因此称为控制反转)。

DI(依赖注入)使代码更简洁和解偶,当为这些对象提供依赖时候是更高效的。(通过依赖注入来注入对象更高效)。对象不用去主动查找它的依赖项并且不用知道依赖类的位置。这样的结果是我们的类变成了更容易被测试,特别地,当这些依赖在接口或者抽象类上时,在单元测试中去使用​​stub​​​或者​​mock​​方式去实现这些接口和抽象类。

DI(依赖注入)存在两个重要的变体:​​基于构造函数的依赖注入​​​和​​基于Setter的依赖注入​

  • 基于构造函数依赖注入
    基于构造函数的DI(依赖注入)是通过容器调用构造函数完成的,每一个构造函数参数代表一个依赖。调用带有特定参数的静态工厂方法来构造Bean几乎是等效的,并且本次讨论将构函数和静态工厂方法的参数视为类似的。下面的例子展示了只能通过构造函数进行对象注入:
public class SimpleMovieLister {

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

//构造函数注入MovieFinder对象
public SimpleMovieLister(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}

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

注意:这个类没有任何特别的。它是一个POJO类并且没有依赖容器特定接口、基础类或注解。

  • 构造函数参数解析
    构造参数解析匹配是通过使用参数的类型进行的。如果bean定义的构造函数参数中不存在潜在的歧义,在bean定义中定义构造函数参数的顺序是在实例化bean时将这些参数提供给适当的构造函数的顺序。考虑下面的类
package x.y;

public class ThingOne {

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

假设​​ThingTwo​​​和​​ThingThree​​类没有继承关系,不存在潜在的歧义。因此,这个配置工作的很好并且我们没有必要显示的在元素中指定构造函数参数的索引或类型。

<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>

当引用另一个bean时,类型是已知的,可以进行匹配。当一个简单类型被使用,例如true,Spring不能确定这个值的类型,因此在没有类型的帮助下是不能被匹配的。考虑下面的类:

package examples;

public class ExampleBean {

// Number of years to calculate the Ultimate Answer
private int years;

// The Answer to Life, the Universe, and Everything
private String ultimateAnswer;

public ExampleBean(int years, String ultimateAnswer) {
this.years = years;
this.ultimateAnswer = ultimateAnswer;
}
}
  • 构造函数参数类型匹配
    在前面的场景中,如果我们通过使用​​​type​​属性明确指定了构造函数参数类型,容器会使用简单类型进行匹配。像下面的例子:
<bean id="exampleBean" class="examples.ExampleBean">
<constructor-arg type="int" value="7500000"/>
<constructor-arg type="java.lang.String" value="42"/>
</bean>
  • 构造函数参数索引
    我们可以明确指定构造函数参数的索引通过​​​index​​属性,像下面例子:
<bean id="exampleBean" class="examples.ExampleBean">
<!--指定第一个参数-->
<constructor-arg index="0" value="7500000"/>
<!--指定第二个参数-->
<constructor-arg index="1" value="42"/>
</bean>

除了解决多个简单值的歧义性之外,指定索引还可以解决歧义,其中构造函数具有两个相同类型的参数。

index索引从0开始

  • 构造函数参数名称
    我们也可以使用构造函数参数名称来消除歧义,例如下面例子:
<bean id="exampleBean" class="examples.ExampleBean">
<!--指定构造函数参数-->
<constructor-arg name="years" value="7500000"/>
<constructor-arg name="ultimateAnswer" value="42"/>
</bean>

请记住,要使此工作开箱即用,必须在启用调试标志的情况下编译代码,以便Spring可以从构造函数中查找参数名称。如果不能或不想在debug标记下编译代码,可以使用JDK注解​​@ConstructorProperties​​ 去明确构造函数参数的名称。参考下面例子:

package examples;

public class ExampleBean {

// Fields omitted
@ConstructorProperties({"years", "ultimateAnswer"})
public ExampleBean(int years, String ultimateAnswer) {
this.years = years;
this.ultimateAnswer = ultimateAnswer;
}
}
  • 基于Setter依赖注入
    基于​​​Setter​​的 DI(依赖注入)是在bean调用无参构造函数或无参​​static​​​工厂函数去实例化​​bean​​​后被容器调用函数去完成的。
    下面的例子展示了一个只能通过使用​​​Setter​​注入的类。这个类是常规的Java。它是一个POJO并且没有依赖容器特定接口、基础类或者注解。
public class SimpleMovieLister {

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

// 通过Setter进行注入
public void setMovieFinder(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}

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

​ApplicationContext​​​支持基于构造函数和​​Setter​​​的依赖注入的​​bean​​​管理。也支持在通过构造函数注入后再通过基于​​Setter​​​方法注入。可以使用​​BeanDefinition​​​的形式配置依赖项,将其与​​PropertyEditor​​​实例结合使用以从一种格式转换为另一种格式。然后,大多数​​Spring​​​用户不会直接使用这些类,而是使用XML的bean定义、注解这些组件(类似​​@Component​​​, ​​@Controller​​​等等),或者基于Java被标注​​@Configuration​​​类的方法使用​​@Bean​​​标注。这些配置数据源内部地转换为​​BeanDefinition​​​实例并且被使用于加载整个​​Spring IoC​​容器实例。

基于构造函数和Setter函数注入选择

由于可以混合基于构造函数和​​Setter​​函数的依赖注入,将构造函数用于强制性依赖项,将Setter方法或配置方法用于可选性依赖项是一个很好的经验法则。需要注意在​​Setter​​​方法上使用 ​​@Required​​表明这个属性需要一个依赖;然而,构造函数注入可以通过编程方式校验参数是可取的。

​Spring​​​团队一般推荐使用构建函数注入,因为可以允许我们去实现不可变的对象组件并且确保需要的依赖不为​​null​​。此外,构造函数注入组件总是被返回一个完整初始化的状态。构造函数大量的参数是一个坏代码味道,暗示着这个有太多的责任并且应该去重构以更好的分离关注点问题。

​Setter​​​注入应该主要使用在可选依赖因为可以在在类中设置一个默认值。否则,必须在代码使用依赖项的任何地方执行非空检查。​​Setter​​​注入的一种好处是可以在后面对​​Setter​​方法进行重新配置或重新注入。

使用对特定类最有意义的DI样式,有时候,当处理第三方类库没有源码的时候,这个选择是非常适合的。例如:如果第三方类库没有暴露任何的​​Setter​​方法,构造函数注入可能是依赖注入的唯一有效方式。

  • 依赖解析处理
    容器执行bean依赖解析过程:
  • ​ApplicationContext​​​通过对所有bean的配置元数据描述进行创建和初始化。配置元数据通过​​XML​​​、​​JavaConfig​​或者注解描述。
  • 对于每个​​bean​​​,它的依赖形式通过属性、构造函数参数或者​​static-factory​​​(如果使用常规的构造函数替换)方法参数表达。当这个bean被创建的时候,这些依赖被提供给​​bean​​。(译者:被依赖bean先创建)
  • 每一个属性或者构造函数参数的都要设置一个实际的定义,或引用容器其他​​bean​​。
  • 每一个属性或构造函数参数的值从指定的格式转换为属性或构造函数参数的真实类型。默认情况下,Spring提供一个字符串格式转换为所有内建类型的值,例如:int、long、String、boolan等等。

Spring容器验证每一个创建bean的配置。然而,bean属性本身没有被设置,直到bean被真正创建。在创建容器时,将创建单例作用域的bean并将其设置为预实例化(缺省值)。Scope被定义在 ​​Bean Scopes​​。除此之外,其他的bean创建仅仅在请求容器的时候。bean的创建潜在的导致一些bean的图被创建(译者:意思是bean所依赖的bean被创建,类似于bean的依赖拓扑图),类似bean的依赖和它的依赖的依赖bean创建和被赋值。注意:这些依赖项之间的解析不匹配可能会在第一次创建受影响的bean时出现。

循环依赖

如果主要使用构造函数注入,则可能会创建无法解决的循环依赖场景。

例如:类​​A​​​通过构造函数需要依赖注入类​​B​​​并且类​​B​​​通过构造函数依赖注入​​A​​​。如果配置类​​A​​​和类​​B​​相互依赖注入,Spring IoC容器在运行时检测到循环依赖会抛出一个​​BeanCurrentlyInCreationException​​异常。

一种解决方法是编辑类的源码通过​​Setter​​​而不是构造函数注入。或者避免使用构造函数注入而是仅仅使用​​Setter​​​方法注入。换句话说,虽然它是不推荐使用的,我们可以通过​​Setter​​​注入配置循环依赖对象。与典型情况(没有循环依赖关系)不同,Bean ​​A​​​和Bean ​​B​​之间的循环依赖关系迫使其中一个Bean在完全初始化之前被注入另一个Bean(经典的“鸡与蛋”场景)

通常,你可以相信​​Spring​​​做的事情是正确的。容器会在加载时候检测配置问题,例如:引用不存在的​​bean​​​、循环依赖。当这个​​bean​​​被真正创建的时候,​​Spring​​​设置属性并且尽可能晚的解析依赖。这意味着如果创建该对象或其依赖项时遇到问题,则已正确加载的​​Spring​​​容器可能在你请求对象时生成异常-例如,这个​​bean​​抛出一个错误或无效的属性异常结果。这可能会延迟某些配置问题的可见性,这就是为什么默认情况下ApplicationContext实现会预先实例化单例bean的原因。在实际需要使用这些bean之前要花一些前期时间和内存,你会在创建ApplicationContext时发现配置问题,而不是以后(使用bean的时候)。你可以覆盖这个默认行为,这样单例bean就可以惰性地初始化,而不是预先实例化。

如果不存在循环依赖关系,则在将一个或多个协作bean注入到依赖​​bean​​​中时,每个协作​​bean​​​在注入到依赖​​bean​​中之前都已完全配置。也就是,如果bean A有依赖bean ​B​,​​Spring IoC​​容器在调用bean ASetter方法之前完整的配置bean ​B​。换句话说,这个bean被实例化,他的依赖被设置并且关联的生命周期函数(例如:​​init方法​​​或者​​InitializingBean 回掉函数​​)已经被调用。

  • 依赖注入例子
    下面的例子基于XML配置元数据去配置基于​​​Setter​​的依赖注入。Spring XML配置文件指定一些bean的定义:
<bean id="exampleBean" class="examples.ExampleBean">
<!-- 属性注入:依赖一个bean -->
<property name="beanOne">
<ref bean="anotherExampleBean"/>
</property>
<!-- 属性注入:依赖一个bean-->
<property name="beanTwo" ref="yetAnotherBean"/>
<!-- 属性值填充-->
<property name="integerProperty" value="1"/>
</bean>
<bean id="anotherExampleBean" class="examples.AnotherBean"/>
<bean id="yetAnotherBean" class="examples.YetAnotherBean"/>

下面定义​​ExampleBean​​的类

public class ExampleBean {

private AnotherBean beanOne;

private YetAnotherBean beanTwo;

private int i;

// 指定属性需要注入bean类型
public void setBeanOne(AnotherBean beanOne) {
this.beanOne = beanOne;
}

// 指定属性需要注入bean类型
public void setBeanTwo(YetAnotherBean beanTwo) {
this.beanTwo = beanTwo;
}

public void setIntegerProperty(int i) {
this.i = i;
}
}

上面的例子,在XML文件中通过​​Setter​​声明属性匹配类型。下面例子使用构造函数注入:

<bean id="exampleBean" class="examples.ExampleBean">
<!-- 构造函数注入bean anotherExampleBean-->
<constructor-arg>
<ref bean="anotherExampleBean"/>
</constructor-arg>
<!-- 构造函数注入bean yetAnotherBean-->
<constructor-arg ref="yetAnotherBean"/>
<constructor-arg type="int" value="1"/>
</bean>
<bean id="anotherExampleBean" class="examples.AnotherBean"/>
<bean id="yetAnotherBean" class="examples.YetAnotherBean"/>

下面例子对于​​ExampleBean​​的类定义

public class ExampleBean {

private AnotherBean beanOne;

private YetAnotherBean beanTwo;

private int i;

//需要在构造函数中声明需要依赖的类型定义
public ExampleBean(
AnotherBean anotherBean, YetAnotherBean yetAnotherBean, int i) {
this.beanOne = anotherBean;
this.beanTwo = yetAnotherBean;
this.i = i;
}
}

​bean​​​定义中指定的构造函数参数用作​​ExampleBean​​​构造函数的参数(意思是xml中指定的构造函数引用作为​​ExampleBean​​构造函数的参数)。

现在考虑这个例子的变体,使用构造函数替换,​​Spring​​​调用​​static​​工厂方法去返回对象的实例:

<bean id="exampleBean" class="examples.ExampleBean" factory-method="createInstance">
<constructor-arg ref="anotherExampleBean"/>
<constructor-arg ref="yetAnotherBean"/>
<constructor-arg value="1"/>
</bean>

<bean id="anotherExampleBean" class="examples.AnotherBean"/>
<bean id="yetAnotherBean" class="examples.YetAnotherBean"/>

​ExampleBean​​对应的类定义:

public class ExampleBean {

// a private constructor
private ExampleBean(...) {
...
}

//静态工厂方法
public static ExampleBean createInstance (
AnotherBean anotherBean, YetAnotherBean yetAnotherBean, int i) {

ExampleBean eb = new ExampleBean (...);
// some other operations...
return eb;
}
}

静态工厂方法的参数由元素提供,与实际使用构造函数时完全相同。通过工厂方法返回的类的类型不必与包含静态工厂方法的类的类型相同(尽管在此例中为相同)。实例(非静态)工厂方法可以以基本上相同的方式使用(除了使用factory-bean属性代替class属性之外),因此不在这里详细讨论。

参考代码:​​com.liyong.ioccontainer.starter.XmlDependecyInjectContainer​

1.4.2 详细介绍依赖项和配置

在前面的章节提到,我们可以定义bean的属性和构造函数参数去引用其他被管理的bean(协作者)或者作为一个内联值定义。Spring的XML元数据配置支持子元素类型和这个为了这个设计目的。

  • 直接值(原生类型、字符串等)
    元素的value属性将属性或构造函数参数指定为人可读的字符串表示形式。Spring的​​​conversion service​​ 被使用从字符串转换属性或参数的实际类型。下面的例子展示各种值的设置:
<bean id="myDataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<!-- results in a setDriverClassName(String) call -->
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mydb"/>
<property name="username" value="root"/>
<property name="password" value="masterkaoli"/>
</bean>

下面的例子使用​​p​​​命名空间使​​xml​​配置方式更简洁:

<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">

<!--使用p命名空间 注意: xmlns:p="http://www.springframework.org/schema/p -->
<bean id="myDataSource" class="org.apache.commons.dbcp.BasicDataSource"
destroy-method="close"
p:driverClassName="com.mysql.jdbc.Driver"
p:url="jdbc:mysql://localhost:3306/mydb"
p:username="root"
p:password="masterkaoli"/>

</beans>

前面的XML配置非常的简洁,但是,拼写错误在运行时被发现而不是在设计时,除非我们使用IDE(例如:​​Intellij IDEA​​​或者​​Spring Tools​​​)支持自动属性完成当我们创建​​bean​​定义的时候。IDE助手是非常推荐的。

我们可以配置​​java.util.Properties​​实例,例如:

<bean id="mappings"
class="org.springframework.context.support.PropertySourcesPlaceholderConfigurer">
<!-- java.util.Properties类型配置 -->
<property name="properties">
<value>
jdbc.driver.className=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/mydb
</value>
</property>
</bean>

Spring容器通过使用​​JavaBeans​​的PropertyEditor机制转换元素值为​​java.util.Properties​​。这是一个不错的捷径,并且是Spring团队更喜欢使用嵌套的元素而不是value属性样式。

  • ​idref​​​元素
    ​​​idref​​元素是一个简单的容错方式,将容器中另外bean的id传递(字符串值-不是引用)到 或元素。下面的例子展示怎样去使用:
<bean id="theTargetBean" class="..."/>
<bean id="theClientBean" class="...">
<property name="targetName">
<idref bean="theTargetBean"/>
</property>
</bean>

前面bean定义片段和下面的片段相同:

<bean id="theTargetBean" class="..." />
<bean id="client" class="...">
<property name="targetName" value="theTargetBean"/>
</bean>

参考代码:​​com.liyong.ioccontainer.starter.XmlIocContainer​

第一种形式优于第二种形式,因为使用​​idref​​标记可使容器在部署时验证所引用的名Bean实际上是否存在。在第二个变体中,不对传递给客户端bean的targetName属性的值执行验证。拼写错误仅在实际实例化客户端bean时才发现(最有可能导致致命的结果)。如果这个客户端bean是原型bean,这个拼写和结果异常可能在这个容器部署后很久才被发现。

在​​idref​​​标签元素上的的​​local​​​属性在​​spring-beans.xsd 4.0​​​后不在支持。因为它没有提供常规的​​bean​​​引用值。当升级到​​spring-beans.xsd 4.0​​​更改​​idref local​​​为​​idref bean​

​idref​​​标签元素带来的价值的地方是在​​ProxyFactoryBean​​​ ​​bean​​​定义中配置​​AOP拦截器​​。指定拦截器名称时使用元素可防止你拼写错误的拦截器ID。

  • 引用其他的bean
    ​​​ref​​​元素是​​<constructor-arg/>​​​ 或中定义的最后一个元素。在这里,我们通过设置一个​​bean​​​的指定属性的值引用被容器管理的其他​​bean​​​。引用的​​bean​​​是要设置其属性的​​bean​​​的依赖关系,并且在设置属性之前根据需要对其进行初始化。(如果这个协作者是一个单例​​bean​​​,它可能已经被容器初始化)所有的引用最终引用其他对象。​​bean​​​的范围和校验依赖你是否有指定其他对象通过​​bean​​​或者​​parent​​​属性指定的​​id​​​或者​​name​​​。
    指定目标bean通过标签的bean属性是最常见的形式并且允许在同一个容器或父容器引用任何的被创建的​​​bean​​​,而不管是否在同一个​​XML​​​配置文件。bean属性的值可以与目标bean的id属性相同,也可以与目标bean的name属性中的值之一相同。下面例子展示怎样使用​​ref​​元素。
<bean id="userService" class="com.liyong.ioccontainer.service.UserService"> 
<!--属性注入 保存一种方式就可以-->
<property name="bookService">
<ref bean="bookService"/>
</property>
</bean>

通过​​parent​​​属性指定目标​​bean​​​的引用,这个​​bean​​​在当前容器的父容器中。​​parent​​​属性的值可以与目标​​Bean​​​的​​id​​​属性或目标​​Bean​​​的​​name​​​属性中的值之一相同(​​id​​​或者​​name​​​指定引用)。这个目标​​bean​​​必须在父容器中。当你有一个分层的容器并且你想去包装一个在父容器中存在的​​bean​​​为代理对象同时有一个相同的名字作为这个父​​bean​​​,你应该主要的使用这个​​bean​​​应用的变体。下面的两个例子展示类怎样使用​​parent​​属性。

<!--在父容器上下文-->
<bean id="accountService" class="com.something.SimpleAccountService">
<!-- insert dependencies as required as here -->
</bean>
<!-- 在子容器上下文 -->
<!-- 产生一个代理bean,bean name is the same as the parent bean -->
<bean id="accountService"
class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="target">
<ref parent="accountService"/>
<!-- notice how we refer to the parent bean -->
</property>
<!-- insert other configuration and dependencies as required here -->
</bean>
  • 内部bean
    或元素在内定义内部bean,像下面例子展示:
<!-- 外部bean定义 -->
<bean id="outer" class="...">
<!-- instead of using a reference to a target bean, simply define the target bean inline -->
<property name="target">
<!-- 内部bean定义 -->
<bean class="com.example.Person">
<property name="name" value="Fiona Apple"/>
<property name="age" value="25"/>
</bean>
</property>
</bean>

一个内部​​bean​​​定义不需要定义一个​​id​​​或名称。如果指定名称,这个容器不会使用这个值作为标识(不会使用定义的作为​​id​​​或​​name​​​标识)。容器在创建内部bean或忽略​​Scope​​​(作用域),因为内部​​bean​​​总是匿名的并且总是依赖外部​​bean​​​的创建。不可能独立访问内部​​bean​​​或将它们注入到协作​​bean​​​中而是封装在​​bean​​中。一个极端的情况,可能接受定制的Scope的销毁回掉-例如:一个请求域内部​​bean​​​包含在一个单例​​bean​​中。内部bean实例的创建与其包含的bean绑定在一起,但是销毁回调使它可以参与请求范围的生命周期。这不是常见的情况。内部bean通常只共享其包含bean的作用域。

参考代码:​​com.liyong.ioccontainer.starter.XmlOutterInnerBeanIocContainer​

  • 集合
    、、和元素分别设置Java集合类型List、Set、Map和Properties的属性和参数。下面例子展示怎样使用:
<bean id="moreComplexObject" class="example.ComplexObject">
<!-- adminEmails属性为Properties类型。 results in a setAdminEmails(java.util.Properties) call -->
<property name="adminEmails">
<props>
<prop key="administrator">[email protected]</prop>
<prop key="support">[email protected]</prop>
<prop key="development">[email protected]</prop>
</props>
</property>
<!--someList属性为java.util.List类型。 results in a setSomeList(java.util.List) call -->
<property name="someList">
<list>
<value>a list element followed by a reference</value>
<ref bean="myDataSource" />
</list>
</property>
<!--someMap属性类型为:java.util.Map。 results in a setSomeMap(java.util.Map) call -->
<property name="someMap">
<map>
<entry key="an entry" value="just some string"/>
<entry key ="a ref" value-ref="myDataSource"/>
</map>
</property>
<!--someSet属性类型为:java.util.Set。 results in a setSomeSet(java.util.Set) call -->
<property name="someSet">
<set>
<value>just some string</value>
<ref bean="myDataSource" />
</set>
</property>
</bean>

Map key的值、值、或者set的值,可以是任何下面元素:

bean | ref | idref | list | set | map | props | value | null

参考代码:​​com.liyong.ioccontainer.starter.XmlCollectionsIocContainer​

  • 集合合并
    Spring容器也支持对集合合并。应用程序开发人员可以定义、、或元素有一个子元素集合、、或继承和覆盖父集合元素。因此,子集合的值是父集合和子集合合并元素后的结果,也就是子集合元素会覆盖父集合的元素值。
    在合并章节讨论父-子bean的机制。不熟悉父bean和子bean定义的读者可能希望先阅读​​​相关​​​部分,然后再继续。
    下面的例子展示集合的合并:
<beans>
<bean id="parent" abstract="true" class="example.ComplexObject">
<property name="adminEmails">
<props>
<prop key="administrator">[email protected]</prop>
<prop key="support">[email protected]</prop>
</props>
</property>
</bean>
<bean id="child" parent="parent">
<property name="adminEmails">
<!-- the merge is specified on the child collection definition -->
<props merge="true">
<prop key="sales">[email protected]</prop>
<prop key="support">[email protected]</prop>
</props>
</property>
</bean>
<beans>

注意:在​​child​​​的​​bean​​​定义的属性​​adminEmails​​​的元素的属性是​​merge=true​​​。当这个​​child​​​ bean被容器解析和初始化的时候,这个结果实例有​​adminEmails​​​ ​​Properties​​集合,这个集合包含了子集合和父集合的adminEmails合并集。结果列表:

[email protected]
[email protected]
[email protected]

为了支持在父​​Properties​​​值被覆盖,子​​Properties​​​集合的值从父中继承所有的值和子​​Properties​​​的值(译者:意思是子Properties会覆盖父​​Properties​​中重复的值)。

这个合并行为适用类似、、集合类型 。在元素的特定情况下,将维护与List集合类型关联的语义(即,值有序集合的概念)。父元素的值优先与所有的子元素值。在​​Map​​​、​​Set​​​和​​Properties​​​集合类型中不存在顺序。因此,对于容器内部使用的关联​​Map​​​、​​Set​​​和​​Properties​​实现类型下的集合类型,没有有效的排序语义。

  • 集合合并限制
    我们不能合并不同集合类型(例如:Map和List)。如果尝试去合并将会抛出一个Exception异常。这个merge属性必须被指定在子类中。在父集合定义中指定merge属性是多余的并且不会达到预期结果。
  • 强类型集合
    在Java 5中泛型被引入,我们可以使用强类型集合。因此,仅仅包含​​​String​​元素的集合声明成为可能。如果我们使用Spring去依赖注入一个强类型的Collection到一个bean中,可以利用Spring的类型转换在添加到Collection集合前对集合实例元素转换为适合的类型。下面的java代码和bean定义展示了怎样去使用:
public class SomeClass {

private Map<String, Float> accounts;

public void setAccounts(Map<String, Float> accounts) {
this.accounts = accounts;
}
}
<beans>
<bean id="something" class="x.y.SomeClass">
<property name="accounts">
<map>
<entry key="one" value="9.99"/>
<entry key="two" value="2.75"/>
<entry key="six" value="3.99"/>
</map>
</property>
</bean>
</beans>

当准备注入​​something​bean的​​accounts​​​属性时,可以通过反射获得有关强类型​​Map <String,Float>​​​的元素类型的泛型信息。因此,​​Spring​​​的类型转换基础设施识别各种元素​​Float​​​类型的值并且这些字符串值能够被转换为真实的​​Float​​类型。

  • Null和空字符串值
    Spring将属性等的空参数视为空字符串。下面基于XML的配置元数据片段设置了email属性为空字符串
<bean class="ExampleBean">
<property name="email" value=""/>
</bean>

和下面java代码相等:

exampleBean.setEmail("");

元素被处理为null值,下面展示例子:

<bean class="ExampleBean">
<property name="email">
<null/>
</property>
</bean>

和下面java代码相等:

exampleBean.setEmail(null);
  • ​p-namespace​​​快捷方式
    ​​​p-namespace​​​让你使用​​bean​​​元素的属性(嵌入元素替换)去描述你的属性值协作者bean,或者两种都使用。
    Spring支持可扩展的​​​namespace​​​配置格式,基于XML Schema定义。本章讨论的bean配置格式在XML Schema文档中定义。然而,​​p-namespace​​在XSD文件中没有被定义仅仅在​​Spring Core​​​中存在。
    下面例子展示两个XML片段其解析结果是一致的.
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">

<bean name="classic" class="com.example.ExampleBean">
<property name="email" value="[email protected]"/>
</bean>

<bean name="p-namespace" class="com.example.ExampleBean"
p:email="[email protected]"/>
</beans>

该示例显示了​​p-namespace​​​中的一个属性,该属性在bean定义中称为​​email​​​。这告诉​​Spring​​​包含一个属性的声明。前面提到,​​p-namespace​​​没有​​schema​​​定义,因此你可以设置属性名称为​​property​​(类字段)名称。

下一个示例包括另外两个bean定义,它们都有对另一个bean的引用:

<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">

<bean name="john-classic" class="com.example.Person">
<property name="name" value="John Doe"/>
<property name="spouse" ref="jane"/>
</bean>

<bean name="john-modern"
class="com.example.Person"
p:name="John Doe"
p:spouse-ref="jane"/>

<bean name="jane" class="com.example.Person">
<property name="name" value="Jane Doe"/>
</bean>
</beans>

这个例子包含了不仅属性值使用p-namespace而且还使用指定格式去声明属性引用。第一个定义使用去创建一个从bean ​​john​​​到bean ​​jane​​​的引用,第二个​​bean​​​定义使用​​p:spouse-ref="jane"​​​作为一个属性去做相同的事情。在这个例子中,​​spouse​​​是属性名称,​​ref​​表示不是一个直接值而是一个引用值。

p-namespace不像标准的XML格式灵活。例如,声明属性引用的格式与以Ref结尾的属性冲突,而标准XML格式则不会。我们推荐你选择你的方式小心地并和你的团队交流去避免在用一时间同时使用XML三种方式。

参考代码:​​com.liyong.ioccontainer.starter.XmlPNameSpaceIocContainer​

  • ​c-namespace​​​快捷方式
    类似p-namespace的快捷方式,在​​​Spring3.1​​​引入,​​c-namespace​​​允许配置构造函数参数内联属性而不是嵌入​​constructor-arg​​​元素。
    下面的例子使用​​​c:​​​命名空间去做相同的基于​​构造函数依赖注入​​事情:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:c="http://www.springframework.org/schema/c"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">

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

<!-- 传统声明可选参数 -->
<bean id="beanOne" class="x.y.ThingOne">
<constructor-arg name="thingTwo" ref="beanTwo"/>
<constructor-arg name="thingThree" ref="beanThree"/>
<constructor-arg name="email" value="[email protected]"/>
</bean>

<!-- c-namespace声明参数名称 -->
<bean id="beanOne" class="x.y.ThingOne" c:thingTwo-ref="beanTwo"
c:thingThree-ref="beanThree" c:email="[email protected]"/>

</beans>

​c:​​​命名空使用类似于​​p:​​​相同的约束(​​bean​​​引用为跟随​​-ref​​​)去通过它们的名字设置构造参数。类似地,即使它没有在​​XSD schema​​​中定义,也需要在XML文件中去声明,(存在​​Spring Core​​中)。

一个少见的场景,构造函数参数名字不能使用(通常如果编译字节码时没有调试信息)可以使用回退参数索引,如下:

<!-- c-namespace index declaration -->
<bean id="beanOne" class="x.y.ThingOne" c:_0-ref="beanTwo" c:_1-ref="beanThree"
c:_2="[email protected]"/>

由于XML语法,要求这个索引符号​​_​​必须存在,因为XML属性名称不能以数字开头(即使IDE工具允许也是不行的)。对应索引的符号在元素也是有效的但是不常用,因为这个声明顺序通常已经足够。

在实践中,构造函数解析机制在匹配参数是非常高效的,因此,除非你真的需要,我们推荐整个配置都使用名字符号。

参考代码:​​com.liyong.ioccontainer.starter.XmlCNameSpaceIocContainer​

  • 复合属性名
    当设置​​​bean​​​属性的时候,我们可以使用复合或嵌入属性名,只要这个​​path​​​(点式引用)下面所有组件期望这个最终属性名不为​​null​​​。考虑下面的​​bean​​定义:
<bean id="something" class="things.ThingOne">
<property name="fred.bob.sammy" value="123" />
</bean>

这个​​something​​​ bean有一个​​fred​​​属性,​​fred​​​有一个​​bob​​​属性,​​bob​​​拥有一个​​sammy​​​属性并且最终​​sammy​​​属性被设置值为​​123​​​。为了能正常工作,​​something​​​的属性​​fred​​​和​​fred​​​的属性​​bob​​​在这个​​bean​​​构造之前必须不能为​​null​​​。否则,会抛出一个​​NullPointerException​​。

1.4.3 使用​​depends-on​

如果​​bean​​​依赖其他​​bean​​​,也就是意味着bean需要设置依赖的​​bean​​​属性。典型地,我们可以基于XML配置元数据使用​​ref​​​去完成。然而,一些bean之间的依赖不是直接的。一个例子是在类中一个静态的初始化器需要被触发,例如:数据库驱动注册。​​depends-on​​​属性能够显示地强制一个或多个​​bean​​​在依赖​​bean​​​初始化之前初始化。下面的例子使用​​depends-on​​​属性去表达对一个简单​​bean​​的依赖。

<!--beanOne依赖manager-->
<bean id="beanOne" class="ExampleBean" depends-on="manager"/>
<bean id="manager" class="ManagerBean" />

为了表达多个bean的依赖,提供一个bean名称的集合列表作为​​depends-on​​​属性值(​​,​​​、​​;​​​、​​空格​​是有效的分隔符)。

<!--beanOne依赖manager,accountDao-->
<bean id="beanOne" class="ExampleBean" depends-on="manager,accountDao">
<property name="manager" ref="manager" />
</bean>

<bean id="manager" class="ManagerBean" />
<bean id="accountDao" class="x.y.jdbc.JdbcAccountDao" />

仅仅在​​单例bean​​​场景下,​​depends-on​​​属性能够指定初始化时间依赖和对应的销毁时间依赖。与给定bean定义依赖关系的从属bean首先被销毁,然后再销毁给定bean本身(译者:被依赖的bean先销毁,在销毁宿主bean)。因此,​​depends-on​​可以控制关闭顺序。

参考代码:​​com.liyong.ioccontainer.starter.XmlDependOnSpaceIocContainer​

1.4.4 bean的懒加载

默认情况下,​​ApplicationContext​​​实现更早的创建和配置所有的​​单例​​​​bean​​​作为初始化过程的一部分。通常地,这个前置初始化是可取的,因为错误的配置或环境变量被立即的发现,而不是几个小时甚至几天后才被发现。当这个行为不是可取的时候,我们可以通过标记​​bean​​​作为一个懒加载的单例​​bean​​​去阻止提前初始化。一个懒加载​​bean​​告诉容器当第一次请求的时候去创建实例而不是在容器启动时候。

在XML配置中,这个行为通过在元素的属性​​lazy-init​​控制的。下面例子展示:

<!--设置bean延迟初始化 注意:Spring中的bean默认是单例的-->
<bean id="lazy" class="com.something.ExpensiveToCreateBean" lazy-init="true"/>
<bean name="not.lazy" class="com.something.AnotherBean"/>

当前面的配置通过​​ApplicationContext​​​加载并启动时,这个​​lazy​​​ bean没有被提前的初始化,而​​not.lazy​​ bean被尽早的初始化。

然而,当一个懒加载bean是另一个单例​​bean​​​的依赖时候,这个懒加载不是懒加载的。​​ApplicationContext​​​在启动时创建这个懒加载​​bean​​​,因为它必须满足这个单例​​bean​​​的依赖。这个懒加载​​bean​​​被注入到一个单例​​bean​​所以它不是懒加载的。

我们也可以在容器级别通过使用元素的​​default-lazy-init​​属性控制懒加载,下面例子展示怎样使用:

<beans default-lazy-init="true">
<!-- no beans will be pre-instantiated... -->
</beans>

参考代码:​​com.liyong.ioccontainer.starter.XmlLazyInitilaizedIocContainer​

1.4.5自动装配协调者

Spring容器能自动装配协调者bean之间的关系。通过检查​​ApplicationContext​​的内容,Spring自动为你的bean解析协作者(其他依赖bean)。自动装配有下面的优势:

  • 自动装配能显著的降低对属性和构造函数参数的需要。(其他机制例如:在这方面,其他机制(例如本章其他地方讨论的bean模板)也很有价值)。
  • 自动装配可以随着对象的演化而更新配置。例如:如果你需要添加一个类的依赖,依赖能够被自动地被满足不需要修改配置。因此,自动装配在开发过程中特别有用,当代码库变得更加稳定时,自动装配可以避免切换到显式连接的选项。

当使用基于XML元数据配置,我们可以为这个​​bean​​​指定自动装配模式通过元素的​​autowire​​属性。自动装配有4种模式。你可以对每个bean指定自动装配因此可以选择自动装配哪些bean。下面的表格描述了4种装配模式:

Mode

Explanation

​no​

默认不自动装配. ​​bean​​​引用必须通过​​ref​​定义. 对于较大的部署,建议不要更改默认设置,因为明确指定协作者可以提供更好的控制和清晰度。 在某种程度上,它记录了系统的结构。

​byName​

通过属性名自动装配。​​Spring​​​查找一个​​bean​​​与自动装配属性名相同的​​bean​​​名字。例如:如果bean定义是被设置为通过名字自动注入并且包含了一个​​master​​​属性(也就是,有一个​​setMaster(..)​​​方法),​​Spring​​​查找一个​​master​​​名字的​​bean​​并且使用它设置到属性上。

​byType​

如果在容器中和属性具有相同类型的唯一​​bean​​​存在会被自动注入到属性。如果有多个​​bean​​​存在,一个致命的异常被抛出,表示你不能使用​​byType​​​为bean自动装配。如果没有匹配的​​bean​​,不发生任何事情(属性不被设置)。

​constructor​

类似于​​byType​​​,但是使用构造函数参数。如果在容器中没有一个​​bean​​​被匹配到会抛出一个致命的​​error​​。

使用​​byType​​​或构造函数自动装配模式,你可以结合数组和类型化的集合,在这种情况下,提供容器中与期望类型匹配的所有自动装配候选,以满足相关性。如果期望​​key​​​的 类型是​​String​​​,你可以自动装配强类型的​​Map​​​实例。一个自动装配​​Map​​​实例的值由所有匹配期望类型实例组成,并且这个Map实例的这些​​key​​​与​​bean​​名称对应。

  • 自动装配的优势和限制
    在一个系统中一致地使用自动装配将工作的更好。如果通常不使用自动装配,则可能使开发人员仅使用自动装配来连接一个或两个bean定义而感到困惑。
    考虑自动装配的限制和优势:
  • 在​​property​​​和​​constructor-arg​​​中显示依赖设置总是覆盖自动装配。你不能自动装配简单的属性例如:原生类型,​​String​​​和​​Class​​(简单属性数组)。这种限制是由设计造成的。
  • 自动装配没有显示装配精确。尽管如前面的表中所述,​​Spring​​​小心地避免猜测,以免产生可能产生意外结果的歧义。​​Spring​​管理对象之间的关系不再明确记录。
  • 装配信息可能对从​​Spring​​容器生成文档的工具不可用。
  • 容器中的多个​​bean​​​定义可能与要自动装配的​​setter​​​方法或构造函数参数指定的类型相匹配。对于数组、集合或​​Map​​​实例,这不一定是问题。然而,为了依赖期望一个简单值,这种歧义不会被任意解决(意思是期望一个​​bean​​​容器中确有多个匹配的​​bean​​​)。如果没有唯一的有效​​bean​​的定义会抛出一个异常。

在最后的场景中,你有一些可选项:

  • 显示的装配放弃自动装配。
  • 通过设置​​bean​​​定义的​​autowire-candidate​​​属性为​​false​​​去避免自动装配,在​​下一个章节​​描述。
  • 通过设置元素属性​​primary​​​为​​true​​​指定一个​​bean​​定义作为主要的候选者。
  • 实现更细粒度的有效控制通过基于注解的配置,在​​基于注解容器配置​​中描述。
  • 自动装配排除bean
    在每个bean的基础上,你可以从自动装配中排除一个​​​bean​​​。在​​Spring​​​的XML格式中,设置元素的​​autowire-candidate​​​属性为​​false​​​。容器使该特定的​​bean​​定义不适用于自动装配基础结构(包括注释样式配置,例如@Autowired)。

autowire-candidate属性被设计仅仅通过基于类型自动装配有影响。它不会影响通过名字来显示引用的方式,即使这个指定bean没有被标记作为一个自动装配候选者这个名字也会被解析。因此,如果名称匹配,按名称自动装配仍然会注入Bean。

  • 可以基于与​​Bean​​​名称的模式匹配来限制自动装配候选者。顶层元素接受一个或多个表达式在​​default-autowire-candidates​​​ 属性中。例如,去限制自动装配候选者任意状态的​​bean​​​,它的名字以​​Repository​​​结尾,提供一个值为​​*Repository​​​的表达式。提供多个表达式可通过​​;​​​号分割。为一个​​bean​​​定义的​​autowire-candidate​​​属性显示指定​​true​​​或​​false​​​总是优先级最高(比如​​default-autowire-candidates​​​优先级高),指定的规则被覆盖。
    当这些​​​bean​​​不想自动装配注入到其他​​bean​​​中时,这些技术是非常有用的。这并不意味着一个被排除的​​bean​​​本身不能通过使用自动装配来配置。而是,​​bean​​​本身不是一个候选者不会被注入到其他​​bean​​中。

参考代码:​​com.liyong.ioccontainer.service.AutowireCandidateService​

1.4.6 方法注入

在大多数应用场景中,在容器中大多数​​bean​​​是单例的。当单例​​Bean​​​需要与另一个单例​​Bean​​​协作或非单例​​Bean​​​需要与另一个非单例​​Bean​​​协作时,典型的处理依赖通过定义一个​​bean​​​作为其他​​bean​​​的属性。当​​bean​​​的生命周期不同时会出现问题。假设单例bean ​​A​​​需要使用非单例bean ​​B​​​(原型),假设​​A​​​的每个方法被调用。这个容器仅仅创建单例bean ​​A​​​一次并且只有一次机会去设置属性。容器无法每次为bean ​​A​​​提供一个新的bean ​​B​​​实例(单例A每次从容器获取bean ​​B​​​不能每次提供一个新​​bean​​)。

一个解决方案时放弃控制反转。我们也可以通过实现​​ApplicationContextAware​​​接口让bean ​​A​​​意识到容器。并在bean ​​A​​​每次需要bean ​​B​​​时,通过使用​​getBean("B")​​​获取一个新实例​​bean​​。下面例子展示使用方式:

package fiona.apple;

// Spring-API imports
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;

public class CommandManager implements ApplicationContextAware {

private ApplicationContext applicationContext;

public Object process(Map commandState) {
// grab a new instance of the appropriate Command
Command command = createCommand();
// set the state on the (hopefully brand new) Command instance
command.setState(commandState);
return command.execute();
}

protected Command createCommand() {
// 通过从容器获取bean
return this.applicationContext.getBean("command", Command.class);
}

public void setApplicationContext(
ApplicationContext applicationContext) throws BeansException {
//容器注入ApplicationContext
this.applicationContext = applicationContext;
}
}

上面的例子不是可取的,因为这个业务代码需要容器回掉耦合了​​Spring​​​ 框架(这个我不敢苟,上面我也发表了观点)。方法注入,​​Spring IoC​​​容器高级特性,让我们处理这种场景更简洁。(译者:上面​​Command​​配置为原型才能达到效果)

可以阅读更多关于方法注入的动机在 ​​博客入口​

  • 查找方法注入
    查找方法注入是容器覆盖容器管理​​​bean​​​上的方法并返回容器中另一个命名​​bean​​​的查找结果的能力。在前面描述的场景中,典型地查找涉及到原型bean。​​Spring​​​框架通过​​CGCLB​​库去动态地生成一个子类去覆盖这些方法以实现方法注入。
  • 为了动态子类能够正常工作,​​Spring bean​​​不能是​​final​​​并且方法也不能是​​final​​。
  • 单元测试一个具有抽象方法的类需要你自己去子类化并且提供一个抽象方法的存根实现。
  • 组件扫描也需要具体方法,这需要具体的类别。
  • 进一步关键限制是方法查找不能在工厂方法并且特别在​​configuration​​​类被​​@Bean​​​标注的方法,因为,在这种情况,容器不负责创建实例,因此无法即时创建运行时生成的子类(因为这种方法​​Bean​​​是由我们自己创建处理的容器不能控制​​bean​​的生成)。
  • 在前面的代码片段​​CommandManager​​​中,​​Spring​​​容器动态的覆盖这个​​createCommand​​​方法的实现。​​CommandManager​​​类没有任何的​​Spring​​的依赖,重构如下:
package fiona.apple;

// 没有Spring的依赖!
public abstract class CommandManager {

public Object process(Object commandState) {
// grab a new instance of the appropriate Command interface
Command command = createCommand();
// set the state on the (hopefully brand new) Command instance
command.setState(commandState);
return command.execute();
}

// okay... but where is the implementation of this method?
protected abstract Command createCommand();
}

在客户端类中包含被注入的方法(在这个例子中是​​CommandManager​​类),这个方法被注入要求一个下面格式的签名:

<public|protected> [abstract] <return-type> theMethodName(no-arguments);

如果这个方法是​​abstract​​,动态地生成子类实现这个方法。除此之外,动态生成子类覆盖在源类中具体方法定义。考虑下面例子:

<!-- a stateful bean deployed as a prototype (non-singleton) -->
<bean id="myCommand" class="fiona.apple.AsyncCommand" scope="prototype">
<!-- inject dependencies here as required -->
</bean>

<!-- commandProcessor uses statefulCommandHelper -->
<bean id="commandManager" class="fiona.apple.CommandManager">
<lookup-method name="createCommand" bean="myCommand"/>
</bean>

每当需要新的​​myCommand​​​ ​​bean​​​实例时,标识为​​commandManager​​​的bean就会调用其自己的​​createCommand()​​​方法。你必须非常的小心应用​​myCommand bean​​​作为一个原型,如果这是真实需要的。如果这个​​bean​​​是单例的,​​myCommand​​​实例每次都返回同一个​​bean​​。

或者,在基于注解组件模式中,你可以声明一个查找方法通过​​@Lookup​​注解,像下面例子:

public abstract class CommandManager {

public Object process(Object commandState) {
Command command = createCommand();
command.setState(commandState);
return command.execute();
}

//指定名称查找,在容器中查找
@Lookup("myCommand")
protected abstract Command createCommand();
}

或者,更习惯地说,你可以依靠针对查找方法的声明返回类型来解析目标​​Bean​​:

public abstract class CommandManager {

public Object process(Object commandState) {
MyCommand command = createCommand();
command.setState(commandState);
return command.execute();
}

//没有指定名称查找,通过查找方法的MyCommand类型去容器查找
@Lookup
protected abstract MyCommand createCommand();
}

注意,通常应该使用具体的存根实现声明此类带注释的查找方法,为了使它们与​​Spring​​​的组件扫描规则兼容,默认情况下,抽象类将被忽略。此限制不适用于显式注册或显式导入的​​Bean​​类。

获取不同范围的目标bean的其他方式是​​ObjectFactory​​​/​​Provider​​​注入点。查看 ​​Scoped Beans as Dependencies​​.

你也可以找到​​ServiceLocatorFactoryBean​​​(在​​org.springframework.beans.factory.config​​包)去使用。

参考代码:

​com.liyong.ioccontainer.starter.XmlLookUpInjectionIocContainer​

​com.liyong.ioccontainer.starter.XmlLookUpInjectionByAnnotaionIocContainer​

  • 任意方法替换
    与查找方法注入相比,方法注入的一种不太有用的形式是能够用另一种方式实现替换托管​​​bean​​​中的任意方法。你可以放心地跳过本节的其余部分,直到你真正需要此功能为止。
    基于XML元素数据配置,你可以使用​​​replaced-method​​​元素将现有的方法实现替换为已部署的​​Bean​​​。考虑下面的类,这个类有一个叫做​​computeValue​​的方法我们想去覆盖这个方法。
public class MyValueCalculator {

public String computeValue(String input) {
// some real code...
}

// some other methods...
}

类实现​​org.springframework.beans.factory.support.MethodReplacer​​接口提供新的方法定义,像下面定义:

public class ReplacementComputeValue implements MethodReplacer {

public Object reimplement(Object o, Method m, Object[] args) throws Throwable {
// get the input value, work with it, and return a computed result
String input = (String) args[0];
...
return ...;
}
}

​bean​​的定义去部署到源类并且指定方法覆盖类似如下例子:

<bean id="myValueCalculator" class="x.y.z.MyValueCalculator">
<!-- 替换方法 -->
<replaced-method name="computeValue" replacer="replacementComputeValue">
<arg-type>String</arg-type>
</replaced-method>
</bean>

<bean id="replacementComputeValue" class="a.b.c.ReplacementComputeValue"/>

你可以使用一个或多个在元素中去指示这个被覆盖的方法签名。仅当方法重载并且类中存在多个变体时,才需要对参数进行签名。

为了方便起见,参数的类型字符串可以是完全限定类型名称的子字符串。例如:​​java.lang.String​​:

java.lang.String
String
Str

因为参数的数量通常足以区分每个可能的选择,所以通过让你仅输入与参类型匹配的最短字符串,此快捷方式可以节省很多输入。

参考代码:​​com.liyong.ioccontainer.starter.XmlMethodReplaceIocContainer​


标签:容器,依赖,定义,Spring,bean,解析,IoC,属性
From: https://blog.51cto.com/u_13991401/5954005

相关文章

  • 3种 Springboot 全局时间格式化方式,别再写重复代码了
    “本文收录在个人博客:www.chengxy-nds.top,技术资料共享,同进步”时间格式化在项目中使用频率是非常高的,当我们的API接口返回结果,需要对其中某一个date字段属性进行......
  • Spring Security 动态url权限控制
    一、前言本篇文章将讲述SpringSecurity动态分配url权限,未登录权限控制,登录过后根据登录用户角色授予访问url权限基本环境 spring-boot2.1.8mybatis-plus2.2.0......
  • Babel源码解析(一):源码调试(上)
    Babel源码解析(一):源码调试(上)Versions开发环境、npm包版本信息:名称版本babelv7.20.6nodev16.16.0npmv8.11.0OSmacOS13.1(22C65)ForkBabel......
  • ConcurrentHashMap完全解析(jdk6/7,8)
    并发编程实践中,ConcurrentHashMap是一个经常被使用的数据结构,相比于Hashtable以及Collections.synchronizedMap(),ConcurrentHashMap在线程安全的基础上提供了更好的写并发能......
  • [编程基础] Python列表解析总结
    dates:2020-06-2614:29:00+0800tags:-编程基础-Python在本教程中,我们将学习使用Python列表解析(listcomprehensions)相关知识1使用介绍列表解析是一种基......
  • 狂神说 spring mvc 完整版
    springmvc1、回顾MVC1.1、什么是MVCMVC是模型(Model)、视图(View)、控制器(Controller)的简写,是一种软件设计规范。是将业务逻辑、数据、显示分离的方法来组织代码。MVC主......
  • Spring Boot 入门
    ​​spring​​ Boot是Spring社区较新的一个项目。该项目的目的是帮助开发者更容易的创建基于Spring的应用程序和服务,让更多人的人更快的对Spring进行入门体验,让​​Java​......
  • Spring处理跨域请求
    [nio-8080-exec-8]o.s.web.cors.DefaultCorsProcessor    :SkipCORSprocessing:requestisfromsameorigin一次正常的请求最近别人需要调用我们系统的某一个......
  • How to use JDBC-Authentication of Spring Boot/Spring Security with Flyway
     java.lang.IllegalStateException:FailedtoloadApplicationContextatorg.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadCon......
  • 新的三种EBS类型解析
    就在前两天,创建EBS的之后页面发生了点变化,出现三种新的类型:​​GeneralPurpose(SSD)Volumes​​​​ProvisionedIOPS(SSD)Volumes​​​​MagneticVolumes​​可见,......