spring学习(十二)——spring官方文档阅读(5.0.7)——ApplicationContext的额外能力

ApplicationContext的额外能力

Spring IOC容器也提供了下列功能:

1、负责国际化的MessageSource接口

2、通过ResourceLoader接口来加载资源

3、负责事件发布的ApplicationEventPublisher,监听器ApplicationListener

4、提供父容器访问功能的HierarchicalBeanFactory

这里只介绍前三项

MessageSource

ApplicationContext接口继承并扩展了MessageSource,提供了国际化的能力,HierarchicalMessageSource接口可以分层次的解析消息,MessageSource接口提供了下列方法:

String getMessage(String code,Object[] args,String default,Locale loc):如果在locale中没有检索到对应消息,则使用default指定的消息,args指定占位符的值,code指定消息名,loc指定国际化信息

String getMessage(String code,Object[] args,Locale loc):与前面的函数功能一样,不一样的是缺少了default参数,如果没有找到对应的消息,会抛出NoSuchMessageException异常

String getMessage(MessageSourceResolvable resolvable,Locale locale):上述两种方法所用到的参数全部封装在MessageSourceResolvable,功能与前两者一样

当ApplicationContext初始化后,会查找MessageSource实现类对应的bean,这个bean的名字必须是messageSource,如果这样的bena被查找到,所有之前之前描述的方法都会由这个bean调用,否则,ApplicationContext会查找父容器,如果父容器也没有查找到,一个空的DelegatingMessageSource对象会被实例化,负责上述方法的调用,MessageSource只能查看类路径下(classpath)的文件

Spring提供了两种MessageSource的实现——ResourceBundleMessageSource和StaticMessageSource,两者都继承并实现了HierarchicalMessageSource(可以嵌套处理message),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>

在basenames属性中可以指定资源文件,上述例子假设我们有三个资源文件,名为format、exceptions、windows,如下:

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

省略了windwos.properties文件,由于ApplicationContext本身继承了MessageSource接口,所以可以如下使用:

public static void main(String[] args) {
    MessageSource resources = new ClassPathXmlApplicationContext("beans.xml");
    String message = resources.getMessage("message", null, "Default", null);
    System.out.println(message);
}

输出结果为:Alligators rock!

上述例子中exceptions.properties使用了占位符,可以在getMessage函数中指明占位符的值:

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", null);
        System.out.println(message);
    }
}

运行结果为:The userDao argument is required.

之前介绍的功能都没有涉及到国际化,接下来介绍一下国际化的方式:

假设我们想根据英国(en-GB)语言环境解析消息,我们需要创建三个文件(format_en_GB.propertiesexceptions_en_GB.properties, 、 windows_en_GB.properties),其中一个文件的内容为:

# in exceptions_en_GB.properties
argument.required=Ebagum lad, the {0} argument is required, I say, required.

获得其中的内容:

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);
}

运行结果:Ebagum lad, the 'userDao' argument is required, I say, required.

标准和自定义事件

ApplicationListener接口会处理事件,每次ApplicationEventPublisher实现类将事件发布到ApplicationContext中时,都会通知ApplicationListener,两者使用的是观察者模式

Spring提供了下列标准事件:

ContextRefreshedEvent:当Application初始化或是刷新(注册了一个Bean以后会使用refresh()刷新容器)完毕,容器初始化完毕意味着所有的bean都被初始化,容器可以被正常使用。

ContextStartedEvent:当ApplicationContext启动时发布,启动是指调用ConfigurableApplication接口的start()方法,此时所有继承并实现了Lifecycle的的bean将会接收到启动信息

ContextStoppedEvent:当ApplicationContext停止时发布,停止是指调用ConfigurableApplication接口的stop()方法,此时所有继承并实现了Lifecycle的的bean将会接收到停止信息

ContextClosedEvent:当ApplicationContext关闭时发布,关闭是指调用ConfigurableApplication接口的close()方法,此时所有的单例bean都会被摧毁,此时容器完全被摧毁,无法刷新或是重新启动

RequestHandledEvent:HTTP请求处理完毕后发布的事件,只在web应用中有效(即使用DispatcherServlet)

spring允许我们自定义事件处理机制,首先先要定义事件类,通过继承ApplicationEvent类,即可成为事件类:

public class BlackListEvent extends ApplicationEvent {

    private final String address;
    private final String test;

    public BlackListEvent(Object source, String address, String test) {
        super(source);
        this.address = address;
        this.test = test;
    }

    // accessor and other methods...
}

