📌 术语定义
- Spring Boot
-
Spring Boot
是启动Spring
项目的工具,是一些库的集合,能够被任意项目的构建系统所使用,用于简化Spring
应用的初始搭建以及开发过程,使研发不再需要定义样板化的配置。更多详情请参见Spring 官网
。 - starter
-
starter
是Spring Boot
提供的一种管理依赖的方式,借助Spring Boot
中自动配置的能力,各个功能组件都直接以starter
的形式存在,使用时只需要引入一个starter
依赖即可。
Spring Boot
提供了很多官方的starter
内容。
在Shoulder
中,各个功能也是以starter
的形态存在的,starter
中包含了该模块所需的所有jar
与相关完整依赖、组件的初始化逻辑以及一些默认配置参数等。更多关于starter
的描述和逻辑关系请参见Spring Boot Starters
内容。
本文档讲述内容与代码示例可参见 Shoulder-Demo1 |
能力激活方式:引入 Shoulder
任意一个 starter
<dependency>
<groupId>cn.itlym</groupId>
<artifactId>shoulder-starter</artifactId>
<version>1.0.0<version>
</dependency>
compile 'cn.itlym:shoulder-starter:1.0.0'
全局ID生成器
Shoulder
提供了两类开箱即用的全局id生成器:StringGuidGenerator
、LongGuidGenerator
,您可以像这样使用它们。
@Autowired
private StringGuidGenerator stringGuidGenerator;
@Autowired
private LongGuidGenerator longGuidGenerator;
public void testGuid() {
long guidLong = longGuidGenerator.nextId();
String guidString = stringGuidGenerator.nextId();
// 批量获取
long[] guidLongArr = longGuidGenerator.nextIds(10);
String[] guidStringArr = stringGuidGenerator.nextIds(10);
}
动态字典
Shoulder
提供了一套开箱即用的字典管理能力,极大的降低了字典模块的编码成本,一个 @EnableDictionaryItemEnum
注解实现以下功能:
-
枚举
←→String
/Number
/DTO
自动转换 -
结合 Shoulder-Web 获得更丰富的功能:
-
支持
JSR 303
规范的校验注解 -
开箱即用的字典搜索、管理接口
-
可视化的查询页面
-
-
-
可定制的存储模式,并提供多种默认的存储模式实现
-
带有缓存的查询与搜索
-
不停机添加枚举项
-
…
-
字典在各行各业都会又,举例:在租借系统,以车为例,汽车品牌 、出租用途 、缴费状态 这些可能又多种可选项作为动态字典是比较合适的;同样的,在办公系统中 办公方式 、办公园区 、考勤方式 等也是合适的项,实际业务系统中非常常见。
|
增强的类型转换器
在 Spring
官方 ConversionService
接口基础上新增更多类型转换,如:
-
String
←→DataSize
←→Long
-
String
←→Date
←→LocalDateTime
←→Instant
-
String
←→Date
←→LocalDate
←→Instant
-
支持多级
ConversionService
供高级用户使用已有的ConversionService
并支持自定义优先级
✅ 请放心使用,这些转换代码几乎都是 JDK
/ Sping
中的工具类,无需担心 BUG
与稳定性,Shoulder
拥抱复用与优雅,杜绝造轮子。
全局锁
基于 JDK
的标准 Lock
接口,提供了 ServerLock
,在其基础之上增加了更丰富的并发接口:如 tryLock
,isHoldLock
,且支持通过配置 cluster=true/false
快速支持集群模式的锁。
@Autowired
private ServerLock serverLock;
public void tryLock() {
LockInfo lockInfo = new LockInfo(shareKey, Duration.ofSeconds(10));
jdbcLock.lock(lockInfo);
try {
// 做你的事情
String xx = "do something";
} catch (Exception) {
log.xxx
} finally {
jdbcLock.unlock(lockInfo);
}
}
基于 Shoulder 全局锁
,即使开始为单机项目,以后集群改造时无需修改代码,通过配置即可支持集群模式下的全局锁。
默认为 单机模式 ,若想支持集群,可以引入数据库或 Redis 相关依赖,设置 shoulder.lock.type=jdbc/reedis 即可,无需修改任何带代码。
|
Shoulder
默认提供了以下几个实现:
-
JdkLock: 基于
JDK
的ReentrantLock
,不支持集群。 -
FileSystemLock: 基于
操作系统-文件系统
的锁实现,支持同一机器的多个应用共享资源控制,适用于小型项目,无数据库场景。 -
JdbcLock: 基于
DB
+JDBC
的分布式锁实现,支持集群,高可用。 -
RedisLock: 基于
Redis
的分布式锁实现,支持集群,高并发下与JdbcLock
相比性能更佳,实现借鉴了Redisson
。 -
MemoryLock:基于内存的锁,主要用于演示工程中使用,不支持集群。【⚠️ 仅用于
Demo
中】
用锁时,牢记口诀 一锁、二判、三更新 用锁不出错哦!
|
运行上下文 AppContext
Shoulder
提供了基于 ThreadLocal
的 AppContext
,用于存储单次业务处理信息的上下文,可据此实现 tracer 追踪
、流量染色
、压测识别
、流量血缘识别
等高级能力。
public void createUser(CreateUserRequest req, HttpHeaders httpHeaders) {
// 操作上下文
AppContext.setUserId(httpHeaders.get("userId"));
AppContext.setUserName(httpHeaders.get("userName"));
AppContext.setTenantCode(httpHeaders.get("tenant"));
AppContext.setLocale(httpHeaders.get("language"));
AppContext.setTraceId(httpHeaders.get("x-trace-id"));
testB(req);
}
public void testB(CreateUserRequest req) {
// 从上下文获取
AppContext.getUserId();
AppContext.getUserName();
AppContext.getTenantCode();
AppContext.getLocale();
AppContext.getTraceId();
}
日志
Shoulder
使用 Slf4j
记录日志,故您可以自行选择 LogBack
、Log4J
、Commonns-Logging
、JUL
… 遵循 Spring Boot
日志的默认项:LogBack
。
自动的链路追踪
使用 org.shoulder.core.log.Logger
打印的日志,都会自动在日志中加上本次请求的 traceId
而无需您手动指定,默认格式如下:
# 未引入 Shoulder,无 trace
2025-01-09 21:34:54.313 DEBUG [nio-8080-exec-1] s.w.s.m.m.a.RequestMappingHandlerMapping : 日志内容
# 引入 Shoulder 后,自动记录 trace 🔍
2025-01-09 21:34:54.360 <00341eda1736415294268100107f000001,> DEBUG [77898] --- [nio-8080-exec-1] o.s.w.t.d.c.DictionaryEnumController : 日志内容
有了 traceId
,线程 id
,还原代码执行现场更方便了!
Shoulder WEB 会自动在 HTTP 请求时生成 traceId 放在上下文中,并在日志、响应的 HttpHeader 中体现。
|
错误码支持
在成熟的系统中,规范的错误码是可长期维护的必要条件,Shoulder
不仅提供了规范参考,还支持日志中方便地打印错误码(ErrorCode
)与异常(BaseRuntimeException
)。
部分代码法示例:
public interface Logger extends org.slf4j.Logger {
void error(ErrorCode errorCode);
void error(ErrorCode errorCode, Throwable t);
void error(String errorCode, String msg);
//...
}
请尽情享用 org.shoulder.core.log.Logger ,以便统一您的日志格式,更有效管理和分析日志,无需再为 日志格式 问题发愁。
|
默认 Logger
Shoulder
默认日志输出已经为您自动配置好一些 Logger
,直接在 org.shoulder.core.log.AppLoggers
使用即可。
示例代码:
AppLoggers.APP_SERVICE_DIGEST.info("url={}, success={}, cost={}, result={}", requestUrl, true, cost, returnStr);
打印在 ${logging.file.path}/${shoulder.application.name}
目录中
LoggerName | 打印建议 | 文件名 |
---|---|---|
|
APP启动/重启等时逻辑,默认包路径日志打在这里 |
default.log |
|
APP启动/重启等运维时逻辑 |
warn.log |
|
所有需要关注的 error 日志都在这里出现 |
error.log |
|
业务逻辑处理过程 |
biz.log |
|
业务路由,新老链路/逻辑切流日志:决策因子、规则、切流结果 |
router.log |
|
调用其他应用 / 服务的入参、出参、错误码等详细信息 |
integration.log |
|
调用其他应用 / 服务的接口、方法、成功、耗时等关键信息 |
integration-digest.log |
|
对外暴露接口/服务的日志详情,入参、出参等 |
service.log |
|
对外暴露接口/服务的接口、方法、成功、耗时等关键信息 |
service-digest.log |
|
对外暴露接口/服务的接口执行出现预期内异常记录(参数校验失败等) |
service-warn.log |
|
对外暴露接口/服务的接口执行失败记录 |
service-error.log |
|
数据访问层-详细日志 |
dal.log |
|
数据访问层-摘要日志 |
dal-digest.log |
|
数据访问层-缓存日志 |
cal.log |
|
数据访问层-缓存-摘要日志 |
cal-digest.log |
|
定时任务触发记录 |
daemon.log |
|
消费消息记录,如消息内容,消息来源等 |
msg-consumer.log |
|
发布消息记录,如消息内容等 |
msg-producer.log |
|
向其他系统发送通知,如外部 |
notify.log |
|
收到配置变更,读取到配置中心的配置内容 |
config.log |
|
错误码映射记录,如调用 x 应用返回 errorCode_123,对于本服务接口应该返回 errorCode_567 时记录 |
code-mapping.log |
Shoulder 内置了一些仅适用于框架内部使用 Logger,若您想了解,可前往 Shoulder 框架内置日志说明。 |
高性能的日志拼接
Shoulder
内置的 org.shoulder.core.log.Logger
在 Slf4j
基础上进一步增强了复杂参数打印的性能,见示例代码:
public class Demo {
private static final Logger log = LoggerFactory.getLogger(Demo.class);
public void testLog() {
if(oldLog.isInfoEnable()) { // ⚠️ 传统需要 isDebugEnable 判断日志级别是否激活,避免无效运算
String argX = mockCalculateArgs();
oldLog.info("打印一个耗时的参数 {}", argX);
}
Supplier<String> argX = Demo::mockCalculateArgs;
log.info("打印一个耗时的参数 {}", argX); // 👈 Shoulder 中自动判断,扔掉冗余的 if isXxxEnable
}
public static String mockCalculateArgs() {
return "模拟组装、拼接一个大对象,涉及很多耗时操作";
}
}
日志格式规范化
默认的 Logger 中已经预设置了 规范的日志格式,且支持用户自行调整与扩展(如自定义 logback.xml
)
TODO 举例打印格式
|
日志文件划分
Shoulder
默认提供了 Logback
的配置文件,已为您提供了默认的日志 内容分类
与 文件拆分
,默认会按照以下划分日志文件:
Shoulder
默认将其他组件的日志(如 redisson
)打印在 ${logging.file.path}/other/common.log
中,若您需要特殊管理,按照 Spring Boot
提供的方案自行定义 logger 即可。
可以在 shoulder-autoconfiguration 的 logback-spring.xml 查看配置内容。
|
该功能基于 |
错误码与异常
为了降低编码 + 维护成本,Shoulder 提供了统一的规范类,对于自定义错误码枚举/异常可用直接继承 org.shoulder.core.exception.ErrorCode
接口即可,并且 Shoulder 提供了错误码多语言、转异常等丰富功能。
|
并发工具
Threads 线程工具类
提供了常用并发操作的封装,确保安全、高效地执行并发任务,以简化Java并发编程。
Threads 提供了多种线程池拒绝策略实现,如 Discard , DiscardOldest , Abort 和 Block ,以多样应对线程池任务队列满的情况。
|
您可以通过 setExecutorService() 方法更改默认内置线程池。
|
Threads 类依赖于 ContextUtils 和 LogHelper 进行上下文获取和日志美化,确保这些类的可用。
|
异步执行
您可以通过 Threads.execute()
让代码在异步执行,如希望异步打印 "hello, shoulder!"
:
Threads.execute(() -> System.out.printLn("hello, shoulder!"));
延迟执行
您可以通过 Threads.delay()
让代码延迟执行,如希望一分钟后打印 "hello, shoulder!"
:
Threads.delay(() -> System.out.printLn("hello, shoulder!"), Duration.ofMinutes(1));
批量异步执行且等待
您可以通过 Threads.executeAndWait()
让代码延迟执行,如希望执行一批任务,10个线程分别打印 "shoulder-processing-task-i"
(i: 1~10):
List<Runnable> tasks = IntStream.range(1, 11)
.mapToObj(i -> new Runnable(() -> System.out.println("shoulder-processing-task-" + i)))
.toList();
Threads.executeAndWait(tasks);
全局异步增强器 ThreadEnhancer
若希望所有异步线程执行前后做一些操作,可以实现 org.shoulder.core.concurrent.enhance.ThreadEnhancer
接口,如 监控记录、自动拷贝/清理 ThreadLocal
等高级能力。
以下是实现【自动统计在线程池队列内等待时间等指标】的例子:
/**
* 自动统计在线程池队列内等待时间等指标
*/
public class MonitorRunnableEnhancer implements ThreadEnhancer {
@Override
public EnhancedRunnable doEnhance(EnhancedRunnable runnable) {
return new DefaultMonitorableRunnable(runnable);
}
@Override
public <T> EnhancedCallable<T> doEnhance(EnhancedCallable<T> callable) {
return new DefaultMonitorableCallable<>(callable);
}
}
// 将该类注入进 Spring 上下文
@Configuration
public class MonitorRunnableAutoConfiguration {
@Bean
public MonitorRunnableEnhancer enqueueTimeEnhancer() {
return new MonitorRunnableEnhancer();
}
}
工具类
Shoulder
提供了一些高频的工具类(基于成熟的工具包:apache-commons
、Spring 内的工具类
、google.guava
、hutools
)
对于 JSON
处理,与 Spring
的默认选型一致:采用安全、扩展良好的 jackson
。
扩展:为什么选 jackson 而非 fastjson 作为默认
|

