xinrong2019 / xinrong2019.github.io

My Blog
https://xinrong2019.github.io
1 stars 1 forks source link

20190718 Spring Framework体系化学习之IOC容器(中) #91

Open xinrong2019 opened 5 years ago

xinrong2019 commented 5 years ago

1.5. Bean Scopes

singleton

(默认)将单个bean定义范围限定为每个Spring IoC容器的单个对象实例。

prototype

将单个bean定义范围限定为任意数量的对象实例。

request

将单个bean定义范围限定为单个HTTP请求的生命周期。也就是说,每个HTTP请求都有自己的bean实例,它是在单个bean定义的后面创建的。仅在Web感知Spring ApplicationContext的上下文中有效。

session

将单个bean定义范围限定为HTTP会话的生命周期。仅在Web感知Spring ApplicationContext的上下文中有效。

application

将单个bean定义范围限定为ServletContext的生命周期。仅在Web感知Spring ApplicationContext的上下文中有效。

websocket

将单个bean定义范围限定为WebSocket的生命周期。仅在Web感知Spring ApplicationContext的上下文中有效。

从Spring 3.0开始,线程范围可用,但默认情况下未注册:请参阅SimpleThreadScope。从Spring 4.2开始,也可以使用事务范围:SimpleTransactionScope。有关如何注册这些或任何其他自定义作用域的说明,请参阅使用自定义作用域

xinrong2019 commented 5 years ago

1.6. Customizing the Nature of a Bean(定制Bean的特性)

Spring Framework提供了许多可用于自定义bean特性的接口。本节将它们分组如下:

1.6.1. Lifecycle Callbacks

要与容器的bean生命周期管理进行交互,可以实现Spring InitializingBean和DisposableBean接口。容器为前者调用afterPropertiesSet(),为后者调用destroy(),让bean在初始化和销毁​​bean时执行某些操作。

JSR-250 @PostConstruct和@PreDestroy注释通常被认为是在现代Spring应用程序中接收生命周期回调的最佳实践。使用这些注释意味着您的bean不会耦合到特定于Spring的接口。有关详细信息,请参阅使用@PostConstruct和@PreDestroy

如果您不想使用JSR-250注释但仍想删除耦合,请考虑init-method和destroy-method bean定义元数据。

在内部,Spring Framework使用BeanPostProcessor实现来处理它可以找到的任何回调接口并调用适当的方法。如果您需要Spring默认提供的自定义功能或其他生命周期行为,您可以自己实现BeanPostProcessor。有关更多信息,请参阅容器扩展点

除了初始化和销毁​​回调之外,Spring管理的对象还可以实现Lifecycle接口,以便这些对象可以参与启动和关闭过程,这是由容器自身的生命周期驱动的。

本节描述了生命周期回调接口。

初始化回调

org.springframework.beans.factory.InitializingBean接口允许bean在容器为bean设置所有必需属性后执行初始化工作。 InitializingBean接口指定一个方法:

void afterPropertiesSet() throws Exception;

我们建议您不要使用InitializingBean接口,因为它会不必要地将代码耦合到Spring。或者,我们建议使用@PostConstruct注释或指定POJO初始化方法。对于基于XML的配置元数据,可以使用init-method属性指定具有void无参数签名的方法的名称。使用Java配置,您可以使用@Bean的initMethod属性。请参阅接收生命周期回调。请考虑以下示例:

<bean id="exampleInitBean" class="examples.ExampleBean" init-method="init"/>
public class ExampleBean {

    public void init() {
        // do some initialization work
    }
}

前面的示例与以下示例几乎完全相同(包含两个列表):

<bean id="exampleInitBean" class="examples.AnotherExampleBean"/>
public class AnotherExampleBean implements InitializingBean {

    public void afterPropertiesSet() {
        // do some initialization work
    }
}

销毁回调(Destruction Callbacks)

实现org.springframework.beans.factory.DisposableBean接口允许bean在包含它的容器被销毁时获得回调。 DisposableBean接口指定一个方法:

