手游服务端框架之使用事件驱动模型解决业务高耦合

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/littleschemer/article/details/77169987

事件驱动的作用与目标

假设这样的业务需求:游戏服务器希望在玩家升级时触发多种效果。例如玩家升级后,各种属性都会提高,开启新的系统玩法,学习新的技能……入门程序员写出来的代码可能是这样——

private void handleRoleUpgrade(Object role){  
        if(meetUpgradeCondition(role)){//满足升级条件  
            RoleManager.getInstance().upgradeAttribution(role);//属性提升  
            SkillManager.getInstance().learnNewSkill(role);//学会新技能  
            //其他一堆业务  
        }  
}  
可以看出,玩家升级后,所有跟升级挂钩的业务都要集中在一起,依次被处理。这样写出来的代码耦合度非常高。一旦有新的业务加入,这里就要继续插代码。

为了达到解耦的效果,我们引入了事件驱动模型。

当玩家触发了升级这个动作,我们完全可以把“升级”这个动作包装成一个事件,任何对这个事件感兴趣的“观察者”就可以捕捉并执行相应的逻辑。

我们希望我们的事件驱动模型能够满足以下几个要求:

1. 当触发某个动作时,将动作包装成事件并进行分发,所有与之相关的监听器自动感应事件的发生;

2. 同一个事件可以被多个监听器响应;

3. 一个监听器可以同时监听多个事件;

4. 事件可以选择同步执行,也可以选择异步执行。

事件驱动的代码实现

下面开始我们的编码逻辑

1. 首先,我们定义”事件“这个抽象概念(GameEvent)。注意,事件有一个基类方法标识是否同步执行。

/**
 * 监听器监听的事件抽象类
 */
public abstract class GameEvent {
	
	/** 创建时间 */
	private long createTime;
	/** 事件类型 */
	private final EventType eventType;
	
	public GameEvent(EventType evtType) {
		this.createTime = System.currentTimeMillis();
		this.eventType  = evtType;
	}
	
	public long getCreateTime() {
		return this.createTime;
	}
	
	public EventType getEventType() {
		return this.eventType;
	}
	
	/**
	 * 是否在消息主线程同步执行
	 * @return
	 */
	public boolean isSynchronized() {
		return true;
	}

}
2. 为了区分各种事件,我们定义一个表示事件类型的枚举器(EventType.java)

public enum EventType {
	
	/** 升级事件 */
	LEVEL_UP;

}
3. 在游戏业务里,很多事件都是绑定角色的,所以定义一个跟玩家关系密切的玩家事件(PlayerEvent.java)
/**
 * 玩家事件抽象类
 */
public abstract class PlayerEvent extends GameEvent {

	/** 玩家id */
	private final long playerId;
	
	public PlayerEvent(EventType evtType, long playerId) {
		super(evtType);
		this.playerId = playerId;
	}
	
	public long getPlayerId() {
		return this.playerId;
	}
}
4.定义一个注解( Listener.java ),标识”监听器“

@Documented
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Listener {

}
5. 定义事件分发器(EventDispatcher),该分发器拥有以下作用:

绑定事件与事件监听者;

分发事件,若为同步事件,则在当前的业务主线程执行,若为异步事件,则放到独立线池异步执行。

public class EventDispatcher {

	private static EventDispatcher instance = new EventDispatcher();

	private EventDispatcher() {
		new NameableThreadFactory("event-dispatch").newThread(new EventWorker()).start();
	};  

	public static EventDispatcher getInstance() {
		return instance;
	}

	/** 事件类型与事件监听器列表的映射关系 */
	private final Map<EventType, Set<Object>> observers = new HashMap<>(); 
	/** 异步执行的事件队列 */
	private LinkedBlockingQueue<GameEvent> eventQueue = new LinkedBlockingQueue<>();

	/**
	 * 注册事件监听器
	 * @param evtType
	 * @param listener
	 */
	public void registerEvent(EventType evtType, Object listener) {  
		Set<Object> listeners = observers.get(evtType);  
		if(listeners == null){  
			listeners = new CopyOnWriteArraySet<>();  
			observers.put(evtType, listeners);  
		}  
		listeners.add(listener);  
	}  

	/**
	 * 分发事件
	 * @param event
	 */
	public void fireEvent(GameEvent event) {  
		if(event == null){  
			throw new NullPointerException("event cannot be null");  
		}  
		//如果事件是同步的,那么就在消息主线程执行逻辑
		if (event.isSynchronized()) {
			triggerEvent(event);
		} else {
		//否则,就丢到事件线程异步执行
			eventQueue.add(event);
		}
		
	}  
	
