为了账号安全,请及时绑定邮箱和手机立即绑定

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

标签:
Java Spring
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,通过将其引用为另一个beanref属性或使用父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接口。更多详情,查看 BeanPostProcessorOrdered接口。参见有关以编程方式注册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后处理器通常检查回调接口,或者可以用代理包装BeanSpring 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注入beanBeanPostProcessor中(可能回退到自动装配),当搜索类型匹配的依赖项候选者时,Spring可能会访问意外的Bean,因此使它们不适合进行自动代理或其他类型的Bean后处理。例如,如果你有一个用@Resource标注的依赖项,其中字段或Setter名称不直接与bean的声明名称相对应,并且不使用name属性,那么Spring将访问其他bean以按类型匹配它们。

下面的例子展示了在ApplicationContext中怎样去编写、注册和使用BeanPostProcessor接口。

例子:Hello World

第一个例子说明基础的使用。这个例子展示一个自定义BeanPostProcessor实现并调用通过容器创建的每个beantoString()方法并且打印结果字符串到控制台。

下面的清单展示了自定义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脚本支持的beanSpring动态语言支持详情在“动态语言支持”一章中。)

下面的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容器常用方法。SpringRequiredAnnotationBeanPostProcessor是一个示例,它是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接口。更多关于BeanFactoryPostProcessorOrdered接口详细信息。

如果想去改变实际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}格式的占位符,该格式遵循Antlog4jJSPEL样式。

真实值来自于标准的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的ApplicationContextpreInstantiateSingletons()阶段,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具有driverurl属性。

复合属性名也是被支持的,只要路径的每个组件(被覆盖的最终属性除外)都是非空的(可能是由构造函数初始化的)。下面的例子,bean的tom属性fredsammy属性设置只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

点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消