论坛首页 Java企业应用论坛

自己做的事件监听处理小框架

浏览 12070 次
精华帖 (0) :: 良好帖 (10) :: 新手帖 (0) :: 隐藏帖 (1)
作者 正文
   发表时间:2009-11-18   最后修改:2010-03-30
    基于SPRING的事件处理其实很简单,初学者不必一开始就担心搞不懂,本文先是介绍完全基于SPRING的事件监听实现(熟识Spring事件监听的朋友应该跳过第一节),然后依葫芦画瓢自已做一个性能更好事件监听小框架,最后在回贴中加入了注解版
    为何要使用事件监听,而不是直接调用?打个比方,在系统删除一个用户时,需要把用户的其他信息和行为记录一起删除,此时最容易想到的是:删除用户时,调用其他Service类接口删除与之相关的信息和用户行为记录,但是用户是一个基础模块,随着系统模块不断增加,会有越来越多的模块与之关联,按以上方案,每增加一个与用户相关的模块,那么删除用户时就要把这个模块的信息也一起删除,这样用户这个模块就与这些业务模块偶合在一起了,只要系统不断的增加或减少模块,用户模块就永远都改不完。(以上逻辑仅为说明问题)
    如果采用事件监听,那么在删除一个用户时,发布一事件就完事了,所有相关的模块监听此事件,在事件处理中删除本模块对应的数据,那么用户模块也不会与这些模块有任何偶合了。
    好,回到主题
    1、完全基于SPRING的事件监听与处理:
    首先,SERVICE总接口类继承org.springframework.context.ApplicationListener接口,并且在SERVICE的抽象类中实现此接口的方法,SERVICE总接口代码:
import org.springframework.context.ApplicationListener;

/**
 * 业务层顶层接口
 */
public interface IBaseService extends ApplicationListener{

}


SERVICE抽象类代码,所有具体业务类皆继承此抽象业务类
import org.springframework.context.ApplicationEvent;

/**
 * 实现顶层接口的抽象类
 */
public abstract class AbstractBaseService implements IBaseService {


	/**
	 * 此方法不允许重写,要监听事件,重写onBaseEvent方法
	 */
	public final void onApplicationEvent(ApplicationEvent event) {
		if (event instanceof BaseEvent) {
			onBaseEvent((BaseEvent) event);
		}
	}

	/**
	 * 要监听事件就重写此方法
	 */
	protected void onBaseEvent(BaseEvent event) {
	}

         /**
	 * 发布事件
	 */
	protected final void publishEvent(BaseEvent event) {
		//取得SPRING容器并发布事件
		org.springframework.web.context.ContextLoader.getCurrentWebApplicationContext().publishEvent(event);
	}
}

BaseEvent类是我自定义的一个类,各业务模块要发布事件,首先定义自已的EVENT类,继承BaseEvent。BaseEvent类代码
import org.springframework.context.ApplicationEvent;

public class BaseEvent extends ApplicationEvent {

	public BaseEvent(Object source) {
		super(source);
	}

}


用户业务类UserSerivceImpl删除一个用户时,需要发布事件,所以要先定义一个UserDeleteEvent事件类
import com.esc.permission.model.User;

public class UserDeleteEvent extends BaseEvent {
	
	public UserDeleteEvent(User user){
		super(user);
	}
	
	public User getUser(){
		return (User) super.getSource();
	}

}

下面是UserSerivceImpl类删除用户的方法模拟
public void deleteUser(String id){
    //先发布删除事件
    publishEvent(new UserDeleteEvent(new User(id)));
    //再把用户删除
    userDao.deleteUser(id);
}

假如有一个OtherServiceImpl业务类需要接收用户删除事件并做一些处理,
public class OtherServiceImpl extends AbstractBaseService {
	private IBaseDAO otherDao;

