Java Votary

            BlogJava :: 首頁(yè) :: 新隨筆 :: 聯(lián)系 :: 聚合  :: 管理 ::
            48 隨筆 :: 1 文章 :: 80 評(píng)論 :: 0 Trackbacks

          AOP@Work: 用 AspectJ 進(jìn)行性能監(jiān)視,第 2 部分

          通過(guò)裝載時(shí)織入使用Glassbox Inspector

          developerWorks
          文檔選項(xiàng)
          將此頁(yè)作為電子郵件發(fā)送

          將此頁(yè)作為電子郵件發(fā)送

          未顯示需要 JavaScript 的文檔選項(xiàng)

          樣例代碼


          對(duì)此頁(yè)的評(píng)價(jià)

          幫助我們改進(jìn)這些內(nèi)容


          級(jí)別: 高級(jí)

          Ron Bodkin , 創(chuàng)始人, New Aspects of Software

          2005 年 12 月 19 日

          有 了基本的面向方面的監(jiān)視基礎(chǔ)架構(gòu)后,可以對(duì)它進(jìn)行擴(kuò)展以滿足真實(shí)世界的監(jiān)視要求。在這篇由兩部分組成的文章的第二部分,Ron Bodkin 展示了如何在 Glassbox Inspector 中添加企業(yè)監(jiān)視功能,包括監(jiān)視多個(gè)應(yīng)用程序、Web 服務(wù)和 Web 應(yīng)用程序框架。他還展示了如何跟蹤應(yīng)用程序錯(cuò)誤并在監(jiān)視代碼中包含它們,并展示了如何以編程方式部署和控制這個(gè)監(jiān)視基礎(chǔ)架構(gòu)。
          關(guān)于這個(gè)系列

          AOP@Work系列 所面向的讀者是在面向方面編程上有些基礎(chǔ),想擴(kuò)展或加深了解的開(kāi)發(fā)人員。同 developerWorks 上的大多數(shù)文章一樣,這個(gè)系列高度實(shí)用:讀完每篇介紹新技術(shù)的文章,都可以立即進(jìn)行實(shí)踐。

          這個(gè)系列的每個(gè)作者在面向方面編程領(lǐng)域都具有領(lǐng)袖地位或?qū)<宜健TS多作者都是系列中介紹的項(xiàng)目和工具的參與者。每篇文章都力圖提供一個(gè)中立的評(píng)述,以確保這里表達(dá)的觀點(diǎn)是公正且正確的。

          如果有對(duì)每個(gè)作者文章的評(píng)論或問(wèn)題,請(qǐng)分別與他們聯(lián)系。要對(duì)這個(gè)系列整體進(jìn)行評(píng)論,可以與系列的負(fù)責(zé)人 Nicholas Lesiecki 聯(lián)系。請(qǐng)參閱 參考資料,獲取關(guān)于 AOP 的更多背景資料。

          我在本文的 第 1 部分 中給出的基本 Glassbox Inspector 只能監(jiān)視簡(jiǎn)單的、基于 Servlet 的應(yīng)用程序(如在 Duke 的 Bookstore 這個(gè)例子中),更系統(tǒng)化的框架應(yīng)包括對(duì)流行的 Web 應(yīng)用程序框架(如 Struts 和 Spring)的支持。如今的 Web 應(yīng)用程序很少使用 Servlet 直接處理請(qǐng)求。這些框架通常將所有動(dòng)態(tài)頁(yè)請(qǐng)求委派給網(wǎng)關(guān) Servlet,如 Spring 框架的 DispatcherServlet,然后它再將請(qǐng)求處理委派給不同的控制器。與此類似,Struts ActionServlet 委派給作為控制器的 Action 子類。

          在 這篇探索用 AspectJ 進(jìn)行性能監(jiān)視的文章的第二部分中,我對(duì) Glassbox Inspector 進(jìn)行擴(kuò)展,通過(guò)增加監(jiān)視器以提供關(guān)于應(yīng)用程序性能的更有意義的信息。這些監(jiān)視器跟蹤 Struts 和 Spring Web 應(yīng)用程序框架的控制器之間的交互以及 Web 服務(wù)的響應(yīng)和請(qǐng)求操作。我還擴(kuò)展了系統(tǒng)以支持多個(gè)應(yīng)用程序,并添加了一個(gè)錯(cuò)誤處理層和在運(yùn)行時(shí)方便地啟用和禁止監(jiān)視的能力。在文章的最后展示如何用裝載時(shí) 織入部署 Glassbox Inspector,以及如何測(cè)量帶來(lái)的開(kāi)銷。

          為了理解本文中的例子,需要對(duì) Spring MVC、Struts 和 Apache Axis for Web Services 有一些了解。Glassbox Inspector 監(jiān)視基礎(chǔ)架構(gòu)的完整代碼請(qǐng)參閱 下載。要下載運(yùn)行本文中的例子所需要的 Aspectj、JMX 和 Tomcat 5 請(qǐng)參閱 參考資料

          可重用的操作監(jiān)視器

          為了得到范圍更廣泛的監(jiān)視功能,我將 第 1 部分的清單 5 中大多數(shù)可重用的 Servlet 監(jiān)視代碼放到 AbstractOperationMonitor 方面中。很多情況下跟蹤嵌套的請(qǐng)求很重要,包括跟蹤在框架不同級(jí)別上的控制器以及跟蹤委派的請(qǐng)求(如從轉(zhuǎn)發(fā)到嵌套請(qǐng)求)的性能。清單 1 顯示了我如何擴(kuò)展 Glassbox Inspector 以跟蹤對(duì)操作的嵌套請(qǐng)求:


          清單 1. 可重用的操作監(jiān)視方面

          public abstract aspect AbstractOperationMonitor extends AbstractRequestMonitor {

          protected abstract class OperationRequestContext extends RequestContext {
          /**
          * Find the appropriate statistics collector object for this
          * operation.
          * @param operation an instance of the operation being
          * monitored
          */
          public PerfStats lookupStats() {
          if (getParent() != null) {
          // nested operation
          OperationStats parentStats =
          (OperationStats)getParent().getStats();
          return parentStats.getOperationStats(getKey());
          }
          return getTopLevelStats(getKey());
          }

          /**
          * Determine the top-level statistics for a given operation
          * key. This also looks up the context name for the
          * application from the operation monitor:
          * @see AbstractOperationMonitor#getContextName(Object)
          * For a Web application, top-level statistics are normally
          * all Servlets, and the key is the Servlet name.
          * @param key An object to uniquely identify the
          * operation being performed.
          */
          protected OperationStats getTopLevelStats(Object key) {
          OperationStats stats;
          synchronized(topLevelOperations) {
          stats = (OperationStats)topLevelOperations.get(key);
          if (stats == null) {
          stats =
          perfStatsFactory.createTopLevelOperationStats(key,
          getContextName(controller));
          topLevelOperations.put(key, stats);
          }
          }
          return stats;
          }

          /**
          * @return An object that uniquely identifies the operation
          * being performed.
          */
          protected abstract Object getKey();

          /** The current controller object executing, if any. */
          protected Object controller;
          };

          /**
          * This advice stores the controller object whenever we construct a
          * request context.
          */
          after(Object controller) returning (OperationRequestContext ctxt) :
          cflow(adviceexecution() && args(controller, ..) &&
          this(AbstractOperationMonitor)) &&
          call(OperationRequestContext+.new(..)) {
          ctxt.controller = controller;
          }

          ...

          AbstractOperationMonitor 的第一部分?jǐn)U展了本文第 1 部分中原來(lái)的 Servlet 監(jiān)視器以查看嵌套操作的統(tǒng)計(jì)。使用嵌套操作使我們可以在分派 JSP 或者分解 Web 服務(wù)或者多方法應(yīng)用程序控制器的不同方法這樣的情況下跟蹤資源占用。原來(lái)的 lookupStats() 方法現(xiàn)在檢查父請(qǐng)求上下文。如果有父請(qǐng)求,那么它調(diào)用新方法 getOperationStats() 以獲取它。否則,它調(diào)用新方法 getTopLevelStats(),這個(gè)方法調(diào)用一個(gè)工廠以創(chuàng)建一個(gè)新的 OperationStats。使用工廠可以保證我的監(jiān)視基礎(chǔ)架構(gòu)不依賴于統(tǒng)計(jì)類的實(shí)現(xiàn)。



          回頁(yè)首


          監(jiān)視多個(gè)應(yīng)用程序

          在清單 1 中,我還加入了對(duì)運(yùn)行在單個(gè)應(yīng)用服務(wù)器中的多個(gè)應(yīng)用程序的監(jiān)視支持。主要是通過(guò)增加一個(gè)檢查應(yīng)用程序上下文的預(yù)查來(lái)做到這一點(diǎn)的。在檢查頂級(jí)統(tǒng)計(jì)時(shí),我調(diào)用一個(gè)監(jiān)視器模板方法 getContextName(),以確定操作關(guān)聯(lián)的是哪一個(gè)應(yīng)用程序。下面將會(huì)看到對(duì)于 Servlet 它是如何處理的。注意,向 getContextName() 方法傳遞了控制器的一個(gè)實(shí)例,這樣就可以檢查關(guān)聯(lián)的應(yīng)用程序上下文。

          在抽象操作監(jiān)視器中,還提供了具有具體建議的切點(diǎn),它定義了監(jiān)視請(qǐng)求的常用方法。對(duì)它做了擴(kuò)展以便監(jiān)視收到的請(qǐng)求,如 Struts 操作和收到的 Web 服務(wù)請(qǐng)求。清單 2 展示了一個(gè)代表性的例子:


          清單 2. AbstractOperationMonitor 中的監(jiān)視模板

          /**
          * This defaults to no join points. If a concrete aspect overrides
          * classControllerExec with a concrete definition,
          * then the monitor will track operations at matching join points
          * based on the class of the controller object.
          */
          protected pointcut classControllerExec(Object controller);

          Object around(final Object controller) :
          classControllerExec(controller) {
          RequestContext rc = new OperationRequestContext() {
          public Object doExecute() {
          return proceed(controller);
          }

          protected Object getKey() {
          return controller.getClass();
          }
          };
          return rc.execute();
          }

          // Controller where the name of the signature at the monitored join point
          // determines what is being executed, for example, the method name
          /**
          * This defaults to no join points. If a concrete monitor overrides
          * methodSignatureControllerExec with a concrete
          * definition, then it will track operations at matching join points
          * based on the run-time class of the executing controller instance
          * and the method signature at the join point.
          */
          protected pointcut methodSignatureControllerExec(Object controller);

          Object around(final Object controller) :
          methodSignatureControllerExec(controller) {
          RequestContext rc = new OperationRequestContext() {
          public Object doExecute() {
          return proceed(controller);
          }

          protected Object getKey() {
          return concatenatedKey(controller.getClass(),
          thisJoinPointStaticPart.getSignature().getName())
          ;
          }

          };
          return rc.execute();
          }

          classControllerExec() 的切點(diǎn)捕獲所有類控制器處理請(qǐng)求的點(diǎn),像 Servlet do 方法執(zhí)行或者普通 Struts action execute 方法,在這里響應(yīng)請(qǐng)求的對(duì)象的類確定要執(zhí)行的操作。更準(zhǔn)確地說(shuō), classControllerExec() 切點(diǎn) 定義了一個(gè)空的切點(diǎn)(它不會(huì)匹配任何連接點(diǎn))。然后它提供一個(gè)具體建議,這個(gè)建議設(shè)置工人對(duì)象并返回對(duì)于這種情況正確的鍵值。這與使用一個(gè)抽象切點(diǎn)類似,其中子方面必須覆蓋切點(diǎn)以使用建議。不過(guò)在這里,我提供了永遠(yuǎn)不匹配的默認(rèn)定義。如果 AbstractOperationMonitor 的子方面不需要監(jiān)視類控制器,那么它不覆蓋這個(gè)切點(diǎn)就行了。如果它需要監(jiān)視類控制器,那么它就提供什么時(shí)候監(jiān)視一個(gè)點(diǎn)的定義。



          回頁(yè)首


          具體化操作監(jiān)視器

          methodSignatureControllerExec() 切點(diǎn)和關(guān)聯(lián)的建議類似:它們提供具體化操作監(jiān)視方面的方法,以根據(jù)連接點(diǎn)上的簽名匹配分派到不同方法的控制器。

          清單 3 展示了擴(kuò)展 AbstractOperationMonitor 以監(jiān)視 Struts 和 Spring MVC 操作的具體方面:


          清單 3. 監(jiān)視 Struts 和 Spring MVC 框架

          public aspect StrutsMonitor extends AbstractOperationMonitor {
          /**
          * Marker interface that allows explicitly _excluding_ classes
          * from this monitor: not used by default. If using Java? 5, an
          * annotation would be better.
          */
          public interface NotMonitoredAction {}

          /**
          * Matches execution of any method defined on a Struts action or
          * any subclass, which has signature of an action execute (or
          * perform) method, including methods dispatched to in a
          * DispatchAction or template methods with the same signature.
          */
          public pointcut actionMethodExec() :
          execution(public ActionForward Action+.*(ActionMapping,
          ActionForm, ServletRequest+, ServletResponse+)) &&
          !within(NotMonitoredAction);


          /**
          * Matches execution of an action execute (or perform) method for
          * a Struts action. Supports the Struts 1.0 API (using the perform
          * method) as well as the Struts 1.1 API (using the execute method).
          */
          public pointcut rootActionExec() :
          actionMethodExec() && (execution(* Action.execute(..)) ||
          execution(* Action.perform(..)));

          /** @Override */
          protected pointcut classControllerExec(Object controller) :
          rootActionExec() && this(controller);

          protected pointcut dispatchActionMethodExec() :
          actionMethodExec() && execution(* DispatchAction+.*(..));

          protected pointcut methodSignatureControllerExec(Object controller):
          dispatchActionMethodExec() && this(controller);
          }

          public aspect SpringMvcMonitor extends AbstractOperationMonitor {
          /**
          * marker interface that allows explicitly _excluding_ classes
          * from this monitor: not used by default
          */
          public interface NotMonitoredController {}

          public pointcut springControllerExec() :
          execution(public ModelAndView Controller+.*(HttpServletRequest,
          HttpServletResponse)) &&
          !within(NotMonitoredController+);


          protected pointcut classControllerExec(Object controller) :
          springControllerExec() && execution(* handleRequest(..)) &&
          this(controller);

          protected pointcut methodSignatureControllerExec(Object controller):
          springControllerExec() &&
          execution(* MultiActionController+.*(..)) && this(controller);
          }

          關(guān)于這兩個(gè)方面首先要注意的是它們非常簡(jiǎn)潔。它們只是擴(kuò)展了操作監(jiān)視器中的兩個(gè)切點(diǎn)以具體地監(jiān)視它們特定的 API。因?yàn)樗鼈兊暮?jiǎn)潔性,也可以在 XML 中定義這兩個(gè)具體工作監(jiān)視器,而不用編譯。關(guān)于 AspectJ 5 的這個(gè)功能的例子請(qǐng)參閱 清單 10

          關(guān)于 StrutsMonitor 和 SpringMvcMonitor

          清單 3 中的 StrutsMonitor 方面設(shè)計(jì)為同時(shí)使用老版本和新版本的 Struts API: Struts 1.0 操作是通過(guò)調(diào)用 perform() 而調(diào)用的,而 Struts 1.1 操作是通過(guò)調(diào)用 execute() 而調(diào)用的。我將正常操作類的具體子類作為 Class 控制器跟蹤:只有執(zhí)行對(duì)象的類才是關(guān)注的。不過(guò),如果一個(gè)類擴(kuò)展了 DispatchAction,Struts 可以讓控制器分派給這個(gè)類中的多個(gè)方法。我通過(guò)匹配 DispatchAction 子類中所有具有 Struts 操作簽名的方法監(jiān)視在這些分派操作中執(zhí)行的各個(gè)方法。這種方式使我可以分別跟蹤每一個(gè)不同控制器方法的統(tǒng)計(jì)。

          我用 iBatis JPetStore 1.3 示例應(yīng)用程序(請(qǐng)參閱 參考資料) 測(cè)試了 StrutsMonitor。與許多應(yīng)用程序一樣,它用自己的一個(gè)小框架擴(kuò)展了 Struts:公共基本操作有一個(gè)名為 perform() 的方法,它向 helper 分派用 doPerform() 作為模板方法的操作。不過(guò),不需要跟蹤這些模板方法的執(zhí)行:類級(jí)別的控制器會(huì)識(shí)別在 execute() 方法中執(zhí)行的 Action 的特定子類,這足以區(qū)分它們了。

          SpringMvcMonitor 方面與 StrutsMonitor 有一點(diǎn)很類似,它們將所有 Controller 對(duì)象作為類控制器,監(jiān)視它什么時(shí)候執(zhí)行 handleRequest()。它還監(jiān)視 MultiActionController 或者它的任何子類中具有 Spring 控制器方法簽名的公共方法的執(zhí)行。例如,我在這段代碼中分別監(jiān)視 welcomeHandler()ownerHandler() 的執(zhí)行:


          public class ClinicController extends MultiActionController
          implements InitializingBean {
          ...
          public ModelAndView welcomeHandler(HttpServletRequest request,
          HttpServletResponse response) throws ServletException {
          return new ModelAndView("welcomeView");
          }
          ...
          public ModelAndView ownerHandler(HttpServletRequest request,
          HttpServletResponse response) throws ServletException {
          Owner owner = clinic.loadOwner(
          RequestUtils.getIntParameter(request, "ownerId", 0));
          ...
          model.put("owner", owner);
          return new ModelAndView("ownerView", "model", model);
          }
          ...

          當(dāng)然,可以容易地?cái)U(kuò)展這種方法以處理其他的框架,包括自定義框架,甚至前面描述過(guò)的用 XML 定義的方面。

          編寫可移植的方面

          注意 StrutsMonitor 方面像我的大多數(shù)監(jiān)視方面一樣,設(shè)計(jì)為針對(duì)常見(jiàn)組件的多個(gè)版本。它的切點(diǎn)匹配不同版本的 Struts,它們可以引入那些在織入時(shí)(例如在裝載時(shí))還不可用的類型。只需在像字段簽名或者類型模式這樣的靜態(tài)可解析的上下文中使用類名就可得到這樣 的切點(diǎn)。如果在動(dòng)態(tài)上下文中(如 thistarget 或者 args)中使用類名,那么就要求織入器能夠訪問(wèn)類型。注意 Struts 監(jiān)視器將 this 切點(diǎn)指示符綁定到 Object 的一個(gè)實(shí)例,這不需要有 Struts JAR。雖然有些別扭(因?yàn)樗チ司幾g時(shí)類型檢查的好處),但是這種方法對(duì)于部署可重用方面時(shí)要求有許多依賴庫(kù)的情況更有利。

          不 過(guò),引用類或者訪問(wèn)其成員的(如通過(guò)調(diào)用一個(gè)方法)的建議或者其他可執(zhí)行代碼需要遵守 Java 代碼的常規(guī)可移植性規(guī)范。如果建議必須調(diào)用在 API 的不同版本中有變化的方法,那么可以使用 Java 反射檢查是否有不同的方法并執(zhí)行它們,例如,對(duì)一個(gè)操作方法顯式地調(diào)用 perform() 或者 execute()。注意,如果只想在一個(gè) around 建議中的連接點(diǎn)處使用原來(lái)的 execute() 或者 perform() 方法,那么 AspectJ 會(huì)替我處理這種情況,不需要做任何特別的工作。實(shí)際上,許多 API 都提供了向后兼容性,因此可以針對(duì)要支持的老版本編譯,它在新版本中也能工作。

          通 常,方面不會(huì)執(zhí)行觸發(fā)裝載一個(gè)類的代碼,除非程序的執(zhí)行總是會(huì)裝載這個(gè)類,例如,引用一個(gè)類的建議在這個(gè)類要執(zhí)行時(shí)會(huì)被觸發(fā)。保留這個(gè)屬性對(duì)于涉及可選類 的庫(kù)方面很重要。對(duì)于 Glassbox Inspector,我不想讓被監(jiān)視的應(yīng)用程序加入可能監(jiān)視的某個(gè)版本的 Struts 或者其他庫(kù)。

          監(jiān)視 JSP 和應(yīng)用程序名

          清單 4 展示了重構(gòu)后的本文第 1 部分中的 ServletMonitor,它使用抽象操作監(jiān)視器作為一個(gè)基本方面。我通過(guò)擴(kuò)展 classControllerExec() 繼續(xù)按類跟蹤 Servlet。增加了通過(guò)監(jiān)視 jspService() 方法對(duì)監(jiān)視 JSP 的支持。因?yàn)槲蚁胍_定 Web 應(yīng)用程序的名字,因此設(shè)置 ServletMonitor 覆蓋 getContext() 方法以檢查 Servlet 上下文的名字。

          我 到目前為止所實(shí)現(xiàn)的所有其他 Web 應(yīng)用程序最終是由 Servlet 調(diào)用的,因此只有這些控制器需要提供應(yīng)用程序上下文的一個(gè)實(shí)現(xiàn)。特別是,我的 Web 應(yīng)用程序框架監(jiān)視器是嵌套在它們的分派 Servlet 調(diào)用中的。在進(jìn)一步擴(kuò)展 Glassbox Inspector 以監(jiān)視其他操作(如收到的 Web 服務(wù)請(qǐng)求)時(shí),將需要提供操作名作為不同應(yīng)用程序的上下文。如果要擴(kuò)展框架以監(jiān)視收到的 JMS 消息或者 EJB 請(qǐng)求,那么這個(gè)框架還要為那些資源提供應(yīng)用程序上下文。


          清單 4. 擴(kuò)展 ServletMonitor


          public aspect ServletMonitor extends AbstractOperationMonitor {

          /**
          * Execution of any Servlet method: typically not overridden in
          * HttpServlets.
          */
          public pointcut ServletService(Servlet Servlet) :
          execution(void Servlet.service(..)) && this(Servlet);

          /** Execution of any Servlet request methods. */
          public pointcut httpServletDo(HttpServlet Servlet) :
          execution(void HttpServlet.do*(..)) && this(Servlet);

          /** Execution of any JSP page service method. */
          public pointcut jspService(JspPage page) :
          execution(* _jspService(..)) && this(page);

          protected pointcut classControllerExec(Object controller) :
          (ServletService(*) || httpServletDo(*) || jspService(*)) &&
          this(controller);

          /**
          * Create statistics for this object: looks up Servlet context to
          * determine application name.
          */
          protected String getContextName(Object controller) {
          Servlet Servlet = (Servlet)controller;
          return Servlet.getServletConfig().getServletContext().
          getServletContextName();
          }
          }



          回頁(yè)首


          更新 JMX 以得到嵌套的統(tǒng)計(jì)

          第 1 部分中,我擴(kuò)展了 Glassbox 監(jiān)視基礎(chǔ)架構(gòu)以提供嵌套的統(tǒng)計(jì),如 Servlet 請(qǐng)求的連接中的 JDBC 語(yǔ)句。在這里,我分析了如何更新 StatsJmxManagement 方面以提供這些嵌套統(tǒng)計(jì)的復(fù)合名。我還展示如何將應(yīng)用程序名加入到這個(gè)復(fù)合名中。例如,數(shù)據(jù)庫(kù)語(yǔ)句的統(tǒng)計(jì)可以用以下字符串命名:


          application=Spring Petclinic,operation=org.springframework.samples.
          petclinic.web.ClinicController,database=jdbc:hsqldb:hsql://localhost:
          9001,statement=SELECT id;name from types ORDER BY name

          這個(gè)字符串使用給定信息的所有前級(jí)統(tǒng)計(jì)的性能統(tǒng)計(jì)的描述和名字。以這種方式命名統(tǒng)計(jì)使 JMX 工具可以自然地將相關(guān)的信息組織并顯示在一起。像 JConsole 這樣的 JMX 工具使用結(jié)構(gòu)化的名字來(lái)組織常見(jiàn)的元素,更容易以層次化的方法瀏覽,如 圖 1 所示。清單 5 展示了為支持這個(gè)功能需要對(duì) StatsJmxManagement 進(jìn)行的更新;


          清單 5. 更新 StatsJmxManagement 以支持嵌套的統(tǒng)計(jì)

          public aspect StatsJmxManagement {

          private String PerfStats.cachedOperationName;

          /** JMX operation name for this performance statistics bean. */
          public String PerfStats.getOperationName() {
          // look up from cache
          // else
          ...
          appendOperationName(buffer);
          ...
          return operationName;
          }

          /** Add bean's JMX operation name to a StringBuffer. */
          public void PerfStats.appendOperationName(StringBuffer buffer) {
          if (cachedOperationName != null) {
          buffer.append(cachedOperationName);
          } else {
          aspectOf().nameStrategy.appendOperationName(this, buffer);
          }
          }

          public void PerfStats.appendName(StringBuffer buffer) {
          // append the appropriate name & JMX encode
          ...
          }

          public StatsJmxNameStrategy getNameStrategy() {...}

          public void setNameStrategy(StatsJmxNameStrategy nameStrategy) { ... }

          private StatsJmxNameStrategy nameStrategy;
          }

          public interface StatsJmxNameStrategy {
          void appendOperationName(PerfStats stats, StringBuffer buffer);
          }

          public class GuiFriendlyStatsJmxNameStrategy extends
          AbstractStatsJmxNameStrategy {

          public void appendOperationName(PerfStats stats,
          StringBuffer buffer) {
          PerfStats parent = stats.getParent();
          if (parent != null) {
          appendOperationName(parent, buffer);
          buffer.append(',');
          else {
          if (stats instanceof OperationStats) {
          OperationStats opStats = (OperationStats)stats;
          String contextName = opStats.getContextName();
          if (contextName != null) {
          buffer.append("application=\"");
          int pos = buffer.length();
          buffer.append(contextName);
          JmxManagement.jmxEncode(buffer, pos);
          buffer.append("\",");
          }
          }
          }
          buffer.append(stats.getDescription());
          buffer.append('=');
          stats.appendName(buffer);
          }
          }

          清單 5 展示了我如何設(shè)置 StatsJmxManagement 以允許不同的命名策略。為了使用像 JConsole 這樣的 GUI 工具,我列出了一個(gè) GuiFriendlyStatsJmxNameStrategy,它返回如清單 5 和圖 1 所示的名字。除此之外,完整的代碼包括另一個(gè)可選的 CanonicalStatsJmxNameStrategy,它遵守 JMX 建議的 MBean 命名方式,但是在我所試過(guò)的所有 JMX 工具中不能顯示。

          用于構(gòu)建這些名字的基本方法是,創(chuàng)建一個(gè)字符串緩沖區(qū)并遞歸地得到父統(tǒng)計(jì)的操作名,然后在后面加上這個(gè)名字。對(duì)于頂級(jí)操作統(tǒng)計(jì),首先加入上下文(應(yīng)用程序)名。為了支持這種方法,我在 PerfStats 中增加了 getDescription() 方法。還更新了操作和資源統(tǒng)計(jì)實(shí)現(xiàn)以設(shè)置合適的值(如 operation, operation1, .., resource, and request)。下載 Glassbox Inspector 源代碼以查看所有細(xì)節(jié)和另一個(gè)可選的 CanonicalStatsJmxNameStrategy



          回頁(yè)首


          監(jiān)視 Web 服務(wù)調(diào)用

          Glassbox Inspector 框架現(xiàn)在很容易擴(kuò)展以支持新類型的請(qǐng)求(操作)以及新資源。監(jiān)視提供的(收到的)遠(yuǎn)程服務(wù)操作與監(jiān)視 Web 應(yīng)用程序框架很相似:本文的源代碼給出了監(jiān)視用 Apache Axis 實(shí)現(xiàn)的服務(wù)的一個(gè)例子。

          只 需要很少的調(diào)整就可以將這個(gè) Glassbox Inspector 擴(kuò)展為監(jiān)視應(yīng)用程序之外的遠(yuǎn)程服務(wù)的使用。隨著具有面向服務(wù)體系結(jié)構(gòu)的應(yīng)用程序越來(lái)越常見(jiàn),跟蹤外部控制的服務(wù)的可靠性及確定應(yīng)用程序問(wèn)題的相互關(guān)系越來(lái) 越重要了。將這些數(shù)據(jù)與應(yīng)用程序上下文(如執(zhí)行的 Web 操作和在其他資源上花費(fèi)的時(shí)間)相關(guān)聯(lián)是迅速找出應(yīng)用程序故障根源的重要方法。這種分析與使用 SOAP 處理程序孤立地監(jiān)視 Web 服務(wù)調(diào)用有很大不同,因?yàn)樗鼘?shí)現(xiàn)信息與外部請(qǐng)求的性能連接到一起了。

          清單 6 展示了一個(gè)遠(yuǎn)程調(diào)用監(jiān)視器,它監(jiān)視通過(guò) JAX-RPC API 對(duì) Web 服務(wù)的調(diào)用以及 RMI 調(diào)用(它本身通常是遠(yuǎn)程的):


          清單 6. 監(jiān)視遠(yuǎn)程調(diào)用


          public aspect RemoteCallMonitor extends AbstractResourceMonitor {
          /** Call to remote proxy: RMI or JAX-RPC */
          public pointcut remoteProxyCall(Object recipient) :
          call(public * Remote+.*(..) throws RemoteException) &&
          target(recipient) && !within(glassbox.inspector..*);

          /** Monitor remote proxy calls based on called class and method */
          Object around(final Object recipient) :
          remoteProxyCall(recipient) {
          RequestContext requestContext = new ResourceRequestContext() {

          public Object doExecute() {
          return proceed(recipient);
          }

          public PerfStats lookupStats() {
          String key = "jaxrpc:"+recipient.getClass().getName()+
          "."+thisJoinPointStaticPart.getSignature().getName();
          key = key.intern();

          return lookupResourceStats(key);
          }
          };
          return requestContext.execute();
          }
          }

          注意,只需要做很少的工作就可以支持這種功能,大多數(shù)支持包含在 AbstractRequestMonitor 中。我只是定義遠(yuǎn)程代理調(diào)用為對(duì)任何實(shí)現(xiàn)了 Remote 接口的公共方法的調(diào)用,這個(gè)方法可以拋出 RemoteException。完成這個(gè)小小的改變后,就可以使用 Worker Object 模式(請(qǐng)參閱 第 1 部分)監(jiān)視遠(yuǎn)程調(diào)用,提供一個(gè)以類名和被調(diào)用的對(duì)象的方法名為基礎(chǔ)的名字。RemoteCallMonitor 方面使用一個(gè) helper 方法尋找頂級(jí)資源統(tǒng)計(jì),它是從 JdbcConnectionMonitor 中的工人對(duì)象中提取的,放入這個(gè)新的公共基本方面:AbstractResourceMonitor

          自 然,可以進(jìn)一步擴(kuò)展這種方法以監(jiān)視其他 Web 服務(wù)調(diào)用以及流行框架(如 Apache Axis)上方法的執(zhí)行。我還可以使用這種技術(shù)監(jiān)視 XML 處理所花費(fèi)的時(shí)間,這對(duì)于任何有大量 XML 的應(yīng)用程序是很有用的。許多利用 Web 服務(wù)的應(yīng)用程序也可受益于這種監(jiān)視。



          回頁(yè)首


          監(jiān)視故障

          合乎邏輯的下一步是擴(kuò)展 Glassbox Inspector 系統(tǒng)以監(jiān)視應(yīng)用程序故障。我首先在系統(tǒng)退出一個(gè)監(jiān)視點(diǎn)時(shí)通過(guò)拋出一個(gè)異常(更準(zhǔn)確地說(shuō)是任何 Throwable)來(lái)記錄故障。故障記錄為跟蹤 Web 操作和數(shù)據(jù)庫(kù)連接工作提供了有用的信息:這些操作中的任何一個(gè)拋出 Throwable 在幾乎任何情況下都表示有問(wèn)題。對(duì)于 JDBC 語(yǔ)句和其他資源訪問(wèn),throwable 通常表明一個(gè)真正的應(yīng)用程序故障,但是在有些情況下,它是正常程序邏輯的一部分。例如,試圖用一個(gè)已經(jīng)有的名字注冊(cè)會(huì)在插入時(shí)觸發(fā)異常。為了照顧到這種情 況,我對(duì)每個(gè)監(jiān)視使用了一個(gè)可配置的策略,以確定給定的 throwable 是真正的錯(cuò)誤還是作為正常的退出條件接受。

          清單 7 包含了使我可以配置 AbstractRequestMonitor 以確定 Throwable 是否為故障的建議和改動(dòng):


          清單 7. 增加可擴(kuò)展的故障檢測(cè)

          public aspect AbstractRequestMonitor {

          /**
          * Record an error if this request exited by throwing a
          * Throwable.
          */
          after(RequestContext requestContext) throwing (Throwable t) :
          requestExecution(requestContext) {
          PerfStats stats = requestContext.getStats();
          if (stats != null) {
          if (failureDetectionStrategy.isFailure(t,
          thisJoinPointStaticPart)) {
          requestContext.recordFailure(t);
          } else {
          requestContext.recordExecution();
          }
          }
          }
          public void setFailureDetectionStrategy(...) { ... }
          public FailureDetectionStrategy getFailureDetectionStrategy() { ... }
          protected FailureDetectionStrategy failureDetectionStrategy =
          new FailureDetectionStrategy() {
          public boolean isFailure(Throwable t, StaticPart staticPart) {
          return true;
          }
          };
          ...
          }

          public interface FailureDetectionStrategy {
          boolean isFailure(Throwable t, StaticPart staticPart);
          }

          清單 7 中最后的效果是,在通過(guò)拋出異常退出一個(gè)監(jiān)視的連接點(diǎn)時(shí)記錄一個(gè)計(jì)數(shù)。注意,我肯定是要識(shí)別那些通過(guò)拋出 Error 退出的情況,因?yàn)樗鼈儙缀蹩隙ㄊ枪收希≡谶@里,可以容易地?cái)U(kuò)展這個(gè) AbstractRequestMonitor 以跟蹤部分或者所有異常、堆棧蹤跡和在發(fā)生異常時(shí)當(dāng)前對(duì)象和參數(shù)值。如果這樣做了,那么就需要有一種保證不記錄敏感信息,如密碼、信用卡號(hào)或者個(gè)人身份信息的方法。為了完成這種集成,我對(duì) AbstractRequestMonitor 做了小小的修改。細(xì)節(jié)請(qǐng)參閱 文章源代碼

          對(duì)錯(cuò)誤使用異常轉(zhuǎn)換

          基于前面的討論,您可能奇怪如何能配置 Glassbox Inspector 以識(shí)別出某些 throwable 不是錯(cuò)誤。最容易的方法是傳遞一個(gè)對(duì)于給定監(jiān)視方法(如 JdbcStatementMonitor)總是返回 false 的策略。另一種簡(jiǎn)單的解決方案是檢測(cè)某種特定的異常類型,如 NumberFormatException,并指明它不是故障。這可以結(jié)合 Exception Conversion 模式(請(qǐng)參閱 參考資料)以將某些異常轉(zhuǎn)換為有意義的層次結(jié)構(gòu)。清單 8 就是如何使用異常轉(zhuǎn)換以及故障檢測(cè)策略的一個(gè)示例:


          清單 8. 跟蹤故障并使用異常轉(zhuǎn)換

          /**
          * Softens and uses Spring's exception translator to convert from
          * SQLException to unchecked, meaningful exception types
          */
          aspect DataAccessErrorHandling {
          declare soft: SQLException: jdbcCall();

          after() throwing (SQLException e) : jdbcCall() {
          throw sqlExceptionTranslator.translate("dbcall", getSql(), e);
          }

          declare precedence: ModelErrorHandling, JdbcStatementMonitor;
          }

          /**
          * Conservative detection strategy: failures from SQL that often
          * indicate valid business conditions are not flagged as failures.
          */
          public class DataAccessDetectionStrategy implements FailureDetectionStrategy {
          public boolean isFailure(Throwable t, StaticPart staticPart) {
          return !(t instanceof ConcurrencyFailureException) &&
          !(t instanceof DataIntegrityViolationException);
          }
          }

          清單 8 的第一部分顯示了一個(gè)簡(jiǎn)單的錯(cuò)誤處理方面,它軟化了來(lái)自 JDBC 調(diào)用的 SQLExceptions,然后使用一個(gè) Spring 框架 SQLExceptionTranslator(如 SQLErrorCodeSQLExceptionTranslator)將異常轉(zhuǎn)換為有意義的(unchecked)異常。這個(gè)方面還用 AspectJ 的 declare precedence 格式聲明優(yōu)于 JdbcStatementMonitor。這保證了在 JdbcStatementMonitor 跟蹤返回的異常類型之前,DataAccessErrorHandling 已經(jīng)轉(zhuǎn)換了異常。

          清單 8 的其他部分展示了一個(gè)示例策略,它只在幾乎確實(shí)表明有故障(如無(wú)法訪問(wèn)資源)的條件下表明故障。特別是,它排除了并發(fā)錯(cuò)誤(在設(shè)計(jì)良好的、多用戶應(yīng)用程序中會(huì)出現(xiàn))和數(shù)據(jù)完整性破壞(如通常在注冊(cè)新用戶時(shí)發(fā)生)的情況。

          要對(duì)故障檢測(cè)有更多的控制,可能要有一個(gè)標(biāo)志性接口或者一個(gè)方法或者類上的注釋,以表明在正常的操作過(guò)程中,系統(tǒng)的某些部分會(huì)拋出 Exception。這可結(jié)合使用 declare parents 或者 AspectJ 5 新的 declare annotation 格式捕獲模塊化規(guī)則,表明拋出一個(gè) Throwable 是否表明故障。在大多數(shù)情況下,我使用的簡(jiǎn)單規(guī)則對(duì)于粗粒度的監(jiān)視是合適的,但是要避免不正確的警報(bào),最好有更多的靈活性。

          檢測(cè)常見(jiàn) Web 故障

          應(yīng)用程序有可能在出現(xiàn)故障時(shí)并不拋出異常。一種最常見(jiàn)的情況是當(dāng)控制器處理異常并將請(qǐng)求轉(zhuǎn)發(fā)給一個(gè)錯(cuò)誤頁(yè)時(shí)。另一種情況是當(dāng)對(duì)于頁(yè)的 HTTP 響應(yīng)指明一個(gè)錯(cuò)誤時(shí)。清單 9 更新了清單 4 中的 ServletMonitor 以檢測(cè)這種情況。我還對(duì) AbstractRequestMonitor.RequestContext 工人對(duì)象做了小小的修改以設(shè)置錯(cuò)誤上下文,并在設(shè)置錯(cuò)誤上下文后記錄故障。


          清單 9. 檢測(cè)常見(jiàn) Web 故障

          /** Call of send error in the Servlet API */
          public pointcut sendingErrorResponse(HttpServletResponse response,
          int sc) :
          target(response) && call(* sendError(..)) && args(sc, ..);
          /** Track a failure after sending an error response */
          after(HttpServletResponse response, RequestContext requestContext,
          int sc) returning :
          sendingErrorResponse(response, sc) && inRequest(requestContext) {
          requestContext.setErrorContext(new Integer(sc));
          }

          /** Execute handle page exception in the JSP API */
          public pointcut handlePageException(PageContext pageContext,
          Throwable t) :
          call(* PageContext.handlePageException(*)) &&
          target(pageContext) && args(t);

          /** Track a failure when showing an error page */
          after(PageContext pageContext, RequestContext requestContext,
          Throwable t) returning :
          handlePageException(pageContext, t) && inRequest(requestContext) {
          requestContext.setErrorContext(t);
          }



          回頁(yè)首


          擴(kuò)展庫(kù)方面

          有些時(shí)候,當(dāng)我構(gòu)建 Glassbox Inspector 時(shí),需要讓庫(kù)方面更有可擴(kuò)展性。在下面列出了一些可用的常見(jiàn)選項(xiàng)。其中一些選項(xiàng)我已經(jīng)展示過(guò)了,它們都很有用:

          • 提供一個(gè)抽象切點(diǎn)以定義方面用在什么地方(例如,一個(gè) scope() 定義)。

          • 提供一個(gè)空的切點(diǎn),必須為要使用的某個(gè)建議覆蓋它(如在 清單 2 中的 AbstractOperationMonitor)。在許多情況下,少數(shù)的具體方面需要使用給定的建議,因此有一個(gè)空的默認(rèn)值更有用。像這樣的模板切點(diǎn)有一個(gè)空的默認(rèn)實(shí)現(xiàn)與模板方面有一個(gè)空的默認(rèn)實(shí)現(xiàn)很相像。

          • 利用標(biāo)志性接口表明方面應(yīng)用于哪些類型。因?yàn)榻涌谑抢^承的,所以這些類型的所有子類型都包括在內(nèi)了。

          • 利用 Java 5 注釋表明方面應(yīng)用于哪些類型和/或方法。這種方法使您可以細(xì)化對(duì)方面應(yīng)用領(lǐng)域的控制,可以按方法定義,以及包括類而不包括它的子類。

          • 提供運(yùn)行時(shí)行為配置,有測(cè)試一種功能是否啟用的邏輯。例子請(qǐng)參閱 運(yùn)行時(shí)控制

          在 應(yīng)用程序中集成了這些擴(kuò)展后,我就完成了對(duì) Glassbox Inspector 監(jiān)視的擴(kuò)展(至少對(duì)于本文的目的 —— 本文結(jié)束的地方就是開(kāi)放源碼項(xiàng)目開(kāi)始的地方!)。我現(xiàn)在有了一個(gè)將應(yīng)用程序性能按照邏輯組織到一起的有用視圖,這樣就可以通過(guò) web 應(yīng)用程序中的請(qǐng)求用標(biāo)準(zhǔn) JMX 客戶機(jī)觀察總體性能統(tǒng)計(jì)和錯(cuò)誤。對(duì)于有疑問(wèn)的地方,我還可以深挖數(shù)據(jù)庫(kù)或者 Web 服務(wù)的性能和可靠性。圖 1 顯示了在 Spring Petclinic、iBatis JPetstore 和 Duke 的 Bookstore Web 應(yīng)用程序例子中使用的 Glassbox Inspector 監(jiān)視工具:


          圖 1. 使用中的完整監(jiān)視基礎(chǔ)架構(gòu)的快照



          回頁(yè)首


          錯(cuò)誤處理

          在這之后,我將重點(diǎn)放到如何更容易地部署擴(kuò)展的 Glassbox Inspector 監(jiān)視基礎(chǔ)架構(gòu)上。邁向用于企業(yè)管理的基礎(chǔ)架構(gòu)的第一步是擴(kuò)展 Glassbox Inspector 來(lái)實(shí)現(xiàn)錯(cuò)誤隔離。傳統(tǒng)上,開(kāi)發(fā)人員盡可能保證像性能監(jiān)視這樣的功能不會(huì)產(chǎn)生 影響被監(jiān)視應(yīng)用程序的錯(cuò)誤。不過(guò),監(jiān)視代碼中未預(yù)料到的行為(如未預(yù)計(jì)到的 null 值、拋出異常的代理等)有時(shí)會(huì)在監(jiān)視代碼自身中產(chǎn)生錯(cuò)誤。在這些情況下,隔離 錯(cuò)誤的能力可提高監(jiān)視實(shí)現(xiàn)的健壯性。系統(tǒng)初始化時(shí)出現(xiàn)的問(wèn)題可能造成錯(cuò)誤隔離方面不能緩沖的錯(cuò)誤,但是這種問(wèn)題通常是立即發(fā)生的,并容易在開(kāi)發(fā)時(shí)捕獲,使得它的風(fēng)險(xiǎn)降低了很多。

          我的基本錯(cuò)誤隔離策略是,捕獲并處理所有可能從建議執(zhí)行連接點(diǎn)拋出的 Throwable,這樣它們就不會(huì)影響底層的應(yīng)用程序。注意,建議執(zhí)行切點(diǎn)匹配一些建議執(zhí)行的連接點(diǎn),因此這個(gè)方面將影響其他方面的行為。這種策略對(duì)于 before 和 after 建議都可以很好地工作,但是 around 建議實(shí)現(xiàn)錯(cuò)誤隔離要復(fù)雜一點(diǎn)兒。如果在達(dá)到 proceed 語(yǔ)句之前拋出異常,那么我會(huì)仍然繼續(xù)原來(lái)的連接點(diǎn)。相反,如果異常是在 proceed 語(yǔ)句 拋出的,那么我會(huì)讓異常通過(guò)而不會(huì)“隔離”它(即不吞掉它)。

          因?yàn)樵诮ㄗh中沒(méi)有調(diào)用 proceed 的連接點(diǎn),因此不能編寫像我需要的那樣完全一般化的建議。不過(guò),可以用 Glassbox Inspector 的結(jié)構(gòu)提供它所使用的那種 around 建議的錯(cuò)誤隔離。在 Glassbox Inspector 中惟一的 around 建議總是構(gòu)造一個(gè)工人對(duì)象,然后對(duì)它調(diào)用 execute() 模板方法,這個(gè)方法調(diào)用 doExecute() 以完成原來(lái)的連接點(diǎn)。因此我將處理由監(jiān)視器中(AbstractRequestMonitor 或者它的任何子類)的 helper 方法或者由在 execute 或者 doExecute() 中的調(diào)用返回的所有異常。清單 10 顯示了我是如何擴(kuò)展 Glassbox Inspector 以處理錯(cuò)誤的:


          清單 10. Glassbox Inspector 中的錯(cuò)誤處理

          public aspect ErrorHandling {
          public pointcut scope() :
          within(glassbox.inspector..*) && !within(ErrorHandling+);

          public pointcut inMonitor() :
          within(AbstractRequestMonitor+) || within(RequestContext+);

          public pointcut voidReturnAdviceExecution() :
          adviceexecution() &&
          if(((AdviceSignature)thisJoinPointStaticPart.getSignature()).
          getReturnType() == void.class);

          protected pointcut monitorHelperExec() :
          inMonitor() && execution(* *(..)) &&
          !execution(* execute(..)) && !execution(* doExecute(..));

          protected pointcut monitorExecuteCall() :
          (inMonitor() && withincode(* execute(..)) ||
          withincode(* doExecute(..))) &&
          (call(* *(..)) && !call(* doExecute(..)) || call(new(..)));

          public pointcut handlingScope() :
          scope() &&
          voidReturnAdviceExecution() || monitorHelperExec() || monitorExecuteCall();

          /**
          * This advice ensures that errors in the monitoring code will not
          * poison the underlying application code.
          */
          Object around() : handlingScope() {
          try {
          return proceed();
          } catch (Throwable e) {
          handleError(e, thisJoinPointStaticPart);
          return null;
          }
          }

          public synchronized void handleError(Throwable t,
          StaticPart joinPointStaticPart) {
          // log 1 in 1000 but don't rethrow
          ...
          }

          /**
          * Count of errors: a weak map so we don't leak memory after apps
          * have been disposed of.
          */
          private Map/* <JoinPoint.StaticPart, MutableInteger> */errorCount =
          new WeakHashMap();

          ...
          }

          關(guān)于錯(cuò)誤隔離層

          從清單 10 中可以看到我首先定義了范圍的切點(diǎn),以限定 ErrorHandling 中的建議只應(yīng)用于 Glassbox Inspector 中的代碼,而不應(yīng)用到 ErrorHandling 方面本身。然后,我定義了 inMonitor() 切點(diǎn)以定義詞句上處于請(qǐng)求監(jiān)視器中的代碼(包括所有工人對(duì)象)。我定義了 voidReturnAdviceExecution() 切點(diǎn),通過(guò)對(duì)建議執(zhí)行的簽名使用一個(gè)基于 if 的測(cè)試以匹配建議執(zhí)行。before 和 after 建議總是返回 void,而 Glassbox Inspector around 建議不會(huì)這樣做。對(duì)于我的系統(tǒng),這相當(dāng)于匹配 before 和 after 建議而不是 around 建議的建議執(zhí)行。

          下一步,我定義 monitorHelperExec() 以匹配監(jiān)視器中 helper 方法的執(zhí)行(即所有不是 execute() 或者 doExecute() 的調(diào)用)。我定義了 monitorExecuteCalls() 為監(jiān)視器中所有在 execute() 或者 doExecute() 方法中的調(diào)用,而不是對(duì) doExecute() 本身的調(diào)用。這些切點(diǎn)結(jié)合在 handlingScope() 中以定義我要處理異常的連接點(diǎn)。

          最終的效果是,在所有 before 或者 after 建議和監(jiān)視 around 建議處處理異常以避免錯(cuò)誤影響應(yīng)用程序代碼,但是又不會(huì)吞掉應(yīng)用程序異常。為了處理異常,我捕獲所有由進(jìn)程拋出的 Throwable,然后委派給 helper 方法以在第一次遇到時(shí)記錄它們。然后建議返回,并且不拋出異常。Glassbox Inspector 還使用 AspectJ 的 declare soft 格式向編譯器表明它在建議中處理了 checked 異常。

          總之,我可以創(chuàng)建一個(gè)非常有效的錯(cuò)誤隔離層。在 Glassbox Inspector 中增加這一功能所需要的修改開(kāi)銷很小,從后面的性能測(cè)量中就可以看出來(lái)。



          回頁(yè)首


          運(yùn)行時(shí)控制

          構(gòu) 建了一個(gè)有用的監(jiān)視基礎(chǔ)架構(gòu)后,現(xiàn)在我想在不重新啟動(dòng)服務(wù)器的條件下增加對(duì)所運(yùn)行的監(jiān)視的運(yùn)行時(shí)控制。運(yùn)行時(shí)控制基礎(chǔ)架構(gòu)還讓 Glassbox Inspector 的用戶增加開(kāi)銷更大的監(jiān)視器以在系統(tǒng)出問(wèn)題時(shí)捕獲更詳細(xì)的數(shù)據(jù)。可以在運(yùn)行時(shí)動(dòng)態(tài)地啟用或者禁止整個(gè)監(jiān)視,也可以通過(guò)增加一個(gè) if 測(cè)試來(lái)檢查每一個(gè)建議的切點(diǎn)的簡(jiǎn)單標(biāo)志,從而分別啟用或者禁止個(gè)別建議。為了提供控制的常用框架,我使用清單 11 中的表達(dá)來(lái)啟用和禁止每一個(gè)方面的建議:


          清單 11. 在運(yùn)行時(shí)啟用和禁止建議


          public aspect AbstractRequestMonitor {
          ...
          protected pointcut scope() : if(true);
          protected pointcut monitorEnabled() : isMonitorEnabled() && scope();
          protected abstract pointcut isMonitorEnabled();

          public boolean isEnabled() {
          return enabled;
          }
          public void setEnabled(boolean enabled) {
          this.enabled = enabled;
          }
          protected boolean enabled = true;
          }

          public aspect JdbcStatementMonitor extends AbstractResourceMonitor {
          ...
          /** Monitor performance for executing a JDBC statement. */
          Object around(final Statement statement) :
          statementExec(statement) && monitorEnabled() {
          ...
          }
          ...
          protected pointcut isMonitorEnabled() : if(aspectOf().isEnabled());
          }

          很自然地,我使用 JMX 提供必要的運(yùn)行時(shí)控制,如清單 12 所示:


          清單 12. 啟用監(jiān)視器的 JMX 管理

          public aspect MonitorJmxManagement {
          /**
          * Management interface for monitors allows enabling and disabling
          * at runtime.
          */
          public interface RequestMonitorMBean extends ManagedBean {
          public boolean isEnabled();
          public void setEnabled(boolean enabled);
          }

          /**
          * Make the {@link AbstractRequestMonitor} aspect implement
          * {@link RequestMonitorMBean}, so all instances can be managed
          */
          declare parents: AbstractRequestMonitor implements MonitorMBean;

          public String AbstractRequestMonitor.getOperationName() {
          return "control=monitor,type="+getClass().getName();
          }

          public MBeanInfoAssembler AbstractRequestMonitor.getAssembler() {
          if (assembler == null) {
          initAssembler();
          }
          return assembler;
          }
          ...
          }

          MonitorJmxManagement 方面定義了監(jiān)視器的管理接口,它只包括一個(gè)屬性 enabled。 通過(guò) JMX 管理監(jiān)視器所需要的其他支持來(lái)自在 第一部分 中討論的 JmxManagement 方面。我還將一些常用代碼從 StatsJmxManagement 中轉(zhuǎn)移到這個(gè)共享類中。圖 2 顯示了用 JMX 在運(yùn)行時(shí)控制監(jiān)視的例子:


          圖 2. 在運(yùn)行時(shí)控制監(jiān)視



          回頁(yè)首


          部署 Glassbox Inspector

          到 此為止,我已經(jīng)可以為應(yīng)用程序監(jiān)視部署這個(gè) Glassbox Inspector 了。我讓它盡可能只應(yīng)用到應(yīng)用程序代碼上。不過(guò),在許多情況下,將方面應(yīng)用到庫(kù)代碼是有利的。對(duì)于我工作的 Petclinic 和 JPetstore 示例應(yīng)用程序來(lái)說(shuō),JDBC 訪問(wèn)是由 Spring 和 iBatis 庫(kù)代碼分別處理的,在 Petclinic 中,代碼控制器邏輯是由 Spring 庫(kù)處理的。因此讓監(jiān)視方面影響應(yīng)用程序所使用的一些庫(kù)代碼是有好處的。有時(shí),可以通過(guò)對(duì)來(lái)自應(yīng)用程序代碼的調(diào)用進(jìn)行建議,或者在可以被建議的應(yīng)用程序代碼 中增加顯式鉤子(如繼承在庫(kù)類中定義的方法),從而避免對(duì)庫(kù)代碼的執(zhí)行進(jìn)行建議。

          我可以選擇兩種基本方法將監(jiān)視織入到應(yīng)用程序中:在編譯時(shí)或者在裝載時(shí)。如果使用編譯時(shí)織入,那么處理二進(jìn)制 jar(例如,使用 ajc -inpath 命令行工具進(jìn)行織入)容易得多,不用從源代碼重新編譯庫(kù)。我在過(guò)去使用這種方法,它對(duì)應(yīng)用程序啟動(dòng)提供了最好的性能,并且只有很少的內(nèi)存開(kāi)銷。不過(guò),它會(huì) 顯著增加編譯環(huán)境的復(fù)雜性。一個(gè)問(wèn)題是要求 AspectJ 織入器能夠解析對(duì)包含在被織入的 jar 中的第三方類的引用。對(duì)于 Spring,這意味著包括 Hibernate、多種開(kāi)放源碼緩沖管理器和 Servlet API 這些 JAR。

          VM 中的 AOP 支持

          BEA JRockIt 最近提供了一種原型 Java VM,它支持方面。這種集成帶來(lái)了 AOP 的高性能和后綁定(late-binding)支持,這是這兩個(gè)方面最好的事情。我期待著這種技術(shù)的成熟和其他 Java VM 創(chuàng)造者的貢獻(xiàn)。

          對(duì)于 AspectJ 5 的另一種可行的方法是裝載時(shí)織入。使用這種方法可以分別編譯監(jiān)視方面,并加入一個(gè)小的部署描述符以定義這個(gè)方面用在什么地方。

          裝載時(shí)織入

          清 單 13 展示了部署描述符 aop.xml 的一個(gè)例子,它描述了裝載時(shí)織入應(yīng)用在什么地方。AspectJ 5 裝載時(shí)織入代理在系統(tǒng)內(nèi)所有類 ClassLoader 都可訪問(wèn)的一個(gè)目錄或者 jar 中尋找所有 META-INF/aop.xml 資源。這些方面可以被任何 Java ClassLoader 代理(如 Java 5 JVMTI -javaagent、JRockIt JVM 代理或者甚至 WebSphere 或者 WebLogic ClassLoader 插件程序)自動(dòng)裝載,以將方面應(yīng)用到系統(tǒng)中所有代碼或者部分代碼上。這使我可以對(duì)一個(gè)應(yīng)用程序或者一個(gè)服務(wù)器進(jìn)行監(jiān)視,而不用事先建立它,也不用改變編譯過(guò)程。

          注 意(例如)Spring 框架中引用不在類路徑中的類的代碼永遠(yuǎn)也不裝載,因此這種方法不需要增加額外的 jar。在 2005 年 11 月撰寫本文的時(shí)候,AspectJ 5 Milestone 4 已經(jīng)發(fā)布,AspectJ 5 的最終版本預(yù)計(jì)在年底發(fā)布。裝載時(shí)織入在保持大型系統(tǒng)中的模塊獨(dú)立性方面非常有用,它為方面提供了 Java 類裝載為對(duì)象提供的同樣的運(yùn)行時(shí)解決方案。


          清單 13. 裝載時(shí)織入配置

          <aspectj>
          <weaver>
          <exclude within="org.springframework.jmx..*"/>
          <!-- don't reweave -->
          <exclude within="glassbox.inspector..*"/>
          </weaver>
          <aspects>
          <aspect
          name="glassbox.inspector.monitor.operation.AxisOperationMonitor"/>
          <aspect
          name="glassbox.inspector.monitor.operation.SpringMvcMonitor"/>
          <aspect
          ...
          <aspect
          name="glassbox.inspector.monitor.resource.JdbcStatementMonitor"/>
          <concrete-aspect
          name="glassbox.inspector.monitor.operation.CustomMvcMonitor "
          extends="glassbox.inspector.monitor.operation.TemplOperationMonitor">
          <pointcut name="classControllerExecTarget"
          expression="execution(* com.ibatis.struts.BaseBean..*(..))
          && cflow(execution(* com.ibatis.struts.BeanAction.execute(..)))"/>
          </concrete-aspect>
          </aspects>
          </aspectj>

          清單 3 中的 aop.xml 文件首先定義 AspectJ 織入器在什么地方應(yīng)該和不應(yīng)該應(yīng)用方面。在默認(rèn)情況下,它影響所有通過(guò) ClassLoader 裝載的代碼。我排除了系統(tǒng)的某些部分,在那些地方不需要裝載時(shí)織入以加快啟動(dòng)時(shí)間。

          AOP 配置文件然后列出要使用的所有方面。目前,這個(gè)方面清單必須手工維護(hù),這通常是容易出錯(cuò)的地方。幸運(yùn)的是,AspectJ 5 的最終版本計(jì)劃包含在編譯方面時(shí)生成這些清單的工具。

          最后注意,這個(gè)示例文件包括一個(gè) XML 定義的方面 CustomMVCMonitor。 這個(gè)方面擴(kuò)展了一個(gè)抽象方面以監(jiān)視一個(gè)特定于應(yīng)用程序的框架。基本上,它只定義了一個(gè)針對(duì)要監(jiān)視的操作的切點(diǎn)。這表明了如何在不編譯任何代碼的情況下擴(kuò)展 Glassbox Inspector 以監(jiān)視自定義代碼。還可以通過(guò)將監(jiān)視方面從 aop.xml 文件中刪除而不讓它們運(yùn)行。如果以這種方式取消它們的部署,可以降低開(kāi)銷,但是不能在不重新啟動(dòng)應(yīng)用程序(或者服務(wù)器)的條件下重新啟用它們。這種技術(shù)還 可以用于限制監(jiān)視。例如,可以定義一個(gè)更狹窄的切點(diǎn)以監(jiān)視在一個(gè) XML 定義的方面中的 Struts 操作,并排除標(biāo)準(zhǔn)的 StrutsMonitor

          運(yùn)行帶裝載時(shí)織入的 Tomcat 5.5

          我將展示在 Tomcat 5.5 應(yīng)用服務(wù)器中部署 Glassbox Inspector。這個(gè)服務(wù)器默認(rèn)實(shí)現(xiàn)了 Java 5,因此我使用 Java 5 的 -javaagent 啟動(dòng)參數(shù)調(diào)用 AspectJ 5 裝載時(shí)織入。(請(qǐng)參閱 參考資料 以了解關(guān)于對(duì)更早版本的 Java 語(yǔ)言使用 AspectJ 裝載時(shí)織入的知識(shí)。)要使用這個(gè)功能,只需要對(duì)啟動(dòng)命令增加一個(gè)新的 java 選項(xiàng)標(biāo)志。這通常是通過(guò)編譯一個(gè) shell 腳本完成的。例如,可以在 Windows 計(jì)算機(jī)中的 setclasspath.bat 的開(kāi)始處增加以下一行:

           set JAVA_OPTS=-javaagent:%CATALINA_HOME%\common\lib\aspectjweaver.jar -Xmx256m 

          可以對(duì)于 Linux 或者其他 Unix 操作系統(tǒng)的 setclasspath.sh 腳本做類似的修改。

          這 一設(shè)置就可以在 Tomcat VM 中允許裝載時(shí)織入。要監(jiān)視 Web 應(yīng)用程序,可以像通常那樣編譯它,并在以后在 WEB-INF/lib 目錄中增加 glassboxInspector.jar 文件及其依賴文件。AspectJ 織入器然后尋找我部署的 aop.xml 文件,并保證它定義的方面在應(yīng)用程序裝載時(shí)被織入。另一種方法是,將 glassboxInspector.jar 和文件加到 Tomcat 的 shared/lib 文件夾中。這會(huì)增加對(duì)服務(wù)器中所有 web 應(yīng)用程序的監(jiān)視,而不需要為每一個(gè)應(yīng)用程序增加這個(gè) jar。這種解決方案類似于為應(yīng)用服務(wù)器中的一個(gè)應(yīng)用程序或者所有應(yīng)用程序增加任何其他庫(kù)功能。我甚至可以將這個(gè)監(jiān)視器放到系統(tǒng)類路徑中或者 Tomcat 的 common/lib 文件夾中。這樣就可以監(jiān)視 Tomcat 的內(nèi)部,不過(guò)我看不出這有什么必要,而且這樣做會(huì)增加啟動(dòng)時(shí)間。

          我選擇一次性地在整個(gè)服務(wù)器上部署監(jiān)視。一般來(lái)說(shuō),應(yīng)用服務(wù)器是作為整體管理的,最好對(duì)服務(wù)器上的所有應(yīng)用程序有一致性的管理和監(jiān)視能力。



          回頁(yè)首


          性能和內(nèi)存使用

          裝 載時(shí)織入的簡(jiǎn)單性的代價(jià)是啟動(dòng)應(yīng)用程序需要更多的時(shí)間。我一直與 Alexandre Vasseur 和 Matthew Webster 進(jìn)行努力,盡量在 AspectJ 5 發(fā)布之前優(yōu)化裝載時(shí)織入的性能。開(kāi)銷已經(jīng)不算大了,我們找到了一些可以加入的優(yōu)化。我預(yù)計(jì)裝載時(shí)織入性能在下一年仍然會(huì)繼續(xù)進(jìn)一步優(yōu)化。同樣,這些測(cè)試是 對(duì) Glassbox Inspector 的 alpha 版進(jìn)行的。我預(yù)計(jì)在調(diào)優(yōu) Glassbox Inspector 和 AspectJ 裝載時(shí)織入后,開(kāi)銷會(huì)顯著降低。我計(jì)劃優(yōu)化 Glassbox Inspector 以使用 java.util.concurrent 庫(kù),使用的方面可以根據(jù)系統(tǒng)運(yùn)行的是哪種 Java VM,使用多個(gè)實(shí)現(xiàn)中最有效的一種。

          我 對(duì) Windows XP 工作站上 2005 年 10 月開(kāi)發(fā)版本的 Aspect J(1.5.0 M4 之后)的服務(wù)器啟動(dòng)時(shí)間和每個(gè)請(qǐng)求的平均時(shí)間做了一些簡(jiǎn)單的測(cè)量。這個(gè)系統(tǒng)是 Inspector 的開(kāi)發(fā)版本(alpha 1 之后)。在我的測(cè)試中,在 JRockIt 1.5.0_03 Java VM 上運(yùn)行 Tomcat 5.5.9,并自動(dòng)啟動(dòng) iBatis 1.3 JPetstore、Spring 1.2.1 Petclinic 和 Duke 的 Bookstore 應(yīng)用程序。

          當(dāng)系統(tǒng)啟動(dòng)后,端 到端的響應(yīng)時(shí)間開(kāi)銷很小。50 位用戶同時(shí)運(yùn)行在同一臺(tái)計(jì)算機(jī)中時(shí),平均服務(wù)器響應(yīng)時(shí)間增加了 10 毫秒。不過(guò),帶裝載時(shí)織入的啟動(dòng)用了約 15 秒,包括織入時(shí)間。對(duì)應(yīng)地,不帶織入的啟動(dòng)用了大約 5 秒。最后,使用裝載時(shí)織入顯著增加了內(nèi)存開(kāi)銷:有織入與沒(méi)織入時(shí)相比,Java 進(jìn)程使用的內(nèi)存增加了 100%。

          表 1 顯示了當(dāng)啟用 Glassbox Inspector 并帶裝載時(shí)織入時(shí)、部署但禁用了裝載時(shí)織入時(shí)以及沒(méi)有部署時(shí),每個(gè)請(qǐng)求的開(kāi)銷、凈啟動(dòng)時(shí)間和內(nèi)存使用:

          表 1. 帶裝載時(shí)織入時(shí)的開(kāi)銷
          監(jiān)視平均端到端
          響應(yīng)時(shí)間(毫秒)
          服務(wù)器啟動(dòng)
          時(shí)間(毫秒)
          啟動(dòng)進(jìn)程
          內(nèi)存使用(MB)
          啟用2015135
          禁用(在運(yùn)行時(shí))10130
          未部署10565



          回頁(yè)首


          結(jié)束語(yǔ)

          在 這篇由兩部分組成的文章中,我向您展示了如何用 AspectJ 處理復(fù)雜的橫切問(wèn)題(在這里是應(yīng)用程序監(jiān)視),并逐步構(gòu)造解決方案。即使您在下一個(gè)監(jiān)視解決方案中不選擇使用面向方面的方法,這里所講的許多內(nèi)容和技巧也 會(huì)證明是很有價(jià)值的。在我的其他 AspectJ 項(xiàng)目中發(fā)現(xiàn)它們顯然是很有用的。

          就是說(shuō),我希望本文提供了使用面向方面的代碼進(jìn)行監(jiān)視的 好例子。與傳統(tǒng)的分散而且糾纏的測(cè)試代碼相比,我編寫的方面非常容易擴(kuò)展和改編,它們提供了獲得關(guān)于生產(chǎn)應(yīng)用程序的數(shù)據(jù)的更好方法。如果沒(méi)有方面,那么實(shí) 現(xiàn)在這里描述的復(fù)雜功能已經(jīng)非常困難,更不用說(shuō)隨著對(duì)生產(chǎn)應(yīng)用程序的了解加深而改寫它了。

          對(duì) Glassbox Inspector 監(jiān)視基礎(chǔ)架構(gòu)的開(kāi)發(fā)和分析不會(huì)隨著本文而結(jié)束。這個(gè)項(xiàng)目正在尋求多個(gè)領(lǐng)域的貢獻(xiàn),如:

          • 捕獲關(guān)于所發(fā)生情況的更多上下文(例如,堆棧跟蹤和參數(shù)),特別是像故障或者不正常響應(yīng)時(shí)間這樣的反常結(jié)果。

          • 監(jiān)視更多操作、資源,如 JMS 和 EJB,并擴(kuò)展 Inspector 對(duì)監(jiān)視 XML 文檔處理的基本支持。

          • 處理分布式監(jiān)視,以便跨集群應(yīng)用程序跟蹤信息,并跨分布式調(diào)用對(duì)信息進(jìn)行關(guān)聯(lián)。

          • 利用 Java 5 管理信息,如 CPU 時(shí)間或者特定于線程的統(tǒng)計(jì)。

          • 使用應(yīng)用服務(wù)器 JMX 統(tǒng)計(jì),如線程池。

          • 捕獲歷史和趨勢(shì),帶持久化存儲(chǔ)和報(bào)告。

          • 使用 JMX 提供報(bào)警,并提供統(tǒng)計(jì)匯總。累積 JMX 統(tǒng)計(jì)中關(guān)于嵌套的監(jiān)視器的關(guān)鍵信息會(huì)很有用。

          • 監(jiān)視頂級(jí)資源信息(例如,調(diào)用服務(wù)或者連接到數(shù)據(jù)庫(kù)所花費(fèi)的總時(shí)間)。這可以提供更好的匯總數(shù)據(jù)。

          • 提供不同程度的統(tǒng)計(jì)匯總(例如,一個(gè)請(qǐng)求所花時(shí)間的柱狀圖)。

          • 適應(yīng)性地發(fā)現(xiàn)要跟蹤的相關(guān)參數(shù)(例如,對(duì)于未知的數(shù)據(jù)查詢或者 Servlet 請(qǐng)求)。

          • 對(duì)于高層數(shù)據(jù)庫(kù)和服務(wù)訪問(wèn)框架提供資源監(jiān)視(如 Hibernate、TopLink、EJB 3 Persistence Managers、JAX-WS 等)。

          • 允許取樣以改變(跨整個(gè)請(qǐng)求)所捕獲的數(shù)據(jù)量。

          • 監(jiān)視對(duì)沒(méi)有綁定到 Servlet 的 Web 應(yīng)用程序的請(qǐng)求錯(cuò)誤,如 404 錯(cuò)誤。這是對(duì) Servlet 過(guò)濾器進(jìn)行建議的好例子,包括增加一個(gè)空的 Servlet 過(guò)濾器以匹配對(duì)一個(gè)應(yīng)用程序的所有請(qǐng)求。

          • 監(jiān)視業(yè)務(wù)事件,如客戶購(gòu)買或者放棄購(gòu)物車。

          現(xiàn)在該您了。我鼓勵(lì)您下載 Glassbox Inspector 并試驗(yàn)它。如果您能為這個(gè)項(xiàng)目出力,不管是提供反饋意見(jiàn)、開(kāi)發(fā)其他的監(jiān)視器、沿上面建議的方向擴(kuò)展系統(tǒng),還是為該項(xiàng)目建立一個(gè)新的方向,我都會(huì)很高興。Glassbox Inspector 的完整源代碼請(qǐng)參閱 下載。請(qǐng)參閱 參考資料 以了解更多關(guān)于這里討論的主題的內(nèi)容。

          致謝

          感 謝 Eugene Kuleshov、Nicholas Lesiecki、Eamonn McManus、Srinivas Narayanan、Ramnivas Laddad、Will Edwards、Matt Hutton、David Pickering、Rob Harrop、Alex Vasseur、Paul Sutter 和 Mik Kersten 對(duì)本文進(jìn)行審校并給予有見(jiàn)地的意見(jiàn)。




          回頁(yè)首


          下載

          描述名字大小 下載方法
          Source codej-aopwork12source.zip72KB  FTP
          關(guān)于下載方法的信息獲取 Adobe? Reader?


          回頁(yè)首


          參考資料

          學(xué)習(xí)

          獲得產(chǎn)品和技術(shù)

          討論


          回頁(yè)首


          關(guān)于作者


          Ron Bodkin 是 New Aspects of Software 的創(chuàng)始人,該公司提供應(yīng)用程序開(kāi)發(fā)和架構(gòu)方面的咨詢和培訓(xùn),側(cè)重于性能管理和有效地使用面向方面編程。Ron 以前在 Xerox PARC 為 AspectJ 小組工作,在那里他領(lǐng)導(dǎo)了第一個(gè) AOP 實(shí)現(xiàn)項(xiàng)目并負(fù)責(zé)客戶的培訓(xùn),他還是 C-bridge 的創(chuàng)始人和 CTO,這家咨詢機(jī)構(gòu)采用 Java 的框架、XML 和其他互聯(lián)網(wǎng)技術(shù)提供企業(yè)應(yīng)用程序。Ron 經(jīng)常為各種會(huì)議和客戶進(jìn)行演講和提供教程,包括在 Software Development、The Colorado Software Summit、 TheServerSide Symposium、EclipseCon、StarWest、Software Test & Performance、OOPSLA 和 AOSD 上做報(bào)告。最近,他一直為 Glassbox 集團(tuán)工作,用 AspectJ 和 JMX 開(kāi)發(fā)應(yīng)用程序性能管理和分析產(chǎn)品。可以通過(guò) Ron 的郵箱 ron.bodkin@newaspects.com 與他聯(lián)系。

          posted on 2005-12-19 21:53 Dion 閱讀(1278) 評(píng)論(0)  編輯  收藏 所屬分類: 企業(yè)架構(gòu)模式
          主站蜘蛛池模板: 资阳市| 新龙县| 通城县| 山阳县| 黔江区| 日土县| 镇平县| 长白| 唐山市| 华池县| 高安市| 冷水江市| 新闻| 卫辉市| 白银市| 贵溪市| 通江县| 梅州市| 兴业县| 阆中市| 沙雅县| 怀化市| 恩施市| 枣强县| 澳门| 富裕县| 宁波市| 旅游| 合水县| 紫金县| 来安县| 赤城县| 罗田县| 色达县| 青冈县| 永福县| 申扎县| 汉源县| 江口县| 鄂托克旗| 志丹县|