天天看点

Spring 5 中文解析核心篇-IoC容器之BeanDefinition继承与容器拓展点

1.7 Bean Definition继承

bean

定义包含一些配置信息,包括构造函数参数、属性值、和容器特定信息,例如初始化方法、静态工厂方法名等等。子

bean

定义继承父

bean

定义配置数据。子

bean

定义能够覆盖一些值或者增加其他需要。使用父

bean

和子

bean

定义能够保存一些类型。实际上,这是一种模版模式。

如果你编程式使用

ApplicationContext

接口,子

bean

定义通过

ChildBeanDefinition

类描述。大多数用户不在这个级别使用(备注:应用开发人员一般不会接触)。相反,它们在例如

ClassPathXmlApplicationContext

之类的类中声明性地配置

bean

定义。当你使用基于XML配置元数据,你可以通过使用

parent

属性指示一个子

bean

的定义,指定

parent

bean作为这个属性的值。下面例子显示怎样做:

<bean id="inheritedTestBean" abstract="true"
        class="org.springframework.beans.TestBean">
    <property name="name" value="parent"/>
    <property name="age" value="1"/>
</bean>
<!--parent指定继承父类-->
<bean id="inheritsWithDifferentClass"
        class="org.springframework.beans.DerivedTestBean"
        parent="inheritedTestBean" init-method="initialize">  
    <property name="name" value="override"/>
    <!-- the age property value of 1 will be inherited from parent -->
</bean>           

如果没有指定,子

bean

定义将使用父定义中的

bean

类,但也可以覆盖它。在后面这种情况下,子

bean

类必须兼容父

bean

定义(也就是说,必须接受父

bean

的属性值)。

bean

定义继承作用域、构造参数值、属性值、和覆盖父类方法,并可以添加新值。任何作用域、初始化方法、销毁方法或

static

工厂方法设置都会覆盖对应的父

bean

设置。

其余设置始终从子

bean

定义中获取:依赖、自动装配、依赖检查、单例和懒加载。

前面的例子通过使用

abstract

属性显示标记父

bean

定义作用一个抽象,如果父

bean

定义没有指定类,显示地标记父

bean

定义为

abstract

是需要的,像下面例子展示:

<bean id="inheritedTestBeanWithoutClass" abstract="true">
    <property name="name" value="parent"/>
    <property name="age" value="1"/>
</bean>

<bean id="inheritsWithClass" class="org.springframework.beans.DerivedTestBean"
        parent="inheritedTestBeanWithoutClass" init-method="initialize">
    <property name="name" value="override"/>
    <!-- age will inherit the value of 1 from the parent bean definition-->
</bean>           

bean

不能被实例化,因为它是不完整的,并且它被显示的标记为

abstract

。当一个定义是

abstract

,它仅仅作为一个

bean

定义的模版且父

bean

定义为子

bean

定义服务。尝试自己使用这样的抽象父

bean

,通过将其引用为另一个

bean

ref

属性或使用父

bean

ID

进行显式的

getBean()

调用将返回错误。类似地,容器的内部

preInstantiateSingletons()

方法将忽略定义为抽象的bean定义。

默认情况下,

ApplicationContext

会预先实例化所有单例。因此,重要的是(至少对于单例bean),如果有一个(父)

bean

定义仅打算用作模板,并且此定义指定了一个类,则必须确保将

abstract

属性设置为

true

,否则应用程序上下文将实际上(试图)预先实例化抽象

Bean

参考实例:

com.liyong.ioccontainer.starter.XmlBeanDefinitionInheritanceContainer

1.8 容器拓展点

典型地,应用程序开发者不需要实现

ApplicationContext

类。相反,

Spring IoC

容器通过插件实现指定的集成接口去扩展。下面的章节描述这些接口的集成。

1.8.1 通过使用

BeanPostProcessor

自定义bean

BeanPostProcessor

接口定义回调方法,你可以实现这个接口提供你自己的(或者覆盖容器的默认设置)初始化逻辑、依赖解析逻辑等等。如果你想实现一些自定义逻辑,在

Spring

容器完成实例化、配置、初始化

bean

之后,你可以插入一个或多个自定义

BeanPostProcessor

实现。

你可以配置多个

BeanPostProcessor

实例并且你可以通过设置

order