void destroy() throws Exception;

我们建议您不要使用DisposableBean回调接口,因为它会不必要地将代码耦合到Spring。或者,我们建议使用@PreDestroy注释或指定bean定义支持的泛型方法。使用基于XML的配置元数据,您可以在<bean />上使用destroy-method属性。使用Java配置,您可以使用@Bean的destroyMethod属性。请参阅接收生命周期回调。考虑以下定义:

<bean id="exampleInitBean" class="examples.ExampleBean" destroy-method="cleanup"/>
public class ExampleBean {

    public void cleanup() {
        // do some destruction work (like releasing pooled connections)
    }
}

前面的定义与以下定义几乎完全相同:

<bean id="exampleInitBean" class="examples.AnotherExampleBean"/>
public class AnotherExampleBean implements DisposableBean {

    public void destroy() {
        // do some destruction work (like releasing pooled connections)
    }
}

您可以为<bean>元素的destroy-method属性分配一个特殊(推断)值,该值指示Spring自动检测特定bean类的公共关闭或关闭方法。 (因此,任何实现java.lang.AutoCloseable或java.io.Closeable的类都将匹配。)您还可以在<beans>元素的default-destroy-method属性上设置此特殊(推断)值,以将此行为应用于一整套bean(参见默认初始化和销毁​​方法)。请注意,这是Java配置的默认行为。

Default Initialization and Destroy Methods

当您编写初始化和销毁​​不使用特定于Spring的InitializingBean和DisposableBean回调接口的方法回调时,通常会编写名称为init(),initialize(),dispose()等的方法。理想情况下,此类生命周期回调方法的名称在项目中是标准化的,以便所有开发人员使用相同的方法名称并确保一致性。

您可以将Spring容器配置为“查找”命名初始化并销毁每个bean上的回调方法名称。这意味着,作为应用程序开发人员,您可以编写应用程序类并使用名为init()的初始化回调,而无需为每个bean定义配置init-method =“init”属性。 Spring IoC容器在创建bean时调用该方法(并且符合前面描述的标准生命周期回调协定)。此功能还强制执行初始化和销毁​​方法回调的一致命名约定。

假设您的初始化回调方法名为init(),而您的destroy回调方法名为destroy()。然后,您的类类似于以下示例中的类:

public class DefaultBlogService implements BlogService {

    private BlogDao blogDao;

    public void setBlogDao(BlogDao blogDao) {
        this.blogDao = blogDao;
    }

    // this is (unsurprisingly) the initialization callback method
    public void init() {
        if (this.blogDao == null) {
            throw new IllegalStateException("The [blogDao] property must be set.");
        }
    }
}

然后,您可以在类似于以下内容的bean中使用该类:

<beans default-init-method="init">

    <bean id="blogService" class="com.something.DefaultBlogService">
        <property name="blogDao" ref="blogDao" />
    </bean>

</beans>

顶级<beans />元素属性上存在default-init-method属性会导致Spring IoC容器将bean类上的init方法识别为初始化方法回调。当bean被创建和组装时,如果bean类具有这样的方法,则在适当的时候调用它。

您可以使用顶级<beans />元素上的default-destroy-method属性,以类似方式(在XML中)配置destroy方法回调。

Spring容器保证在为bean提供所有依赖项后立即调用已配置的初始化回调。因此,在原始bean引用上调用初始化回调,这意味着AOP拦截器等尚未应用于bean。首先完全创建目标bean,然后应用带有拦截器链的AOP代理(例如)。如果目标bean和代理是分开定义的,那么您的代码甚至可以绕过代理与原始目标bean交互。因此,将拦截器应用于init方法是不一致的,因为这样做会将目标bean的生命周期耦合到其代理或拦截器,并在代码直接与原始目标bean交互时留下奇怪的语义。

Combining Lifecycle Mechanisms (组合生命周期机制)

从Spring 2.5开始,您有三个控制bean生命周期行为的选项:

