
我一开始没想好从哪里入手,想了好久因为切入点不同要做的基础工作就不同,切入不够合理后续翻工就比较麻烦。这次github上的提交的代码主要完成了国际化和异常处理的基础开发。
后来我想到要启动一个工程第一件事是搭建环境嘛,那环境配置好后大家通常做的第二件事情是启动项目看看环境配置对不对,于是从这里得到了启发。既然我的配置都弄好了,那就让网站的首页可以正常打开吧。
我用的tomcat做容器,由于不打算让tomcat接管网页等文件的读取和下载,我打算自己实现全部逻辑从而获得全部的控制权,所以我轻车熟路的在web.xml上配置了一个过滤器,让所有请求都被我的过滤器托管。
考虑到后续工程调试的方便性和开源代码让有兴趣的朋友把玩,我把过滤器类放在项目根目录包com.xanglong下,以后就算是一个陌生人都可以轻松展开项目断点到入口,然后跟踪整个项目的运作流程。
首先来看一张图,然后展开得到这张图的心路历程的话题。

看到有两个类Filter、Config是独立在包外的。Filter类前面说过了是为了方便开发调试和把玩学习才放这个位置的。Config类是放了一些项目的默认配置但是有必要也可以修改,再有开源后新人把玩项目可以根据Config类大致了解项目的五脏六腑,而至于一些二次开发的配置项则后续会走配置文件读取的模式,后续开发。
然后看到的是两个包frame包和i18n包。前者是要撑起整个网站的后端自研框架,后者是国际化语言包。
frame包下以后还会多出很多东西,这里我又分出了exception、io、log、net、session、util几个包。exception包里面有异常接口、异常包装、异常处理;io包里面目前有文件处理工具类后续会加入流的处理工具类;net包里目前有http工具类后续会加入socket工具类;sesison包里面处理session定义和会话信息管理;util包存放一些基础类型的工具类。还有Current类和Reflect类,Current类是ThreadLocal类的工具封装类,用于后续的会话信息存储,线程任务存储,连接对象存储等的操作;Reflect类是反射用的工具类,以后用到的东西会比较多,这个类也比较重要。
i18n包下放了一个中文简体语言包和几个类。像异常提示信息类的定义现在就存放在zh_ch包下,根据语言简写来做报名也方便后端实现语言包的解析和国际化。后面几个类一个是接口定义类I18n,一个是语言枚举类Language,还有一个是语言管理类LanguageManager。接口定义了国际化资源对象必须有code和name,然后在写语言包扫描的时候就可以通过接口实现反射出资源内容存储到缓存,最终就可以根据语言和编码提取到国际化文本内容。
写了一个请求状态码的异常枚举类,测试提取里面的文本资源,截图如下。

然后再来看一下本次代码提交的记录。

还不知道自己的包目录分配的合理不合理,我是按照项目角度来划分包的,像frame和i18n可以视作是两个项目,遇到同类型的项目要考虑后续移植和拓展的可能性。
来看一下github上发起pull-request的截图。

下面谈一下国际化的技术实现。
首先定义一个session会话保存对象,存储会话信息和一些跟随会话的配置信息,比如语言、请求地址等,然后这些信息跟随请求创建、存储、读取,为了方便获取会话信息,而不是作为参数传递,在ThreadLocal技术下存储本次会话的id,从而在主线程中的所有操作都可以跟踪到会话信息。然后在项目启动的时候,扫描语言包的资源目录,读取里面的所有枚举类,提取资源到缓存,根据包名来区分语言分类。这样在需要用到前端交互的地方就可以取到国际化的文本资源。
会话存储的语言选择是存储的枚举类,枚举值和包名一致,这样就可以从前端语言选择到后端资源文件对应起来。选择枚举作为资源文件有两个好处,一是在单个类里面天然去重保证唯一,在全局工程里面用项目包名做前缀可以做到全局唯一;二是枚举类在代码中做逻辑判断比较方便,方便链路跟踪和调试。
下面谈一下异常处理的技术实现。
定义异常接口IException要求实现getCode和getMessage方法然后就可以定义异常提示枚举类了。然后在定义异常封装类BizException在构造异常封装对象的实现里面完成国际化的处理,无会话信息默认按中文简体处理,有会话信息则从语言配置读取语言包。异常封装对象接收code和message,再添加一个Throwable成员变量用于接收所有java异常对象,并用业务异常抛出,最后统一由ThrowableHandler处理,这样就不用再关心一些方法开发的异常抛出还是自己处理问题了,统统可以交由自定义异常抛出再捕获处理。
而在异常处理类ThrowableHandler里面我可以处理日志、邮件等综合问题。根据配置开发模式可以打印异常栈,运行模式就不打印做到性能提升,同样也可以区分是否发送邮件告警,日志记录等。实际业务中有对外对接接口会有容错、重试、幂等这些操作,在异常情况需要单独捕获时可以在业务代码中记录日志,否则这些都可以在抛出业务异常后交由ThrowableHandler来处理。
还有一些根据指定异常来处理的,比如重定向到登陆页、错误页面等则是根据异常枚举来做逻辑判断的,这就让ThrowableHandler由能力统一处理所有异常。我这样的异常处理后续开发可以让一些工具方法不声明式的抛出指定异常或者捕获处理异常,均可向外抛出交由最外层处理,由于我完全保留了Java自带异常的对象信息,所以要介入第三方异常处理进行二次开发还是很轻松的,只需要在ThrowableHandler里面介入二次开发即可。
最后对于国际化,后续可以走资源文件包的分开部署或者数据库读取也是可以的,只需要在加载资源文件包的代码里面做二次开发即可。