基于springboot实现http响应异常信息国际化
背景
国际化是指在设计软件,将软件与特定语言及地区脱钩的过程。当软件被移植到不同的语言及地区时,软件本身不用做内部工程上的改变或修正。
本文提到的异常响应信息国际化是指:前端向后台发起请求,后台在处理逻辑中发生异常,把异常信息返回给前端,返回的异常信息应该支持国际化,能够对应特定的语言、地区等环境。例如,中文语言环境下返回的异常信息应该是中文的,英文语言环境下返回的则是对应的英文。
javaweb项目中,不管是对底层的数据操作,还是业务层的处理过程,还是控制层的处理,都不可避免的会遇到各种可预知的(业务异常主动抛出)、不可预知的异常需要处理。一般dao层、service层的异常都会直接抛出,最后由controller统一进行处理,并对前端请求进行异常响应。针对此处的业务异常应该做到设计合理,统一格式,并且支持国际化。
springboot是一个开源的Java/Java EE的应用程序框架,其设计目的是用来简化新Spring应用的初始搭建以及开发过程。利用springboot提供的API库,我们能方便地做到全局异常统一拦截,并且Spring提供了完善的国际化支持,在此基础上,我们能方便地做到全局异常信息的国际化。
原来存在的方案:
-
局部的异常信息国际化:程序在抛出异常时手动设置好国际化之后的异常message。
此种方案代码耦合性强,代码繁琐,处理逻辑过程中抛出异常时还得额外考虑对异常进行国际化操作,使得代码逻辑无法很好的集中在业务逻辑的处理。示意图如图1.1。
-
半异常信息国际化:程序中未采用配置文件来记录国际化资源,而是直接在程序中写死了固定的异常值,每条异常值记录分别对应着不同的异常编号、某几种语言的异常信息。
这是目前比较常见的方式,写起来方便,不需要在程序抛出异常时做国际化,只需在全局异常拦截中做统一的异常返回。但是,这种方式支持的异常信息比较单一,不能轻易添加多种语言的支持,只能改动现有程序的代码。示意图如图1.2。
-
使用编程语言自带的异常本地化接口。
不少后台语言提供了异常信息本地化接口,比如java提供的java.lang.Throwable#getLocalizedMessage方法,但是这种方式只能获取到本地语言相关的对应的信息,并不能很好的支持多种语言,以及多种语言间的自由切换。
现在的方案
此方案结合异常码、字符串占位符和资源文件的特点及优势。异常码可理解为一个异常信息的ID,具有唯一性,必要性;异常信息资源文件中的值可包含占位符(格式为{n}),处理异常时可用实际值替换,达到异常信息的进一步具体化,但不是必须的。资源文件就是内容为“异常码=提心信息”的记录的文件。
方案具体描述如下:
-
首先,在springboot项目中定义系统的http请求统一返回的数据格式,此处我们的定义包含三个属性: code(业务异常状态码)、message(异常提示信息)、data(无异常时返回的数据体)。对异常信息国际化主要体现在message属性,也就是返回前端给用户展示的异常提示信息。code属性用于确定唯一的异常。其次定义需要抛出的业务异常,其中主要包括code(异常码)和params(用于替换异常信息字符串占位符{n}的实际值)属性。
-
在项目路径下定义异常信息的资源文件,文件内容包含多条记录,每条记录为键值对的形式,即key=value。对需要实现的语言种类分别提供一份对应的文件,这些文件名除默认信息外,均包含语言(地区信息),文件中包含中key是一致的,value为相应语言的字符串组成。如果添加语言的支持,只需要添加一份对应的资源文件即可。以下包含默认、英文环境和简体中文环境下对应的异常提示信息国际化资源文件,如图2.1
-
利用springboot的提供的接口定义统一拦截器,并且注入http请求上下文和资源获取器,在拦截器中对应用抛出的异常进行捕获,针对上述特定的异常做国际化处理。
其国际化处理逻辑如下:根据http请求传入的语言信息指定特定的语言(地区)环境,如没有传入语言信息,则采用应用默认的语言配置;据此读取上述编写好的对应的异常信息资源文件,并依据捕获的异常中的code(异常码)查到到对应的字符串,将params实际值替换掉字符串中的占位符,完成后即为返回给前端的异常提示信息。
-
在controller、service和dao层中根据程序逻辑抛出相应异常,其中异常码应该在资源文件中已定义。示例代码如下图2.3
-
整体流程图如下图2.4
demo
以下是一个demo,其中提供了必要的代码注释,以便于理解。
-
自定义异常结构,包含code和params
-
异常资源文件定义
-
全局异常拦截
-
代码逻辑中抛出异常
-
请求响应示例
请求1:http://127.0.0.1:8080/demo?lang=en&msg=a
返回:
请求2:http://127.0.0.1:8080/demo?lang=zh_CN&msg=a
返回:
请求3:http://127.0.0.1:8080/demo?lang=en&msg=c
返回:
请求4:http://127.0.0.1:8080/demo?lang=zh_CN&msg=c
返回:
源码
https://github.com/newhoo/i18n-exception-demo
总结
- 此方案能较好地处理异常信息的国际化,能自如地添加资源文件,不需要改动此方案的现有代码,这也使得程序耦合性降低;
- 统一的异常格式定义、全局异常拦截极大程度上减少了代码耦合性,大大简化了团队编码中对异常逻辑的处理,能减少重复性编码,有利于提高工作效率;
- 现有方案未能较好地支持异常信息中特定值的支持,此方案利用设置占位符的方式,较好地做到了支持,灵活方便;
- springboot支持的国际化实现中默认提供了缓存支持,性能良好。
参考
(文中配图有空再改!!)