# EVENT事件监听

场景:开发需要做一个第三方组织机构同步集成功能,当系统内出现人员、部门、单位新增、修改、删除的时候,开发需要将这些信息同步给第三方,让第三方同步新增、修改、删除对应组织机构数据。

传统的做法是找到新增、删除、修改的源代码,在后面追加同步的代码,这种方式会侵入源码,耦合性极高。

CTP平台基于此问题,提供了一套事件分发和监听机制:组织机构开发在新增、修改、删除的代码位置封装推送一个Event对象,第三方集成只需监听这个Event对象,去实现数据同步即可。这种思路能降低代码耦合,提升可维护性。

# 实现原理

采用类似Observable+Observer模式,使用java.util.EventObject技术,并进行了简化开发。方案特点:注解驱动、无需注册监听、无需定义事件类型、异步分发、易扩展。

1714303088004.png

# 定义Event

下面以人员新增事件为例做Event事件开发演示。

标准产品侧,要触发事件则要先定义Event事件实例对象,实例如下,“人员新增”事件中存放了新增的人员对象V3xOrgMember:

public class AddMemberEvent extends Event {
	private static final long serialVersionUID = -5719181273255663176L;
	private V3xOrgMember member;
	private boolean isBatch = false;;

	public V3xOrgMember getMember() {
		return member;
	}
	public void setMember(V3xOrgMember member) {
		this.member = member;
	}
	public AddMemberEvent(Object source) {
		super(source);
	}
    public boolean isBatch() {
        return isBatch;
    }
    public void setBatch(boolean isBatch) {
        this.isBatch = isBatch;
    }
}

# 发布Event

标准产品侧,当执行人员新增逻辑的时候,就需要封装AddMemberEvent对象并通过fireEvent将事件发布出去:

    public void addMembers(List<V3xOrgMember> members) throws BusinessException {
        for (V3xOrgMember v3xOrgMember : members) {
            try {
                // 实例化Event对象
                AddMemberEvent event = new AddMemberEvent(this);
                // 传入必要的参数
                event.setMember(v3xOrgMember);
                // 分发Event事件,此步动作发送之后,外部即可监听
                EventDispatcher.fireEvent(event);
            } catch (Exception e) {
                log.error("AddMemberEvent error:"+v3xOrgMember.getName(),e);
            }
        }
    }

发布时机&发布类型:

# 立即触发事件,出现异常也不抛出,只记录日志
EventDispatcher#fireEvent

# 立即触发事件,出现异常时会抛出异常,供发出者捕获
EventDispatcher#fireEventWithException

# 事务提交成功后才触发,事务回滚则不触发
EventDispatcher#fireEventAfterCommit

# 监听Event

“定义Event”和“发布Event”是标准产品侧实现的代码,只要完成了事件的发布,标准产品侧就不需要再做别的事情。

下面就是需求方的任务,需求方需要在自己的业务类中(或者单独新增一个Listener)来监听Event事件,示例如下:

public class WeixinOrganizationListener {

    private DingdingManager dingdingManager;

    private FeishuManager feishuManager;

    public void setDingdingManager(DingdingManager dingdingManager) {
        this.dingdingManager = dingdingManager;
    }

    public void setFeishuManager(FeishuManager feishuManager) {
        this.feishuManager = feishuManager;
    }

    @ListenEvent(event = AddMemberEvent.class, async = true)
    public void addMember(AddMemberEvent event) {
        V3xOrgMember member = event.getMember();
        WeixinUtil.syncMember(member, "add");
        dingdingManager.syncMemberDing(member, "add");
        feishuManager.syncMember(member, "add");
    }

    @ListenEvent(event = UpdateMemberEvent.class, async = true)
    public void updateMember(UpdateMemberEvent event) {
        V3xOrgMember member = event.getMember();
        WeixinUtil.syncMember(member, "update");
        if (member.isValid()) {
            dingdingManager.syncMemberDing(member, "update");
            feishuManager.syncMember(member, "update");
        } else {
            dingdingManager.syncMemberDing(member, "del");
            feishuManager.syncMember(member, "del");
        }
    }
}

用于事件监听的类需要通过插件化开发的方式注册到Spring容器中:

<bean id="weixinOrganizationListener" class="com.seeyon.apps.weixin.listener.WeixinOrganizationListener" />

关于注解@ListenEvent:其定义信息如下:

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.METHOD/*, ElementType.TYPE */})
@Inherited
public @interface ListenEvent {
 /**
  * 监听的事件类型。
  * 
  * @return 监听的事件类型。
  */
 Class event();
 /**
  * 执行模式,同步或异步。为true时异步(asynchronous)执行,为false时同步(synchronization)执行。缺省为false。
  * @return 执行模式。
  */
 boolean async() default false;
 /**
  * 事件触发模式,如不指定,则立刻触发。
  * <color>不推荐使用:如果需要立即执行,请将async=false</color>
  * @return 事件触发模式。
  */
 @Deprecated
 EventTriggerMode mode() default EventTriggerMode.immediately;
 /**
  * <description>顺序(越小越在前面)</description>
  *
  * @return
  * @author: ouyp
  * @since: Seeyon V7.1
  * @date: 2019年1月10日 下午4:15:25
  */
 int order() default 0;
}

如无特殊需要,我们一概建议异步执行监听:设置async参数为true!

# 产品标准Event接口

详见SeeyonAPI Event事件部分:https://open.seeyoncloud.com/seeyonapi/8.2/event/

# 常见问题

1)Listener获取不到AppContext.currentUser()

用户掉线或离线状态、或者你当前是异步线程执行。

编撰人:het、admin