	/**
	 * 重写父类的方法,处理用户删除事件
	 */
	protected void onBaseEvent(BaseEvent baseEvent){
		if(baseEvent instanceof UserDeleteEvent){//如果是用户删除事件
			otherDao.deleteOtherData(((User)baseEvent.getSource()).getId());
		}
	}
}

    到此,所有逻辑就完了,用户业务类中根本不需要知道谁将会处理此事件,所以完全与其它模块解偶了。
    这里用了一个偷懒的方法,那就是在业务类的顶层接口IBaseService中实现了ApplicationListener接口,这样其实所有具体业务类都参与了所有事件的处理,即使是那些根本不需要监听事件的业务类。对于所有根本不需要监听事件的业务类,事件发生时就调用其父类空方法onBaseEvent,这里有一点性能浪费,如果改进?不要在顶层接口IBaseService中实现ApplicationListener接口,而是在具体的需要监听事件的业务类中实现此接口,但是这样还是有一点缺陷,那就是此具体业务类还是会监听到它不关心的事件,不论是用户删除事件还是用户修改事件,没办法,有得必有失,想要方便,肯定会有所牺牲。

    如果只想监听到自已所关心的事件呢,那么就要自已实现一套事件处理的小框架了:

    2、自定义的事件监听与处理框架。
    首先重新定义事件监听器接口,不再使用SPRING提供的ApplicationListener接口:
import java.util.List;

/**
 * 事件处理接口,实现此接口并且getEventClasses方法的返回结果条数大于0,方可处理对应的事件
 */
public interface IBaseEventListener {
	/**
	 * 事件处理的方法
	 */
	public void onBaseEvent(BaseEvent event);
	
	/**
	 * 所要处理的事件类型
	 */
	public List<Class<? extends BaseEvent>> getEventClasses();
}

    这个接口比SPRING的事件接口多了一个方法。因为SPRING原来做法是只要实现了它提供的接口,任何事件发生时都会得到调用,这种做法是很方便使用的,这里沿用了这种思路。多出的那个方法作用是描述本类处理哪些事件。这时我需要一个类,这个类要在系统启动时把所有实现此接口的业务类找出来,归好类,以便事件发生时进行精确调用,并且提供事件发布事件的静态方法,这样我们才能知道事件在何时发生了。好,下面就来编写这个类

/**
 * 事件处理相关操作工具类
 */
public class EventController {
	/** KEY:事件类的类名,值:所有监听此事件的处理类实例  */
	private static Map<String,List<IBaseEventListener>> listeners = new LinkedHashMap<String, List<IBaseEventListener>>();
	
	private EventController(){
		//no instance
	}
	
	/**
	 * 扫瞄所有bean,进行事件监听(业务类按事件类型归类),此方法要在系统启动完后立即调用
	 * 此方法大概过程是
	 * 1、从SPRING中找出所实现了IBaseEventListener的具体业务类实例
	 * 2、把这些实例归类装进MAP变量listeners中,此MAP变量的结构是:
	 * "UserDeleteEvent.class",{ortherServiceImpl,xxxServiceImpl,...}
	 * "UserUpdateEvent.class",{yyyServiceImpl,zzzServiceImpl,...}
	 * key,valueList
	 */
	public static void initBaseEventListener(){
		//从SPRING中得到实现了某接口的业务类
		Map<String,IBaseEventListener> beans = ContextLoader.getCurrentWebApplicationContext().getBeansOfType(IBaseEventListener.class);
		if(beans==null || beans.size()==0)
			return;
		//下面循环进行归类
		Collection<IBaseEventListener> services = beans.values();
		for (IBaseEventListener service : services) {
			List<Class<? extends BaseEvent>> eventClasses = service.getEventClasses();
			if(eventClasses==null || eventClasses.size()==0)
				continue;
			
			for (int i = 0; i < eventClasses.size(); i++) {
				List<IBaseEventListener> list = listeners.get(eventClasses.get(i).getName());
				if(list==null){
					list = new ArrayList<IBaseEventListener>();
					listeners.put(eventClasses.get(i).getName(), list);
					list.add(service);
				}else{
					if(list.contains(service)){
						continue;
					}else{
						list.add(service);
					}
				}
			}
		}
	}
	
	/**
	 * 发布事件的静态方法
	 */
	public static void publishEvent(BaseEvent event){
		//根据实际事件名称,从listeners中找出监听了此事件的业务类,调用之
		List<IBaseEventListener> list = listeners.get(event.getClass().getName());
		if(list!=null && list.size()>0){
			for (IBaseEventListener listener : list) {
				//此处不能捕捉异常,因为任何一个处理类实例出错都应该全部回滚
				listener.onBaseEvent(event);
			}
		}
	}
}

    接着重新定义事件类本身,此类也不再继承SPRING的ApplicationEvent类,其它基本还和原来一样。
public class BaseEvent {
	private Object source;
	public BaseEvent() {
	}
	
	public BaseEvent(Object source) {
		this.source = source;
	}

	public Object getSource() {
		return source;
	}
}


业务类顶层接改而继承自已的事件接口

/**  
 * 业务层顶层接口  
 */  