事件的发布需要通过ApplicationEventPublisher接口,该接口的publishEvent()方法即可发布事件,事件得发布一般通过继承ApplicationEventPublisherAware接口实现

public class EmailService implements ApplicationEventPublisherAware {

    private List<String> blackList;
    private ApplicationEventPublisher publisher;

    public void setBlackList(List<String> blackList) {
        this.blackList = blackList;
    }

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

    public void sendEmail(String address, String text) {
        if (blackList.contains(address)) {
            BlackListEvent event = new BlackListEvent(this, address, text);
            publisher.publishEvent(event);
            return;
        }
        // send email...
    }
}

在配置阶段,Spring容器会检测到EmailService继承了ApplicationEventPublisherAware接口,会自动调用该接口的setApplicationEventPublisher()方法,该方法传入的是Spring容器本身(其继承实现了ApplicationEventPublisher),此时便可以将事件发布到容器当中,为了接收到事件,需要继承ApplicationListerner接口:

public class BlackListNotifier implements ApplicationListener<BlackListEvent> {

    private String notificationAddress;

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

    public void onApplicationEvent(BlackListEvent event) {
        // notify appropriate parties via notificationAddress...
    }
}

注意到ApplicationListener<BlackListEvent>,意味着这个类为BlackListEvent的事件监听器,由它负责处理BlackListEvent事件,监听器同步接收事件并进行处理,意味着publishEvent()方法是阻塞的,接着onApplicationEvent方法会被调用处理事件

基于注解的事件监听器

事件监听器除了继承ApplicationListener实现外,也可以使用@EventListener注解,可以将上述例子的BlackListNotifier更改为:

public class BlackListNotifier {

    private String notificationAddress;

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

    @EventListener
    public void processBlackListEvent(BlackListEvent event) {
        // notify appropriate parties via notificationAddress...
    }
}

使用注解,不用继承接口,同时事件监听的方法名称也可以自定义,监听的事件也可以通过函数参数类型进行解析,如果一个方法需要监听许多事件或者不想使用函数参数,也可以如下定义:

@EventListener({ContextStartedEvent.class, ContextRefreshedEvent.class})
public void handleContextStart() {
    ...
}

通过注解的condition属性,也可以监听满足一定条件的事件,condition属性的值是SPEL表达式:

@EventListener(condition = "#blEvent.test == 'foo'")
public void processBlackListEvent(BlackListEvent blEvent) {
    // notify appropriate parties via notificationAddress...
}

上述例子表明监听事件必须含有test属性,并且该属性值为foo,当我们处理完这个事件后,也可以发布另外一个事件(通过函数返回值):

@EventListener
public ListUpdateEvent handleBlackListEvent(BlackListEvent event) {
    // notify appropriate parties via notificationAddress and
    // then publish a ListUpdateEvent...
}

如果想发布多个事件,则返回事件的Collection,异步监听器不支持这种使用方式

异步监听器

@Async注解允许异步执行方法,用于监听器,可实现异步监听器:

@EventListener
@Async
public void processBlackListEvent(BlackListEvent event) {
    // BlackListEvent is processed in a separate thread
}

是用异步监听器需要注意以下几点:

1、如果监听器抛出异常,会将异常传递给调用者,更多详情请查看AsyncUncaughtExceptionHandler

2、不允许通过return的方式发布事件

监听器的执行顺序

一个事件可以有多个监听器处理,此时我们可以通过@Order注解声明执行顺序,值越大越慢执行

@EventListener
@Order(42)
public void processBlackListEvent(BlackListEvent event) {
    // notify appropriate parties via notificationAddress...
}

在监听器中使用泛型

监听器的参数允许使用泛型

@EventListener
public void onPersonCreated(EntityCreatedEvent<Person> event) {
    ...
}

加载资源文件

Applicationcontext是一个资源加载器,资源的位置通过url的方式指定,只要能通过url定位的资源都可以导入

我们也可以使用Resource类型来获得静态资源,它可以作为依赖被注入,当将一个 ResourceLoaderAware 接口的实现类部署到应用上下文时(此类会作为一个 spring 管理的 bean), 应用上下文会识别出此为一个 ResourceLoaderAware 对象,并将自身作为一个参数来调用 setResourceLoader() 函数,如此,该实现类便可使用 ResourceLoader 获取 Resource 实例来加载你所需要的资源,更多详情请看:https://blog.csdn.net/xiangjai/article/details/53954252

猜你喜欢

转载自blog.csdn.net/dhaiuda/article/details/82077616