November 5, 2011

Servlet Filter ဖြင့် AOP

ကျွှန်တော်သည် Java EE ၏ အခန်းဆက်အနေဖြင့် Servlet အကြောင်းကို စတင်ရေးသားခဲ့သည်မှာ ၅ကြိမ်မြောက်ကို ရောက်ခဲ့ပါပြီ။ အခြေခံ Servlet ကို ရေးသားပုံမှအစ၊ Request Parameter များကိုအသုံးပြုပုံ၊ ထိုမတဆင့် Cookies နှင့် Session ကို အသုံးပြုပြီး Client နှင့် Server အကြားမှာ အဆင်ပြေပြေ ဆက်သွယ်အသုံးပြုနိုင်ပုံနှင့် RequestDispatcher ၏ include နှင့် forward ကိုအသုံးပြုခြင်း အားဖြင့် Servlet တစ်ခုနှင့် တစ်ခုဆက်သွယ်ပြီး အသုံးပြုနိုင်ပုံတို့ကို လေ့လာခဲ့ပါသည်။ ယနေ့ ဘလောဂ်ဖြင့် Servlet 1.3 တွင် စတင်ပါဝင်ခဲ့သော Filter အကြောင်းကို ဖော်ပြသွားပါမည်။

Intercepting Filter Pattern



J2EE ၏ ထင်ရှားသော Design Pattern တစ်ခုဖြစ်ပါသည်။ Application တစ်ခုတွင်ပါဝင်သော Program များတွင်၊ အဓိကကျသော Business Logic များအပြင်၊ Security, Log နှင့် DB Access အစရှိသော နေရာတကာတွင်ပါဝင်သည့် လုပ်ဆောင်ချက်များကိုလည်း ရေးသားရလေ့ရှိ၏။ ထိုနေရာတကာတွင် ပါဝင်သော အခြေခံလုပ်ဆောင်ချက်များသည် များသောအားဖြင့် ဘယ်လိုပရိုဂရမ်များမှာမဆို လုပ်ဆောင်နေ ကြသည်မှာ အတူတူပင် ဖြစ်၏။ ဤကဲ့သို့သော တူညီသည့် အခြေခံလုပ်ဆောင်ချက်များကို ပရိုဂရမ်တိုင်းတွင်မရေးသားတော့ပဲ၊ Filter အနေနဲ့ပြင်ပတွင် တစ်ခုတည်းသာ ပြင်ဆင်ထားပြီး၊ Business Logic များကို ခေါ်ယူရာတွင် ထို Filter များကို ဖြတ်ပြီးမှ ခေါ်ယူစေခြင်း ဖြစ်သည်။



Security၊ Log နှင့် DB Access အစရှိသော Filter များကို Filter Chain အနေနဲ့ပြင်ဆင်ထားပြီး၊ Business Logic ကို ခေါ်ယူသောအခါ Filter Chain အတွင်းတွင်ရှိသော Filter များကို အလုပ်လုပ်ဆောင်စေပြီးမှ Target ဖြစ်သော Business Logic ကို လုပ်ဆောင်စေသော Pattern မျိုးကို Intercepting Filter Pattern ဟုခေါ်ပါသည်။

Aspect Oriented Programming




အထက်ပါ Intercepting Filter တွင်ဖော်ပြထားသော Class များကြားတွင် တည်ရှိသော တူညီသည့်လုပ်ဆောင်ချက်များကို Business Logic နှင့် ခွဲထုတ်ခြင်း အတွေးအခေါ်ကို ကျင့်သုံးခြင်းအားဖြင့် Program များကို နားလည်ရလွယ်ကူစေခြင်း၊ Source Code များကို နည်းပါးစေခြင်းအားဖြင့် Maintenance အပိုင်းကို တိုးတက်စေခြင်းကို ဦးစားပေးသည့် အမြင်မျိုးကို Aspect Oriented ဟုခေါ်ပါသည်။

ထင်ရှားသည့် Aspect Oriented Programming ဘာသာရပ်များမှာ Aspect C++, Aspect J, Aspect R နှင့် JBossAOP တို့ဖြစ်ကြ၏။ တဖန် Aspect Oriented အမြင်ကို ထည့်သုံးထားသော Framework များမှာ Aspectcocoa, AspectWerkz, Spring Framework နှင့် Seasor Framework တို့ဖြစ်ကြ၏။

