หน้าเว็บ

วันจันทร์ที่ 23 กรกฎาคม พ.ศ. 2555

เขียนโปรแกรม Event Driven ด้วย EventBus Google Guava (java)

การเขียน  Event Driven คืออะไร
        การเขียน  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  ผ่าน HttpSessionListener
package 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 จากข้อ 2
package 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 น่ะครับ ^_____^

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

แสดงความคิดเห็น