📌 术语定义
- 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>0.8.1<version>
</dependency>
compile 'cn.itlym:shoulder-starter:0.8.1'
全局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
提供了一套开箱即用的字典管理能力,极大的降低了字典模块的编码成本。如:
-
枚举
←→String
/Number
自动转换 -
可定制的存储模式,并提供多种默认的存储模式实现:
-
适用于集群部署模式的DB模式
-
适用于高并发场景的缓存查询能力
-
适用于单机部署的无DB模式
-
-
结合 Shoulder-Web 获得更丰富的功能:
-
支持
JSR 303
规范的校验注解 -
开箱即用的字典搜索、管理接口
-
可视化的查询页面
-
核心接口
-
字典类型(配置类型):
DictionaryType
-
字典项(配置项)
DictionaryItem
如果是变化较少的场景,可采用以枚举代码为存储,枚举直接继承 DictionaryItemEnum 便能获得大量通用方便的类型转换工具代码(id → Enum、name → Enum、enum.order → Enum、compareWithXXX…)。
|
静态字典-枚举模式
轻松实现枚举 & 字典转换,无需再为编写大量转换代码而担忧,保留代码原本的高可读性。
如对以下枚举可以直接在 Controller 的参数中接收多种格式
public enum MyColorEnum implements NameAsIdDictionaryItemEnum<DictionaryTestEnum> {
BLUE, YELLOW, GREEN, RED, BLACK, PINK, GOLDEN,
;
@Override
public String getName() {
return name();
}
@Override
public Integer getDisplayOrder() {
return ordinal();
}
}
这两种访问都能正确访问到接口
-
/color/updateFavorite?color=BLUE
-
/color/updateFavorite?color=0
@RestController
public class MyColorController {
@RequestMapping("/color/updateFavorite")
public String updateFavorite(MyColorEnum color) {
// /color/updateFavorite?color=BLUE
// /color/updateFavorite?color=0
return color.name();
}
}
/**
* 接口中不使用枚举,使用DTO
* 还支持设置 allowCodes、forbiddenCodes 来实现更有意思的设计,如 GOLDEN 只能由系统发放,而不能由用户设置。
*/
@RequestMapping("/color/updateFavorite2")
public String updateFavorite2(@DictionaryEnumItem(value=MyColorEnum.class, forbiddenCodes={"GOLDEN"}) DictionaryItemDTO colorDto) {
return color.name();
}
}
增强的类型转换器
在 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, HLOD_LOCK_DURATION);
jdbcLock.lock(lockInfo);
try {
// 做你的事情
String xx = "do something";
} catch (Exception) {
log.xxx
} finally {
jdbcLock.unlock(lockInfo);
}
}
默认为 单机模式 ,若想支持集群,可以引入数据库或 Redis 相关依赖,设置 shoulder.lock.type=jdbc/reedis 即可,无需修改任何带代码。
|
基于此,即使开始为单机项目,也可以不需要修改任何代码支持集群模式的全局锁。Shoulder 默认提供了以下几个实现:
|
-
MemoryLock:基于内存的锁,主要用于演示工程中使用,不支持集群。
-
JdkLock: 基于
JDK
的ReentrantLock
,不支持集群。 -
FileSystemLock: 基于
操作系统-文件系统
的锁实现,支持同一机器的多个应用共享资源控制,适用于小型项目,无数据库场景。 -
JdbcLock: 基于
DB
+JDBC
的分布式锁实现,支持集群,高可用。 -
RedisLock: 基于
Redis
的分布式锁实现,支持集群,高并发下与JdbcLock
相比性能更佳,实现借鉴了Redisson
。
MemoryLock 仅在 Shoulder Demo、测试使用!演示 Shoulder 代码,不要在生产环境使用!
|
运行上下文 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
。
扩展的 Logger
Shoulder
内置的 org.shoulder.core.log.Logger
在 Slf4j
基础上,额外扩展了关于 org.shoulder.core.exception.ErrorCode
的打印方法。
部分代码法示例:
public interface Logger extends org.slf4j.Logger {
void error(ErrorCode errorCode);
void error(ErrorCode errorCode, Throwable t);
void error(String errorCode, String msg);
//...
}
请尽情享用 Shoulder 的 Logger ,以便统一您的日志格式,更有效管理和分析日志,无需再为 日志格式 问题发愁。
|
默认 Logger
Shoulder
默认日志输出已经为您自动配置好一些 Logger
,直接在 org.shoulder.core.log.AppLoggers
使用即可。
示例代码:
AppLoggers.APP_SERVICE_DIGEST.info("url={}, success={}, cost={}, result={}", requestUrl, true, cost, returnStr);
|
日志文件划分
Shoulder
默认提供了 Logback
的配置文件,无需再考虑日志内容分类与文件拆分,默认会按照以下划分日志文件:
可以在 shoulder-autoconfiguration 的 logback-spring.xml 查看配置内容。
|
该功能基于 |
应用日志
打印在 ${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
默认将其他组件的日志(如 redisson
)打印在 ${logging.file.path}/other/common.log
中,若您需要特殊管理,按照 Spring Boot
提供的方案自行定义 logger 即可。
Shoulder 内置了一些仅适用于框架内部使用 Logger,若您想了解,可前往 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
)基础上升级,使用更简单、便捷。
配置
在您的 resources
目录下添加 language/zh_CN/loginpage.properties
i18n.page.button.login=您好,{}!
使用
@Autowried
private Translator translator;
// 返回:您好,shoulder!
// 若以上配置未完成,返回 welcome, shoulder
public String genHelloTip() {
String msg = "i18n.page.button.login";
String userName = "shoulder";
// 无需传入语言,默认从上下文中取 AppContext.getLocaleOrDefault()
return translator.getMessageOrDefault(msg, "welcome, {}", userName);
}
应用元信息 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();
借助 扩展: |
更多应用元信息配置请参考 应用元信息配置总览 |