属性来控制这些

BeanPostProcessor

实例的执行顺序。仅仅

BeanPostProcessor

实现

Ordered

接口是可以设置这个属性。如果自己实现

BeanPostProcessor

,你应该考虑实现

Ordered

接口。更多详情,查看

BeanPostProcessor

Ordered

接口。参见

有关以编程方式注册BeanPostProcessor实例的说明

BeanPostProcessor

接口在

bean

(对象)实例上操作。也就是说,

Spring IoC

容器实例化一个

bean

实例,然后

BeanPostProcessor

实例执行它们的工作。(备注:在Spring容器初始化bean过程中执行相关的回调操作)

BeanPostProcessor

实例是按容器划分作用域。仅仅在使用继承容器才有意义。如果在一个容器中定义

BeanPostProcessor

,仅仅在那个容器中的

bean

被后置处理。换句话说,定义在一个容器中的

bean

不会被在其他容器定义的

BeanPostProcessor

所处理,即使两个容器都属于同一层次结构。

改变实际

bean

定义(也就是定义

bean

的蓝图),你需要使用

BeanFactoryPostProcessor

,如使用 BeanFactoryPostProcessor自定义配置元数据中所述

org.springframework.beans.factory.config.BeanPostProcessor

接口恰好地由两个回调方法组成。当一个类作为后置处理起被注册到容器中时,对于每个被容器创建

bean

实例,后置处理器从容器初始化方法(例如:

InitializingBean.afterPropertiesSet()

或者任何被声明init方法)被调用之前,并且任何

bean

初始化回调之后获得回调。后置处理器能够处理bean实例任何操作,包括忽略所有的回调。

Bean

后处理器通常检查回调接口,或者可以用代理包装

Bean

Spring AOP

基础设施类中实现

bean

的后置处理去提供一个代理包装逻辑。

ApplicationContext

自动的检查所有

bean

,这些

bean

在配置元数据中实现了

BeanPostProcessor

接口。

ApplicationContext

注册这些

bean

作为后置处理器,以便以后在创建

bean

时可以调用它们。

Bean

后处理器可以与其他

bean

相同的方式部署在容器中。

注意,当通过在类上使用

@Bean

工厂方法声明

BeanPostProcessor

时,工厂方法返回类型应该是实现类本身或只是实现

org.springframework.beans.factory.config.BeanPostProcessor

接口,清晰地表明该

bean

的后处理器性质。否则,

ApplicationContext

无法在完全创建之前按类型自动检测它。由于

BeanPostProcessor

需要及早实例化才能应用于上下文中其他

bean

的初始化,因此这种早期类型检测至关重要。

编程式地注册BeanPostProcessor实例

推荐的去注册

BeanPostProcessor

方式是通过

ApplicationContext

自动检测(前面描述),可以使用

addBeanPostProcessor

方法以编程方式针对

ConfigurableBeanFactory

注册它们。当注册之前你需要评估条件逻辑甚至需要跨层次结构的上下文复制后置处理器时,这是非常有用的。注意,然而,

BeanPostProcessor

实例编程式的添加不遵循

Ordered

排序接口。在这里,注册的顺序决定了执行的顺序。需要注意是编程式的注册

BeanPostProcessor

实例总是在这些通过自动检测的后置处理器之后被处理,而不管显示的顺序。

BeanPostProcessor实例和AOP自定代理

BeanPostProcessor

接口的类是特殊的,并且容器对它们的处理方式有所不同。在启动时会实例化它们直接引用的所有

BeanPostProcessor

实例和

Bean

,这是

ApplicationContext

特殊启动阶段的一部分。接下来,以排序方式注册所有

BeanPostProcessor

实例,并将其应用于容器中的所有其他

bean

。因为

AOP

自动代理是作为

BeanPostProcessor

本身实现的,所以

BeanPostProcessor

实例或它们直接引用的

bean

都不适合进行自动代理,因此,没有编织的方面。

对于任何这样的

bean

,你应该查看日志消息:

Bean someBean is not eligible for getting processed by all BeanPostProcessor interfaces (for example: not eligible for auto-proxying)

.

如果你有通过使用自动装配或

@Resource

注入

bean

BeanPostProcessor

中(可能回退到自动装配),当搜索类型匹配的依赖项候选者时,

