Spring 5 中文解析核心篇-IoC容器之IoC容器和Bean概述

关于本书

本书

本书编写主要目的在于翻译官方spring.io关于SpringFramework模块文档之核心篇,但是本书不仅仅是简单的翻译,我会根据相应的模块给出一些代码的操作实践以及给出相应的源码分析,SpringFramework文档我个人认为在所有开源框架中算是写得最好的了,But如果对于初学者或者是实践经验较少的小伙伴来说还是比较困难的。这里不仅仅是文档全是英文形式而且根据文档的概述是比较难理解和应用到实践项目开发中,这里需要开发人员积累了相应的项目实践经验才行。So我在翻译SpringFramework过程中会不断编写相应的示例代码、结合文档和源码分析达到更好的理解。在后面的书中我可能会翻译现在比较流行的微服务框架SpringBootSpringCloud以及Spring的其他模块比如:SpringDataSpringSessionSpringSecurity 等,SpringFramework在整个Spring体系中起着基石的作用,SpringBoot基于SpringFramework进行了相应的拓展包括:自动化配置外部化配置组件化(start模式)等,而SpringCloud基于SpringBoot对整个微服务体系的服务治理、服务注册发现、统一配置中心等做了相关的集成和统一相关的抽象例如: Spring Cloud Commons等。对应SpringFramework这块主要分为以下模块来解析:Spring核心测试数据存储Web ServletWeb Reactive集成

版本

当前选择的版本Version 5.2.7.RELEASE进行分析。

约束

  • 使用字体加粗对重点关键字进行标注
  • 使用灰色线框对专业术语进行标识
  • 本文使用不同层级对文档结构进行描述
  • 文章的一级、二级分类尽量保存英文不做翻译(能够帮助读者能够快速定位原文位置)
  • 示例代码地址:https://gitee.com/newitman/SpringFramework5.2.6.git
  • 本书开始编写时间2020-05-05

作者

个人从事金融行业,就职过易极付、思建科技、某网约车平台等重庆一流技术团队,目前就职于某银行负责统一支付系统建设。自身对金融行业有强烈的爱好。同时也实践大数据、数据存储、自动化集成和部署、分布式微服务、响应式编程、人工智能等领域。同时也热衷于技术分享创立公众号和博客站点对知识体系进行分享。

博客地址:http://youngitman.tech

CSDN:https://blog.csdn.net/liyong1028826685

微信公众号:

image

概述

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

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

GroovyKotlinJava一样可以编译成JVM支持的字节码格式,运行在JVM中。

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

What We Mean by "Spring"

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

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

关于模块需要注意的:Spring的这些框架jar包允许被部署到JDK9的模块路径。为了在支持Jigsaw的应用程序中使用,Spring Framework 5 jar附带了自动模块名称清单条目,这些清单条目定义了与jar无关的稳定语言级别的模块名称(spring.corespring.context等)名称(这些jar遵循相同的命名模式,用-代替.,例如spring-corespring-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 8Tomcat 9, WebSphere 9, JBoss EAP 7服务容器。

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

Spring不断的创新和发展。除了Spring Framework,还有其他项目,例如Spring BootSpring SecuritySpring DataSpring CloudSpring 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

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

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

  • 更容易的集成SpringAOP特性
  • 消息资源处理(国际化)
  • 事件发布器
  • 应用层特定的上下文,例如:对应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接口代表SpringIoC容器,同时有责任对这些bean的实例化、配置和组装。容器通过获取配置元数据知道哪些对象需要进行实例化、配置和组装。这些配置元数据通过xml、java的注解或者java的配置(Java Config)。它允许你去表达组成应用程序的对象以及这些对象之间丰富的相互依赖关系。

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

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

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

container magic
1.2.1 配置元数据

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

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

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

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

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

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

这些bean的定义对应到真实的bean对象实例同时我们的应用程序由这些对象实例所构建。比如:我们定义的service层数据存储层(dao)展示层 ** 比如:StrutsAction实例、HibernateSessionFactories实例、JMS**的Queues等等。典型地,不需要在容器中配置细粒度的域对象,因为通常由DAO和业务逻辑负责创建和加载域对象。但是,你可以使用SpringAspectJ的集成来配置在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类和数据存储层JpaAccountDaoJpaItemDao对象组成(以JPA的对象关系映射为标准)。属性名称元素引用JavaBean的属性名称,并且ref元素引用其他bean定义的名称。id和ref之间的联系表达了协作对象的依赖性。更多的关于对象依赖的配置查看Dependencies

  • 组成基于XML的配置元数据

配置元数据可以跨多个XML配置文件。通常情况下,每个独立的XML配置文件代表项目架构中的逻辑层或者模块。

你可以使用ApplicationContext构造函数从这些配置的XML片段中加载bean的定义。这个构造函数可以使用多个Resource来定位资源,在上面的例子中可以查看。或者,使用一个或多个<import/>标签元素从其他的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.xmlmessageSource.xmlthemeSource.xml。所有被导入文件路径是相对应导入文件位置。(备注:大白话就是相对于当前导入文件的路径),因此services.xml必须在导入文件相同的目录或者类路径下,而messageSource.xmlthemeSource.xml必须在导入文件下的resources路径。正如你所看到/被忽略。但是,鉴于这些路径是相对的,最好不要使用斜线。

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

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

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

  • Groovy的bean定义

为外部配置元数据例举更进一步的例子,bean的定义能够使用SpringGroovy语言定义来表达(可以从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指令去导入XMLbean定义。

参考示例代码: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)。

博客地址:http://youngitman.tech

CSDN:https://blog.csdn.net/liyong1028826685

微信公众号:


qrcode_for_gh_2a18e179fcca_430-2.jpg

技术交流群:


草料活码430*430.png
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,132评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,802评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,566评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,858评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,867评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,695评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,064评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,705评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,915评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,677评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,796评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,432评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,041评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,992评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,223评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,185评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,535评论 2 343