如果为bean配置了多个生命周期机制,并且每个机制都配置了不同的方法名称,则每个配置的方法都按照此注释后列出的顺序执行。但是,如果为初始化方法配置了相同的方法名称(例如,init() - 对于多个这些生命周期机制,该方法将执行一次,如上一节中所述。

为同一个bean配置的多个生命周期机制具有不同的初始化方法,如下所示:

Destroy方法以相同的顺序调用:

Startup and Shutdown Callbacks

Lifecycle接口为任何具有自己的生命周期要求的对象(例如启动和停止某些后台进程)定义了基本方法:

public interface Lifecycle {

    void start();

    void stop();

    boolean isRunning();
}

任何Spring管理的对象都可以实现Lifecycle接口。然后,当ApplicationContext本身接收到启动和停止信号时(例如,对于运行时的停止/重启场景),它会将这些调用级联到该上下文中定义的所有生命周期实现。它通过委托LifecycleProcessor完成此操作,如下面的清单所示:

public interface LifecycleProcessor extends Lifecycle {

    void onRefresh();

    void onClose();
}

请注意,LifecycleProcessor本身是Lifecycle接口的扩展。它还添加了另外两种方法来响应刷新和关闭的上下文。

请注意,常规org.springframework.context.Lifecycle接口是显式启动和停止通知的简单合约,并不意味着在上下文刷新时自动启动。要对特定bean的自动启动(包括启动阶段)进行细粒度控制,请考虑实现org.springframework.context.SmartLifecycle。

此外,请注意,在销毁之前不保证停止通知。在常规关闭时,所有Lifecycle bean首先会在传播常规销毁回调之前收到停止通知。但是,在上下文生命周期中的热刷新或中止刷新尝试时,仅调用destroy方法。

启动和关闭调用的顺序非常重要。如果任何两个对象之间存在“依赖”关系,则依赖方在其依赖之后开始,并且在其依赖之前停止。但是,有时,直接依赖性是未知的。您可能只知道某种类型的对象应该在另一种类型的对象之前开始。在这些情况下,SmartLifecycle接口定义了另一个选项,即在其超级接口Phased上定义的getPhase()方法。以下清单显示了Phased接口的定义:

public interface Phased {

    int getPhase();
}

以下清单显示了SmartLifecycle接口的定义:

public interface SmartLifecycle extends Lifecycle, Phased {

    boolean isAutoStartup();

    void stop(Runnable callback);
}

启动时,具有最低相位的对象首先开始。停止时,遵循相反的顺序。因此,实现SmartLifecycle并且其getPhase()方法返回Integer.MIN_VALUE的对象将是第一个开始和最后一个停止的对象。在频谱的另一端,Integer.MAX_VALUE的阶段值将指示对象应该最后启动并首先停止(可能是因为它依赖于正在运行的其他进程)。在考虑阶段值时,同样重要的是要知道未实现SmartLifecycle的任何“正常”Lifecycle对象的默认阶段为0。因此,任何负相位值都表示对象应该在这些标准组件之前启动(并在它们之后停止)。任何正相值都是相反的。

SmartLifecycle定义的stop方法接受回调。在该实现的关闭过程完成之后,任何实现都必须调用该回调的run()方法。这样可以在必要时启用异步关闭,因为LifecycleProcessor接口的默认实现DefaultLifecycleProcessor等待每个阶段内的对象组的超时值来调用该回调。默认的每阶段超时为30秒。您可以通过在上下文中定义名为lifecycleProcessor的bean来覆盖缺省生命周期处理器实例。如果您只想修改超时,则定义以下内容就足够了:

<bean id="lifecycleProcessor" class="org.springframework.context.support.DefaultLifecycleProcessor">
    <!-- timeout value in milliseconds -->
    <property name="timeoutPerShutdownPhase" value="10000"/>
</bean>

如前所述,LifecycleProcessor接口还定义了用于刷新和关闭上下文的回调方法。后者驱动关闭过程,就像显式调用了stop()一样,但是当上下文关闭时会发生。另一方面,'refresh'回调启用了SmartLifecycle bean的另一个功能。刷新上下文时(在实例化并初始化所有对象之后),将调用该回调。此时,默认生命周期处理器检查每个SmartLifecycle对象的isAutoStartup()方法返回的布尔值。如果为true,则在该点启动该对象,而不是等待显式调用上下文或其自己的start()方法(与上下文刷新不同,上下文启动不会自动发生在标准上下文实现中)。如前所述,阶段值和任何“依赖”关系确定启动顺序。

在非Web应用程序中优雅地关闭Spring IoC容器

本节仅适用于非Web应用程序。 Spring的基于Web的ApplicationContext实现已经具有代码,可以在相关Web应用程序关闭时正常关闭Spring IoC容器。

如果在非Web应用程序环境中使用Spring的IoC容器(例如,在富客户机桌面环境中),请使用JVM注册关闭挂钩。这样做可确保正常关闭并在单例bean上调用相关的destroy方法,以便释放所有资源。您仍然必须正确配置和实现这些destroy回调。

要注册关闭挂钩,请调用ConfigurableApplicationContext接口上声明的registerShutdownHook()方法,如以下示例所示:

import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public final class Boot {

    public static void main(final String[] args) throws Exception {
        ConfigurableApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml");

        // add a shutdown hook for the above context...
        ctx.registerShutdownHook();

        // app runs here...

        // main method exits, hook is called prior to the app shutting down...
    }
}
xinrong2019 commented 5 years ago

1.6.2. ApplicationContextAware and BeanNameAware

当ApplicationContext创建实现org.springframework.context.ApplicationContextAware接口的对象实例时,将为该实例提供对该ApplicationContext的引用。以下清单显示了ApplicationContextAware接口的定义:

public interface ApplicationContextAware {

    void setApplicationContext(ApplicationContext applicationContext) throws BeansException;
}

因此,bean可以通过ApplicationContext接口以编程方式操作创建它们的ApplicationContext,或者通过将引用转换为此接口的已知子类(例如ConfigurableApplicationContext,它公开其他功能)。一种用途是对其他bean进行编程检索。有时这种能力很有用。但是,一般情况下,您应该避免使用它,因为它将代码耦合到Spring并且不遵循Inversion of Control样式,其中协作者作为属性提供给bean。ApplicationContext的其他方法提供对文件资源的访问,发布应用程序事件和访问MessageSource。这些附加功能在ApplicationContext的附加功能中描述

从Spring 2.5开始,自动装配是另一种获取ApplicationContext引用的替代方法。 “传统”构造函数和byType自动装配模式(如自动装配协作者中所述)可以分别为构造函数参数或setter方法参数提供ApplicationContext类型的依赖关系。为了获得更大的灵活性,包括自动装配字段和多参数方法的能力,请使用基于注释的新自动装配功能。如果这样做,ApplicationContext将自动装入一个字段,构造函数参数或方法参数,如果相关的字段,构造函数或方法带有@Autowired批注,则该参数需要ApplicationContext类型。有关更多信息,请参阅使用@Autowired

当ApplicationContext创建实现org.springframework.beans.factory.BeanNameAware接口的类时,将为该类提供对其关联对象定义中定义的名称的引用。以下清单显示了BeanNameAware接口的定义:

public interface BeanNameAware {

    void setBeanName(String name) throws BeansException;
}

The callback is invoked after population of normal bean properties but before an initialization callback such as InitializingBean, afterPropertiesSet, or a custom init-method.

1.6.3. Other Aware Interfaces

除了ApplicationContextAware和BeanNameAware(前面讨论过)之外,Spring还提供了大量的Aware回调接口,让bean向容器指示它们需要某种基础结构依赖性。作为一般规则,名称表示依赖关系类型。下表总结了最重要的Aware接口:

Table 4. Aware interfaces

Name Injected Dependency Explained in…
ApplicationContextAware Declaring ApplicationContext. ApplicationContextAware and BeanNameAware
ApplicationEventPublisherAware Event publisher of the enclosing ApplicationContext. Additional Capabilities of the ApplicationContext
BeanClassLoaderAware Class loader used to load the bean classes. Instantiating Beans
BeanFactoryAware Declaring BeanFactory. ApplicationContextAware and BeanNameAware
BeanNameAware Name of the declaring bean. ApplicationContextAware and BeanNameAware
BootstrapContextAware Resource adapter BootstrapContext the container runs in. Typically available only in JCA-aware ApplicationContext instances. JCA CCI
LoadTimeWeaverAware Defined weaver for processing class definition at load time. Load-time Weaving with AspectJ in the Spring Framework
MessageSourceAware Configured strategy for resolving messages (with support for parametrization and internationalization). Additional Capabilities of the ApplicationContext
NotificationPublisherAware Spring JMX notification publisher. Notifications
ResourceLoaderAware Configured loader for low-level access to resources. Resources
ServletConfigAware Current ServletConfig the container runs in. Valid only in a web-aware Spring ApplicationContext. Spring MVC
ServletContextAware Current ServletContext the container runs in. Valid only in a web-aware Spring ApplicationContext. Spring MVC

请再次注意,使用这些接口会将您的代码绑定到Spring API,而不会遵循Inversion of Control样式。因此,我们建议将它们用于需要以编程方式访问容器的基础架构bean。

1.7 Bean定义继承

bean定义可以包含许多配置信息,包括构造函数参数,属性值和特定于容器的信息,例如初始化方法,静态工厂方法名称等。子bean定义从父定义继承配置数据。子定义可以覆盖某些值或根据需要添加其他值。使用父bean和子bean定义可以节省大量的输入。实际上,这是一种模板形式。

如果以编程方式使用ApplicationContext接口,则子bean定义由ChildBeanDefinition类表示。大多数用户不在此级别上使用它们。相反,它们在类(如ClassPathXmlApplicationContext)中以声明方式配置bean定义。使用基于XML的配置元数据时,可以使用parent属性指定子bean定义,将父bean指定为此属性的值。以下示例显示了如何执行此操作:

<bean id="inheritedTestBean" abstract="true"
        class="org.springframework.beans.TestBean">
    <property name="name" value="parent"/>
    <property name="age" value="1"/>
</bean>

<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定义从父级继承范围,构造函数参数值,属性值和方法覆盖,并带有添加新值的选项。您指定的任何范围,初始化方法,销毁方法或静态工厂方法设置都会覆盖相应的父设置。

其余设置始终取自子定义:取决于,autowire模式,依赖性检查,单例和惰性初始化。

前面的示例通过使用abstract属性将父bean定义显式标记为abstract。如果父定义未指定类,则需要将父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不能单独实例化,因为它不完整,并且也明确标记为抽象。当定义是抽象的时,它仅可用作纯模板bean定义,用作子定义的父定义。尝试使用这样一个抽象的父bean,通过将它作为另一个bean的ref属性引用或者使用父bean ID进行显式getBean()调用会返回错误。类似地,容器的内部preInstantiateSingletons()方法忽略定义为abstract的bean定义。

ApplicationContext默认情况下预先实例化所有单例。因此,重要的是(至少对于单例bean),如果你有一个(父)bean定义,你只打算用作模板,并且这个定义指定了一个类,你必须确保将abstract属性设置为true否则应用程序上下文将实际(尝试)预先实例化抽象bean。

xinrong2019 commented 5 years ago

1.8. Container Extension Points

通常,应用程序开发人员不需要继承ApplicationContext实现类。相反,可以通过插入特殊集成接口的实现来扩展Spring IoC容器。接下来的几节将介绍这些集成接口。

1.8.1. Customizing Beans by Using a BeanPostProcessor

BeanPostProcessor接口定义了可以实现的回调方法,以提供您自己的(或覆盖容器的默认)实例化逻辑,依赖关系解析逻辑等。如果要在Spring容器完成实例化,配置和初始化bean之后实现一些自定义逻辑,可以插入一个或多个自定义BeanPostProcessor实现

您可以配置多个BeanPostProcessor实例,并且可以通过设置order属性来控制这些BeanPostProcessor实例的执行顺序。仅当BeanPostProcessor实现Ordered接口时,才能设置此属性。如果编写自己的BeanPostProcessor,则应考虑实现Ordered接口。有关更多详细信息,请参阅BeanPostProcessorOrdered接口的javadoc。另请参阅有关BeanPostProcessor实例的编程注册的说明

BeanPostProcessor实例在bean(或对象)实例上运行。也就是说,Spring IoC容器实例化一个bean实例,然后BeanPostProcessor实例完成它们的工作。

BeanPostProcessor实例的范围是每个容器的范围。仅当您使用容器层次结构时,这才是相关的。如果在一个容器中定义BeanPostProcessor,它只对该容器中的bean进行后处理。换句话说,在一个容器中定义的bean不会被另一个容器中定义的BeanPostProcessor进行后处理,即使两个容器都是同一层次结构的一部分。

要更改实际的bean定义(即定义bean的蓝图),您需要使用BeanFactoryPostProcessor,如使用BeanFactoryPostProcessor定制配置元数据中所述。

org.springframework.beans.factory.config.BeanPostProcessor接口由两个回调方法组成。当这样的类被注册为容器的后处理器时,对于容器创建的每个bean实例,后处理器在容器初始化方法之前从容器中获取回调(例如InitializingBean.afterPropertiesSet()或在任何bean初始化回调之后调用任何声明的init方法)。后处理器可以对bean实例执行任何操作,包括完全忽略回调。 bean后处理器通常检查回调接口,或者它可以用代理包装bean。一些Spring AOP基础结构类实现为bean后处理器,以便提供代理包装逻辑。

ApplicationContext自动检测在实现BeanPostProcessor接口的配置元数据中定义的任何bean。 ApplicationContext将这些bean注册为后处理器,以便在创建bean时可以稍后调用它们。 Bean后处理器可以以与任何其他bean相同的方式部署在容器中。

注意,在配置类上使用@Bean工厂方法声明BeanPostProcessor时,工厂方法的返回类型应该是实现类本身,或者至少是org.springframework.beans.factory.config.BeanPostProcessor接口,指示该bean的后处理器性质。否则,ApplicationContext无法在完全创建之前按类型自动检测它。由于BeanPostProcessor需要尽早实例化以便应用于上下文中其他bean的初始化,因此这种早期类型检测至关重要。

以编程方式注册BeanPostProcessor实例

虽然BeanPostProcessor注册的推荐方法是通过ApplicationContext自动检测(如前所述),但您可以使用addBeanPostProcessor方法以编程方式对ConfigurableBeanFactory注册它们。当您需要在注册前评估条件逻辑或甚至跨层次结构中的上下文复制Bean post处理器时,这非常有用。但请注意,以编程方式添加的BeanPostProcessor实例不遵循Ordered接口。这里,注册的顺序决定了执行的顺序。另请注意,以编程方式注册的BeanPostProcessor实例始终在通过自动检测注册的实例之前处理,而不管任何显式排序。

BeanPostProcessor实例和AOP自动代理

实现BeanPostProcessor接口的类是特殊的,容器会对它们进行不同的处理。作为ApplicationContext的特殊启动阶段的一部分,它们直接引用的所有BeanPostProcessor实例和bean都在启动时实例化。接下来,所有BeanPostProcessor实例都以排序方式注册,并应用于容器中的所有其他bean。因为AOP自动代理是作为BeanPostProcessor本身实现的,所以BeanPostProcessor实例和它们直接引用的bean都不符合自动代理的条件,因此没有编入方法。

For any such bean, you should see an informational log message: Bean someBean is not eligible for getting processed by all BeanPostProcessor interfaces (for example: not eligible for auto-proxying).

如果您通过使用自动装配或@Resource(可能会回退到自动装配)将Bean连接到BeanPostProcessor,则Spring可能会在搜索类型匹配依赖项候选项时访问意外的bean,因此使它们不符合自动代理或其他类型的条件豆后处理。例如,如果您有一个使用@Resource注释的依赖项,其中字段或setter名称不直接对应于bean的声明名称而且没有使用name属性,则Spring会访问其他bean以按类型匹配它们。

以下示例显示如何在ApplicationContext中编写,注册和使用BeanPostProcessor实例。

Example: Hello World, BeanPostProcessor-style

第一个例子说明了基本用法。该示例显示了一个自定义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) {
        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 = (Messenger) ctx.getBean("messenger");
        System.out.println(messenger);
    }

}