Filter ဖြင့် Web Application တွင် AOP

Java EE တွင် Interceptor၊ DI နှင့် AOP Framework များကို အသုံးပြုပြီး AOP Application များကို ရေးသားနိုင်ရန်ပြင်ဆင်ထားပါသည်။ တဖန် Servlet ၏ Filter နှင့် FilterChain Interface များကို အသုံးပြုပြီး Intercepting Filter Pattern ကို ရေးသားခြင်း အားဖြင့် Aspect လုပ်ဆောင်ချက်များကို Filter များအဖြစ်ခွဲထုတ်ခြင်းအားဖြင့် AOP ကို လည်းပုံဖော်နိုင်မည် ဖြစ်သည်။

Java Web Application တွေမှာ Filter ကို အသုံးပြုနိုင်ဖို့အတွက် ဦးစွာအသုံးပြုမည့် Filter Class ကို ရေးသားရန်လိုအပ်ပါတယ်။ ပြီးလျှင် web.xml တွင် Filter အမည်နှင့် class ကို သတ်မှတ်ရေးသားပြီး၊ မည်သည့် url ကိုခေါ်ဆိုသည့်အခါမျိုးတွင် Filter ကို အသုံးပြုပါမည်ဟု filter-mapping ကို သတ်မှတ်ရန်လိုအပ်ပါသည်။

ကျွှန်တော်တို့ ပြီးခဲ့သော နမှုနာများတွင် မြန်မာစာကို အသုံးပြုနိုင်ရန် HTTPServletResponse တွင် Encoding ကို UTF-8 အဖြစ် အမြဲသတ်မှတ်နေခဲ့ပါသည်။ ဤနမှုနာတွင် Encoding ကို သတ်မှတ်ပေးသော Filter တစ်ခုကိုရေးကြည့်ပါမည်။

EncodingFilter
public class EncodingFilter implements Filter {
    
    private String encoding = null;

    @Override
    public void destroy() {
        this.encoding = null;
    }

    @Override
    public void doFilter(ServletRequest req, ServletResponse res,
            FilterChain chain) throws IOException, ServletException {
        res.setCharacterEncoding(encoding);
        chain.doFilter(req, res);
    }

    @Override
    public void init(FilterConfig config) throws ServletException {
        this.encoding = config.getInitParameter("encoding");
    }

}


Filter အဖြစ်အသုံးပြုလိုသည့် Class များသည် Filter Interface ကို implement လုပ်၍ ရေးသားရန်လိုအပ်ပါသည်။ တဖန် ထို Class များသည် Filter ၏ init, doFilter, destroy လုပ်ဆောင်ချက်များကို override လုပ်ရန်လိုအပ်ပါသည်။

init
Filter#init လုပ်ဆောင်ချက်သည် Filter Class ကို JVM ဆီသို့ စတင် Load လုပ်သောအခါ တွင် တစ်ကြိမ်တည်းသာ အခေါ်ခံရမည့် လုပ်ဆောင်ချက်ဖြစ်ပါသည်။ Filter အတွက်အခြေခံ လိုအပ်ချက်များကို ဤနေရာတွင် ရေးသားသင့်ပါသည်။ ဤနမှုနာထဲတွင် FilterConfig#getInitParameter ကို အသုံးပြု၍ Filter ၏ encoding init-parameter ကို ရယူပြီး private member ဖြစ်သော encoding တွင် အစားထိုးနေပါသည်။

doFilter
Filter#doFilter သည် Filter ကိုအသုံးပြုသော Request များကို ခေါ်ယူသည့်အခါတွင် အသုံးပြုမည့် အဓိက လုပ်ဆောင်ချက်တစ်ခု ဖြစ်ပါသည်။ Request လာသည့်အခါတိုင်း အခေါ်ခံရမည့် လုပ်ဆောင်ချက်ဖြစ်ပြီး၊ Filter မှ Business Logic များအပေါ်တွင် လုပ်ဆောင်ပေးလိုသည့် လုပ်ဆောင်ချက်များကို ဤနေရာတွင် ရေးသားရမည်ဖြစ်၏။

