Spring官方文档阅读(十)之IOC容器(ApplicationCotext其他的功能和BeanFactory)

15.ApplicationContext的其他功能

如本章简介中所述,org.springframework.beans.factory包提供了用于管理和操作bean的基本功能,包括以编程方式。 org.springframework.context包添加了ApplicationContext接口,该接口扩展了BeanFactory接口,此外还扩展了其他接口以提供更多面向应用程序框架的样式的附加功能。 许多人以完全声明性的方式使用ApplicationContext,甚至没有以编程方式创建它,而是依靠诸如ContextLoader之类的支持类来自动实例化ApplicationContext作为Java EE Web应用程序正常启动过程的一部分。

为了以更加面向框架的方式增强BeanFactory的功能,上下文包还提供以下功能:

  • 通过MessageSource界面访问i18n样式的消息。
  • 通过ResourceLoader界面访问资源,例如URL和文件。
  • 通过使用ApplicationEventPublisher接口,将事件发布到实现ApplicationListener接口的bean。
  • 加载多个(分层)上下文,使每个上下文都通过HierarchicalBeanFactory接口集中在一个特定层上,例如应用程序的Web层。

15.1使用MessageSource进行国际化

ApplicationContext接口扩展了一个称为MessageSource的接口,因此提供了国际化(“ i18n”)功能。 Spring还提供了HierarchicalMessageSource接口,该接口可以分层解析消息。 这些接口一起提供了Spring影响消息解析的基础。 这些接口上定义的方法包括:

  • String getMessage(String code,Object [] args,String default,Locale loc):用于从MessageSource检索消息的基本方法。 如果找不到指定语言环境的消息,则使用默认消息。 使用标准库提供的MessageFormat功能,传入的所有参数都将成为替换值。
  • String getMessage(String code,Object [] args,Locale loc):与先前的方法基本相同,但有一个区别:无法指定默认消息。 如果找不到该消息,则抛出NoSuchMessageException
  • String getMessage(MessageSourceResolvable resolvable,Locale locale):前述方法中使用的所有属性也都包装在一个名为MessageSourceResolvable的类中,您可以将其与该方法一起使用。

加载ApplicationContext时,它将自动搜索在上下文中定义的MessageSource bean。 Bean必须具有名称messageSource。 如果找到了这样的bean,则对先前方法的所有调用都将委派给消息源。 如果找不到消息源,则ApplicationContext尝试查找包含同名bean的父级。 如果是这样,它将使用该bean作为MessageSource。 如果ApplicationContext找不到任何消息源,则将实例化一个空的DelegatingMessageSource,以便能够接受对上面定义的方法的调用。

Spring提供了两个MessageSource实现,即ResourceBundleMessageSourceStaticMessageSource。 两者都实现HierarchicalMessageSource以便进行嵌套消息传递。 StaticMessageSource很少使用,但是提供了将消息添加到源中的编程方式。 以下示例显示ResourceBundleMessageSource

<beans>
    <bean id="messageSource"
            class="org.springframework.context.support.ResourceBundleMessageSource">
        <property name="basenames">
            <list>
                <value>format</value>
                <value>exceptions</value>
                <value>windows</value>
            </list>
        </property>
    </bean>
</beans>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

该示例假定您在类路径中定义了三个资源包,分别称为formatexceptionswindows。 解析消息的任何请求都通过JDK标准的ResourceBundle对象解析消息来处理。 就本示例而言,假定上述两个资源束文件的内容如下:

 # in format.properties
 message=Alligators rock!
  • 1
  • 2
# in exceptions.properties
argument.required=The {0} argument is required.
  • 1
  • 2

下一个示例显示了运行MessageSource功能的程序。 请记住,所有ApplicationContext实现也是MessageSource实现,因此可以转换为MessageSource接口。