上述应用程序的输出类似于以下内容:

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

Example: The RequiredAnnotationBeanPostProcessor

将回调接口或注释与自定义BeanPostProcessor实现结合使用是扩展Spring IoC容器的常用方法。一个例子是Spring的RequiredAnnotationBeanPostProcessor - 一个随Spring发行版一起提供的BeanPostProcessor实现,它确保用(任意)注释标记的bean上的JavaBean属性实际上(配置为)依赖注入值。

xinrong2019 commented 5 years ago

1.8.2. Customizing Configuration Metadata with a BeanFactoryPostProcessor

我们看到的下一个扩展点是org.springframework.beans.factory.config.BeanFactoryPostProcessor。此接口的语义类似于BeanPostProcessor的语义,但有一个主要区别:BeanFactoryPostProcessor对bean配置元数据进行操作。也就是说,Spring IoC容器允许BeanFactoryPostProcessor读取配置元数据,并可能在容器实例化除BeanFactoryPostProcessor实例之外的任何bean之前更改它。

您可以配置多个BeanFactoryPostProcessor实例,并且可以通过设置order属性来控制这些BeanFactoryPostProcessor实例的运行顺序。但是,如果BeanFactoryPostProcessor实现Ordered接口,则只能设置此属性。如果编写自己的BeanFactoryPostProcessor,则应考虑实现Ordered接口。有关更多详细信息,请参阅BeanFactoryPostProcessorOrdered接口的javadoc。