	private void triggerEvent(GameEvent event) {
		EventType evtType = event.getEventType();  
		Set<Object> listeners = observers.get(evtType);  
		if(listeners != null){  
			listeners.forEach(listener->{
				try{  
					ListenerManager.INSTANCE.fireEvent(listener, event);
				}catch(Exception e){  
					LoggerUtils.error("triggerEvent failed", e);;  //防止其中一个listener报异常而中断其他逻辑  
				}  
			});
		}  
	}
	
	private class EventWorker implements Runnable {
		@Override
		public void run() {
			while(true) {
				try {
					GameEvent event = eventQueue.take();
					triggerEvent(event);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
		}
	}

}
6.分发器只是绑定了事件与其监听器,并没有说明,这个事件由其监听器的哪个方法监听。为了达到方法级别的绑定,我们引入另一个注解(EventHandler.java)。
/**
 * 事件处理者
 * @author kingston
 */
@Documented
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface EventHandler {
	
	/** 绑定的事件类型列表 */
	public EventType[] value();
	
}

也就是说,监听器由Listener注解标识,该监听器要监听多个事件,那么就在每一个事件的执行者方法加一个EventHandler注解。如果看过之前的文章,那篇关于使用Controller, RequestMapper来自动映射玩家消息与业务执行者。就会发现,Listerner与ListenerHandler所发挥的作用,跟之前是完全一样的。

7. 由于一个监听器可以同时监听多个事件,为了精确找到具体的业务执行者,我们必须将监听器与事件类型作为联合主键,缓存这样一个映射关系。key=listener_eventType, value=listenerMethod。所以,我们又加入一个工具类
(ListenerManager.java)

public enum ListenerManager {

	INSTANCE;

	private Map<String, Method> map = new HashMap<>();

	private final String SCAN_PATH = "com.kingston.game";

	public void initalize() {
        Set<Class<?>> listeners = ClassScanner.getClasses(SCAN_PATH, new ClassFilter() {
            @Override
            public boolean accept(Class<?> clazz) {
                return clazz.getAnnotation(Listener.class) != null;
            }
        });

        for (Class<?> listener: listeners) {
            try {
                Object handler = listener.newInstance();
                Method[] methods = listener.getDeclaredMethods();
                for (Method method:methods) {
                	EventHandler mapperAnnotation = method.getAnnotation(EventHandler.class);
                    if (mapperAnnotation != null) {
                    	EventType[] eventTypes = mapperAnnotation.value();
                    	 for(EventType eventType: eventTypes) {
                    		 EventDispatcher.getInstance().registerEvent(eventType, handler);
                    		 map.put(getKey(handler, eventType), method);
                         }
                    }
                }
            }catch(Exception e) {
                LoggerUtils.error("", e);
            }
        }
    }

	/**
	 * 分发给具体监听器执行
	 * @param handler
	 * @param event
	 */
	public void fireEvent(Object handler,GameEvent event) {
		try {
			Method method = map.get(getKey(handler, event.getEventType()));
			method.invoke(handler, event);
		} catch (Exception e) {
			LoggerUtils.error("", e);
		}

	}

	private String getKey(Object handler, EventType eventType) {
		return handler.getClass().getName() + "-" + eventType.toString();
	}
}
至此,事件驱动器的全部代码就完成了。

事件驱动模拟的测试案例

我们直接将引子的场景作为测试吧,当玩家升级时,就会触发学习一个新技能。
1.申明事件类型,EventType.LEVEL_UP
2.申明事件监听器(Listener)与执行事件的方法(ListenerHandler)
@Listener
public class SkillListener {
	
	@EventHandler(value=EventType.LEVEL_UP)
	public void onPlayerLevelup(EventPlayerLevelUp levelUpEvent) {
		System.err.println(getClass().getSimpleName()+"捕捉到事件"+levelUpEvent);
	}

}
3. 测试代码,服务启动时,直接抛出一个升级事件
		//启动socket服务
		try{
			new SocketServer().start();
		}catch(Exception e) {
			LoggerUtils.error("ServerStarter failed ", e);
		}

		Player player = PlayerManager.getInstance().get(10000L);
		EventDispatcher.getInstance().fireEvent(new EventPlayerLevelUp(EventType.LEVEL_UP,
				player.getId(), 2));
4. SkillListener直接捕捉到升级事件,输出如下
SkillListener捕捉到事件EventPlayerLevelUp [upLevel=2, playerId=2815129724291645440, EventType=LEVEL_UP]

到这里,关于事件驱动模型的实现就介绍完毕了。



文章预告:下一篇主要介绍如何使用http服务构建后台管理系统
手游服务端开源框架系列完整的代码请移步github ->>game_server





猜你喜欢

转载自blog.csdn.net/littleschemer/article/details/77169987