ဤနမှုနာထဲတွင် ServletResponse#setCharactersetEncoding လုပ်ဆောင်ချက်ဖြင့် Character Set Encoding အမျိုးအစားကို private member encoding ဖြင့် သတ်မှတ်ပေးနေပါသည်။ ဤကဲ့သို့ သတ်မှတ်ပေးခြင်းအားဖြင့် ServletResponse တွင် utf-8 အမျိုးအစား Character Set ကို အသုံးပြုနိုင်မည်ဖြစ်၏။ ပြီးပါက FilterChain#doFilter ကိုခေါ်ယူပြီး Filter Chain အတွင်းတွင်ရှိသော အခြားသော Filter များကို အလုပ်လုပ်စေမည်ဖြစ်သည်။

destroy
Filter#destroy လုပ်ဆောင်ချက်သည် JVM အပေါ်မှ Filter Class ကို ဖျက်စီးပစ်သောအခါတွင် တစ်ကြိမ်တည်းသာ အခေါ်ခံရမည့် လုပ်ဆောင်ချက်ဖြစ်ပြီး၊ ဤနမှုနာထဲတွင် member ဖြစ်သော encoding အား null ဖြင့် အစားထိုးနေပါသည်။ Filter Class ကို ရေးသားပြီးပါက၊ web Application တွင် Filter အဖြစ်အသုံးပြုနိုင်ရန် web.xml တွင် အောက်ပါအတိုင်း သတ်မှတ်ရေးသားရန် လိုအပ်ပါသည်။