如果要更改实际的bean实例(即,从配置元数据创建的对象),则需要使用BeanPostProcessor(前面在使用BeanPostProcessor定制Bean中进行了描述)。虽然技术上可以在BeanFactoryPostProcessor中使用bean实例(例如,通过使用BeanFactory.getBean()),但这样做会导致过早的bean实例化,从而违反标准的容器生命周期。这可能会导致负面影响,例如绕过bean后期处理。

此外,BeanFactoryPostProcessor实例的范围是每个容器的范围。仅当您使用容器层次结构时,这才有意义。如果在一个容器中定义BeanFactoryPostProcessor,则它仅应用于该容器中的bean定义。一个容器中的Bean定义不会被另一个容器中的BeanFactoryPostProcessor实例后处理,即使两个容器都是同一层次结构的一部分。

bean工厂后处理器在ApplicationContext中声明时自动执行,以便将更改应用于定义容器的配置元数据。 Spring包含许多预定义的bean工厂后处理器,例如PropertyOverrideConfigurer和PropertyPlaceholderConfigurer。您还可以使用自定义BeanFactoryPostProcessor - 例如,注册自定义属性编辑器。

ApplicationContext自动检测部署到其中的任何实现BeanFactoryPostProcessor接口的bean。它在适当的时候使用这些bean作为bean工厂后处理器。您可以像处理任何其他bean一样部署这些后处理器bean。

