spring 事件处理模型/观察者

spring 事件处理模型/观察者


Spring的事件(Application Event)为Bean与Bean之间的消息通信提供了支持,当一个Bean处理完一个任务之后,希望另外一个Bean知道并且能做及时的处理,这时我们需要让另外一个bean监听当前Bean所发送的事件。个人认为主要优点是降低耦合。Spring事件需要遵循如下流程

  • 自定义事件,继承ApplicationEvent
  • 自定义事件监听器,实现ApplicationListener
  • 使用容器发布事件
  • 消息对列

  简单理解就是为系统业务逻辑之间进行了解耦,事件发布者并不需要考虑谁去监听,监听具体的实现内容是什么,发布者的工作只是为了发布事件而已。

例如:用户注册的例子:
用户注册成功后,需要做很多事情:

  1. 增加积分
  2. 发送确认邮件
  3. 如果式游戏用户,还需要赠送游戏大礼包
  4. 建立索引用户数据

  5.   解决的方法是,增加了一个Listener来解耦UserService和其他服务,即注册成功后,只需要通知相关的监听器,不需要关系它们如何处理。增删功能非常容易。

一. 自定义事件

@Data
public class DemoEvent extends ApplicationEvent {
    private String text;

    /**
     * Create a new ApplicationEvent.
     *
     * @param source 发生事件的对象
     */
    public DemoEvent(Object source, String text) {
        super(source);
        this.text = text;
    }
}

  定义了一个事件DemoEvent继承了ApplicationEvent,继承后必须重载构造函数,构造函数的参数可以任意指定,其中source参数指的是发生事件的对象,一般我们在发布事件时使用的是this关键字代替本类对象。

二.事件监听

在Spring内部中有多种方式实现监听如:

  1. @EventListener注解
  2. 实现ApplicationListener泛型接口
  3. 实现SmartApplicationListener接口,有序监听器

2.1 @EventListener注解

    /**
     * 注册监听实现方法
     * @param DemoEvent 用户注册事件
     */
    @EventListener
    public void register(DemoEvent demoEvent)
    {
        //获取信息
        String  text = demoEvent.getText();

        //../省略逻辑
    }

  监听类被Spring管理即可,注册监听实现方法上添加@EventListener注解,该注解会根据方法内配置的事件完成监听。使用注解式,可以监听统一事件的不同实现,只需要再方法上加注解,节省代码。

2.2 实现ApplicationListener泛型接口

Component
public class DemoEventListener implements ApplicationListener<DemoEventListener>
{
    /**
     * 实现监听
     * @param userRegisterEvent
     */
    @Override
    public void onApplicationEvent(DemoEvent demoEvent) {
        //获取注册用户对象
        String text = demoEvent.getText();
        //../省略逻辑
    }
}

ApplicationListener 源码如下:

package org.springframework.context;

import java.util.EventListener;

@FunctionalInterface
public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {
    void onApplicationEvent(E var1);
}

  使用@Component注解来声明该监听需要被Spring注入管理,当有DemoEvent事件发布时监听程序会自动调用onApplicationEvent方法并且将DemoEvent对象作为参数传递。这样的监听器是无序的

2.3 定义有序的监听器

方法一: @EventListener + @Order
方法二:实现SmartApplicationListener接口

package org.springframework.context.event;

public interface SmartApplicationListener extends ApplicationListener<ApplicationEvent>, Ordered {

    // 如果实现支持该事件类型 那么返回true
    boolean supportsEventType(Class<? extends ApplicationEvent> var1);

    default boolean supportsSourceType(@Nullable Class<?> sourceType) {
        return true;
    }

    //顺序,即监听器执行的顺序,值越小优先级越高
    default int getOrder() {
        return 2147483647;
    }

示例一:

@Component
public class syncKeywordDeleteListener implements SmartApplicationListener {
    @Override
    public boolean supportsEventType(Class<? extends ApplicationEvent> aClass) {
        return KeywordDeleteEvent.class == aClass;
    }

    @Override
    public int getOrder() {
        return 1;
    }

    @Override
    public void onApplicationEvent(ApplicationEvent applicationEvent) {
       //逻辑代码
    System.out.println("hello");
    }
}
//-------------------
@Component
public class DeleteKeywordsLitener implements SmartApplicationListener {
    @Override
    public boolean supportsEventType(Class<? extends ApplicationEvent> aClass) {
        return KeywordDeleteEvent.class == aClass;
    }

