沿續上篇的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"
最後在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取得^^。