与BeanPostProcessors一样,您通常不希望为延迟初始化配置BeanFactoryPostProcessors。如果没有其他bean引用Bean(Factory)PostProcessor,则该后处理器根本不会被实例化。因此,将忽略将其标记为延迟初始化,即使在<beans />元素的声明中将default-lazy-init属性设置为true,也会急切地实例化Bean(Factory)PostProcessor。

Example: The Class Name Substitution PropertyPlaceholderConfigurer

您可以使用PropertyPlaceholderConfigurer通过使用标准Java Properties格式从单独文件中的bean定义外部化属性值。这样做可以使部署应用程序的人员自定义特定于环境的属性,例如数据库URL和密码,而不会出现修改主XML定义文件或容器文件的复杂性或风险。

请考虑以下基于XML的配置元数据片段,其中定义了具有占位符值的DataSource:

<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
    <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>

该示例显示了从外部属性文件配置的属性。在运行时,PropertyPlaceholderConfigurer应用于替换DataSource的某些属性的元数据。要替换的值被指定为$ {property-name}形式的占位符,它遵循Ant和log4j以及JSP EL样式。

实际值来自标准Java Properties格式的另一个文件:

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

因此,$ {jdbc.username}字符串在运行时将替换为值'sa',并且同样适用于与属性文件中的键匹配的其他占位符值。 PropertyPlaceholderConfigurer检查bean定义的大多数属性和属性中的占位符。此外,您可以自定义占位符前缀和后缀。