Spring

可能会访问意外的Bean,因此使它们不适合进行自动代理或其他类型的Bean后处理。例如,如果你有一个用

@Resource

标注的依赖项,其中字段或

Setter

名称不直接与

bean

的声明名称相对应,并且不使用

name

属性,那么

Spring

将访问其他

bean

以按类型匹配它们。

下面的例子展示了在

ApplicationContext

中怎样去编写、注册和使用

BeanPostProcessor

例子:Hello World

第一个例子说明基础的使用。这个例子展示一个自定义

BeanPostProcessor

实现并调用通过容器创建的每个

bean

toString()

方法并且打印结果字符串到控制台。

下面的清单展示了自定义

BeanPostProcessor

实现类定义:

package scripting;

import org.springframework.beans.factory.config.BeanPostProcessor;

public class InstantiationTracingBeanPostProcessor implements BeanPostProcessor {

    // simply return the instantiated bean as-is
    public Object postProcessBeforeInitialization(Object bean, String beanName) {
        return bean; // we could potentially return any object reference here...
    }

    public Object postProcessAfterInitialization(Object bean, String beanName) {
      //打印bean信息
        System.out.println("Bean '" + beanName + "' created : " + bean.toString());
        return bean;
    }
}           

下面

beans

元素使用

InstantiationTracingBeanPostProcessor

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:lang="http://www.springframework.org/schema/lang"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/lang
        https://www.springframework.org/schema/lang/spring-lang.xsd">

    <lang:groovy id="messenger"
            script-source="classpath:org/springframework/scripting/groovy/Messenger.groovy">
        <lang:property name="message" value="Fiona Apple Is Just So Dreamy."/>
    </lang:groovy>

    <!--
    when the above bean (messenger) is instantiated, this custom
    BeanPostProcessor implementation will output the fact to the system console
    -->
    <bean class="scripting.InstantiationTracingBeanPostProcessor"/>

</beans>           

注意怎样定义

InstantiationTracingBeanPostProcessor

。它甚至没有名字,并且它是一个

bean

,可以像其他bean一样被依赖注入。(前面的配置还定义了一个由

Groovy

脚本支持的

bean

Spring

动态语言支持详情在“

动态语言支持

”一章中。)

下面的Java应用程序运行前面的代码和配置:

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.scripting.Messenger;

public final class Boot {

    public static void main(final String[] args) throws Exception {
        ApplicationContext ctx = new ClassPathXmlApplicationContext("scripting/beans.xml");
        Messenger messenger = ctx.getBean("messenger", Messenger.class);
        System.out.println(messenger);
    }

}           

前面的应用输出结果类似下面:

Bean 'messenger' created : org.springframework.scripting.groovy.GroovyMessenger@272961
org.springframework.scripting.groovy.GroovyMessenger@272961           

例子:RequiredAnnotationBeanPostProcessor

将回调接口或与自定义

BeanPostProcessor

实现结合使用是扩展

Spring IoC

容器常用方法。

Spring

RequiredAnnotationBeanPostProcessor

是一个示例,它是

Spring

发行版附带的

BeanPostProcessor

实现,可以确保标有(任意)注解的

bean

上的

JavaBean

属性实际上(配置为)依赖注入了一个值。说明:就是常用的依赖注入。

参考代码:com.liyong.ioccontainer.starter.XmlBeanPostProcessorIocContainer

1.8.2 BeanFactoryPostProcessor自定义配置元数据

下一个拓展点是

org.springframework.beans.factory.config.BeanFactoryPostProcessor

。这个接口语义类似BeanPostProcessor,一个重要的不同点:

BeanFactoryPostProcessor

是操作在bean的配置元数据上。也就是说,Spring IoC容器允许除

BeanFactoryPostProcessor

实例外其他任何bean被

BeanFactoryPostProcessor

读取配置元数据和改变它。

BeanFactoryPostProcessor

实例,并且你可以通过设置order属性在这些

BeanFactoryPostProcessor

实例上来控制顺序。然而,如果

BeanFactoryPostProcessor

Ordered

接口才能设置这个属性。如果你写自己的

BeanFactoryPostProcessor

Ordered

接口。更多关于

BeanFactoryPostProcessor

Ordered

接口详细信息。