Shoulder
在 JSON 序列化上默认使用的是 Jackson
(与 Spring-Boot
一致)。
多语言/国际化工具 Translator
简化您的多语言应用开发 —— Translator
,精心设计的接口,在 Spring
的国际化功能(MessageSource
)基础上升级,使用更简单、便捷。
亮点
-
去除资源文件命名限制:命名无须为
messages_
,Shoulder
允许您更自由地命名以更方便的按模块管理您的多语言文件。 -
语言自动识别:根据当前用户设置或系统默认值自动选择合适的语言环境([_运行上下文_appcontext]),实现上下文感知,高度个性化的翻译。
-
简化的方法签名:提供
getMessageOrDefault
方法,允许指定默认值,避免因找不到翻译而中断程序。 -
链式调用友好:默认值和参数化的翻译方法,支持直接使用或结合参数动态生成消息。
-
智能默认处理:当特定翻译不可用时,立即回退到提供的默认消息,保证应用流畅运行。
-
改良的资源文件目录:让多语言资源文件的管理更轻松。
NOTE: .去除资源文件命名限制
无资源文件命名限制, Spring
/ JDK
要求翻译文件命名必须为 messages_
,—— 这陈旧的规则完全没必要,Shoulder
允许您更自由地命名以更方便的按模块管理您的多语言文件,如 user.properties
dictionary.properties
,这在大型的项目中更加容易开发、管理和维护。
配置
在您的 resources
目录下添加 language/zh_CN/loginpage.properties
,就可以使用 Translator
轻松翻译多语言。
i18n.page.button.login=您好,{}!
@Autowried
private Translator translator;
public String genHelloTip() {
String msg = "i18n.page.button.login";
String userName = "shoulder";
return translator.getMessageOrDefault(msg, "welcome, {}", userName); // 不指定语言时默认从上下文取 AppContext.getLocaleOrDefault()
// 返回:您好,shoulder! (若未配置,返回 welcome, shoulder)
}
应用元信息 AppInfo
Shoulder
提供了获取应用元信息的统一门面:org.shoulder.core.context.AppInfo
。
// 应用id:默认使用 spring.application.name
String appId = AppInfo.appId();
// 应用版本号:默认使用 v1
String version = AppInfo.version();
// 应用示例id:默认为 0
String instanceId = AppInfo.instanceId();
// 运行环境,如 dev、test、prod
String env = AppInfo.env();
借助 扩展: |
更多应用元信息配置请参考 应用元信息配置总览 |