    //执行顺序二
    @Override
    public int getOrder() {
        return 2;
    }

    @Override
    public void onApplicationEvent(ApplicationEvent applicationEvent) {
        System.out.println("hello,顺序二");
    }

示例二:

@Component
public class KeywordDeleteEventListener {

    /**
     * @param event event
     */
    @EventListener
    @Order(1)
    public void syncKeywordDelete(KeywordDeleteEvent event) throws NoSuchMethodException {
        String name = Thread.currentThread().getStackTrace()[1].getMethodName();
        Method method = this.getClass().getDeclaredMethod(name, KeywordDeleteEvent.class);
        Integer order = method.getAnnotation(Order.class).value();
        if (event.getOrder() > order) {
            log.debug("当前step已经执行过,不再执行");
            return;
        }
        //逻辑代码
        System.out.println("hello");
    }
    //-------------------------------------------------------------------
    @EventListener
    @Order(2)
    public void DeleteKeywords(KeywordDeleteEvent event) throws NoSuchMethodException {
        String name = Thread.currentThread().getStackTrace()[1].getMethodName();
        Method method = this.getClass().getDeclaredMethod(name, KeywordDeleteEvent.class);
        Integer order = method.getAnnotation(Order.class).value();
        if (event.getOrder() > order) {
            log.debug("当前step已经执行过,不再执行");
            return;
        }
         System.out.println("hello,顺序二");
    }

}

巧妙的利用了@Order注解
@Order,可以定义在类上,也可以定义到方法上,指定一个先后顺序。

三. 发布事务

    @Resource
    private ApplicationContext applicationContext;
    KeywordDeleteEvent event = new KeywordDeleteEvent(this);
    applicationContext.publishEvent(event);

在这里插入图片描述

  ApplicationContext接口继承了ApplicationEventPublisher,并在AbstractApplicationContext实现了具体代码如下,实际执行是委托给ApplicationEventMulticaster(可以认为是多播):

 protected void publishEvent(Object event, @Nullable ResolvableType eventType) {
        //省略部分
		getApplicationEventMulticaster().multicastEvent(event);
		if (this.parent != null) {
			this.parent.publishEvent(event);
		}
    }

  Spring事件机制是观察者模式的一种实现,但是除了发布者和监听者者两个角色之外,还有一个EventMultiCaster的角色负责把事件转发给监听者如下;
在这里插入图片描述

applicationContext.publishEvent(event); 是会将事件发送给了EventMultiCaster,EventMultiCaster注册着所有的Listener,然后根据事件类型决定转发给那个Listener。
TODO EventMultiCaster,源码学习

四. 实现异步监听

  @Aysnc其实是Spring内的一个组件,可以实现方法实现异步调用,节省耗时。内部实现机制是线程池任务ThreadPoolTaskExecutor,被注解的方法调用的时候,会在新的线程中执行,而调用它的方法会在原来的线程中执行。

4.1 配置线程池,开启异步处理

@Configuration
@EnableAsync
public class TaskExecutorConfig implements AsyncConfigurer {
    @Override
    public Executor getAsyncExecutor() {
        ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
        taskExecutor.setCorePoolSize(5);//线程池大小
        taskExecutor.setMaxPoolSize(10);//线程池最大线程数
        taskExecutor.setQueueCapacity(25);//最大等待任务数
        taskExecutor.initialize();
        return taskExecutor;
    }
}

@EnableAsync注解开启支持异步处理
异步监听,本质上还是Spring的异步任务,只是在监听事件上加了异步

    @EventListener
    @Async
    @Order(3)
    public void upodate(KeywordDeleteEvent event) throws NoSuchMethodException {
        //异步r任务
        String name = Thread.currentThread().getStackTrace()[1].getMethodName();
        Method method = this.getClass().getDeclaredMethod(name, UpdateCampaignDateEvent.class);
        Integer order = method.getAnnotation(Order.class).value();
        if (event.getOrder() > order) {
            log.debug("当前step已经执行过,不再执行");
            return;
        }
        //具体逻辑
    }
}

五 消息队列的联想

  上面的这种形式让我联想到消息队列。消息队列有两种类型,点对点消息队列模型和发布订阅消息模型。

