异常以及异常处理框架探析
简介:
本文主要与您探讨异常以及异常处理相关方面的一些知识,主要包括检查异常与非检查异常的理解,多视角观察理解异常,进而探讨关于构建稳健且可扩展的异常框架方面的一些设计原则。
概述:
一般情况下,企业级应用都对应着复杂的业务逻辑,为了保证系统的健壮,必然需要面对各种系统业务异常和运行时异常。
不好的异常处理方式容易造成应用程序逻辑混乱,脆弱而难于管理。应用程序中充斥着零散的异常处理代码,使程序代码晦涩难懂、可读性差,并且难于维护。
一个好的异常处理框架能为应用程序的异常处理提供统一的处理视图,把异常处理从程序正常运行逻辑分离出来,以至于提供更加结构化以及可读性的程序架构。另外,一个好的异常处理框架具备可扩展性,很容易根据具体的异常处理需求,扩展出特定的异常处理逻辑。
另外,异常处理框架从一定程度上依赖并体现系统架构层次。系统架构决定了系统中各个子系统,各个层次之间的交互,而异常处理框架则统一体现这种架构中的各种交互所发生的错误、异常。因此,异常处理框架是系统架构时就应该考虑的问题。
本文将对异常相关方面做一些讨论,并进而探讨一些关于构建稳健且可扩展的异常处理框架方面的视角或设计原则。由于本文引入一部分 Java 语言中异常相关的概念,因此本文假设您熟悉 Java 相关基础知识以及了解 Java EE 和 EJB 相关技术。
在 Java 程序设计语言中,使用一种异常处理的错误捕获机制。当程序运行过程中发生一些异常情况,程序有可能被中断、或导致错误的结果出现。在这种情况下,程序不会返回任何值,而是抛出封装了错误信息的对象。Java 语言提供了专门的异常处理机制去处理这些异常。如图 1 所示为 Java 异常体系结构:
图 1. Java 异常体系结构
检查 (Checked) 异常与非检查 (Unchecked) 异常
Java 语言规范将派生于 Error 类或 RuntimeException 类的所有异常都称为非检查异常。除非检查异常以外的所有异常都称为检查异常。检查异常对方法调用者来说属于必须处理的异常,当一个应用系统定义了大量或者容易产生很多检查异常的方法调用,程序中会有很多的异常处理代码。
如果一个异常是致命的且不可恢复并且对于捕获该异常的方法根本不知如何处理时,或者捕获这类异常无任何益处,笔者认为应该定义这类异常为非检查异常,由顶层专门的异常处理程序处理;像数据库连接错误、网络连接错误或者文件打不开等之类的异常一般均属于非检查异常。这类异常一般与外部环境相关,一旦出现,基本无法有效地处理。
而对于一些具备可以回避异常或预料内可以恢复并存在相应的处理方法的异常,可以定义该类异常为检查异常。像一般由输入不合法数据引起的异常或者与业务相关的一些异常,基本上属于检查异常。当出现这类异常,一般可以经过有效处理或通过重试可以恢复正常状态。
由于检查异常属于必须处理的异常,在存在大量的检查异常的程序中,意味着很多的异常处理代码。另外,检查异常也导致破坏接口方法。如果一个接口上的某个方法已被多处使用,当为这个方法添加一个检查异常时,导致所有调用此方法的代码都需要修改处理该异常。当然,存在合适数量的检查异常,无疑是比较优雅的,有助于避免许多潜在的错误。
到底何时使用检查异常,何时使用非检查异常,并没有一个绝对的标准,需要依具体情况而定。很多情况,在我们的程序中需要将检查异常包装成非检查异常抛给顶层程序统一处理;而有些情况,需要将非检查异常包装成检查异常统一抛出。
从应用系统最终用户的角度来看,用户所面对的是系统中所提供的各种业务功能以及系统本身的管理功能。用户并不理解系统内部是如何实现以及如何运行的,与系统开发者存在天然的鸿沟,系统运行对用户来说如同一个黑盒一样。对用户而言,系统所出现的任何异常或错误,都属于系统运行时异常。对于这些异常,有些异常是用户可以理解并能解决的;而另外一些异常是用户无法理解和解决的。当一个系统错误出现时,系统本身需要反馈给用户一种可理解的业务相近的信息,从而用户可以根据这些信息去尽可能解决问题。另一方面,有一类错误属于系统内部运行异常或错误,用户对此类错误根本无能为力。而这类异常同样需要提供足够详细的信息,系统管理员可根据这类异常尽可能解决。一般情况下,如果异常面向系统用户,以系统异常呈现更好。
从系统开发者角度来看,更多的是从系统内部逻辑来看异常。有一部分异常需要内部截获处理,而另外一部分异常对于异常产生源而言无法进行有效处理,从而需要向外抛出异常以待合适的调用者进行处理。对于开发者而言,需要预见异常,并且需要考虑何时处理异常,何时抛出异常,必要时以某种方式记录或通知异常。总而言之,开发者需要通过对系统运行时可能出现的异常尽可能地处理以保证系统的正常运行,并对于无法处理的异常以一种合适的方式记录、通知、呈现以便找到发生异常的原因,从而解决或避免异常。
图 2. 异常视图
如图 3 所示,为一个常见的一般性异常处理代码结构。其中,try 语句块代表要运行的代码并受异常监控,其中代码发生异常时,会创建一个异常对象并抛出。
catch 语句块会捕获 try 代码块中发生的异常,并与自己的异常类型进行匹配,若匹配,则在其 catch 代码块中进行异常处理。catch 语句块可以有多个,当 try 语句块中抛出一个异常时,会针对每个 catch 块进行匹配,一旦与某个 catch 块匹配,就进入该 catch 块处理并不再与其他 catch 块匹配。
finally 语句块是紧跟 catch 语句后的语句块,该语句块总会在方法返回前执行,无论 try 语句块是否发生异常。
图 3. 异常处理代码结构
前面说过,一般当程序发生异常时,通常异常处理可能需要做一些通用处理,如异常日志记录、异常通知,重定向到一个统一的错误页面(如 Web 应用)等。如果这些通用异常处理放置于 catch 块中,将导致大量的重复代码,从而可能引起日志冗余、同一异常的实现多样化等问题。另外,大量异常处理程序放置于 catch 块中造成程序的高耦合性。为了解决此类问题,有必要分离出异常处理程序、统一异常处理风格、降低耦合性、增强异常处理模块的复用程度。通常的异常处理模式包括业务委托模式(Business Delegate)、前端控制器模式(Front Controller)、拦截过滤器模式(Intercepting Filter)、AOP 模式、模板方法模式等。
为了解决基本异常处理结构所带来的问题,不妨把异常相关处理委托给一个专用 service 代理,从而分离出异常处理业务,以一种统一的方式和逻辑进行处理,如图 4 所示。异常 Service 主要处理两个方面:一方面是要按照实际系统要求调用通用处理程序处理异常,如日志记录、异常界面展示、异常通知等;另外一方面,需要通过过滤所接受到的异常类型,找到定制的异常处理程序进行异常处理。对于异常 service 的应用一般可以通过在系统的顶层进行异常自动拦截(一般多层系统中尤为普遍,如放置于前端控制器 Front Controller),或者主动调用异常 service 进行处理。
图 4. 一般性异常处理框架
如图 5 所示类图显示了一个具体的异常处理框架:
图 5. 异常处理框架类图样例
该框架主要包括三部分:异常 Service、异常处理过滤器、系统异常层次定义。
异常 Service:整个异常框架的核心,通常用于主动拦截异常或被动调用处理异常。根据具体业务需要,调用通用服务程序进行一般化异常处理,如日志服务、异常消息通知服务等;另外,异常 Service 最主要目的用于拦截并处理异常,其需要维护定制的异常处理器链,用于特定类型异常的特定处理。
异常处理过滤器:维护系统中各种异常处理器,包括增加异常处理器、删除异常处理器、查找异常处理器操作等。其中最主要的功能是接收特定异常并找到与之匹配的异常处理器进行处理。异常处理过滤器具体实现可以通过一个配置文件维护所有异常与异常处理器的映射,另外可以通过另外一个配置文件维护系统中所有已定义的异常处理器。从而,异常处理过滤器可以通过配置文件进行初始化操作。
异常层次定义:异常层次定义应用系统的异常基础结构,是异常处理过滤器所处理的目标异常类型集合。
异常层次结构应该以一种普遍通用的原则定义。为此,我们可以利用面向对象语言具备多态的性质,隐藏异常的实际实现。对于异常 service 而言,只需要捕获最基本的应用程序异常 AppException,异常处理过滤器会自动过滤实际异常类型并找到相应的异常处理器。另外,在方法的 throws 语句中勿需放入大量的检查异常;对方法调用者也不会出现混乱的 catch 块,最多可能只存在一个用于处理基本应用程序异常 AppException(委托给异常 service 处理)。
前面的章节过,应用系统异常可以从用户和开发者两个视角去考虑。因此,我们可以把异常划分为业务操作异常和系统内部运行时异常两种类型。抛出业务级异常或系统运行时异常的决策,需要与应用系统本身的架构层次相结合,考虑所要处理异常的层次。如图 6 所示为一个典型的异常层次结构:
图 6. 异常层次类图样例
其中,BussinessException 属于基本业务操作异常,所有业务操作异常都继承于该类。例如,通常 UI 层或 Web 层是由系统最终用户执行业务操作驱动,因此最好抛出业务类异常。ServiceException 一般属于中间服务层异常,该层操作引起的异常一般包装成基本 ServiceException。DAOException 属于数据访问相关的基本异常。
对于多层系统,每一层都有该层的基本异常。在层与层之间的信息传递与方法调用时候,一旦在某层发生异常,传递到上一层的时候,一般包装成该层异常,直至与用户最接近的 UI 层,从而转化成用户友好的错误信息。
前面关于检查异常和非检查异常的论述中提到,在存在大量的检查异常的程序中,意味着很多的异常处理代码,导致晦涩的异常处理,并且检查异常容易破坏接口方法。为了解决检查异常带来的缺陷,我们可以利用异常转译的方法,将检查异常转化为非检查异常,由异常 service 拦截处理。
异常转译就是将一种异常转换为另一种异常。异常转译针对所有继承 Throwable 超类的异常类而言的。如下图 7 中代码所示展示了异常转译的一个例子:
图 7. 异常转译代码样例
对于任何一个应用系统而言,系统运行过程中所发生的任何异常或错误都应该以合适的方式通知用户或记录;由于异常源可能来自很多方面,其所抛出的异常大多不能为系统用户所理解,此时就必须将各种类型的异常转化成各种用户可理解的异常。这也是异常框架所需要关注和解决的方面。
在异常的层层转译过程中,就形成一个异常链。整个异常链保存了每一层的异常原因。通过递归调用 getCause() 方法可以遍历所有的异常原因。需要注意的是,在形成异常链的过程中,会消耗较多的资源,导致系统性能降低。这里涉及异常原理,在此不必多说,有兴趣可查阅相关资料。在本文提出的异常框架中,异常 service 可以截获来自系统各层的异常,而勿需异常层层转译。
本文首先简要介绍了异常的基本概念以及 Java 语言中基本异常体系结构,重点介绍了 Java 异常中的检查 (checked) 异常和非检查 (unchecked) 异常两个概念。然后,着重介绍了对于一个应用系统从用户和开发者两个角度如何去看应用系统所发生的异常;通过多视角看应用系统异常对于设计一个合理的系统异常框架可以提供较好的设计原则。最后介绍了一个通用可扩展的异常处理框架,包括设计原则,异常层次结构的定义以及异常转译方面的考虑。
尤其对于比较大的软件系统,异常处理框架是软件系统体系结构需要考量的很重要的一方面。好的异常处理结构既能条理清晰地处理异常,又能保证异常处理的可扩展性与可用性,最后还需要保证系统的性能不受额外的损失。
相关推荐
:本文主要与您探讨异常以及异常处理相关方面的一些知识,主要包括检查异常与非检查异常的理解,多视角观察理解异常,进而探讨关于构建稳健且可扩展的异常框架方面的一些设计原则。一般情况下,企业级应用都对应着...
【大数据环境下的分布式数据流处理关键技术探析】 随着信息技术的快速发展,大数据已成为现代企业和机构的重要资产。大数据环境下的数据流处理面临着实时性、持续性和高可靠性的挑战。分布式数据流处理系统(DDSPS...
JSON序列化工具探析 JSON序列化工具是指将Java对象转换为JSON格式的工具。JSON作为一种轻量级的客户端数据交换格式,经常被应用于Web应用程序中,是一种理想的数据交换格式。在开发软件的过程中,选择合适的JSON...
这篇学术论文《SDN攻防技术探析》主要探讨了SDN在安全方面的挑战,以及针对这些挑战提出的防御策略。 文章首先指出了传统网络由于硬件、操作系统和应用的紧密耦合,导致创新和演进困难,而SDN通过将控制平面和数据...
Java程序设计语言自1995年发布以来,已经成为全球软件开发领域不可或缺的重要工具,尤其在企业级应用、网络服务、移动应用以及大数据处理等方面表现出卓越的性能和灵活性。本篇将深入探讨Java在软件开发中的核心优势...
智能巡检系统的主要功能包括数据采集、数据分析和处理、数据监控以及异常告警。数据采集利用SNMP等工具获取资源配置、性能、网络告警和脆弱性等信息,并进行预处理。数据分析和处理涉及实时数据处理总线、事件分析、...
Java编程语言自1995年发布以来,已经成为全球范围内广泛应用的编程语言,尤其在企业级应用、Android移动开发以及云计算领域占据主导地位。本文将深入探讨Java编程的特点和技术,帮助读者理解其核心优势和应用。 一...
对于初学者,理解Java基础语法、面向对象编程概念和异常处理至关重要。随着经验积累,深入学习集合框架、多线程、网络编程、设计模式以及框架应用将有助于提升开发能力。 总之,Java编程语言凭借其强大的功能、...
3. **异常处理**:Java异常处理机制通过try-catch-finally语句块来捕获和处理运行时错误,确保程序的健壮性。 4. **I/O流**:Java的I/O流体系提供了处理输入输出的强大功能,包括字符流、字节流、缓冲流和转换流等...
本文首先探讨了三种主流的大数据处理方式,即Java分布式编程、Hadoop框架和Spark框架,并通过对比这三种技术在处理广播发射机自动化系统数据中的实际应用,揭示了大数据技术在该领域的优势和潜力。 Java分布式编程...
3. 数据处理层:采用大数据处理框架,如MapReduce或Spark,进行数据清洗、转换和分析。 4. 数据分析层:利用机器学习和人工智能算法,对数据进行深度挖掘,发现规律,生成洞察。 5. 应用服务层:将分析结果通过...
在软件工程实践中,反射机制的应用场景包括但不限于:实现依赖注入框架,编写通用测试框架,开发插件系统和动态代理,以及处理运行时出现的异常情况等。通过合理运用反射,开发者可以编写出更加灵活和可维护的代码。...
Java的异常处理机制强制程序员处理可能出现的问题,通过try-catch-finally语句块捕获并处理异常,提高了程序的健壮性。 八、Java的开源生态 由于Java的开源性质,它拥有庞大的开发者社区和丰富的开源项目,如Spring...
例如,实时流处理技术、并行计算框架、数据挖掘算法和隐私保护技术等。 【大数据工程】大数据工程关注如何将大数据技术应用于实际项目中,包括数据质量保证、系统集成、数据生命周期管理等,旨在建立可靠、高效的...
总结来说,Java语言借助JDBC提供了一套强大且灵活的数据库访问机制,通过合理使用JDBC特性以及优化SQL语句和其他数据库操作,可以显著提升Java应用程序的数据处理效率。在实际开发中,结合最佳实践和具体场景选择...
例如,Hadoop、Spark等大数据处理框架,其默认配置往往不够安全,需要进行适当的调整和加固。同时,对于数据湖、数据仓库等存储解决方案,必须强化访问控制和权限管理,防止未授权访问。 此外,威胁检测和响应机制...
【Hibernate查询方法之探析】 Hibernate作为一款强大的Java持久化框架,提供了多种查询方式来适应不同的数据检索需求。本文将深入解析五种主要的Hibernate查询方法:QBE(Query By Example)、QBC(Query By ...
这可能包括异常检测算法、网络重构策略以及容错机制。 7. **法律和政策框架**:除了技术层面的解决方案,也需要建立相应的法规和政策,以规范无线传感器网络的使用,保护用户隐私,防止滥用。 总的来说,这篇论文...
数据挖掘层还可以对数据进行关系、分类、聚集同类数据以及发现异常等操作。应用层则实现了人机交互,包括用户注册、识别和服务结果展示等功能。 在数字图书馆的实际应用中,数据挖掘可以针对个人提供个性化服务,...