{"id":36420416,"url":"https://github.com/yxc023/expressive-exception","last_synced_at":"2026-01-11T17:09:42.277Z","repository":{"id":57729856,"uuid":"179086968","full_name":"yxc023/expressive-exception","owner":"yxc023","description":"A More Expressive Exception Library.  Build an exception with optional fields: code, message, tip, level, data.","archived":false,"fork":false,"pushed_at":"2021-05-25T10:12:05.000Z","size":124,"stargazers_count":4,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2023-07-28T22:08:34.690Z","etag":null,"topics":["exception","java"],"latest_commit_sha":null,"homepage":"http://blog.yangxiaochen.com/page/projects.html","language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/yxc023.png","metadata":{"files":{"readme":"README.adoc","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2019-04-02T13:44:00.000Z","updated_at":"2022-05-28T19:08:53.000Z","dependencies_parsed_at":"2022-08-30T09:11:01.862Z","dependency_job_id":null,"html_url":"https://github.com/yxc023/expressive-exception","commit_stats":null,"previous_names":["yxc023/exception"],"tags_count":0,"template":null,"template_full_name":null,"purl":"pkg:github/yxc023/expressive-exception","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/yxc023%2Fexpressive-exception","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/yxc023%2Fexpressive-exception/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/yxc023%2Fexpressive-exception/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/yxc023%2Fexpressive-exception/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/yxc023","download_url":"https://codeload.github.com/yxc023/expressive-exception/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/yxc023%2Fexpressive-exception/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28314264,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-11T14:58:17.114Z","status":"ssl_error","status_checked_at":"2026-01-11T14:55:53.580Z","response_time":60,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["exception","java"],"created_at":"2026-01-11T17:09:41.559Z","updated_at":"2026-01-11T17:09:42.271Z","avatar_url":"https://github.com/yxc023.png","language":"Java","funding_links":[],"categories":[],"sub_categories":[],"readme":"= A More Expressive Exception Library\n杨晓辰\n2019-04-14\n:toc:\n:toclevels: 4\n:icons: font\n\nA More Expressive Exception Library.\n\nBuild an exception with optional fields: `code`, `message`, `tip`, `level`, `data`.\n\n一个能够包含更多信息的异常基础库. 是一套异常设计和处理的方法论的落地.\n\n== 如何抛出一个异常\n\n. 继承 `BaseExprException`\n+\n----\npublic class ServiceException extends BaseExprException {\n    // ...\n}\n----\n\n. 抛出个富有表达性的异常吧\n+\n----\nthrow new ServiceException(\"Order not found\")\n                .code(\"ORDER_NOT_FOUND\")\n                .tip(\"订单不存在, 请确认订单号是否正确\")\n                .data(new HashMap\u003cString, String\u003e(){{put(\"key\", \"value\");}})\n                .var(\"orderId\", 1002)\n                .var(\"time\", new Date())\n                .serviceLevel();\n----\n\n. 异常打印出来信息是这样\n+\n----\ncom.yangxiaochen.exception.test.application.exception.ServiceRuntimeException: [ORDER_NOT_FOUND] Order not found, tip: 订单不存在, 请确认订单号是否正确, ctxVars: {orderId=1002, time=Thu Feb 25 17:22:09 CST 2021}\n----\n\n. 如果 web 层捕获这个异常, 我们可以:\n.. `code: ORDER_NOT_FOUND` 作为返回 code - **给程序读**\n.. `tip: 订单不存在, 请确认订单号是否正确` 作为弹出提示 - **给用户读**\n.. `ctxVars: orderId=1002` 和 `message: Order not found` 为排查问题提供更多的信息 - **给程序员读**\n\n\n== USAGE\n\n=== 引入\n----\ndependencies {\n    compile 'com.yangxiaochen:expressive-exception-core:1.3.1-RELEASE'\n}\n----\n\n=== 使用\n\n在 `exception-core` 中, 提供了 `HasTip`, `HasCode`, `HasData`, `HasLevel` 几个接口, 你需要定义自己的异常类时:\n----\npublic class MyException extends Exception implements HasTip, HasCode, HasData, HasLevel {\n    ...\n}\n----\n\n为了方便定义异常类, 提供了两个抽象类 `BaseExprException`, `BaseExprRuntimeException`, 可以直接继承这两个类:\n\n----\npublic class MyException extends BaseExprException {\n    ...\n}\n----\n\n打印出的异常 log 例子:\n\n----\ncom.yangxiaochen.exception.test.application.exception.ServiceRuntimeException: [SERVICE_EXCEPTION] default service exception, tip: 默认业务异常, ctxVars: {fooId=1002, time=Wed Aug 21 18:17:26 CST 2019}\n...\n----\n\n=== 拓展异常 level\n\n可以通过实现 `ExceptionLevel` 来定义新的异常 level.\n\n== 如何处理异常\n\n##异常定义只是一个方面,  如何看待, 解释, 处理我们定义的异常是另一个方面.##\n\n=== spring mvc\n\nsee link:expressive-exception-spring-mvc/src/main/java/com/yangxiaochen/exception/spring/ExceptionHandler.java[spring mvc exception handler]\n\n----\n\ndependencies {\n    compile 'com.yangxiaochen:expressive-exception-spring-mvc:1.2.1-RELEASE'\n}\n----\n\n提供了一个默认的 `ExceptionHandler`, 来统一处理异常, 其核心异常处理方法实现如下:\n\n----\npublic ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {\n    if (pathPrefixs.stream().noneMatch(prefix -\u003e request.getRequestURI().startsWith(prefix))) {\n        return null;\n    }\n\n    ex = translateException(ex, request);\n    if (ex == null) {\n        return null;\n    }\n    logAction.log(request, ex);\n\n    return errorViewResolver.resolve(request, response, ex);\n}\n----\n\n加入到 spring mvc 框架中实现异常的统一处理:\n\n----\n@Configuration\npublic class MvcConfig implements WebMvcConfigurer {\n\n    private boolean printStack = false;\n    private MappingJackson2JsonView view = new MappingJackson2JsonView();\n\n    @Override\n    public void extendHandlerExceptionResolvers(List\u003cHandlerExceptionResolver\u003e resolvers) {\n        ExceptionHandler exceptionHandler = new ExceptionHandler();\n        resolvers.add(0, exceptionHandler);\n    }\n}\n----\n\n可以对 `ExceptionHandler` 的处理行为进行定制:\n\n----\nexceptionHandler.setPathPrefixs(Arrays.asList(\"/web/\", \"/api/\"));\nexceptionHandler.setErrorViewResolver((request, response, ex) -\u003e {\n    ModelAndView mv = new ModelAndView();\n    mv.addObject(\"msg\", ex.getMessage());\n    mv.addObject(\"success\", false);\n    if (ex instanceof HasCode) {\n        mv.addObject(\"code\", ((HasCode) ex).getCode());\n        if (((HasCode) ex).getCode() == null) {\n            mv.addObject(\"code\", 0);\n        }\n    }\n    if (ex instanceof HasTip) {\n        mv.addObject(\"tip\", ((HasTip) ex).getTip());\n        if (ex.getMessage() == null) {\n            mv.addObject(\"msg\", ((HasTip) ex).getTip());\n            mv.addObject(\"message\", ((HasTip) ex).getTip());\n        }\n    }\n    if (ex instanceof HasData) {\n        mv.addObject(\"data\", ((HasData) ex).getData());\n    }\n    if (printStack) {\n        mv.addObject(\"stackTrace\", getStackFrames(ex));\n    }\n    mv.setView(view);\n    return mv;\n});\n----\n\n=== dubbo\n\nsee link:expressive-exception-dubbo/src/main/java/com/yangxiaochen/exception/dubbo/GlobalExceptionFilter.java[dubbo filter]\n\n\n== 动机\n在业务项目实践中, 异常经常用来传递一些业务错误或者警告.\n\n通常, 这些业务错误和警告, 经常要包含更多的信息, 比如错误编码, 错误消息. 有时为了给用户更好体验, 还会放入一些便于用户阅读的消息. 甚至, 还会需要一些数据.\n\n== 设计意图\n\n在多个业务系统实践中, 我做了一个总结, 一个好用的异常, 要包含以下几个数据:\n\n* `message` - 异常都会包含的消息\n* `code` - 异常编码\n* `tip` - 异常提示\n* `data` - 可选, 异常携带的数据\n* `level` - 可选, 异常级别\n\n下面对每一项进行详细说明.\n\n=== message\n\n通常意义下的 exception message, 通常是对异常的描述. 比如当要删除一个订单, 但给的订单号并不存在时:\n\n----\nOrder not found, id: 1001\n----\n\n通常是英文, 且格式标准专业, 包含了异常相关足够的信息.\n\n=== code\n\n因为业务比较复杂, 异常情况也很多, 我们基本不会对每一种异常设计一个异常类型. 比如在处理订单操作时, 我们只定义一种异常类型: `OrderOperationException`.\n\n那么更细节的异常我们可以通过编码来表示:\n\n----\nSUCCESS - 成功都是相同的\n\n// 而失败各有不同\nFAILURE - 通用的失败编码\nORDER_NOT_FOUND - 订单不存在\nORDER_HAD_PAYED - 订单状态异常: 已经支付过了\n...\n----\n\n`code` 使用字符串, 好处是更易读.\n\n=== tip\n\n`tip` 和 `message` 很像, 都是用来表达异常的信息. `tip` 的设计意图在于##提供用户可读的异常信息##. 比如\n\n----\n要操作的订单号[1001]不存在.\n订单[1001]已经支付过了.\n----\n\n=== data\n\n`data` 的作用是与请求成功响应时返回的数据项对齐.\n\n在发生异常时, `data` 其实并不常用, 场景比较少. 只是在发生异常时需要返回一些关联数据. 举一个场景:\n\n当用户购买一个比较抢手的产品时, 有一个购买限制: 一个用户下单后必须支付才能下第二单.\n\n那么, 当用户触发这个限制时, 返回的异常中要包含未支付的订单号, 再由统一的异常处理转换成带有 data 的异常返回信息.\n\n=== level\n\n异常为什么要分级? 因为我在业务逻辑中, 所有不符合最常规业务逻辑流程的, 都使用异常来返回.\n\n那么有的异常可能并不算是错误. 比如登录时账号密码不匹配, 这并不是系统 bug 引起的错误, 也不需要记录 error 日志, 甚至报警.\n\n而有的, 比如逻辑执行中, 某个数据一定应该存在的, 结果没有查询到, 代表着数据完整性异常, 那么这是真真正正的 error.\n\n而其他的, 甚至还有说偶尔异常没问题, 大量异常有问题的. 比如客户端断开连接, 偶尔出现很正常, 但大量出现就是有问题的.\n\n所以在设计中, 默认将异常 level 分为了两类:\n\n* SERVICE_LEVEL\n* ERROR_LEVEL\n\n\n\n== 意见收集\n\n这个项目即是一个类库, 更是一个异常设计和处理的方法论, 类库是方便方法论落地的措施.\n\n如果你有不同的想法和意见, 欢迎 issue 交流.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fyxc023%2Fexpressive-exception","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fyxc023%2Fexpressive-exception","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fyxc023%2Fexpressive-exception/lists"}