public interface IBaseService extends IBaseEventListener{   
  
} 



在SERVICE的抽象类中去掉onApplicationEvent方法,不需要了,改写发布事件的方法,修改后变成这样
import org.springframework.context.ApplicationEvent;

/**
 * 实现顶层接口的抽象类
 */
public abstract class AbstractBaseService implements IBaseService {
         /**
	 * 发布事件
	 */
	protected final void publishEvent(BaseEvent event) {
		//使用自已事件工具类发布事件
		EventController.publishEvent(event);
	}

         /**  
          * 默认实现处理事件的方法 
            */  
         public void onBaseEvent(BaseEvent event) {   
         }

         /**
	 * 注意虽然也是在顶层接口中都实现了事件监听,但是只有这个方法有返回值时,才真正的会被调用
	 */
	public List<Class<? extends BaseEvent>> getEventClasses(){
                   return null;
         }

}

    到此框架就完成了,UserServiceImpl删除用户时发布事件的代码还和原来一样,不需要改变,只是监听类要变一下,不再实现ApplicationListener接口,改而实现IBaseEventListener接口,不过不需要再具体类中实现,因为已经在顶层接口中继承了,代码
public class OtherServiceImpl extends AbstractBaseService {
	private IBaseDAO otherDao;

	/**
	 * 重写父类的方法,处理用户删除事件
	 */
	protected void onBaseEvent(BaseEvent baseEvent){
                  //如果本类只处理一个事件,这里就不需要再类型判断了
                  UserDeleteEvent event = (UserDeleteEvent)baseEvent;
		otherDao.deleteOtherData(event.getSource().getId());
	}
         
         /**
	 * 返回本类所要处理所有事件
	 */
	public List<Class<? extends BaseEvent>> getEventClasses() {
		List<Class<? extends BaseEvent>> eventClasses = new ArrayList<Class<? extends BaseEvent>>(1);
		//本类只监听用户删除事件
		eventClasses.add(UserDeleteEvent.class);
		return eventClasses;
	}
}


   发表时间:2009-11-18  
看了一遍,不错,谢谢了
0 请登录后投票
   发表时间:2009-11-19  
这种情况可以用AOP搞定不?
0 请登录后投票
   发表时间:2009-11-19  
AOP应该是可以

我还见过小鬼子用Annotation

对修改删除的记录保存到另一套以h_开头的表中

还加入是什么操作(C,U, D)
0 请登录后投票
   发表时间:2009-11-19  
学习啦,这种情况用aop应该也能实现
0 请登录后投票
   发表时间:2009-11-19  
whaosoft 写道
学习啦,这种情况用aop应该也能实现

你是说在AOP中检测到是正在删除用户时,就调用其它的业务是吗?这样做确实也是可以的,可以将监听这个操作的类配置进来,但是这样就要对每一个被拦截的方法都要检查一下是否有监听者了,随着系统越来越大,监听者会越来越多,被监听的方法,不被监听的方法都会越来越多,每一个都检测一下就有点浪费了。
0 请登录后投票
   发表时间:2009-11-19  
qbq 写道
AOP应该是可以

我还见过小鬼子用Annotation

对修改删除的记录保存到另一套以h_开头的表中

还加入是什么操作(C,U, D)

是可以用Annoation实现,我本是想再搞个Annotation版的,一偷懒,还是算了,够用就行。
0 请登录后投票
   发表时间:2009-11-20  
恩 不错不错 事件驱动编程在JAVA的各个领域都很有用 实质类似观察者模式 只是这些类的引用被封装在SPRING容器里  而观察者是封装在自己继承的observable里 但是正是容器的这种公共提取而又不强关联 给开发者实现了几乎完全的模块间脱耦 spring的出现真是一场巨大的变革啊。
0 请登录后投票
   发表时间:2009-11-22  
lgdlgd 写道
qbq 写道
AOP应该是可以

我还见过小鬼子用Annotation

对修改删除的记录保存到另一套以h_开头的表中

还加入是什么操作(C,U, D)

是可以用Annoation实现,我本是想再搞个Annotation版的,一偷懒,还是算了,够用就行。


请问可以讲解一下如何实现吗??
0 请登录后投票
   发表时间:2009-11-22  
监听如果不结合异步操作,将是个灾难。
对比C#中的异步委托,方便太多了。
0 请登录后投票
论坛首页 Java企业应用版

跳转论坛:
Global site tag (gtag.js) - Google Analytics