【Java笔记02】Spring文档翻译-Core-1.The IoC Container

其实官方文档写得挺好,由浅入深且通俗易懂,但是一般人很少上手文档,网上的中文翻译大多也都是机器翻译+少量人工,看着看着就不知道在讲什么了,查博客又经常不知道博主在讲什么,所以我决定自己翻译文档,并且加入自己的一些见解,如有不对,请在评论区指出,感谢阅读~

双引号内的内容为我自己的注解,可能存在些许错误,翻译中也省略了原文少部分不影响阅读的无关紧要的句子,并且采用通俗化的方式进行翻译,没有原原本本的一个词一个词翻译,如果存在可能存在误解的句子或单词,我会在注解当中给出原文或者在括号中给出原词,以便您自行判断。

SpringFramework5.2.2 最新版本的官方文档总共分为八个部分

Overview history, design philosophy, feedback, getting started.
Core IoC Container, Events, Resources, i18n, Validation, Data Binding, Type Conversion, SpEL, AOP.
Testing Mock Objects, TestContext Framework, Spring MVC Test, WebTestClient.
Data Access Transactions, DAO Support, JDBC, O/R Mapping, XML Marshalling.
Web Servlet Spring MVC, WebSocket, SockJS, STOMP Messaging.
Web Reactive Spring WebFlux, WebClient, WebSocket.
Integration Remoting, JMS, JCA, JMX, Email, Tasks, Scheduling, Caching.
Languages Kotlin, Groovy, Dynamic Languages.

Overview讲的是一些Spring是什么以及Spring发展历史,有兴趣的建议查看百度百科或者是维基百科快速了解,没兴趣的可以直接看下面开始翻译Core部分

起始时间:2020-01-14


核心技术

5.2.2RELEASE版本

这部分的参考文献覆盖了所有Spring框架的技术部分。

其中最重要的就是控制反转(IoC)容器。在对IoC容器进行全面的介绍以后,还会对面向切面技术(AOP)进行全面的介绍。Spring框架有自己的AOP框架,它很容易理解,而且已经解决了企业应用中80%的AOP需求。

IoC(又叫依赖注入DI)具体是什么,推荐去掘金上进行细致的了解。大致来说就是不要让对象在实例化时在类内部进行依赖其他对象。A、B之间存在依赖可以这么理解,在类A的构造函数里实例化一个类B,每当A创建时就有个B随之创建,这样A、B之间就是相互依赖的。IoC的思想是不要这样做,而是把他们统一管理起来,用IoC容器统一分配,分别创建A、B两个实例,接着再把B注入到A中,这就是控制反转或者是依赖注入。

还提供了Spring与AspectJ集成的内容(目前在特性方面是最丰富的,当然,也是Java企业里最成熟的AOP方案)

1.IoC容器

这一章涵盖了Spring的控制反转容器内容。

1.1Spring IoC容器和Beans的介绍

这一节涵盖了Spring框架IoC实现的原理。IoC被认为是依赖注入,它是一个只通过构造参数、工厂方法的参数或者对象实例的属性在对象实例化后或者被工厂方法返回后,注入依赖的过程。容器在这些对象被创建的时候注入这些依赖。这个过程是一种反方向的过程,即当A需要B时不是A请求B,而是将B注入A,故称为控制反转。