使用Spring 2.5中引入的上下文命名空间,您可以使用专用配置元素配置属性占位符。您可以在location属性中以逗号分隔列表的形式提供一个或多个位置,如以下示例所示:

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

PropertyPlaceholderConfigurer不仅在您指定的属性文件中查找属性。默认情况下,如果它在指定的属性文件中找不到属性,它还会检查Java System属性。您可以通过使用以下三个受支持的整数值之一设置configurer的systemPropertiesMode属性来自定义此行为:

See the PropertyPlaceholderConfigurer javadoc for more information.

您可以使用PropertyPlaceholderConfigurer替换类名,这在您必须在运行时选择特定实现类时有时很有用。以下示例显示了如何执行此操作:

<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
    <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时,bean的解析将失败,这是在非lazy-init bean的ApplicationContext的preInstantiateSingletons()阶段期间。

Example: The PropertyOverrideConfigurer

PropertyOverrideConfigurer是另一个bean工厂后处理器,类似于PropertyPlaceholderConfigurer,但与后者不同,原始定义可以具有默认值,或者根本不具有bean属性的值。如果重写的Properties文件没有某个bean属性的条目,则使用默认的上下文定义。

请注意,bean定义不知道被覆盖,因此从XML定义文件中可以立即看出正在使用覆盖配置器。如果多个PropertyOverrideConfigurer实例为同一个bean属性定义了不同的值,则由于覆盖机制,最后一个实例将获胜。