public static void main(String[] args) {
    MessageSource resources = new ClassPathXmlApplicationContext("beans.xml");
    String message = resources.getMessage("message", null, "Default", Locale.ENGLISH);
    System.out.println(message);
}
  • 1
  • 2
  • 3
  • 4
  • 5

以上程序的结果输出如下:

Alligators rock!
  • 1

总而言之,MessageSource是在名为beans.xml的文件中定义的,该文件位于类路径的根目录下。 messageSource bean定义通过其basenames属性引用了许多资源包。 列表中传递给basenames属性的三个文件在类路径的根目录下以文件形式存在,分别称为format.propertiesexceptions.propertieswindows.properties

下一个示例显示了传递给消息查找的参数。 这些参数将转换为String对象,并插入到查找消息中的占位符中。

<beans>

    <!-- this MessageSource is being used in a web application -->
    <bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource">
        <property name="basename" value="exceptions"/>
    </bean>

    <!-- lets inject the above MessageSource into this POJO -->
    <bean id="example" class="com.something.Example">
        <property name="messages" ref="messageSource"/>
    </bean>

</beans>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
public class Example {

    private MessageSource messages;

    public void setMessages(MessageSource messages) {
        this.messages = messages;
    }

    public void execute() {
        String message = this.messages.getMessage("argument.required",
            new Object [] {"userDao"}, "Required", Locale.ENGLISH);
        System.out.println(message);
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

调用execute()方法的结果输出如下所示

关于国际化(“ i18n”),Spring的各种MessageSource实现遵循与标准JDK ResourceBundle相同的语言环境解析和后备规则。 简而言之,并继续前面定义的示例messageSource,如果要针对英国(en-GB)语言环境解析消息,则将分别创建名为format_en_GB.propertiesexceptions_en_GB.propertieswindows_en_GB.properties的文件。

通常,语言环境解析由应用程序的周围环境管理。 在以下示例中,手动指定了针对其解析(英国)消息的语言环境:

# in exceptions_en_GB.properties
argument.required=Ebagum lad, the ''{0}'' argument is required, I say, required.
  • 1
  • 2
public static void main(final String[] args) {
    MessageSource resources = new ClassPathXmlApplicationContext("beans.xml");
    String message = resources.getMessage("argument.required",
        new Object [] {"userDao"}, "Required", Locale.UK);
    System.out.println(message);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

运行上述程序的结果输出如下:

Ebagum lad, the 'userDao' argument is required, I say, required.
  • 1

您还可以使用MessageSourceAware接口获取对已定义的任何MessageSource的引用。 创建和配置bean时,在ApplicationContext中实现MessageSourceAware接口的所有bean都会与应用程序上下文的MessageSource一起注入。

注意:

  • 作为ResourceBundleMessageSource的替代,Spring提供了ReloadableResourceBundleMessageSource类。 此变体支持相同的包文件格式,但比基于标准JDK的ResourceBundleMessageSource实现要灵活得多。 特别是,它允许从任何Spring资源位置(不仅从类路径)读取文件,并支持热重载捆绑属性文件(同时在它们之间进行有效缓存)。

15.2标准和自定义事件

通过ApplicationEvent类和ApplicationListener接口提供ApplicationContext中的事件处理。 如果将实现ApplicationListener接口的bean部署到上下文中,则每次将ApplicationEvent发布到ApplicationContext时,都会通知该bean。 本质上,这是标准的Observer设计模式。

注意:

  • 从Spring 4.2开始,事件基础结构得到了显着改进,并提供了基于注解的模型以及发布任意事件(即不一定从ApplicationEvent扩展的对象)的功能。 发布此类对象后,我们会为您包装一个事件。
事件 解释
ContextRefreshedEvent 在初始化或刷新ApplicationContext时发布(例如,通过使用ConfigurableApplicationContext接口上的refresh()方法)。 在这里,“已初始化”是指所有Bean都已加载,检测到并激活了后处理器Bean,已预先实例化单例并且可以使用ApplicationContext对象。 只要尚未关闭上下文,只要所选的ApplicationContext实际上支持这种“热”刷新,就可以多次触发刷新。 例如,XmlWebApplicationContext支持热刷新,但GenericApplicationContext不支持。
ContextStartedEvent 在ConfigurableApplicationContext接口上使用start()方法启动ApplicationContext时发布。 在这里,“已启动”是指所有Lifecycle bean都收到一个明确的启动信号。 通常,此信号用于在显式停止后重新启动Bean,但也可以用于启动尚未配置为自动启动的组件(例如,尚未在初始化时启动的组件)。
ContextStoppedEvent 通过使用ConfigurableApplicationContext接口上的stop()方法停止ApplicationContext时发布。 在这里,“已停止”表示所有Lifecycle bean都收到一个明确的停止信号。 停止的上下文可以通过start()调用重新启动
ContextClosedEvent 通过使用ConfigurableApplicationContext接口上的close()方法或通过JVM关闭钩子关闭ApplicationContext时发布。 在这里,“封闭”意味着所有单例豆将被销毁。 关闭上下文后,它将达到使用寿命,无法刷新或重新启动。
RequestHandledEvent 一个特定于Web的事件,告诉所有Bean HTTP请求已得到服务。 请求完成后,将发布此事件。 此事件仅适用于使用Spring的DispatcherServlet的Web应用程序。
ServletRequestHandledEvent RequestHandledEvent的子类,添加了特定于Servlet的上下文信息。

您还可以创建和发布自己的自定义事件。 以下示例显示了一个简单的类,该类扩展了Spring的ApplicationEvent基类:

public class BlockedListEvent extends ApplicationEvent {

    private final String address;
    private final String content;

    public BlockedListEvent(Object source, String address, String content) {
        super(source);
        this.address = address;
        this.content = content;
    }

    // accessor and other methods...
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

若要发布自定义ApplicationEvent,请在ApplicationEventPublisher上调用publishEvent()方法。 通常,这是通过创建一个实现ApplicationEventPublisherAware的类并将其注册为Spring Bean来完成的。 以下示例显示了此类:

public class EmailService implements ApplicationEventPublisherAware {

    private List<String> blockedList;
    private ApplicationEventPublisher publisher;

    public void setBlockedList(List<String> blockedList) {
        this.blockedList = blockedList;
    }

    public void setApplicationEventPublisher(ApplicationEventPublisher publisher) {
        this.publisher = publisher;
    }

    public void sendEmail(String address, String content) {
        if (blockedList.contains(address)) {
            publisher.publishEvent(new BlockedListEvent(this, address, content));
            return;
        }
        // send email...
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

在配置时,Spring容器检测到EmailService实现了ApplicationEventPublisherAware并自动调用setApplicationEventPublisher()。 实际上,传入的参数是Spring容器本身。 您正在通过其ApplicationEventPublisher接口与应用程序上下文进行交互。

要接收自定义ApplicationEvent,可以创建一个实现ApplicationListener的类,并将其注册为Spring Bean。 以下示例显示了此类:

public class BlockedListNotifier implements ApplicationListener<BlockedListEvent> {

    private String notificationAddress;

    public void setNotificationAddress(String notificationAddress) {
        this.notificationAddress = notificationAddress;
    }

    public void onApplicationEvent(BlockedListEvent event) {
        // notify appropriate parties via notificationAddress...
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

注意,ApplicationListener通常使用您的自定义事件的类型(在前面的示例中为BlockedListEvent)进行参数化。 这意味着onApplicationEvent()方法可以保持类型安全,从而避免了向下转换的任何需要。 您可以根据需要注册任意数量的事件侦听器,但是请注意,默认情况下,事件侦听器会同步接收事件。 这意味着publishEvent()方法将阻塞,直到所有侦听器都已完成对事件的处理为止。 这种同步和单线程方法的一个优点是,当侦听器接收到事件时,如果有可用的事务上下文,它将在发布者的事务上下文内部进行操作。 如果有必要采用其他发布事件的策略.

以下示例显示了用于注册和配置上述每个类的Bean定义:

<bean id="emailService" class="example.EmailService">
    <property name="blockedList">
        <list>
            <value>known.spammer@example.org</value>
            <value>known.hacker@example.org</value>
            <value>john.doe@example.org</value>
        </list>
    </property>
</bean>

<bean id="blockedListNotifier" class="example.BlockedListNotifier">
    <property name="notificationAddress" value="blockedlist@example.org"/>
</bean>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

将所有内容放在一起,当调用emailService bean的sendEmail()方法时,如果有任何应阻止的电子邮件,则会发布BlockedListEvent类型的自定义事件。 blockedListNotifier bean被注册为ApplicationListener并接收BlockedListEvent,这时它可以通知适当的参与者。

注意:

  • Spring的事件机制旨在在同一应用程序上下文内在Spring bean之间进行简单的通信。 但是,对于更复杂的企业集成需求,单独维护的Spring Integration项目为基于著名的Spring编程模型构建轻量级,面向模式,事件驱动的架构提供了完整的支持。

15.21基于注解的事件监听器

从Spring 4.2开始,您可以使用@EventListener注解在托管Bean的任何公共方法上注册事件侦听器。 BlockedListNotifier可以重写如下:

public class BlockedListNotifier {

    private String notificationAddress;

    public void setNotificationAddress(String notificationAddress) {
        this.notificationAddress = notificationAddress;
    }

    @EventListener
    public void processBlockedListEvent(BlockedListEvent event) {
        // notify appropriate parties via notificationAddress...
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

方法签名再次声明其侦听的事件类型,但是这次使用灵活的名称并且没有实现特定的侦听器接口。 只要实际事件类型在其实现层次结构中解析您的通用参数,也可以通过通用类型来缩小事件类型。

如果您的方法应该侦听多个事件,或者您要完全不使用任何参数来定义它,则事件类型也可以在注释本身上指定。 以下示例显示了如何执行此操作:

@EventListener({ContextStartedEvent.class, ContextRefreshedEvent.class})
public void handleContextStart() {
    // ...
}
  • 1
  • 2
  • 3
  • 4

还可以通过使用定义SpEL表达式的注释的condition属性来添加其他运行时过滤,该注释应匹配以针对特定事件实际调用该方法。

以下示例显示了仅当事件的content属性等于my-event时,才可以重写我们的通知程序以进行调用:

@EventListener(condition = "#blEvent.content == 'my-event'")
public void processBlockedListEvent(BlockedListEvent blockedListEvent) {
    // notify appropriate parties via notificationAddress...
}
  • 1
  • 2
  • 3
  • 4

每个SpEL表达式都会根据专用上下文进行评估。 下表列出了可用于上下文的项目,以便您可以将它们用于条件事件处理:

Name Location Description Example
Event root object 实际的ApplicationEvent。 #root.event or event
Arguments array root object 用于调用方法的参数(作为对象数组)。 #root.args或args; args [0]访问第一个参数,依此类推。
Argument name evaluation context 任何方法参数的名称。 如果由于某种原因这些名称不可用(例如,由于在编译的字节码中没有调试信息),则还可以使用#a <#arg>语法(其中<#arg>代表 参数索引(从0开始)。 #blEvent或#a0(您也可以使用#p0或#p <#arg>参数表示法作为别名)

请注意,即使您的方法签名实际上引用了已发布的任意对象,#root.event也使您可以访问基础事件。

如果由于处理另一个事件而需要发布一个事件,则可以更改方法签名以返回应发布的事件,如以下示例所示:

@EventListener
public ListUpdateEvent handleBlockedListEvent(BlockedListEvent event) {
    // notify appropriate parties via notificationAddress and
    // then publish a ListUpdateEvent...
}
  • 1
  • 2
  • 3
  • 4
  • 5

注意:异步监听器不支持此特性。

此新方法为上述方法处理的每个BlockedListEvent发布一个新的ListUpdateEvent。 如果需要发布多个事件,则可以返回事件的Collection

15.22异步侦听器

如果希望特定的侦听器异步处理事件,则可以重用常规的@Async支持。 以下示例显示了如何执行此操作:

@EventListener
@Async
public void processBlockedListEvent(BlockedListEvent event) {
    // BlockedListEvent is processed in a separate thread
}
  • 1
  • 2
  • 3
  • 4
  • 5

使用异步事件时,请注意以下限制:

  • 如果异步事件侦听器引发Exception,则不会将其传播到调用方。
  • 异步事件侦听器方法无法通过返回值来发布后续事件。 如果您需要发布另一个事件作为处理的结果,请注入ApplicationEventPublisher以手动发布事件。

15.23Ordering Listeners

如果需要先调用一个侦听器,则可以在方法声明中添加@Order批注,如以下示例所示:

@EventListener
@Order(42)
public void processBlockedListEvent(BlockedListEvent event) {
    // notify appropriate parties via notificationAddress...
}
  • 1
  • 2
  • 3
  • 4
  • 5

15.24一般事件

您还可以使用泛型来进一步定义事件的结构。 考虑使用EntityCreatedEvent <T>,其中T是已创建的实际实体的类型。 例如,您可以创建以下侦听器定义以仅接收PersonEntityCreatedEvent

@EventListener
public void onPersonCreated(EntityCreatedEvent<Person> event) {
    // ...
}
  • 1
  • 2
  • 3
  • 4

由于类型擦除,只有在触发的事件解析了事件侦听器用来过滤的通用参数(即类似PersonCreatedEvent的类扩展EntityCreatedEvent <Person> {…})时,此方法才起作用。

在某些情况下,如果所有事件都遵循相同的结构,这可能会变得很乏味(就像前面示例中的事件一样)。 在这种情况下,您可以实现ResolvableTypeProvider来指导框架,使其超出运行时环境提供的范围。 以下事件显示了如何执行此操作:

public class EntityCreatedEvent<T> extends ApplicationEvent implements ResolvableTypeProvider {

    public EntityCreatedEvent(T entity) {
        super(entity);
    }

    @Override
    public ResolvableType getResolvableType() {
        return ResolvableType.forClassWithGenerics(getClass(), ResolvableType.forInstance(getSource()));
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

注意:这不仅适用于ApplicationEvent,而且适用于您作为事件发送的任何任意对象。

15.3方便地访问低级资源

为了获得最佳用法和对应用程序上下文的理解,您应该熟悉Spring的Resource抽象。

应用程序上下文是ResourceLoader,可用于加载Resource对象。 Resource本质上是JDK java.net.URL类的功能更丰富的版本。 实际上,Resource的实现在适当的地方包装了java.net.URL的实例。 资源可以以透明的方式从几乎任何位置获取低级资源,包括从类路径,文件系统位置,可使用标准URL描述的任何位置以及其他一些变体。 如果资源位置字符串是没有任何特殊前缀的简单路径,则这些资源的来源是特定的并且适合于实际的应用程序上下文类型。

您可以配置部署到应用程序上下文中的Bean,以实现特殊的回调接口ResourceLoaderAware,以便在初始化时自动调用,并将应用程序上下文本身作为ResourceLoader传入。 您还可以公开Resource类型的属性,以用于访问静态资源。 它们像其他任何属性一样注入其中。 您可以将那些Resource属性指定为简单的String路径,并在部署bean时依靠从这些文本字符串到实际Resource对象的自动转换。

提供给ApplicationContext构造函数的一个或多个位置路径实际上是资源字符串,并且根据特定的上下文实现以简单的形式对其进行适当处理。 例如,ClassPathXmlApplicationContext将简单的位置路径视为类路径位置。 您也可以使用带有特殊前缀的位置路径(资源字符串)来强制从类路径或URL中加载定义,而不管实际的上下文类型如何。

15.4Web应用程序的便捷ApplicationContext实例化

您可以使用例如ContextLoader声明性地创建ApplicationContext实例。 当然,您也可以使用ApplicationContext实现之一以编程方式创建ApplicationContext实例。

您可以使用ContextLoaderListener注册ApplicationContext,如以下示例所示:

<context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>/WEB-INF/daoContext.xml /WEB-INF/applicationContext.xml</param-value>
</context-param>

<listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

侦听器检查contextConfigLocation参数。 如果参数不存在,那么侦听器将使用/WEB-INF/applicationContext.xml作为默认值。 当该参数确实存在时,侦听器将使用预定义的定界符(逗号,分号和空格)来分隔String,并将这些值用作搜索应用程序上下文的位置。 还支持蚂蚁风格的路径模式。 示例包括/WEB-INF/*Context.xml(适用于所有名称以Context.xml结尾且位于WEB-INF目录中的文件)和/ WEB-INF / ** / * Context.xml(适用于所有此类文件) 文件在WEB-INF的任何子目录中)。

15.5将Spring ApplicationContext部署为Java EE RAR文件

可以将Spring ApplicationContext部署为RAR文件,将上下文及其所有必需的bean类和库JAR封装在Java EE RAR部署单元中。 这相当于引导独立的ApplicationContext(仅在Java EE环境中托管)能够访问Java EE服务器功能。 RAR部署是部署无头WAR文件的方案的一种更自然的选择-实际上,这种WAR文件没有任何HTTP入口点,仅用于在Java EE环境中引导Spring ApplicationContext

对于不需要HTTP入口点而仅由消息端点和计划的作业组成的应用程序上下文,RAR部署是理想的选择。 在这样的上下文中,Bean可以使用应用程序服务器资源,例如JTA事务管理器和JNDI绑定的JDBC DataSource实例以及JMS ConnectionFactory实例,并且还可以在平台的JMX服务器上注册-贯穿于Spring的标准事务管理以及JNDI和JMX支持工具。 应用程序组件还可以通过Spring的TaskExecutor抽象与应用程序服务器的JCA WorkManager进行交互。

对于将Spring ApplicationContext作为Java EE RAR文件的简单部署:

  1. 将所有应用程序类打包到RAR文件(这是具有不同文件扩展名的标准JAR文件)中。 将所有必需的库JAR添加到RAR归档文件的根目录中。 添加一个META-INF / ra.xml部署描述符(如SpringContextResourceAdapter的javadoc中所示)和相应的Spring XML bean定义文件(通常为META-INF / applicationContext.xml)。
  2. 将生成的RAR文件拖放到应用程序服务器的部署目录中。

注意:

  • 此类RAR部署单元通常是独立的。 它们不会将组件暴露给外界,甚至不会暴露给同一应用程序的其他模块。 与基于RAR的ApplicationContext的交互通常是通过与其他模块共享的JMS目标进行的。 例如,基于RAR的ApplicationContext还可以安排一些作业或对文件系统(或类似文件)中的新文件做出反应。 如果需要允许从外部进行同步访问,则可以(例如)导出RMI端点,该端点可以由同一台计算机上的其他应用程序模块使用。

16.BeanFactory

BeanFactory API为Spring的IoC功能提供了基础。 它的特定合同主要用于与Spring的其他部分以及相关的第三方框架集成,并且其DefaultListableBeanFactory实现是更高级别的GenericApplicationContext容器中的关键委托。

BeanFactory和相关接口(例如BeanFactoryAwareInitializingBeanDisposableBean)是其他框架组件的重要集成点。 通过不需要任何注释甚至反射,它们可以在容器及其组件之间进行非常有效的交互。 应用程序级Bean可以使用相同的回调接口,但通常更喜欢通过注释或通过程序配置进行声明式依赖注入。

请注意,核心BeanFactory API级别及其DefaultListableBeanFactory实现不对配置格式或要使用的任何组件注释进行假设。 所有这些风味都是通过扩展(例如XmlBeanDefinitionReaderAutowiredAnnotationBeanPostProcessor)引入的,并以核心元数据表示形式对共享BeanDefinition对象进行操作。 这就是使Spring的容器如此灵活和可扩展的本质。

16.1BeanFactory或ApplicationContext?

本节说明BeanFactoryApplicationContext容器级别之间的区别以及对引导的影响。

除非有充分的理由,否则应使用ApplicationContext,将GenericApplicationContext及其子类AnnotationConfigApplicationContext作为自定义引导的常见实现。 这些是用于所有常见目的的Spring核心容器的主要入口点:加载配置文件,触发类路径扫描,以编程方式注册Bean定义和带注释的类,以及(从5.0版本开始)注册功能性Bean定义。

因为ApplicationContext包含BeanFactory的所有功能,所以通常建议在普通BeanFactory上使用,除非需要对Bean处理的完全控制。 在ApplicationContext(例如GenericApplicationContext实现)中,按照惯例(即,按Bean名称或Bean类型(尤其是后处理器))检测到几种Bean,而普通的DefaultListableBeanFactory则与任何特殊的Bean无关。

对于许多扩展的容器功能,例如注解处理和AOP代理,BeanPostProcessor扩展点至关重要。 如果仅使用普通的DefaultListableBeanFactory,则默认情况下不会检测到此类后处理器并将其激活。 这种情况可能会造成混乱,因为您的bean配置实际上并没有错。 而是在这种情况下,需要通过其他设置完全引导容器。

下表列出了BeanFactoryApplicationContext接口和实现所提供的功能。

Feature BeanFactory ApplicationContext
Bean instantiation/wiring Yes Yes
Integrated lifecycle management No Yes
Automatic BeanPostProcessor registration No Yes
Automatic BeanFactoryPostProcessor registration No Yes
Convenient MessageSource access (for internalization) No Yes
Built-in ApplicationEvent publication mechanism No Yes

要向DefaultListableBeanFactory显式注册Bean后处理器,需要以编程方式调用addBeanPostProcessor,如以下示例所示:

DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
// populate the factory with bean definitions

// now register any needed BeanPostProcessor instances
factory.addBeanPostProcessor(new AutowiredAnnotationBeanPostProcessor());
factory.addBeanPostProcessor(new MyBeanPostProcessor());

// now start using the factory
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

要将BeanFactoryPostProcessor应用于普通的DefaultListableBeanFactory,您需要调用其postProcessBeanFactory方法,如以下示例所示:

DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory);
reader.loadBeanDefinitions(new FileSystemResource("beans.xml"));

// bring in some property values from a Properties file
PropertySourcesPlaceholderConfigurer cfg = new PropertySourcesPlaceholderConfigurer();
cfg.setLocation(new FileSystemResource("jdbc.properties"));

// now actually do the replacement
cfg.postProcessBeanFactory(factory);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

在这两种情况下,显式的注册步骤都不方便,这就是为什么在Spring支持的应用程序中,各种ApplicationContext变量比普通的DefaultListableBeanFactory更为可取的原因,尤其是在典型企业设置中依赖BeanFactoryPostProcessorBeanPostProcessor实例来扩展容器功能时。

注意:

  • AnnotationConfigApplicationContext已注册了所有常用的注释后处理器,并且可以通过配置注释(例如@EnableTransactionManagement)在幕后引入其他处理器。 在Spring基于注释的配置模型的抽象级别上,bean后处理器的概念仅是内部容器详细信息。
上一篇:Spring IoC依赖注入:Spring提供了哪些依赖注入模式和类型呢?


下一篇:Spring IoC依赖注入:Spring提供了哪些依赖注入模式和类型呢?