การเขียน Event Driven คือการเขียนโปรแกรมรูปแบบหนึ่ง ที่จะตอบสนองต่อผู้ใช้งานก็ต่อเมื่อมีเหตุการณ์(Event)บางอย่างตามที่ได้กำหนดไว้เกิดขึ้น
เหตุการณ์ต่างๆ ที่ว่านั้นจะเป็นอะไรก็ได้ แล้วแต่จะกำหนด เช่น มีการ insert ข้อมูลลง database, มีการลบข้อมูลออกจาก database, หรือมีการอัพเดทข้อมูลบางข้อมูลเกิดขึ้น หรือการคลิกเมาส์, หรือการกดปุ่ม Enter หรือๆๆๆๆๆๆๆๆอะไรก็ได้ ที่เราจะมองว่ามันคือเหตุการณ์ๆหนึ่ง มีเยอะแยะมากมายจนอธิบายไม่หมดครับ
วันนี้ผมจะมาแนะนำการเขียน Event Driven ด้วย Event Bus Google Guava ซึ่งเป็นการเขียนโปรแกรมที่เราสามารถกำหนดและโยน Event ต่างๆตามที่เราต้องการได้เอง และสามารถเขียนโปรแกรม เพื่อรอรับ Event ที่เกิดขึ้นได้เองอีกด้วย
แนวคิดคร่าวๆ ดังนี้ครับ
1. Class ไหนต้องการรับ Event ให้ทำการ Register Event Bus ไว้
2. ทำ Subscribe Class นั้น(Class ที่ Register Even Bus) คือการรอรับ Event ที่จะเกิดขึ้นตามชนิดที่กำหนด (Event Type) เช่น Subscribe (B) คือ การรอรับ Event (B), Subscribe (C) คือ การรอรับ Event (C) เป็นต้น
3. เมื่อ Class ใด ต้องการให้มี Event เกิดขึ้น ก็จะทำการโยน Event ชนิดนั้นๆ ออกมา และ Class ใดที่ Subscribe Event ชนิดนั้นไว้ ก็จะได้รับ Event นั้นๆ
แนวคิดมีแค่นี้ครับ ง่ายมั้ยครับ
มาดูโค๊ดกันครับ อันนี้เป็นตัวอย่างในการประยุกต์ร่วมกับ JavaServer Faces 2.0 น่ะครับ
Dependecies
...
...
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>13.0.1</version>
<type>jar</type>
</dependency>
...
...
ServletContextListener จะทำงานก่อน application start เสมอหน้าที่ในที่นี้คือ เก็บตัวแปร application scope ไว้
package com.blogspot.na5cent.web;
//package ที่สำคัญของเราคือ com.google.common.eventbus.EventBus;
import com.google.common.eventbus.EventBus; //****************
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.annotation.WebListener;
@WebListener
public class RegisteredServletListener implements ServletContextListener {
@Override
public void contextInitialized(ServletContextEvent sc) {
sc.getServletContext().setAttribute("applicationBus", new EventBus());
//อันนี้ผมจะใช้ Event Bus เป็นตัวแปรในระดับ Application Scope น่ะครับ
//เราจะใช้ Event Bus ใน Session Scope ก็ได้
//แต่ใน Request Scope Event Bus มันไม่ค่อยมีประโยชน์เท่าไหร่ครับ(ตามความคิดของผมน่ะ)
}
@Override
public void contextDestroyed(ServletContextEvent sc) {
}
}
library
package com.blogspot.na5cent.web;
import com.google.common.eventbus.EventBus;
import javax.faces.context.FacesContext;
public class MyEvent {
//method นี้เอาไว้ get ตัวแปร Application scope เฉยๆครับ
public static EventBus getApplicationBus() {
return (EventBus) FacesContext.getCurrentInstance().getExternalContext().getApplicationMap().get("applicationBus");
}
}
ใน JavaServer Facesตัวรับ Event
package com.blogspot.na5cent.managedbean;
import java.io.Serializable;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.SessionScoped;
import javax.annotation.PostConstruct;
import com.google.common.eventbus.Subscribe;//***********************
import com.blogspot.na5cent.web.MyEvent;
import com.blogspot.na5cent.model.Group;
import com.blogspot.na5cent.model.User;
import java.util.ArrayList;
import java.util.List;
@ManagedBean
@SessionScoped
public class UserManageBean implements Serializable {
//...
//...
@PostConstruct
public void postConstruct() {
//...
//...
//register event bus ในระดับ application scope ตามแนวคิดข้อที่ 1
MyEvent.getApplicationBus().register(this);//*********
//...
//...
}
//ทำ subscripe ตามแนวคิดข้อที่ 2
@Subscribe
public void groupChange(List<Group> groups) {
//...
//ทำ เมื่อมี Event เกิดขึ้น โดยจะต้องเป็น Event ที่มีการโยน List<Group> ออกมาเท่านั้น
//...
}
@Subscribe
public void userChange(List<User> users) {
//...
//ทำ เมื่อมี Event เกิดขึ้น โดยจะต้องเป็น Event ที่มีการโยน List<User> ออกมาเท่านั้น
//...
}
//...
//...
}
ตัวส่ง Event
package com.blogspot.na5cent.managedbean;
import java.io.Serializable;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.SessionScoped;
import javax.annotation.PostConstruct;
import com.blogspot.na5cent.web.MyEvent;
import com.blogspot.na5cent.model.Group;
import com.blogspot.na5cent.service.GroupService;
import java.util.ArrayList;
import java.util.List;
@ManagedBean
@SessionScoped
public class GroupManageBean implements Serializable {
//...
//...
private List<Group> groups;
//service
private GroupService groupService = /************/;
//...
public void onSaveGroup(){
//...
groups = groupService.save(groups);
//โยน Event ตามแนวคิดข้อที่ 3 ครับ
//โยน Event ออกไป โดยค่าที่โยนออกไปคือ Type List<Group>
//ฉะนั้น Class ใดที่ Subscribe List<Group> ไว้ ก็จะได้รับ Event นี้
//ซึ่งข้างบนที่ผ่านมาก็คือ UserManageBean นั่นเอง
MyEvent.getApplicationBus().post(groups);
//...
}
}
ตัวอย่างต่อมา การใช้งานจริงครับ(ประยุกใช้งานในระบบ modular) (ระดับ session scope)
1. สร้าง class JSFEventBus ใช้สำหรับการ get ค่า event bus ในระดับ scope ต่างๆ เช่น session scope, application scope
package com.blogspot.na5cent.librarys.eventbus;
import com.google.common.eventbus.EventBus;
import javax.faces.context.FacesContext;
import javax.servlet.ServletContext;
/**
*
* @author Redcrow
*/
public class JSFEventBus {
public static final String APPLICATION_BUS_KEY = "APPLICATION_BUS_KEY";
public static final String SESSION_BUS_KEY = "SESSION_BUS_KEY";
public static EventBus getApplicationBus() {
return (EventBus) FacesContext
.getCurrentInstance()
.getExternalContext()
.getApplicationMap()
.get(APPLICATION_BUS_KEY);
}
public static EventBus getSessionBus() {
return (EventBus) FacesContext
.getCurrentInstance()
.getExternalContext()
.getSessionMap()
.get(SESSION_BUS_KEY);
}
}
2. สร้าง event type ModuleEventBus ซึ่งก็คือ event type ที่จะโยนออกไปpackage com.blogspot.na5cent.librarys.modular;
/**
*
* @author Redcrow
*/
public class ModuleEventBus {
private Module module;
public ModuleEventBus(Module module) {
this.module = module;
}
public Module getModule() {
return module;
}
}
3. สร้าง InitialEventBusHttpSessionListener ใช้สำหรับการ initial event bus ในแต่ละ session ผ่าน HttpSessionListenerpackage com.blogspot.na5cent.librarys.modular;
import com.blogspot.na5cent.librarys.eventbus.JSFEventBus;
import com.google.common.eventbus.EventBus;
import javax.servlet.annotation.WebListener;
import javax.servlet.http.HttpSession;
import javax.servlet.http.HttpSessionEvent;
import javax.servlet.http.HttpSessionListener;
/**
*
* @author Redcrow
*/
@WebListener
public class InitialEventBusHttpSessionListener implements HttpSessionListener {
@Override
public void sessionCreated(HttpSessionEvent se) {
HttpSession httpSession = se.getSession();
httpSession.setAttribute(JSFEventBus.SESSION_BUS_KEY, new EventBus());
}
@Override
public void sessionDestroyed(HttpSessionEvent se) {
HttpSession httpSession = se.getSession();
httpSession.removeAttribute(JSFEventBus.SESSION_BUS_KEY);
}
}
4. สร้าง ModuleStatusMB สำหรับรอรับ event bus ที่มี event type เป็น ModuleEventBus จากข้อ 2package com.blogspot.na5cent.librarys.modular;
import com.blogspot.na5cent.librarys.eventbus.JSFEventBus;
import com.google.common.eventbus.Subscribe;
import javax.annotation.PostConstruct;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.SessionScoped;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
*
* @author Redcrow
*/
@ManagedBean
@SessionScoped
public class ModuleStatusMB implements Serializable {
private static final Logger LOG = LoggerFactory.getLogger(ModuleStatusMB.class);
private Module currentModule;
@PostConstruct
public void postConstruct() {
JSFEventBus.getSessionBus().register(this);
}
@Subscribe
public void moduleChangeEvent(ModuleEventBus event) {
Module module = event.getModule();
if (module != null) {
currentModule = module;
// LOG.debug("module change event action");
// LOG.debug("module name => {}", module.getName());
// LOG.debug("module label => {}", module.getLabel());
// LOG.debug("module title => {}", module.getTitle());
// LOG.debug("module role => {}", module.getRole());
// LOG.debug("module icon => {}", module.getIcon());
// LOG.debug("module hidden icon => {}", module.getHiddenIcon());
// LOG.debug("module logical path => {}", module.getLogicalPath());
// LOG.debug("module physical path => {}", module.getPhysicalPath());
// LOG.debug("module position => {}", module.getPosition());
// LOG.debug("module disabled => {}", module.getDisabled());
// LOG.debug("module hidden => {}", module.getHidden());
// LOG.debug("module sub module size => {}", module.getSubModules().size());
}
}
public Module getCurrentModule() {
return currentModule;
}
}
สังเกตว่ามีการ register (บรรทัดที่ 24) และการทำ subscripe(บรรทัดที่ 27) เอาไว้ด้วย
5. สร้าง ModuleMB ซึ่งเป็นตัวโยน event bus ที่มี event type เป็น ModuleEventBus ออกไป
package com.blogspot.na5cent.librarys.modular;
import com.blogspot.na5cent.librarys.eventbus.JSFEventBus;
import com.blogspot.na5cent.librarys.utilities.FacesUtils;
import com.blogspot.na5cent.librarys.utilities.RequestClients;
import java.io.Serializable;
import java.util.List;
import javax.annotation.PostConstruct;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.SessionScoped;
import org.apache.commons.lang3.StringUtils;
import org.primefaces.model.DefaultTreeNode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
*
* @author Redcrow
*/
@ManagedBean
@SessionScoped
public class ModuleMB implements Serializable {
private static final Logger LOG = LoggerFactory.getLogger(ModuleMB.class);
private String center = "home/";
//new
private List<Module> toolbar;
private Module rootModule;
private DefaultTreeNode rootWestMenu;
@PostConstruct
public void postConstruct() {
rootModule = Modules.getRootModule(FacesUtils.getServletContext());
toolbar = rootModule.getSubModules();
}
public List<Module> getToolbar() {
return toolbar;
}
public DefaultTreeNode getRootWestMenu() {
return rootWestMenu;
}
public String getCenter() {
return center;
}
public void navigetURL() {
try {
String request = RequestClients.requestString("center");
String reload = RequestClients.requestString("reload");
String[] split = StringUtils.split(request, "/");
LOG.debug("reload => {}", reload);
Module checkModule = getLastModule(split);
if (checkModule == null) {
center = "error/";
} else {
if (!checkModule.isHidden() && !checkModule.isDisabled()) {
center = checkModule.getPhysicalPath();
if (split.length == 1 || "true".equals(reload)) {
Module module = getModule(rootModule, split[0]);
rootWestMenu = new DefaultTreeNode(module, null);
loadLeftMenu(module, rootWestMenu);
LOG.debug("left menu loaded");
}
/** post event bus **/
JSFEventBus.getSessionBus().post(new ModuleEventBus(checkModule));
/** post event bus **/
} else {
center = "error/";
}
}
LOG.debug("center => {}", center);
} catch (Exception ex) {
center = "error/";
ex.printStackTrace();
}
}
private Module getLastModule(String[] modules) {
Module parent = rootModule;
for (String module : modules) {
parent = getModule(parent, module);
if (parent == null) {
return parent;
}
}
return parent;
}
private void loadLeftMenu(Module parentModule, DefaultTreeNode parentNode) {
for (Module menu : parentModule.getSubModules()) {
loadLeftMenu(menu, new DefaultTreeNode(menu, parentNode)); //*****
}
}
private Module getModule(Module parent, String moduleName) {
List<Module> subModules = parent.getSubModules();
int index = subModules.indexOf(new Module(moduleName));
if (index == -1) {
return null;
}
return subModules.get(index);
}
}
การโยน event type (ModuleEventBus) อยู่ในบรรทัดที่
70
หวังว่าคงจะพอเข้าใจ Concept และวิธีการทำงานของการเขียนโปรแกรมแบบ Event Driven ด้วย Google Guava น่ะครับ ^_____^

ไม่มีความคิดเห็น:
แสดงความคิดเห็น