- 浏览: 373442 次
- 性别:
- 来自: 北京
文章分类
最新评论
-
y806839048:
启动activemq我试了,也不行
ActiveMQ5.0实战三:使用Spring发送,消费topic和queue消息 -
y806839048:
是不是要另外启动activemq
ActiveMQ5.0实战三:使用Spring发送,消费topic和queue消息 -
y806839048:
为什么我的两个应用中,不能实现通信
ActiveMQ5.0实战三:使用Spring发送,消费topic和queue消息 -
fengfujie:
[flash=200,200][/flash][tr][th] ...
JMS消息类型模型 -
fengfujie:
...
JMS消息类型模型
摘要
有效的异常处理策略是一大架构关注点,它超越了独立应用程序组件的边界。有效的Java异常(Dev2Dev 中文版,2007年2月)概述了错误-意外事件(Fault-Contingency)异常模型,消除了在Java应用程序中使用已检查还是未检查异常的 迷惑。使用传统Java技术实现这种模型要求所有组件都遵循一组规则和行为。这也就暗中表明原本无关的组件间的耦合需要为意料之外的失误和故障留出空间。 在错误-意外事件异常模型中应用面向方面的技术需要首先处理这一关注点,并允许其他组件专心于其主要工作。
本文解释了为什么基于错误- 意外事件异常模型的异常处理方面是对传统实现的重大改进。还提供了使用AspectJ(Java的一种面向方面的扩展)创建的异常处理方面的完整示例,来 展示这种概念。文中提供的代码可在BEA WebLogic 9.2 和 Tomcat 5.0 应用服务器上运行。
AOP 与架构
作为一名应用程序架构师,您的主要职责是制订决策来监管组件之间的关系。架构决策会影响到组件的设计方法、组件用来协作的模式以及所遵循的惯 例。如果决策合理、沟通充分,并得到了项目团队的遵从,那么就会得到一个易于理解、维护和扩展的软件系统。每个人都希望得到这样的结果,但实现起来却极具 挑战。架构是跨组件的,它要求组件执行某些操作,或者避免特定的行为,以使一切在一个总体愿景下协调工作。
开发团队是由人构成的,而人 都是不完美的。即便最出色的开发团队也会在维护架构愿景的纯净方面遇到麻烦。团队可以利用两种传统的对策来避免架构违规。第一种对策是规定对设计和编码进 行定期审查。第二种是构建框架。审查的目标是在问题刚刚出现时发现问题。而框架提供了一种可重用的基础架构,其约束的目标是从一开始就预防问题,避免问题 出现。
面向方面的设计是应对架构关注点的第三种选择。它不是将架构行为分散到所有无关的组件,而是将行为封装在一个方面中,并在特定执 行点应用。面向方面编程(AOP)方面的工作自20世纪90年代就已启动,但可以公正地说,AOP的广泛采用依然有待时日。或许造成这种情况的原因之一就 是缺乏令人鼓舞的典范,表明这种技术能够带来怎样的收益。一个引人注目的AOP实例应具有如下特点:
- 有价值——解决公认的问题
- 没有AOP就难以解决
- 使用AOP可轻松解决
跟踪方法执行的常用示例是展示方面功能的好办法,但并不那么令人鼓舞——或许应该说,鼓舞的程度对于大多数人来说还不够,不足以投入人力物力去 学习这种技术,不足以成为在下一个项目中使用AOP的理由。其实存在更好的例子,但您需要详查所有记录了方法的例子,来找到所需的那些。
在许多软件项目中,Java应用程序中的异常处理就是一个公认的关注点。管理不善的异常规程将导致难以理解和维护的易出错代码。一致的异常处理方法对于 多数应用程序来说都是一项重大收益。即便在团队采用了异常的架构模型时,确保每个模型都符合模型也需要不懈的努力和深入的洞察力。看上去异常处理模型似乎 是探索AOP的不错方法。这是否能成为一个鼓舞人心的例子,一切由您决定。
错误-意外事件异常模型
异常处理方面从您希望应用到整个应用程序中的一个模型或者一组行为开始。错误-意外事件异常模型提供了一种切实可行的方式来考虑执行软件时所遇 到异常。该模型将不规则的输出描述为意外事件(Contingency)或错误(Fault)。意外事件是一种可选输出,能够使用组件目标用途的词汇表加 以描述。方法或构造方法的调用方具有处理其意外事件输出的战略。另一方面,错误是无法在语义契约方面进行描述的一种故障,仅可就实现细节进行描述。举例来 说,考虑一个Account Service,它带有一个getAccount()方法,在为此方法提供一个Account ID后,它将返回Account对象。很容易就能设想出可能出现的意外事件,例如“No such account”或“Invalid Account ID”,是就方法的目标用途表述的。要预计可能出现的错误,您首先需要了解getAccount() 方法是怎样实现的。它是否因未连接到数据库而接收到了一个SQLException?或许存在超时,正在等待一个宕机维护的Web服务。或许一个丢失的文 件(本应存在)导致了FileNotFoundException。此处的要点在于getAccount() 的调用方不应对实现有任何了解,也不应被迫为预计到的任何错误捕捉已检查异常。
错误-意外事件异常模型的一个简单Java实现具有三个 基本概念:意外事件异常、错误异常、错误屏障。方法和构造方法使用意外事件异常来通知作为其契约一部分的可选输出。意外事件异常是已检查的异常,因此编译 器将帮助确保调用方考虑到所有约定的输出。错误异常用于通知特定于实现的故障。错误异常是未检查的异常,运行中的代码通常会避免捕捉这些异常,将这一责任 留给作为错误屏障的类处理。错误屏障的主要责任就是在获得错误异常时为正在处理的活动提供一个出色的出口。这种出色的出口通常包含对正在处理的故障的表 示,例如在用户界面(如果有用户界面)上显示一条道歉信息,或者通过其他方式向“外部世界”指出故障。
传统的实现使用 RuntimeException的一个子类(比如FaultExceptio)来表示错误异常。作为一个未检查的异常,FaultException可 在未在方法和构造方法的签名中被显式捕获或声明的情况下抛出。因此,这种异常完全可能在未被错误屏障捕获或处理时处于未发现的状态。意外事件异常基于 Exception的一个子类(比如ContingencyException),该子类使Java编译器可以检查此类异常。由于 ContingencyException是语义契约的完整组成部分,因此可以借助编译器来保证调用方具备处理此类异常的战略。
模型中 的组件需要遵循一组使一切正常工作的规范。首先,组件不能抛出FaultException 或 ContingencyException子类以外的异常。其次,组件必须避免捕获FaultException,将这一责任留给错误屏障。组件负责处理 它们所调用的外部方法抛出的异常,并在必要时将其转换为FaultException或ContingencyException。任何未捕获的 RuntimeException都被视为错误,需要错误屏障的关注。这些规则非常简单,有效地消除了应用程序代码中混乱、令人迷惑的异常序列。通过清晰 地将错误划分出来,由错误屏障负责处理,使用低级代码处理错误条件的诱惑得到了极大的消减。错误不再干预应由意外事件异常处理的情况,意外事件异常的目的 显然是在组件间传输有意义的信息。
传统实现的不足之处
错误-意外事件异常模型的传统实现是对临时异常处理的极大改进,但离理想还相去甚远。所有的组件都必须遵循规范,即便这些组件之间再无其他关 联。确保它们确实遵循了规范的惟一方法就是审查代码。可能有一个组件无意中捕获了错误异常,使之无法传递给错误屏障。如果发生这种情况,您可以顺利从那个 出色的出口退出并离开,但没有任何办法去诊断所发生的错误。
传统实现给错误屏障设定了两方面的责任。其固有的责任就是完美地终止处理序 列。由于其位置靠近调用堆栈的顶端,因此错误屏障了解周围环境,了解哪些内容能够构成恰当的输出响应。另外一种责任是记录与错误相关的分析信息,以使人们 了解发生了什么。它具有这种责任的惟一原因就是没有其他合适的位置来完成这个任务。如果系统需要多个错误屏障(有些系统确实需要),那么每个错误屏障都必 须包含类似的逻辑,来捕获可用信息。
修正一个问题的能力取决于可用信息的质量。实际上,传统实现能够提供的信息仅限于 RuntimeException能够提供的那些:堆栈跟踪和错误消息。每一名Java程序员都会乐于在没有任何与实际发生情况有关的线索的前提下,启动 一次堆栈跟踪。堆栈跟踪将显示发生了什么、在哪里发生,但不会显示为什么发生这样的情况。理想情况下,您希望了解哪些方法被调用,以及它们是怎样被调用的 ——传入各方法且导致错误的参数类型和值。将代码分散到每一个方法之中并在输入时记录其参数这种方法令人不满、不切实际、易于出错,如果未实际出现任何错 误,那么所做的一切都是白费功夫。
方面、切入点和通知
方面编程正是为解决此类问题而出现的。在我们的例子中,应用程序内的所有组件都必须关注错误和意外事件的规则。如果一个类中出现失误,会波及众 多不相关的类,导致较大的异常模型出现故障。同样,我们可以使用错误屏障来完成记录分析信息的任务,尽管其自身的角色只是了解如何为外部世界生成一般响应 并执行清除操作。
AOP的理念是将所需行为封装在一个实体中:方面。一个方面包含在应用程序中某些定义好的点上运行的逻辑。所运行的这 种逻辑就称为通知。应用通知的点称为连接点。可通过定义切入点来指定一组应用通知的连接点。切入点基本上就是一个表达式,过滤应用程序中所有潜在连接点, 并根据标准(如接入点的类型和各种类型的模式匹配)来选择部分连接点。如果恰当地制作了方面,它会执行一些操作,若不使用方面,这些操作将分散在应用程序 之中。将一切都集中到一处之后,应用程序中的其他组件即可集中关注其主要任务。最终得到更出色的组件内聚,这是人人都希望得到的结果。
示例应用程序包含我们的异常处理方面,它是使用Java语言的一个超集AspectJ构建的。这种语言支持对于任何应用程序中的异常处理都非常重要的连接 点多样性。在Java应用程序的执行过程中,可以通过多种方式生成并捕获异常。方法执行只是其中的一种方式。异常处理方面需要考虑构造方法的执行、对象初 始化和类初始化,这些都可能导致异常。此外还要考虑显式抛出和捕获异常的位置。AspectJ的切入点语言支持实现理想模型所需的一切。
ExceptionHandling方面
ExceptionHandling方面在设计时就考虑到了最大化灵活性,因此它只有两个编译时依赖项。它们是表示错误和意外事件的两个Java类:
- FaultException:表示错误条件的RuntimeException类的一个子类。
- ContingencyException:表示意外事件输出的Exception类的一个子类。子类表示具体的意外事件条件。
- 方 面假设(并强制)应用程序的其他部分按照模型规则使用这些类。AspectJ系统的绝妙特性之一就是能够“为编译器编程”,从而实施超越标准Java语言 规则的策略。在我们的例子中,我们希望鼓励开发人员以错误和意外事件为依据进行思考,并清楚地加以区分。由于我们的架构提供了一个 ContingencyException基类,我们希望确保开发人员仅使用该类的子类来表示意外事件条件。通过这种做法,就能够避免开发人员尝试声明一 个抛出(比如说)SQLException的方法,此方法应将任何意料之外的JDBC问题都视为错误。
- ExceptionHandling方面使用切入点来检测声明了已检查异常而非基于ContingencyException的异常的方法和构造方法。违规将作为编译错误标出,这种方式能确保相关人员注意到问题。
package exception; import java.io.ByteArrayOutputStream; import java.io.PrintStream; import java.util.Stack; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.reflect.CodeSignature; public abstract aspect ExceptionHandling { ... pointcut methodViolation(): execution(* *(..) throws (Exception+ && !ContingencyException+ && !RuntimeException+)); declare error: methodViolation(): "Method throws a checked exception that is not a ContingencyException"; pointcut constructorViolation(): execution(*.new(..) throws (Exception+ && !ContingencyException+ && !RuntimeException+)); declare error: constructorViolation(): "Constructor throws a checked exception that is not a ContingencyException"; ... }
清单 1. 编译时异常策略实施
在下面的例子中,Transaction类的commit()方法声明其抛出 SQLException,而在这种环境中SQLException应视为错误,因而它违背了异常策略。编译器以ExceptionHandling方面 中的声明为依据,将这一违规作为编译错误标出。rollback()方法将意料之外的SQLException视为错误处理,符合此模型,因而不会出现任 何标记。为本文开发的示例是使用安装了AspectJ Development Tools (AJDT) 1.4.1插件的Eclipse 3.2.1开发的。
图 1.异常策略违规将生成一个错误标记
异常通知连接点
ExceptionHandling方面使用exceptionAdvicePoints()切入点来为任何能够抛出异常的执行序列应用通知。这个方面多次使用此切入点,在可能抛出异常时注入处理。切入点包含如下连接点:
- 所有方法执行
- 所有构造方法执行
- 所有对象初始化
- 所有对象预初始化
- 所有类初始化
由于ExceptionHandling方面具有自己的方法、自己的构造方法,也要经历类和对象的初始化过程,上述连接点中有一些就处于这个方 面之中。通常这不是什么好事,会在方面尝试发布自己的通知时引起递归循环。为避免这样的可能性,exceptionAdvicePoints()切入点明 确地在上述连接点中排除了部分连接点:
- 方面自身的词法作用域(lexical scope)内的任何连接点
- 其子方面的词法作用域内的任何连接点
- 通知执行控制流内的任何连接点
public abstract aspect ExceptionHandling { ... pointcut exceptionAdvicePoints(): (execution (* *.*(..)) || execution (*.new(..)) || initialization(*.new(..)) || preinitialization(*.new(..)) || staticinitialization(*)) && !within(ExceptionHandling+) && !cflow(adviceexecution()); ... }
清单 2. 异常通知点
现在,ExceptionHandling方面能够为自身以外的所有应用程序组件应用与异常相关的通知。不会尝试为那些可能是作为执行其自身通知的结果运行的方法应用通知。
运行时异常转换
错误-意外事件模型的惯例表明,未被捕获的Throwable应视为错误条件。在传统实现中,完全不能保证此类异常在较低级别被捕获并转换,因 此错误屏障必须随时准备捕捉Throwable,而不仅仅是FaultException。在AOP实现中,我们可以更好地完成这个任务。可以确保从任一 组件抛出且未被捕获的Throwable都会自动转换成FaultException。这使错误屏障的实现更为简单,也确保了 ExceptionHandling方法内的任何其他通知都会将未被捕获的Throwable视为错误处理。
如果一个执行序列抛出了任 何类型的Throwable,“抛出后”通知将运行。如果异常是FaultException或ContingencyException,通知不会采取 任何措施。否则,通知会将违规的异常替换为FaultException的一个新实例,将未被捕获的异常作为诱因。请注意,方面的编译时异常策略实施简化 了通知必须进行的检查。
public abstract aspect ExceptionHandling { ... after() throwing(Throwable throwable):exceptionAdvicePoints(){ if (!(throwable instanceof FaultException || throwable instanceof ContingencyException)) { throw new FaultException("Unhandled exception: ", throwable); } } ... }
清单 3.运行时异常转换
到达错误屏障
ExceptionHandling 方面确保FaultException总是会到达错误屏障,无论中间参与的代码行为如何。这保证了错误屏障总是以一种有序的方式终止处理序列。它使中间参 与的代码免于担忧意外捕获FaultException。此通知使这些异常更加棘手,错误屏障以外的任何处理程序都无法捕获它们。allHandlers ()切入点应用到应用程序中的所有异常处理程序,并使包含处理程序和所处理异常的类对before() 通知逻辑可用。通知会在异常处理程序内代码执行前执行。除非异常是一个FaultException,否则通知不会采取任何措施。对于 FaultException,通知会检查处理程序是否位于指定错误屏障的类中。如果是,则允许该处理程序捕获FaultException。如果不是, 则再次抛出FaultException,忽略将捕获它的处理程序。最终,FaultException将到达指定错误屏障类中的一个处理程序处。
public abstract aspect ExceptionHandling { ... pointcut allHandlers(Object handlerType, Throwable throwable): handler(Throwable+) && args(throwable) && this(handlerType); before(Object handler, Throwable throwable): allHandlers(handler, throwable) { if (throwable instanceof FaultException) { if (!(isFaultBarrier(handler))) { FaultException fault = (FaultException) throwable; throw (fault); } } } abstract boolean isFaultBarrier(Object exceptionHandler); ... }
清单 4. 仅错误屏障捕获错误
方面要如何知道一个处理程序是否位于指定错误屏障内呢?一种方法就是将指定错误屏障的类名称 硬编码到ExceptionHandling方面当中。但那会将方面与特定应用程序绑定在一起。为了使ExceptionHandling 方面尽可能地灵活,它声明了一个抽象方法,回答错误屏障的问题。isFaultBarrier()的实现是在了解应用程序细节且能判断一个处理程序对象是 否为错误屏障的子方面中提供的。这也就是说,ExceptionHandling必须声明为抽象方面。它必须被具体子方面扩展,之后其通知才能被激活。子 方面仅需要应用isFaultBarrier()的一个实现,加上另外一个方法,下面将讨论这个方法。
更好的错误诊断
上面介绍的ExceptionHandling方面确保了应用程序抛出且未被捕获的所有异常都会作为FaultException到达错误屏 障。这适用于来自Java库方法的意外异常、应用程序代码的bug导致的意外异常,以及在错误条件未被发现时显式抛出的FaultExceptions。 错误屏障仅需捕获FaultException,而非传统实现需要捕获的Throwable。可以通过任何看似自然的方式构造应用程序代码,无需考虑对应 用程序的错误处理能力造成的影响。
这是面向方面方法的一大优势。而ExceptionHandling方面实际证明了在错误发生时它能 够提供的诊断信息的质量极具价值。一个方面能够在应用程序运行时观测整个应用程序。ExceptionHandling 方面利用这种能力跟踪传递给应用程序中各方法和构造方法的参数。出现错误时,该方面为所记录的标准异常和堆栈跟踪信息附加一个特殊的应用程序跟踪 (Application Trace)部分。应用程序跟踪中的每个条目都描述了处理的类型;类、方法或构造方法的名称;用于调用它的参数名称、类型和值。结果如下所示:
FATAL : exception.ServiceExceptionHandling - Application Fault Detected exception.FaultException: Unexpected failure on catalog query: com.ibm.db2.jcc.b.SQLException: The string constant beginning with "'" does not have an ending string delimiter. at domain.CatalogDAO.performQuery(CatalogDAO.java:86) at domain.CatalogDAO.getCatalogEntries(CatalogDAO.java:57) at domain.CatalogService.getCatalogEntry(CatalogService.java:16) at domain.CartItem.<init>(CartItem.java:22) at domain.ShoppingCart.addToCart(ShoppingCart.java:28) at action.SelectItemAction.performAction(SelectItemAction.java:44) at action.BaseAction.execute(BaseAction.java:57) at org.apache.struts.action.RequestProcessor.processActionPerform(RequestProcessor.java:484) at org.apache.struts.action.RequestProcessor.process(RequestProcessor.java:274) at org.apache.struts.action.ActionServlet.process(ActionServlet.java:1482) at org.apache.struts.action.ActionServlet.doPost(ActionServlet.java:525) at javax.servlet.http.HttpServlet.service(HttpServlet.java:763) at javax.servlet.http.HttpServlet.service(HttpServlet.java:856) at weblogic.servlet.internal.StubSecurityHelper$ServletServiceAction.run(StubSecurityHelper.java:225) at weblogic.servlet.internal.StubSecurityHelper.invokeServlet(StubSecurityHelper.java:127) at weblogic.servlet.internal.ServletStubImpl.execute(ServletStubImpl.java:283) at weblogic.servlet.internal.ServletStubImpl.execute(ServletStubImpl.java:175) at weblogic.servlet.internal.WebAppServletContext$ServletInvocationAction.run(WebAppServletContext.java:3214) at weblogic.security.acl.internal.AuthenticatedSubject.doAs(AuthenticatedSubject.java:321) at weblogic.security.service.SecurityManager.runAs(SecurityManager.java:121) at weblogic.servlet.internal.WebAppServletContext.securedExecute(WebAppServletContext.java:1983) at weblogic.servlet.internal.WebAppServletContext.execute(WebAppServletContext.java:1890) at weblogic.servlet.internal.ServletRequestImpl.run(ServletRequestImpl.java:1344) at weblogic.work.ExecuteThread.execute(ExecuteThread.java:209) at weblogic.work.ExecuteThread.run(ExecuteThread.java:181) Application Trace: method-execution: domain.CatalogDAO.performQuery(query:java.lang.String=SELECT * FROM ADMINISTRATOR.CATALOG WHERE CATALOGID = 'BAD'INPUT', transaction:integration.Transaction=jdbc:db2://localhost:50000/DOCMGMT) method-execution: domain.CatalogDAO.getCatalogEntries(catalogID:java.lang.String=BAD'INPUT, transaction:integration.Transaction=jdbc:db2://localhost:50000/DOCMGMT) method-execution: domain.CatalogService.getCatalogEntry(catalogID:java.lang.String=BAD'INPUT) constructor-execution: domain.CartItem.<init>(catalogID:java.lang.String=BAD'INPUT, quantity:int=1) initialization: domain.CartItem.<init>(catalogID:java.lang.String=BAD'INPUT, quantity:int=1) method-execution: domain.ShoppingCart.addToCart(catalogID:java.lang.String=BAD'INPUT, quantity:int=1) method-execution: action.SelectItemAction.performAction(cart:domain.ShoppingCart=Cart0024988, action:org.apache.struts.action.ActionMapping=ActionConfig[path=/SelectItem,name=SelectItemForm,scope=session,type=action.SelectItemAction, form:org.apache.struts.action.ActionForm=CatalogID-BAD'INPUT Quantity-1, request:javax.servlet.http.HttpServletRequest=Http Request: /ShoppingServices/SelectItem.do, response:javax.servlet.http.HttpServletResponse=weblogic.servlet.internal.ServletResponseImpl@22a6c2) method-execution: action.BaseAction.execute(action:org.apache.struts.action.ActionMapping=ActionConfig[path=/SelectItem,name=SelectItemForm,scope=session,type=action.SelectItemAction, form:org.apache.struts.action.ActionForm=CatalogID-BAD'INPUT Quantity-1, request:javax.servlet.http.HttpServletRequest=Http Request: /ShoppingServices/SelectItem.do, response:javax.servlet.http.HttpServletResponse=weblogic.servlet.internal.ServletResponseImpl@22a6c2)
清单 5. 应用程序跟踪的结果
应用程序跟踪仅包含受ExceptionHandling方面影响的那些方法:作为应用程序 特定部分的方法。请注意,应用程序跟踪中的条目大致对应于堆栈跟踪顶端的项目。(堆栈跟踪的下端涵盖作为WebLogic Server实现的具体部分的类。)这里给出的示例来自一个允许用户应用形参(BAD'INPUT)的Struts应用程序,该参数包含单个引号字符,从 而在SQL预计中导致语法错误。在诊断记录中显示参数值有助于确定错误在何处发生。这是ExceptionHandling方面中与错误记录相关的一段出 色代码。首先,观察一下方面是如何控制错误记录方式的。
public abstract aspect ExceptionHandling { ... private boolean FaultException.logged = false; private boolean FaultException.isLogged() { return this.logged; } private void FaultException.setLogged() { this.logged = true; } after() throwing(FaultException fault): exceptionAdvicePoints(){ if (!fault.isLogged()) { logFault(fault); fault.setLogged(); } } ... }
清单 6. 错误记录通知
只要应用程序的任何一点抛出FaultException,抛出后通知就会运行。它的任务是调用 方面的logFault()方法,此方法完成实际记录工作。单独一个错误在调用堆栈的所有方法上传播时可能会多次触发通知,因此通知需要找到一种方法,来 了解记录在何时完成。为此使用了另外一种AOP技术:成员引入(member introduction)。方面将一个布尔标记引入FaultException 类型,附带一些用于访问的方法。这个标记和这些方法被合理地标为私有,仅在ExceptionHandling方面内可见。总体影响是:诊断记录在错误出 现时立即发生,而且不再重复。
错误可能在任何时候出现。要为可能出现的错误做好准备,ExceptionHandling 方面需要在应用程序运行的时候跟踪其活动。这样,如果出现错误,它就可以随时记录导致错误的调用序列及其参数值。为此,该方面维护了一个 JoinPoint对象引用的每线程堆栈。执行应用程序时,方面的跟踪堆栈随调用堆栈一起伸缩。AspectJ运行时利用语言构造 thisJoinPoint使JoinPoint对通知逻辑可用。JoinPoint 对象包含通知逻辑的动态上下文信息,使逻辑能够了解关于触发通知的环境的细节。
public abstract aspect ExceptionHandling { ... private static ThreadLocal<Stack<JoinPoint>> traceStack = new ThreadLocal<Stack<JoinPoint>>() { protected Stack<JoinPoint> initialValue() { return new Stack<JoinPoint>(); } }; private static void pushJoinPoint(JoinPoint joinPoint) { traceStack.get().push(joinPoint); } private static JoinPoint popJoinPoint() { Stack<JoinPoint> stack = traceStack.get(); if (stack.empty()) { return null; } else { JoinPoint joinPoint = stack.pop(); return joinPoint; } } private static JoinPoint[] getJoinPointTrace() { Stack<JoinPoint> stack = traceStack.get(); return stack.toArray(new JoinPoint[stack.size()]); } ... }
清单 7. ThreadLocal 调用跟踪方法
有了这些方法之后,跟踪应用程序调用的通知就非常简单了。 exceptionAdvicePoints()切入点(可能因异常突然终止的任何执行序列)标识的连接点被推入堆栈。在序列开始之前, JoinPoint对象被推入线程的跟踪堆栈。序列完成后,其JoinPoint对象从堆栈弹出。跟踪堆栈中的JoinPoint 对象永远不会被解除引用,除非出现错误。
public abstract aspect ExceptionHandling { ... before(): exceptionAdvicePoints(){ pushJoinPoint(thisJoinPoint); } after(): exceptionAdvicePoints(){ popJoinPoint(); } ... }
清单 8. 调用跟踪通知
发生错误时,将运行清单6中的通知,同时调用下面的方法来呈现诊断。来自堆栈跟踪的信息包含在 FaultException 中,这些信息来自方面自身的每线程连接点堆栈。formatJoinPoint()方法从各JoinPointobject对象中提取我们需要的信息:限 定的方法或构造方法名称、其形参的名称和类型、作为自变量传递给那些参数的值。
public abstract aspect ExceptionHandling { ... private void logFault(FaultException fault) { ByteArrayOutputStream traceInfo = new ByteArrayOutputStream(); PrintStream traceStream = new PrintStream(traceInfo); fault.printStackTrace(traceStream); StringBuffer applicationTrace = new StringBuffer(); JoinPoint[] joinPoints = getJoinPointTrace(); for (int i = joinPoints.length - 1; i >= 0; i--) { applicationTrace.append("\n\t" + formatJoinPoint(joinPoints[i])); } recordFaultDiagnostics("Application Fault Detected" + "\n" + traceInfo.toString() + "\nApplication Trace:" + applicationTrace.toString()); } abstract void recordFaultDiagnostics(String diagnostics); private String formatJoinPoint(JoinPoint joinPoint) { CodeSignature signature = (CodeSignature) joinPoint.getSignature(); String[] names = signature.getParameterNames(); Class[] types = signature.getParameterTypes(); Object[] args = joinPoint.getArgs(); StringBuffer argumentList = new StringBuffer(); for (int i = 0; i < args.length; i++) { if (argumentList.length() != 0) { argumentList.append(", "); } argumentList.append(names[i]); argumentList.append(":"); argumentList.append(types[i].getName()); argumentList.append("="); argumentList.append(args[i]); } StringBuffer format = new StringBuffer(); format.append(joinPoint.getKind()); format.append(": "); format.append(signature.getDeclaringTypeName()); format.append("."); format.append(signature.getName()); format.append("("); format.append(argumentList); format.append(")"); return format.toString(); } }
清单 9.错误记录方法
ExceptionHandling 方面定义了抽象方法recordFaultDiagnostics(),允许应用程序指定希望如何记录方面所产生的诊断信息。应用程序在具体子方面内提供 该方法的一个实现。这种安排使记录细节脱离基本方面,从而保证了方面的最大灵活性。
一个方面观测应用程序其他部分的能力使之能够在错误发生时提供全面的诊断。它能够在不了解其他应用程序组件或不与其协作的前提下完成这一任务。将实现诊断记录关注点的代码聚集在一处是面向方面方法的一大优势。
结束语
错误-意外事件异常模型对于许多Java应用程序都很有帮助。使用AOP技术实现该模型的关注点具有一些令人着迷的优势。在编译时检测模型偏差 的能力只是其中之一。将与错误处理相关的逻辑隔离起来是另外一种优势。以错误和意外事件为依据进行思考能够消除应用程序中众多令人迷惑的代码。而以方面为 已经进行思考则会使应用程序的代码更简单,减少代码中充满无心之错的机会。那么,在考虑采用AOP时,这是否足够令人鼓舞?只有您能决定。
下载
- 下载本文所使用的 源代码
参考资料
- Dev2Dev文章 有效的Java异常(Dev2Dev中文版,2007年2月)提供了错误-意外事件异常模型的更详尽讨论。
- Eclipse AspectJ 项目站点包含关于AspectJ的各个方面。
- AspectJ Programming Guide 全面探讨了AspectJ语言的所有概念和结构。
- Dev2Dev文章 JRockit JVM对AOP的支持,第1部分(Dev2Dev中文版,2005年10月)简要介绍了AOP和AspectJ。
原文出处:http://dev2dev.bea.com/pub/a/2007/06/exception-advice.html
作者简介 | |
Barry Ruzek 被Open Group组织授予Master Certified IT Architect的称号。他有30多年开发操作系统和企业应用程序的经验。 |
评论
发表评论
-
Spring Milestone Maven Repository地址
2009-05-11 10:52 7871使用maven又想试用spring 3.0 m3的朋友可以用s ... -
HiddenHttpMethodFilter:自动转换Http Method
2009-03-29 11:21 5495REST的核心之一就是提供统一接口,也就是说对所有的资源(UR ... -
ActiveMQ5.0实战三:使用Spring发送,消费topic和queue消息
2008-08-28 18:21 33121ActiveMQ5.0实战一: 安装配 ... -
The smallwig theory of optimization
2008-04-17 09:49 1381There are three kinds of optimi ... -
ElementType.LOCAL_VARIABLE目前基本没用
2008-04-07 18:30 3905jdk5.0引入Annotation语法,@Target中的E ... -
Memcached java client 2.01发布
2008-04-05 21:39 2383com.danga.MemCached 发布2.0.1包括许多 ... -
Struts2中使用Stream Result Type
2008-04-05 18:25 18109Stream result type是Struts2中比较有用 ... -
NotSerializableException: EnhancerByCGLIB
2008-04-03 12:23 4295使用Ibatis时,为了获得更好的性能,我们一般都会将enha ... -
Pointcut命名有可能导致错误
2008-02-28 19:16 4317使用Spring @AspectJ方式的AOP,代码@Aspe ... -
Sping容器加载xsd文件时的问题
2008-01-31 17:56 6374今天遇到一个非常奇怪的spring容器问题,先看日志]-303 ... -
关于memcached client的选择
2008-01-10 15:29 13544Memcached(http://www.danga.com/ ... -
Java确实不适合于作为主要编程教学语言
2008-01-10 12:12 1651最近米国那边又在讨论这个话题, 孟岩也发了一篇帖子http:/ ... -
Spring 2.5Annotation使用基本类型和${}
2008-01-08 19:08 2748最近使用了Spring2.5 annotation风格的DI, ... -
ActiveMQ5.0实战二: 基本配置
2008-01-08 17:33 12459/** *作者:andyao,email:andyaoy@gm ... -
JMS消息类型模型
2008-01-04 18:12 9547/**作者:andyao,email:andyaoy@gmai ... -
ActiveMQ5.0实战一: 安装配置ActiveMQ5.0
2008-01-05 18:03 10095/** *作者:andyao,email:andyaoy@gm ... -
Spring Annotation和XML风格的声明式事务
2008-01-04 14:02 5608/***作者:andyao,email:andyaoy@gma ... -
国际化异常消息
2007-12-21 14:26 3868/**作者:andyao,email:andyaoy@gmai ... -
Exception for Action
2007-12-17 16:31 1544原文:http://www.javaworld.com/ja ... -
有效的Java异常
2007-12-17 15:51 1533原文出处:http://dev2dev.b ...
相关推荐
### ATM系统的面向方面模型 #### 一、引言 随着软件工程的发展,越来越多的技术被引入以提高系统的可维护性和灵活性。面向方面编程(Aspect-Oriented Programming, AOP)作为一种新兴的技术,它允许将横切关注点从...
1. **方面**:方面是封装横切关注点的独立单元,它包含一组相关的交叉切割行为和数据,比如一个日志记录系统就可以被视为一个方面。 2. **切面织入**:织入是将方面与主程序代码结合的过程,可以发生在编译时、加载...
面向方面编程方法引入了几个核心概念,包括方面(Aspect)、连接点(Join point)、切点(Pointcut)和通知(Advice)。方面是一种模块化横切关注点的方式;连接点是程序执行过程中的某个特定点,比如方法调用;切点...
3. **灵活的通知模型**:Spring AOP提供了多种类型的通知,包括around、before、after returning、after throwing等,使得开发者可以根据实际需求选择最适合的通知类型。 4. **丰富的切入点表达式语言**:Spring ...
- **包图**:用于组织模型元素,提高模型的可读性和可维护性。 - **部署图**:描述了系统硬件的物理配置及软件组件的部署情况。 #### 五、总结 综上所述,该在线火车票订购系统通过面向对象的方法进行了详尽的需求...
Spring AOP是Spring框架的一部分,它提供了全面的AOP支持,包括切入点定义、通知类型(前置通知、后置通知、异常通知、环绕通知、最终通知)、目标对象代理以及自定义AOP代理的能力。下面我们将深入探讨Spring AOP的...
- 内存模型:了解JVM内存结构,如堆、栈、方法区等。 - 面向对象:封装、继承、多态的概念与应用。 - 异常处理:异常分类,如何自定义异常,finally块的作用。 - 集合框架:List、Set、Map的区别,HashMap、...
综上所述,门禁系统的面向对象设计是一个综合性的实践项目,涉及到类的设计、数据库规划、异常处理、安全策略以及测试等多个方面。通过这个项目,学员可以提升自己的面向对象编程能力,理解如何将实际问题转化为...
不过,了解基本的MVC(模型-视图-控制器)架构仍然是有益的,因为它是许多Web应用程序设计的基础。 3. **数据库交互**:对于电子商城来说,数据存储和检索至关重要。PHP常与MySQL等数据库配合,使用SQL语句进行数据...
AOP(Aspect-Oriented Programming,面向切面编程)是一种编程思想和技术,它作为OOP(面向对象编程)的一种补充,主要解决了OOP在处理横切关注点方面的不足。在传统的面向对象程序设计中,通常会将应用划分为不同的...
- AOP(面向切面编程):通知类型,切点表达式,以及Aspect的定义。 - IOC(依赖注入):Bean的生命周期,以及@Autowired和@Resource的差异。 10. **设计模式** - 常见设计模式:单例模式、工厂模式、建造者模式...
- I/O模型:同步异步、阻塞非阻塞、缓冲I/O、异步非阻塞I/O(如epoll)。 5. **网络编程**: - 网络协议:TCP/IP协议栈,HTTP、FTP等应用层协议。 - 套接字编程:理解和编写客户端与服务器端的套接字程序,理解...
比如在家庭保安系统中,传感器检测到异常情况后,可能会向中央处理器发送消息,中央处理器再通知报警器启动报警。这些图可以帮助我们理解系统中各个组件如何协同工作以满足需求。 此外,状态图和活动图则用于描绘...
面向切面编程的主要目的是对程序运行期间的某些方面进行解耦,使得各个业务模块之间的依赖关系尽可能地减少。通过这种方式,可以更方便地维护代码,同时提高代码的复用性。AOP 是一种补充传统的面向对象编程(OOP)...
- 内存模型:了解Java内存模型(JMM),包括堆内存、栈内存和方法区。 3. **多线程**: - 线程创建:通过Thread类和实现Runnable接口两种方式创建线程。 - 线程同步:理解synchronized关键字、wait/notify机制...
综上所述,面向用户式智能告警系统的实现涉及数据处理、异常检测、机器学习、用户个性化等多个领域,需要综合运用多种技术和方法。这样的系统能有效提升问题发现效率,减轻运维人员的工作负担,为企业运营保驾护航。
- After-throwing通知:在目标方法抛出异常后执行。 - Around通知:包裹整个目标方法,可以控制方法的执行过程,包括是否执行方法本身。 - Introduction通知:允许向目标对象添加新的方法和属性。 2. 连接点...
- 学习如何分析和设计问题,将现实世界的问题映射到面向对象的模型中。 6. **进阶概念** - **设计原则**:SOLID原则(单一职责原则、开闭原则、里氏替换原则、接口隔离原则、依赖倒置原则)。 - **框架与库**:...
2. 内存模型:堆、栈、方法区、程序计数器、本地方法栈等区域的理解。 3. 垃圾回收:GC的工作原理,GC Roots,新生代和老年代的划分。 七、Spring框架 1. IoC容器:Bean的生命周期,依赖注入。 2. AOP:面向切面...