  • 发布订阅模型
    支持向特定的消息主题发布消息,0个或者1个订阅者可以接收特定的topic的消息。在这种模型下,发布者和订阅者彼此不知道对方。这种模型如下图:

在这里插入图片描述

  多个消费者可以获得消息在发布者和订阅者之间存在时间依赖性,即必须先订阅,再发送消息,而后接收订阅的消息,这个操作顺序必须保证。

  • 点对点消息队列模型

在这里插入图片描述

每一个成功处理的消息都由消息消费者签收确认(Acknowledge)

此处只是简单的说明自己的联想,详细的消息队列还会有文章介绍,敬请期待


六 Java的事件(对比Spring,原理一样)

Java事件主要角色

  • 事件源(Source):规定事件由谁来产生,在listener中作路由
  • 事件对象(EventObject):传递信息的作用,对source的包装,根据不同的构造方法,可以附带除source之外的其他信息(参考上面)
  • 事件监听(EventListener):业务逻辑处,根据事件中的事件源(source)或者事件对象,进行不同的路由,实现不同的业务。

source+event -> 触发 Listener

参考:
消息队列技术之基本概念

[

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
这是2个压缩包中的第二个,请一并将第一个下载后,随便解压其中一个即可。 如果只希望下载其中一部分即可阅读,可下载另一个pdf: http://download.csdn.net/detail/oqqsun12345678/5215337 内容简介 出版日期: 2012年1月1日 《JavaServer Faces 2.0完全参考手册》针对javaserver faces(jsf)2.0中的变化进行了全面的修订与更新,涵盖了javeee的官方标准web开发架构的每个方面。在这本权威著作中,sun microsystems公司中的jsf合作规范领导者展示了如何创建动态的、跨浏览器的web应用程序,由于保留了较高质量的代码和可扩展性,这些应用程序可以给用户带来极为优秀的体验。 《JavaServer Faces 2.0完全参考手册》提供了一个综合的示例应用程序,可以将其用作您自己的jsf应用程序的模型。该示例应用程序的代码可以从网上下载。《JavaServer Faces 2.0完全参考手册》对所有jsf功能都进行了解释,包括请求处理生命周期、托管bean、页面导航、组件开发、ajax、验证器、国际化和安全。贯穿全书的专家组意见提供了关于jsf设计的内部信息。 推荐编辑 《JavaServer Faces 2.0完全参考手册》主要内容:搭建开发环境并构建JSF应用程序。理解JSF请求处理生命周期。使用Facelets视图声明语言、托管bean和JSF表达式语言(EL)。按照JSF导航模型声明一个页面,包括新的“隐式导航”功能。使用用户界面组件模型和JSF事件模型,包括支持可添加书签的页面以及POST、REDIRECT、GET模式。使用为模型数据验证设立的新的JSR-303bean验证标准。创建可以使用Ajax的定制用户界面组件。使用定制的非用户界面组件来扩展JSF。管理安全、可访问性、国际化和本地化。学会使用Liferay的JSF团队领导开发的JSF和Portlet,Liferay是处于领导地位的JavaPortal开发商。 全面介绍JSF2.0、详述如何使用Ajax,以及按照JSF2.0、的方式构建组件、快速理解众多可以直接运行的代码示例。 作者 作者:(美国)伯恩斯 (Ed Burns) (美国)沙尔克 (Chris Schalk) (美国)格里芬 (Neil Griffin) 译者:陶克 熊淑华 伯恩斯,Ed Burns是Sun Microsystems公司的高级主管工程师,此外还是JavaServer Faces共同规范的领导者。他与其他人合著了JavaServer Faces:The Complete Reference一书,并且是Secrets of the Rock Star Programmers一书的作者。 沙尔克,Chris Schalk是developer advocate,致力于提升Google的API和技术。他当前在国际化Web开发社区工作,主要研究新的Google App Engine和Open Social API。 格里芬,Neil Griffin是Liferay Portal的委员以及JSF开发团队领导者,并且是Portlet Faces项目的合作创始人。 目录 第I部分 javaserver faces框架 第1章 javaserver faces简介 1.1 什么是javaserver faces 1.2 javaserver faces的历史 1.2.1 公共网关接口 1.2.2 servletapi 1.2.3 java服务器页面 1.2.4 apachestruts 1.2.5 spring框架和springmvc 1.2.6 javaserverfaces的诞生 1.3 javaserver faces设计目标 1.4 jsf应用程序架构 1.4.1 jsf请求处理生命周期 1.4.2 jsf导航模型 第2章 构建一个简单的javaserver faces应用程序 2.1 应用程序概述 2.1.1 jsfreg应用程序文件 2.1.2 jsf软件栈 2.1.3 装配jsfreg应用程序 2.1.4 配置文件 2.1.5 facelets页面 2.2 构建和运行应用程序 2.3 应用程序关键部分 回顾 第3章 javaserver faces请求处理生命周期 3.1 jsf请求处理生命周期概述 3.1.1 请求处理生命周期的功能 3.1.2 与其他web技术的区别 3.1.3 服务器端视图的自动管理与同步 3.1.4 请求处理生命周期的各阶段 3.2 观察请求处理生命周期 3.3 与请求处理生命周期有关的高级主题 3.3.1 使用immediate属性 3.3.2 阶段侦听器 3.3.3 异常处理程序 3.4 关键的生命周期概念 第4章 facelets视图声明语言 4.1 jsf中使用模板化的威力 4.2 jsp与facelets的异同 4.3 使用facelets执行模板化 4.4 facelets模板化标签使用指南 4.4.1 ui:composition 4.4.2 ui:decorate 4.4.3 ui:define 4.4.4 ui:insert 4.4.5 ui:include 4.4.6 ui:param 4.5 facelets非模板化标签使用指南 4.5.1 ui:component 4.5.2 ui:fragment 4.5.3 ui:remove 4.5.4 ui:debug 第5章 托管bean与JSF表达式语言 5.1 托管bean概念 5.1.1 简单托管bean示例 5.1.2 初始化托管bean属性 5.1.3 把List和Map声明为托管bean 5.1.4 托管bean的相互依赖 5.1.5 使用EL设置托管属性 5.2 控制托管bean生命周期 5.3 JSF表达式语言 5.3.1 JSFl.1 与JSFl.2 之间表达式语言的关键区别 5.3.2 统一EL概念 5.3.3 值表达式 5.3.4 表达式操作符 5.3.5 方法表达式 5.4 托管bean的Web应用程序开发细节 5.4.1 采用编程方式访问托管bean 5.4.2 使用托管bean作为JSF页面的支撑bean 第6章 导航模型 6.1 使用隐式导航 6.2 JSF导航系统概述 6.2.1 回顾MVC-控制器 6.2.2 Navigation HandleI-幕后主管 6.2.3 Faces动作方法说明 6.3 构建导航规则 6.3.1 静态导航示例 6.3.2 动态导航示例 6.4 更复杂的导航示例 6.4.1 使用通配符 6.4.2 使用条件导航 6.4.3 使用重定向 6.4.4 视图参数的XML配置 6.4.5 在Servlet错误页上使用JSF组件 第7章 用户界面组件模型 7.1 什么是用户界面组件 7.1.1 基于组件的Web开发的兴起 7.1.2 Java Server Faces用户界面组件的目标 7.2 JSF用户界面组件架构介绍 7.2.1 用户界面组件树(视图) 7.2.2 用户界面组件和相关的“活动部分 7.3 组件资源 7.4 用户界面组件和Facelets 7.4.1 用编程方式访问用户界面组件 7.4.2 在JSF视图中绑定用户界面组件的有用建议 第8章 数据转换与数据验证 8.1 验证和转换的示例 8.2 转换和验证揭秘 8.3 Faces转换器系统 8.3.1 Date Time Converter 8.3.2 Number Converter 8.3.3 关联转换器与UI Component实例 8.3.4 转换器的生命周期 8.3.5 定制转换器 8.4 Faces验证系统 8.4.1 Long Range Validator 8.4.2 Double Range Validator 8.4.3 Length Validator 8.4.4 必需的工具Required Validator 8.4.5 Reg Ex Validator 8.4.6 Bean Validator …… 第9章 jsf事件模型 第II部分 扩展javaserver faces 第10章 应用jsf:虚拟教练应用程序简介 第11章 构建定制用户界面组件 第12章 jsf与ajax 第13章 构建非用户界面定制组件 第14章 保护javaserver faces应用程序 第III部分 javaserver faces工具与库 第15章 配置javaserver faces应用程序 第16章 标准的jsf组件库 附录jsf portlet

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值