如果想去改变实际bean实例(也就是,这个对象是从配置元数据被创建的),你需要使用

BeanPostProcessor

(前面描述通过使用

BeanPostProcessor

自定义bean)。从技术上讲,可以在

BeanFactoryPostProcessor

中使用Bean实例(例如,通过使用

BeanFactory.getBean()

),这样做导致过早的初始化bean,违反了标准容器生命周期。这会导致负面的影响,例如:绕过后置处理。

同样,

BeanFactoryPostProcessor

实例是按容器划分。(如果你使用分层的容器才会有意义) 如果你定义在容器中定义一个

BeanFactoryPostProcessor

,它仅适用于该容器中的bean定义。在一个容器中的bean定义不会被其他的容器中

BeanFactoryPostProcessor

后置处理,即使两个容器在同一个层级。

Bean工厂后处理器在

ApplicationContext

中声明时会自动执行,以便将更改应用于定义容器的配置元数据。Spring包括一些预定义的工厂后置处理器,例如

PropertyOverrideConfigurer

PropertySourcesPlaceholderConfigurer

。你可以使用一个自定义的

BeanFactoryPostProcessor

-例如,注册一个自定义属性编辑器。

ApplicationContext

自动地检测任何部署到容器中并实现

BeanFactoryPostProcessor

接口的实例bean。使用这些bean作为bean工厂后置处理器,在适当的时间。你可以像其他ban一样部署这些后置处理器。

BeanPostProcessors

一样,通常不希望配置

BeanFactoryPostProcessors

进行延迟初始。如果没有其他bean引用

Bean(Factory)PostProcessor

,这个后置处理器不会被初始化。因此,标记它为延迟初始化将被忽略并且

Bean(Factory)PostProcessor

将被提前初始化即使你的声明

default-lazy-init

属性为true。

参考代码:

com.liyong.ioccontainer.starter.XmlBeanFactoryPostProcessorIocContainer

例如:类名替换

PropertySourcesPlaceholderConfigurer

可以使用

PropertySourcesPlaceholderConfigurer

通过使用标准Java属性格式将属性值从bean定义外部化到一个单独的文件中(意思是:bean配置数据可以配置到一个单独文件中)。这样做使部署应用程序的人员可以自定义特定于环境的属性,例如数据库URL和密码,而无需为修改容器的主要XML定义文件而复杂或冒风险。

考虑下面基于XML配置元数据片段,

DataSource

使用占位符值定义:

<bean class="org.springframework.context.support.PropertySourcesPlaceholderConfigurer">
    <property name="locations" value="classpath:com/something/jdbc.properties"/>
</bean>

<bean id="dataSource" destroy-method="close"
        class="org.apache.commons.dbcp.BasicDataSource">
    <property name="driverClassName" value="${jdbc.driverClassName}"/>
    <property name="url" value="${jdbc.url}"/>
    <property name="username" value="${jdbc.username}"/>
    <property name="password" value="${jdbc.password}"/>
</bean>           

这个例子显示从外部化

Properties

文件的属性配置。在运行时,

PropertySourcesPlaceholderConfigurer

应用元数据替换

DataSource

的属性值。将要替换的值指定为

$ {property-name}

格式的占位符,该格式遵循

Ant

log4j

JSPEL

样式。

真实值来自于标准的

Properties

格式的其他文件:

jdbc.driverClassName=org.hsqldb.jdbcDriver
jdbc.url=jdbc:hsqldb:hsql://production:9002
jdbc.username=sa
jdbc.password=root           

因此,

${jdbc.username}

字符串在运行时被替换为

sa

值,并且同样应用到其他的占位符在

properties

文件中匹配这些key。

PropertySourcesPlaceholderConfigurer

检查Bean定义的大多数属性和属性中的占位符。此外,你可以自定义占位符的前缀和后缀。

context命名空间在Spring2.5中被引入,你可以配置一个专用配置元素的占位符。在

location

属性中你可以提供一个或多个

location

通过逗号分隔,类似下面例子:

<context:property-placeholder location="classpath:com/something/jdbc.properties"/>           

PropertySourcesPlaceholderConfigurer

不仅仅在你指定的Properties中查找属性。默认情况,如果在指定的属性文件中没有找到属性,它会再次检查Spring

Environment

属性和常规的Java System属性。