web.xml
    <filter>
        <filter-name>enc-filter</filter-name>
        <filter-class>com.episode5.EncodingFilter</filter-class>
        <init-param>
            <param-name>encoding</param-name>
            <param-value>utf-8</param-value>
        </init-param>
    </filter>
    
    <filter-mapping>
        <filter-name>enc-filter</filter-name>
        <url-pattern>/enc/*</url-pattern>
        <url-pattern>/filter/*</url-pattern>
    </filter-mapping>

ဦးစွာ filter tag အတွင်းတွင် အသုံးပြုမည့် filter အမည်နှင့် class အပြင် အသုံးပြုမည့် init-param များကို သတ်မှတ်ရေးသားပါသည်။ ပြီးပါက filter-mapping tag အတွင်းတွင် filter ကို အသုံးပြုမည့် url-pattern နှင့် mapping ကို သတ်မှတ်ရေးသားပါသည်။ အထက်ပါ နမှုနာထဲတွင် /enc/* ဟုရေးသားထားပါသဖြင့် /enc/ ၏အောက်မှ Servlet များကို ခေါ်ယူရာတွင် အထက်ပါ Filter ကို ဖြတ်ပြီး ခေါ်ယူအသုံးပြုနိုင်မည် ဖြစ်သည်။

ပြီးလျှင် Filter ကို သုံးပြီးခေါ်ယူရန်အတွက် Servlet တစ်ခုကို ရေးကြည့်ပါဦးမည်။ Encoding ကို ရေးမထားပဲ "မင်္ဂလာပါ မြန်မာအိုင်တီပရို" ဟု ဖော်ပြနေသော Servlet တစ်ခုကို ရေးပြီး HTML စာမျက်နှာမှ Filter ကို မသုံးပဲတစ်မျိုး၊ Filter ကို သုံးပြီးတခါခေါ်ကြည့်ပါမည်။

TargetServlet

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp)
            throws ServletException, IOException {
        resp.getWriter().println("မင်္ဂလာပါ မြန်မာအိုင်တီပရို။");
    }


HttpServletResponse ၏ Writer ကို ခေါ်၍ "မင်္ဂလာပါ မြန်မာအိုင်တီပရို။" ဟု ရေးသားနေခြင်းသာဖြစ်၏။ ဦးစွာ Filter ကို အသုံးမပြုပဲ TargetServlet ကို ခေါ်ကြည့်ပါမည်။ No Filter လင့်ခ်တွင် အောက်ပါအတိုင်း ရေးသားထားပါသည်။

<li><a href="/target">No Filter</a></li>


Encoding ကို မရေးသားထားသောကြောင့် မြန်မာစာလုံးများကို ဖော်ပြနိုင်ခြင်းမရှိပါ။

<li><a href="/enc/target">Encoding Filter</a></li>

အထက်ပါလင့်ခ်တွင် /enc/target ဟု ခေါ်ယူနေပါသဖြင့် Encoding Filter ၏ filter-mapping တွင် ရေးသားထားသော /enc/* နှင့် ကိုက်ညီသော ပုံစံဖြစ်ပါသဖြင့် TargetServlet ကို ခေါ်ယူသည့်အခါတွင် Encoding Filter ကို ဖြတ်၍ ခေါ်ယူမည် ဖြစ်သည်။ တဖန် EncodingFilter ၏ doFilter လုပ်ဆောင်ချက်အတွင်းတွင် ServletResponse#setCharacterEncoding ဖြင့် Encoding ကို utf-8 ဟု သတ်မှတ်ထားသောကြောင့် မြန်မာစာကို ဖော်ပြပေးနိုင်မည်ဖြစ်သည်။ လက်တွေ့စမ်းကြည့်ပါမည်။



မြန်မာစာလုံးကို ဖော်ပြပေးနိုင်သည်ကို တွေ့မြင်ရမည် ဖြစ်သည်။

Encoding သည် Business Logic ဖြစ်သော "မင်္ဂလာပါ မြန်မာအိုင်တီပရို။" ကိုဖော်ပြရာတွင် တိုက်ရိုက်မပတ်သက်သော်လည်း မြန်မာစာကို ဖော်ပြရန်အတွက် လိုအပ်ပါသည်။ အလားတူ မြန်မာစာကို သုံးတဲ့ Servlet တိုင်းမှာ လိုအပ်ပါသည်။ အဲ့ဒီလို Business Logic နှင့်မပတ်သက်သော Encoding သက်မှတ်ချက်ကို ပြင်ပသို့ခွဲထုတ်ပြီး မြန်မာစာကိုဖော်ပြ နေသော Servlet ကို ခေါ်ယူတိုင်း Encoding Filter ကို ကြားဖြတ်အလုပ်လုပ်ခိုင်နေခြင်းသည် Filter ဖြစ်ပါသည်။

Filter များကို အသုံးပြုခြင်း

EncodingFilter ၏ doFilter လုပ်ဆောင်ချက်၏ ပါရာမီတာများတွင် FilterChain ၏ instance ကိုလည်း ရယူနေသည်ကို သတိပြုမိမည်ဖြစ်သည်။ တဖန် လုပ်ဆောင်ချက်ပြီးလျှင် FilterChain#doFilter ကို ခေါ်ယူနေသည်ကို သတိပြုမိမည်ဖြစ်မည်။ Servlet Container သည် web.xml တွင်သတ်မှတ်ထားသော Filter များကို FilterChain အဖြစ်ပြောင်းယူထားပါသည်။ ပြီးလျှင် ပဋ္ဌမဆုံး Filter မှ စတင်ပြီး doFilter ကိုခေါ်ယူမည်ဖြစ်သည်။ Filter အတွင်းတွင် လုပ်ဆောင်ပြီးပါက FilterChain#doFilter ကိုပြန်ခေါ်နေပါသည်။ FilterChain မှ တဆင့်ပြီးတဆင့် Filter များကို ကုန်အောင်ခေါ်ယူမည်ဖြည်သည်။ နောက်ဆုံးခေါ်စရာ Filter မရှိတော့မှ Target Servlet ကို ခေါ်ယူမည်ဖြစ်သည်။

ဤနည်းအားဖြင့် Filter များကို တစ်ပြိုင်နက်တည်း အသုံးပြုလိုပါက၊ လွယ်လင့်တကူ အသုံးပြုနိုင်မည် ဖြစ်သည်။ နမှုနာအဖြစ် HTML Header များကို ရေးသားနေသော အပိုင်းနှင့် မာတိကာသို့ လင့်ခ်ကို ရေးသားနေသောအပိုင်းကို HeaderFilter အဖြစ်ရေးသားပြီး EncodingFilter နှင့် တစ်ပြိုင်နက်တည်း အသုံးပြုကြည့်ပါမည်။

HeaderFilter

    @Override
    public void doFilter(ServletRequest req, ServletResponse res,
            FilterChain chain) throws IOException, ServletException {
        PrintWriter writer = res.getWriter();
        writer.println(getHeaderHtml(req.getParameter("id"), req.getParameter("title")));
        chain.doFilter(req, res);
        writer.println(getFooterHtml());        
    }


HeaderFilter#doFilter လုပ်ဆောင်ချက်ထဲတွင် Common Class တစ်ခုဖြစ်သော HtmlStringUtil#getHeaderHtml နှင့် getFooterHtml ကို ခေါ်ယူပြီး HTML စာသားများကို ရယူနေပါသည်။ ရယူထားသော စာသားများကို PrintWriter မှ တဆင့် ServletResponse တွင် ဖြည့်စွက်ရေးသားနေစေခြင်းပင်ဖြစ်၏။

HtmlStringUtil

    /**
     * အထက်ပိုင်း HTML စာသားများကိုရယူခြင်း
     * @param title
     * @return
     */
    public static String getHeaderHtml(String episode, String title) {
        StringBuilder sb = new StringBuilder("<!DOCTYPE html>");
        sb.append("<html>");
        sb.append("<head>");
        sb.append("<meta charset='UTF-8'>");
        sb.append("<link rel=\"stylesheet\" href=\"/theme/episode.css\" type=\"text/css\" />");
        sb.append("<title> ").append(episode).append("</title>");
        sb.append("</head>");
        sb.append("<body style='font-family:Myanmar3'>");

        sb.append("<div style='float:right'><a href='/page/"+ episode +".html'>မာတိကာသို့</a></div>");
        sb.append("<h3>");
        sb.append(title);
        sb.append("</h3>");
        return sb.toString();
    }
    
    /**
     * အောက်ပိုင်း HTML စာသားများကို ရယူခြင်း
     * @return
     */
    public static String getFooterHtml() {
        return "</body></html>";
    }

