沿續上篇的AspectJ 5 for Spring annotation的疑問,也讓我首次去檢驗Open Source的程式碼。其實Open Source的code以前都不看,就有一種怕改到不能用的自卑心作祟。而OOP的好處不在於改,可以繼承,所以我寫了MyDispatchAction繼承MappingDispatchAction的class,讓原本protected的getMethodName,變成public探出頭來呼吸一下,而業務Action再繼承我寫的。

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.struts.action.ActionForm;
import org.apache.struts.action.ActionForward;
import org.apache.struts.action.ActionMapping;
import org.apache.struts.actions.MappingDispatchAction;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class MyDispactionAction extends MappingDispatchAction {
    private static Logger logger = LoggerFactory.getLogger(MyDispactionAction.class);
    private String methodName;
   
@Override
    public ActionForward execute(ActionMapping mapping, ActionForm form,
            HttpServletRequest request, HttpServletResponse response)
            throws Exception {
        if (isCancelled(request)) {
            ActionForward af = cancelled(mapping, form, request, response);
            if (af != null) {
                return af;
            }
        }
        // Get the parameter. This could be overridden in subclasses.
        String parameter = getParameter(mapping, form, request, response);

        // Get the method's name. This could be overridden in subclasses.
        String name = getMethodName(mapping, form, request, response, parameter);
        this.methodName = name;
        // Prevent recursive calls
        if ("execute".equals(name) || "perform".equals(name)){
            String message = messages.getMessage("dispatch.recursive", mapping.getPath());
            logger.error(message);
            throw new ServletException(message);
        }
        // Invoke the named method, and return the result
        return dispatchMethod(mapping, form, request, response, name);
    }

    public String getMethodName() {
        return this.methodName;
    }

}

  上述程式碼中,粗體字是原MappingDispatchAction的execute method內容,加了紅色一行this.methodName = name。而在AOP程式,可以透過getMethodName取得資訊,但需在JointPoint.proceed執行之後才會有值,所以理論上也適於@After、@AfterReturn等annotation,如下:

import org.apache.commons.lang.time.StopWatch;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import com.mycompany.actions.MyDispactionAction;

@Aspect
@Component
public class WebLogger {
    private static Logger logger = LoggerFactory.getLogger(WebLogger.class);
    @Pointcut("target(com.foo.bar.action.ILoginAction)")
    public void timer() {
        logger.info("call timer!!");
    }

    @Around("timer()")
    public Object time(ProceedingJoinPoint joinPoint) throws Throwable {
        String clazzName = joinPoint.getTarget().getClass().getSimpleName(); 
        String methodName = joinPoint.getSignature().getName();
       
MyDispactionAction action = null;
        try {
            action = (MyDispactionAction) joinPoint.getTarget(); // 將target強制轉型為MyDispatchAction
        } catch (Exception e) {
            logger.warn("Cast Error", e);
        }
        StopWatch clock = new StopWatch();
        clock.start(); 
        Object result = joinPoint.proceed(); 
        clock.stop(); 
        if (action != null) methodName = action.getMethodName(); // 必須在jointPoint.proceed之後
        String[] params = new String[] { clazzName, methodName, clock.getTime() + "" }; 
        logger.info("[{}] Run [{}] method spent [{}] ms", params);     
        return result;
    }
}

  如此一來,業務Action繼承MyDispatchAction和實作ILoginAction,就可被AOP WebLogger攔截到ILoginAction的實作品的method name,如下:

public class LoginAction extends MyDispactionAction implements ILoginAction {
    private static Logger logger = LoggerFactory.getLogger(LoginAction.class);
    public ActionForward login(ActionMapping mapping, ActionForm form,
            HttpServletRequest request, HttpServletResponse response) {
        // 略...
    }
}

  當然,別忽略在application-context.xml裡的設定,要設成proxy-target-class="true"

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="
http://www.springframework.org/schema/beans"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns:context="http://www.springframework.org/schema/context"
        xmlns:aop="http://www.springframework.org/schema/aop"
        xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.0.xsd">   
    <!-- <context:annotation-config/> 可移除 -->
    <context:component-scan base-package="com.foo.bar"/>
    <aop:aspectj-autoproxy proxy-target-class="true"/>
</beans>

  最後在WebLogger程式如願攔截到login method name了,Log如下:

2010/03/14 15:34:37 INFO  [WebLogger.java:51] : [LoginAction] Run [login] method spent [163] ms

  附帶一提,ILoginAction可以是一個什麼method都沒定的interface,照樣能運行。更聰明的作法,是MyDispatchAction去實作一個介面,便於被AOP的target取得^^。

arrow
arrow
    全站熱搜

    Jemmy 發表在 痞客邦 留言(0) 人氣()