你可以使用

PropertySourcesPlaceholderConfigurer

去替代类名称,有时候者非常有用,当你在运行时必须选择一个特定的实现类。下面例子展示怎样去做:
<bean class="org.springframework.beans.factory.config.PropertySourcesPlaceholderConfigurer">
 <property name="locations">
     <value>classpath:com/something/strategy.properties</value>
 </property>
 <property name="properties">
     <value>custom.strategy.class=com.something.DefaultStrategy</value>
 </property>
</bean>

<bean id="serviceStrategy" class="${custom.strategy.class}"/>           
如果在运行时无法将类解析为有效的类,则在即将创建bean时,即在非

lazy-init

bean的

ApplicationContext

preInstantiateSingletons()

阶段,bean的解析将失败。

com.liyong.ioccontainer.starter.XmlPropertySourcesPlaceholderConfigurerIocContainer

例子:

PropertyOverrideConfigurer

另一个bean工厂后处理器

PropertyOverrideConfigurer

类似于

PropertySourcesPlaceholderConfigurer

,但与后者不同,原始定义可以具有bean属性的默认值,也可以完全没有值。如果覆盖的属性文件对于一些bean属性没有符合内容,默认的上下文定义被使用。

注意:bean定义没有意识到被覆盖,因此从XML定义文件中不能立即看出覆盖的配置正在被使用。由于存在覆盖机制,在多个

PropertyOverrideConfigurer

实例情况下对应相同bean属性不同的值,最后一个将被使用。

属性文件配置格式:

beanName.property=value           

下面清单显示格式化例子:

dataSource.driverClassName=com.mysql.jdbc.Driver
dataSource.url=jdbc:mysql:mydb           

此示例文件可与容器定义一起使用,容器定义包含一个名为

dataSource

的bean,该bean具有

driver

url

属性。

复合属性名也是被支持的,只要路径的每个组件(被覆盖的最终属性除外)都是非空的(可能是由构造函数初始化的)。下面的例子,bean的

tom

属性

fred

sammy

属性设置只123:

tom.fred.bob.sammy=123           
指定的重写值总是文本值。它们没有被转译为bean引用。当XML bean定义中的原始值指定bean引用时,这种约定也适用。

context命名空间在Spring2.5中被引入,可以使用专用配置元素配置属性覆盖,类似下面例子:

<context:property-override location="classpath:override.properties"/>           

com.liyong.ioccontainer.starter.XmlPropertyOverrideConfigurerIocContainer

1.8.3 FactoryBean自定义初始化逻辑

可以为本身就是工厂的对象实现

org.springframework.beans.factory.FactoryBean

这个

FactoryBean

接口是Spring IoC容器的实例化逻辑可插拔点。如果你有一个复杂的初始化代码在Java中比冗余XML更好的表达,你可以创建你自己的

FactoryBean

,在实现类中写复杂的初始化逻辑,然后插入你的自定义

FactoryBean

到容器中。

FactoryBean

接口提供三个方法:

  • Object getObject()

    :返回这个工厂创建的一个实例。这个实例可能被共享,依赖于这个工厂返回的是单例或原型。
  • boolean isSingleton()

    :如果

    FactoryBean

    返回一个单例返回true否则为false
  • Class getObjectType()

    :返回由getObject()方法返回的对象类型;如果类型未知,则返回null

FactoryBean

概念和接口在Spring框架中一些地方被使用到。Spring有超过50个

FactoryBean

接口的实现。

当你需要向容器获取一个实际的

FactoryBean

实例本身而不是由它产生的bean时请在调用ApplicationContext的

getBean()

方法时在该bean的ID前面加上一个

符号。因此,对于id为

myBean

的给定

FactoryBean

,在容器上调用

getBean(“myBean”)

将返回

FactoryBean

的产生的bean,而调用

getBean(“&myBean”)

FactoryBean

实例本身。

com.liyong.ioccontainer.service.CustomFactorBean

作者

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

博客地址:

http://youngitman.tech

CSDN:

https://blog.csdn.net/liyong1028826685

微信公众号:

Spring 5 中文解析核心篇-IoC容器之BeanDefinition继承与容器拓展点

技术交流群:

Spring 5 中文解析核心篇-IoC容器之BeanDefinition继承与容器拓展点