HTML စာသားများကို ပြန်ပေးနေခြင်းသာဖြစ်၏။ ပုံမှန်ဆိုလျှင် JSP ကို သုံး၍ ဖော်ပြစေသည်က များသောပါသော်လည်း၊ လက်ရှိ JSP အကြောင်းကို မဖော်ပြရသေးသောကြောင့် ဤကဲ့သို့ ရေးနေခြင်းဖြစ်ပါသည်။

web.xml

    <filter>
        <filter-name>head-filter</filter-name>
        <filter-class>com.episode5.HeaderFilter</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>head-filter</filter-name>
        <url-pattern>/filter/*</url-pattern>
    </filter-mapping>


HeaderFilter ကို အသုံးပြုနိုင်ရန် အထက်ပါအတိုင်း သက်မှတ်ချက်ကို web.xml တွင် ဖြည့်စွက်ရေးသားရန် လိုအပ်ပါသည်။ EncodingFilter ၏သတ်မှတ်ချက်တွင် url-pattern တွင် /filter/* ဟု ရေးသားထားသည်ကို မှတ်မိဦးမည်ထင်သည်။ ဤ HeaderFilter တွင်လည်း အလားတူ သက်မှတ်ထားပါသဖြင့် /filter/* ဟုခေါ်ယူသောအခါ EncodingFilter ကို ခေါ်ယူပြီးသည့်နောက် HeaderFilter ကို ခေါ်ယူလိမ့်မည် ဖြစ်သည်။

<li><a href="/filter/target?id=episode5&title=Header Filterကိုအသုံးပြုထားခြင်း">
               Encoding And Header Filter</a></li>


အထက်ပါအတိုင်း TargetServlet အား id နှင့် title request parameter များဖြင့် ခေါ်ယူကြည့်ပါမည်။

 

အထက်ပါအတိုင်း ခေါင်းစဉ်နှင့် မာတီကာသို့ လင့်ခ်ကို ရေးသားနိုင်သည်ကို တွေ့မြင်ရမည်ဖြစ်သည်။ ဤနည်းအားဖြင့် Filter များကို အသုံးပြု၍ Business Logic နှင့်တိုက်ရိုက်မသက်ဆိုင်သော်လည်း ရေးသားရန်လိုအပ်သည့် အပိုင်းများကို Filter အဖြစ်ရေးသားပြီး လိုအပ်သည့်အခါ ကြားဖြတ် လုပ်ဆောင် စေခြင်း အားဖြင့် Business Logic အပိုင်းကို တတ်နိုင်သလောက် ရှင်းလင်းစွာရေးသားနိုင်မည် ဖြစ်သည်။

ကိုးကား

http://java.sun.com/blueprints/corej2eepatterns/Patterns/Intercepti...
http://legacy.techscore.com/tech/J2EE/Servlet/6.html

No comments:

Post a Comment