从Overview开始到以上大多数都是闲谈,这是非常不好的现象,现在很多教科书式的东西,都喜欢在开头扯一堆有的没的,等他扯完了,你已经开始觉得累了,这个时候他就开始讲重点了。我认为正确的做法应当是用具象的方法快速介绍完要讲的概念,让读者快速进入重点才是合理的。(

org.springframework.beans和org.springframework.context这两个包是Spring框架IoC容器的基础。BeanFactory接口提供了一个能够管理任何类型对象的高级管理机制,ApplicationContext是它的一个子接口,它比前者额外增加了:

总之,BeanFactory提供了配置的框架和基本特性,ApplicationContext则添加了更多为不同应用定制的功能。ApplicationContext对于BeanFactory是类似超类的一个超集合。在本节中描述Spring IoC容器时会专门使用它。更多关于如何使用BeanFactory代替ApplicationContext的信息请查看[beans-beanfactory]

在Spring中,所有对象形成了你的应用的脊梁柱,它们都被Spring IoC容器管理并且被称为Beans(所以提到Bean可以和Object等价)。一个Bean是由Spring IoC容器实例化、组装和管理的对象。Bean及其之间的依赖关系通过 配置元数据(configuration metadata)来让容器获知。

1.2容器概况

org.springframework.context.ApplicationContext接口展现了Spring IoC容器和负责实例化、配置和组装Beans。容器读取配置获取关于实例化、配置和组装哪些对象的指令。配置元数据可以通过XML文件、Java注解或者Java代码三种方式书写。这可以让你决定用哪些对象组成应用以及表达这些对象间丰富的依赖关系。

关于ApplicationContext接口的实现是Spring提供的。在独立的应用里,创建ClassPathXmlApplicationContext或FileSystemXmlApplicationContext是很常见的事情。虽然XML一直是定义配置元数据的传统方式,但也可以通过提供少量的XML配置以声明使用Java注解或者Java代码作为元数据格式。

在大多数的应用场景中,不需要用户显性的实例化Spring IoC容器。比如,在web应用场景中,一个大约8行的web.xml文件就足够了( [context-create])。如果你使用eclipse的Spring开发套件Spring Tool Suite,则只需要轻松点几下鼠标键盘就可以创建这样的样本配置。

下图展现了Spring的工作流程。你的程序的对象和配置元数据相结合,这样,在初始化ApplicationContext之后,就拥有一个完全配置并且可执行的应用程序了。

container magic

1.2.1配置元数据

正如上图表示的,Spring IoC容器需要一个配置元数据,这个配置元数据是你用来告诉Spring容器如何实例化、配置和组装你应用程序里的对象的。

配置元数据通常以简单的XML格式展现,本章大部分内容都使用这个格式。

基于XML的元数据并不是唯一的方式,Spring IoC容器本身与配置元数据格式完全解耦。现在许多的开发人员为他们的Spring应用选择给予Java的配置

关于其他元数据的信息:

Spring配置包括至少一个Bean。基于XML的配置元数据在<beans />里面使用<bean />元素配置这些beans。Java配置通常在@Configurationlei类中使用@Bean注解。

这些bean定义对应了应用程序里的实际对象。通常,你要定义服务层对象,数据访问对象(DAOs),表示对象(如Struts Action实例),基础设施对象(如Hibernate SessionFactories,JMS队列)。通常不用在配置文件中深入配置域对象,这些通常是由DAOs和事务逻辑来编写的。但是你可以使用Spring与AspectJ的集成来配置那些在IoC容器控制外的对象。详情请看Using AspectJ to dependency-inject domain objects with Spring

接下来的例子会展示基于XML的配置元数据的基础结构:

id属性是一个字符串,它表示一个bean的定义名称。

class属性定义了bean所对应的类,必须使用完整正确的类名。

id的值是指协作对象,本例中没有引用到协作对象。详情请了解Dependencies

1.2.2初始化容器

一个ApplicationContext的构造器需要提供一个作为资源字符串的路径参数,这样才能让容器加载配置元数据。

在你了解了IoC容器以后,你也许想要进一步了解Spring的资源抽象(详细请看[resources]),它能提供一个方便的机制去读取URI语法下的路径所对应位置的输入流,特别是资源路径被用来构造应用程序的Context,如[resources-app-ctx]

下面的例子将展示服务层的对象(service.xml):

下面的例子展示了数据访问对象(daos.xml):

在上面的例子中,服务层包括一个PetStoreServiceImpl类和两个数据访问对象类型分别是JpaAccountDao和JpaItemDao(这是基于JPA对象关系映射标准)。property元素的name属性是指另一个bean的name或者id。property这种在id和ref之间的联系表示的是一种依赖关系,更多关于依赖的配置请看Dependencies

源代码里的文档解释,大概是这样的

首先你新建一个对象用来依赖,为了直观表示,我们定义一个init用来作为它被构造时的一个反馈

接着新建另一个对象让它获取前面那个对象,同样的也定义一个init作为构造时的反馈

注意这里一定要有setTest函数,这个是JavaBean的一个定义要求,也是Spring文档里明确写出的,属性元素一定要有对应的setter方法

接着我们定义配置文件day1.xml

最后在主程序加载配置文件并且新建实例就行了,由于我用的是springboot,所以主函数可能有些许不一样,这里把不一样的行注释掉

运行结果显示如果定义了一个Test1实例,则会打印test1,test2的字样,这说明这个依赖是正确有效的。

这样就把一个Another对象注入到Hello对象中了。

组合基于XML的配置元数据

跨越多个XML文件的bean定义通常是有益的,每个XML代表你架构里的逻辑层或者模块。

你可以使用Application Context构造器来加载所有的XML碎片文件里的bean定义,这个构造器如前面章节所示可以获取多个资源位置。或者你也可以使用多个import元素来加载这些XML文件,如下:

在这个例子中,外部的bean被定义在文件services.xml、messageSource.xml、themeSource.xml中,且所有路径都是相对于当前正在进行import的文件目录,所以,services.xml一定是和这个文件同目录的,而messageSource.xml和themeSource.xml一定是在这个文件路径下的resources文件夹里的。你也可以注意到,第一个和第二个resource的路径前面的斜杠被省略了,但是,考虑到这些路径都是相对的,所以最好不要使用斜杠(避免产生误解)。根据Spring模式,导入的文件内容,包括<beans />元素,必须都是有效的XML内容。

如果要用到父目录,可以使用"../",但这是不推荐的。这么做会在应用程序之外的文件上创造一层依赖。尤其是不推荐使用于classpath:的URL里,如"classpath:../services.xml",因为运行时解析过程会使用最近的路径根,然后查看它的父目录,而路径的更改可能会导致不同的、不正确的目录。

你可以使用绝对路径作为替代方案,例如file:C:/config/services.xml或者classpath:/config/services.xml,但是这会使你将配置文件耦合到一个特定的绝对位置。通常更可取的方法是在这些绝对路径中保留一个间接的路径地址,例如通过占位符"${…​}",这些占位符会在运行时由JVM系统的属性解析。

命名空间本身提供了import指令的特性。除了普通的bean定义以外,更多的配置特性可以在Spring提供的XML命名空间定义里查看,例如context、util等命名空间。

这一句写得不是很清楚,我猜测应该是http://www.springframework.org/schema/这里面这些xsd后缀的文件里的document部分。通过阅读这些文件可以了解到更详细的配置信息,包括标签、属性、定义、使用等等信息。

Groovy Bean定义DSL

作为外部化配置元数据的另一个示例,bean定义也可以用Spring的Groovy bean定义DSL表示,正如在Grails框架中一样。通常,这样的配置位于".groovy"的文件中,其结构如下例所示:

这个定义很大程度上等同于XML中bean定义。它还允许通过importBeans指令导入XML bean的定义文件。

1.2.3 使用容器

ApplicationContext是一个能维护不同bean及其依赖项的注册表的高级工厂的接口。通过使用方法T getBean(String name, Class<T> requiredType),你可以获取你所定义的bean的实例。

ApplicationContext可以让你读取你的bean定义并且访问它们,如下所示:

对于Groovy配置,引导看起来非常类似。它有一个不同的Context实现类,它支持groovy(也能理解XML bean定义)。下面的示例展现了Groovy配置:

最灵活的变体是GenericApplicationContext与reader相结合,例如将XmlBeanDefinitionReader用于XML文件,如下例所示:

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

你可以混合使用来从不同的配置源中读取bean。

ApplicationContext接口有不少其他方法用来获取bean,但理想情况下,你应该避免使用它们。事实上,你的代码应该从来不调用getbean方法,因为这样就完全不依赖于Spring APIs。例如,Spring和web框架集成为各种web框架组件提供依赖注入并且允许你通过元数据对特定的bean依赖进行声明(如一个自动装配注解),例如控制器和JSF管理bean。

For example, Spring’s integration with web frameworks provides dependency injection for various web framework components such as controllers and JSF-managed beans, letting you declare a dependency on a specific bean through metadata (such as an autowiring annotation).

此段不是很好理解,原句对应斜体部分

1.3 Bean 概况

一个Spring IoC容器管理一个或多个bean,这些bean通过你提供给容器的配置元数据被创造出来(例如XML文件里的<bean />定义)

在容器内部,这些bean被表示为BeanDefinition对象,其中还包含一下元数据。

这些元数据转化成组成每个bean定义的一组属性,下面描述了这些属性:

Table 1. The bean definition
Property Explained in…​
Class Instantiating Beans
Name Naming Beans
Scope Bean Scopes
Constructor arguments Dependency Injection
Properties Dependency Injection
Autowiring mode Autowiring Collaborators
Lazy initialization mode Lazy-initialized Beans
Initialization method Initialization Callbacks
Destruction method Destruction Callbacks

 

除了包含创建特定bean信息的bean定义之外,ApplicationContext也允许对用户定义在容器外的对象进行注册,这可以通过getBeanFactory()方法访问ApplicationContext的BeanFactory来完成,getBeanFactory()会返回一个BeanFactory的具体对象:DefaultListableBeanFactory。DefaultListableBeanFactory通过registerSingleton()和registerBeanDefinition()方法支持这种注册。但普通的应用仅仅只通过配置元数据定义的bean运作就足够了。

这一段存在一定的问题,正在考究其具体操作

bean元数据和手动提供的单例实例需要尽早注册。重写已存在的元数据和现有的单例实例在某种程度上是支持的,在运行时注册新bean(对工厂的访问同时进行注册)是不受官方支持的,并且可能会导致访问异常、bean容器中不一致的状态或两者都有。

1.3.1 命名Beans

每一个bean都有自己的标识符,这些标识符在承载bean的容器中唯一,一个bean通常只有一个标识符,但是如果要更多个,其他的名字可以被视为是别名。

在基于XML的配置元数据中,可以使用id或者是name属性,或者两者都用来标识。id属性让你指定唯一一个id。这些名字用字母数字表示(例如'myBean', 'someService'等等),也可以包含一些特殊的字符。如果你想要为Bean采用其他别名,你也可以指定他们的name属性,用逗号、分号或者空格隔开每个名字。这里有一个历史版本问题的注意点,在Spring3.1以前,id属性被定义为一个xsd:ID类,这限制了可能的字符。从3.1开始,它被定义为xsd:String类型,注意,容器依然可以强制bean的id的唯一性,但不再由XML解析器强制。

你并不需要给一个bean提供一个name或者id,如果你不提供name或者id,容器会为其生成一个独一无二的名字。但是如果你想要通过名字访问这个bean,则必须通过ref元素或者服务定位器的样式查找名称。不提供名称的使用场景通常是内部bean自动装配协作

Bean命名规定

bean名称以小写字母开头并且采用驼峰命名法,例如:accountManager,accountService,userDao,loginController等

命名bean通常让你的配置文件更容易阅读和理解,另外如果你使用Spring AOP,它会起到很大的帮助。

在classpath里扫描组件时,Spring会按照前面约定的规则对未命名组件进行命名(使用简单的类名并将其首字母改为小写)。但是在少有的特殊情况下,例如有超过一个字符是大写或者是第一个和第二个字符是大写,则名称会完整保留,这些规定和java.beans.Introspector.decapitalize的规定是一样的。

为了了解它说的是什么意思,我做了一个实验,定义一个新的对象Test2,完全名称是com.test.demo.core.day1.Test2,在定义bean时特地不使用id或者name属性,在主程序中我找到了ApplicationContext的一个打印全部bean的方法getBeanDefinitionNames(),结果发现它的默认名字会被定义为com.test.demo.core.day.Test2#0

在bean定义外为其提供别名

在一个bean定义时,你可以提供一个或多个的name值(例如name="test1,test2"或name="test1 test2"或name="test1;test2",id不行,id必须是有且仅有一个的名称),这些名称是相同的bean的别名,在某些情况下它非常有用,比如让每个组件分别通过特定的bean名称引用一个公共的依赖。

但是,在bean定义的时候就指定所有的别名这种方式往往是不可能的,有时候我们想要为一个bean在其它位置引入一个新的别名,这在一个“配置文件分散在各个子系统中,且每个子系统都有自己的一组对象定义”的大型系统里是很常见的操作。在基于XML的配置元数据中,你可以使用<alias />元素来完成这个事情。如下所示:

在这个例子中,一个被命名为fromName的bean通过这个别名定义,引入了一个新的别名toName(在同一个容器中)。

例如,配置元数据为子系统A通过名字subsystemA-dataSource来引用数据源,而为子系统B通过名字subsystemB-dataSource来引用数据源。当组装主程序使用这两个子系统时,主程序通过名字myApp-dataSource引用数据源。为了使三个名字引用同一个对象,你可以添加以下的别名定义在配置元数据中。

现在,每个组件和主程序都可以通过一个名字来引用数据源,并且该名字是独一无二和保证不会与其他定义发生冲突的(有效的创建了一个命名空间),而且他们引用的是同一个Bean。

基于Java的定义

如果你使用基于Java的定义方式,@Bean这个注解可以提供别名的操作。更多详情请看 [beans-java-bean-annotation]

1.3.2 实例化Beans

一个Bean定义本质上是一个或多个对象的配置。当被请求时,容器查看一个被命名的Bean的配置,并使用该Bean定义的配置元数据来创建(或是获取)一个实际的对象。

如果你使用基于XML的配置元数据,你需要在<bean />元素中的class属性指定要实例化的对象(或者类)。这个class属性通常是强制性要求的。(对于出现异常,查看 Instantiation by Using an Instance Factory Method 和 Bean Definition Inheritance.)你可以使用Class属性通过以下一种或者是两种方式

You can use the Class property in one of two ways:

通常,在容器本身通过反射调用其构造函数直接创建bean,这需要指定要构造的bean类,这有点类似于Java代码中的new操作。

另一种不常见的方法,要指定包含“用于创建对象的静态工厂方法”的实际类,容器调用类上的静态工厂方法来创建Bean。从静态工厂方法调用返回的对象类型可能是同一个类或者完全是另一个类。

To specify the actual class containing the static factory method that is invoked to create the object, in the less common case where the container invokes a static factory method on a class to create the bean. The object type returned from the invocation of the static factory method may be the same class or another class entirely.

内部类名

如果你想为一个静态嵌套类配置bean定义,你不得不使用嵌套类的二进制名。

例如,如果你想有一个在com.example包中的一个SomeThing的类,并且这个SomeThing类有一个静态嵌套类叫做OtherThing,则在bean定义中,class属性的值应该是com.example.SomeThing$OtherThing。

注意,$符号用于分离从外部引用的嵌套类名。

经过尝试,这几句话表达的意思大致如下例所示:

写一个类Test3

这时候会遇到一个问题,如果我们要单独命名一个类里的类,即Test3Another这个类,咋整?

就使用这样的定义

那么使用时是这么使用的

带构造函数的实例化

当你通过构造函数的方式来实例化一个bean时,所有普通的类都可以被Spring使用和兼容,也就是说,正在开发中的类不需要实现特定的接口,也不需要以指定的方式进行编码,‘

只需要指定bean的class就够 了。但是,根据具体的bean使用IoC类型,你可能需要一个默认(空)的构造函数。

事实上,Spring的IoC容器可以管理任何你想管理的类。它不仅限于管理真正的JavaBeans,大多数Spring的用户更喜欢真正的JavaBeans,它有一个默认(无参数)的构造函数,以及根据容器中的属性建立的setter和getter方法。你还可以在容器中包含非Bean风格的类。例如,如果你要使用绝对不符合JavaBean规范的遗留连接池,Spring也照样可以管理它。

使用基于XML的配置元数据,你可以指定你的bean如下:

关于向构造函数提供参数(如果必要),和在构造对象后设置实例属性的技巧,可以参阅Injecting Dependencies获取更多信息。

通过静态工厂方法实例化

通过静态工厂方法创建Bean时,使用class属性指定包含静态工厂方法的类,使用一个factory-method属性来指定工厂方法的名字。你应该能调用这个方法(带有可选参数,后面叙述),并返回一个活动对象,该对象随后被视为通过构造函数构造的。这种Bean定义的一个用途是在遗留代码中调用静态工厂。

下面的Bean定义指定一个被工厂方法创建的Bean。这个定义不指定返回类型(类),只指定包含这个工厂方法的类。在这个例子中,createInstance()方法必须是一个静态方法。下面向你展示如何指定一个工厂方法。

下面展示一个和上述Bean定义共同协作的类:

更多提供(可选)参数给工厂方法和在被工厂返回后设置对象实例属性的技巧请参阅Dependencies and Configuration in Detail

这种技巧一般是用于单例设计模式,即你设计了某个类,但你不希望它被多次实例化,你希望这个类有且只被实例化一次,这个时候使用这种方式可以确保该类一定是单例的设计模式。经过实验检验,这个操作和在<bean/>元素中,把scope="singleton"是等效的,都是属于单例设计。而如果用这种方式定义后,又把scope="prototype",则scope会自动失效,还是会按照单例模式来。

通过工厂方法实例来实例化对象

和通过静态工厂方法实例化类似,通过一个实例化工厂方法

-- 热度:217 ℃
-- 于 共写了11850个字
-- 文内使用到的标签:

4条回应:“【Java笔记02】Spring文档翻译-Core-1.The IoC Container”

  1. SEO Services说道:

    Awesome post! Keep up the great work! 🙂

  2. 刘晒说道:

    高总牛逼!
    全体起立!
    给高总来一杯卡布奇诺

  3. 只有博主的文章这么走心了!!!

发表评论

电子邮件地址不会被公开。 必填项已用*标注