属性文件配置行采用以下格式:

beanName.property=value

The following listing shows an example of the format:

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

此示例文件可以与容器定义一起使用,该容器定义包含名为dataSource的bean,该bean具有driver和url属性。

也支持复合属性名称,只要路径的每个组件(重写的最终属性除外)都已经非空(可能由构造函数初始化)。在下面的示例中,tom bean的fred属性的bob属性的sammy属性设置为标量值123:

tom.fred.bob.sammy=123

指定的覆盖值始终是文字值。它们不会被翻译成bean引用。当XML bean定义中的原始值指定bean引用时,此约定也适用。

使用Spring 2.5中引入的上下文命名空间,可以使用专用配置元素配置属性覆盖,如以下示例所示:

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

xinrong2019 commented 5 years ago

1.8.3. Customizing Instantiation Logic with a FactoryBean

FactoryBean接口是Spring IoC容器实例化逻辑的可插拔点。如果您有更复杂的初始化代码,这些代码在Java中更好地表达,而不是(可能)冗长的XML,您可以创建自己的FactoryBean,在该类中编写复杂的初始化,然后将自定义FactoryBean插入容器中。

FactoryBean接口提供了三种方法:

FactoryBean概念和接口用于Spring Framework中的许多位置。 FactoryBean接口的50多个实现随Spring一起提供。

当您需要向容器询问实际的FactoryBean实例本身而不是它生成的bean时,在调用ApplicationContext的getBean()方法时,使用&符号(&)作为bean的id前缀。因此,对于id为myBean的给定FactoryBean,在容器上调用getBean(“myBean”)将返回FactoryBean的产品,而调用getBean(“&myBean”)则返回FactoryBean实例本身。