无规范不成方圆。我自己非常注重搭建项目结构的起步过程,应用命名规范、模块的划分、目录(包)的命名,我觉得非常重要,如果做的足够好,别人导入项目后可能只需要10分钟就可以大概了解系统结构。具体规范包括包命名、类的命名、接口命名、方法命名、变量命名、常量命名。本文是研发规范第九讲,通用类命名规范。
写代码,少不了对统一资源的管理,清晰的启动过程可以有效地组织代码。为了让程序运行起来,少不了各种资源的注册、调度,少不了公共集合资源的管理。
一般作为程序启动器使用,或者作为启动器的基类。通俗来说,可以认为是main函数的入口。
AbstractBootstrap
ServerBootstrap
MacosXApplicationBootstrap
DNSTaskStarter
Demo1:
@Component
public class ExtensionBootstrap implements ApplicationContextAware {
@Resource
private ExtensionRegister extensionRegister;
private ApplicationContext applicationContext;
@PostConstruct
public void init() {
Map<String, Object> extensionBeans = applicationContext.getBeansWithAnnotation(Extension.class);
extensionBeans.values().forEach(
extension -> extensionRegister.doRegistration((ExtensionPointI) extension)
);
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
}
Demo2:MQ消费者启动
@Slf4j
@Component
public class mqConsumerStarter implements ApplicationRunner {
@Autowired(required = false)
Set<MessageCallback> messageCallbackSet;
@Autowired
private MQTemplate mqTemplate;
@Autowired
private mqAsyncRetryConsumer mqAsyncRetryConsumer;
@Override
public void run(ApplicationArguments args) throws Exception {
registerAllConsumer();
}
}
某一类功能的处理器,用来表示某个处理过程,是一系列代码片段的集合。
CompoundProcessor
BinaryComparisonProcessor
Demo:车队订单处理器
@Slf4j
@Component
public class ConveyOrderProcessor {
@RpcConsumer
private ConvoyOrderQueryHService convoyOrderQueryHService;
public List<ConvoySubOrderHVO> getOrderInfoVOByUpdateTimeRange(Date startTime, Date endTime) {
if (ObjectUtils.anyNull(startTime, endTime)) {
return Lists.newArrayList();
}
...
}
对有生命状态的对象进行管理,通常作为某一类资源的管理入口。
AccountManager
DevicePolicyManager
TransactionManager
Demo:巡检生命周期管理
public interface InspectionManager<T> {
/**
* 获取业务类型
*/
String bizType();
/**
* 初始化执行日期
* @param context 上下文
*/
void initBatchId(BizContext<T> context);
/**
* 执行器标识
*/
String getExecutorKey();
/**
* 执行器执行标识
*/
String getProcessingKey();
/**
* 执行器是否正在执行
*/
boolean isProcessing();
/**
* 设置执行器状态
*
* @param status 状态
*/
void setStatus(ExecutorStatusEnum status);
/**
* 获取当前执行器状态
*/
String getCurrentStatus();
}
表示持有某个或者某类对象的引用,并可以对其进行统一管理。多见于不好回收的内存统一处理,或者一些全局集合容器的缓存。
QueryHolder
InstructionHolder
ViewHolder
Demo:简单缓存-单例
public class SimpleCache {
private SimpleCache() {
//do-nothing
}
public static FIFOCache<String, Object> getInstance() {
return SimpleCacheHolder.instance;
}
private static class SimpleCacheHolder {
// 先入先出,一旦缓存满了,先放进去的,先被清空
private static final FIFOCache<String, Object> instance = CacheUtil.newFIFOCache(100);
}
}
工厂模式,表示此类为工厂类
SessionFactory
ScriptFactory
LiveCaptureFactory
Demo:商品操作工厂
public class ItemOptFactory {
private Map<String, ItemOptHandler> beanFactory;
public ItemOptFactory(@Autowired Map<String,ItemOptHandler> beanFactory) {
this.beanFactory = beanFactory;
}
public ItemOptHandler getInstance(ItemOperateSceneEnum operateSceneEnum){
ItemOptHandler itemOptFactory = beanFactory.get(operateSceneEnum.getBeanName());
if (Objects.isNull(itemOptFactory)) {
return null;
}
return itemOptFactory;
}
public Response<Boolean> commonBiz(Integer fixMethod, List<Long> bizIds) {
if (Objects.isNull(fixMethod)
|| !FixSkuImageMethod.STABLE_ITEM_ID.getCode().equals(fixMethod)
|| !FixSkuImageMethod.FLEXIBLE_ITEM_ID.getCode().equals(fixMethod)) {
Response.fail("请选择数据订正方式,1、磐石调用业务id 2、读取文本中固定业务id");
}
if (FixSkuImageMethod.FLEXIBLE_ITEM_ID.getCode().equals(fixMethod) && CollectionUtils.isEmpty(bizIds)) {
Response.fail("缺少业务id,请输入业务id");
}
return Response.ok();
}
}
Provider = Strategy + Factory Method。它更高级一些,把策略模式和方法工厂揉在了一块,让人用起来很顺手。Provider 一般是接口或者抽象类,以便能完成子实现
AccountFeatureProvider
ApplicationFeatureProvider
CollatorPorvider
Demo:平台商品扫描接口
@Component
@Slf4j
public class OptItemScanProvider {
@Autowired
private MQTemplate mqTemplate;
// 用于SPU待扫描数据发送
public void sendMsg(PlatformItemSearchDto platformItemSearchDto) throws MQException {
String message = ImJsonUtils.objToJson(platformItemSearchDto);
MQResponse mqResponse = mqTemplate.sendOrderMsg(MqConfigContant.CATEGORY_SCAN_TOPIC, MqConfigContant.OPT_ITEM_CATEGORY_SCAN_TAG, null, message, getCodeKey(platformItemSearchDto));
if (!mqResponse.isSuccess()) {
throw new ServiceException("【标准化类目扫描】平台商品待扫描下发MQ失败");
}
log.info("【发送消息结束, topic:{}, response:{},message:{},tag:{}", MqConfigContant.CATEGORY_SCAN_TOPIC, mqResponse, message, MqConfigContant.OPT_ITEM_CATEGORY_SCAN_TAG);
}
}
注册并管理一系列资源
ImportServiceRegistrar
IKryoRegistrar
PipelineOptionRegistrar
Demo:分布式事务注册器
@Configuration
public class DistributedTransactionRegistrar {
@Bean
public DistributedTransactionPolicyProperties distributedTransactionPolicyProperties(){
return new DistributedTransactionPolicyProperties();
}
@Bean
public DistributedTransactionDao distributedTransactionDao(){
return new DistributedTransactionDao();
}
@Bean
public DistributedTransactionAspect distributedTransactionAspect(){
return new DistributedTransactionAspect();
}
}
一般是核心模块,用来处理一类功能。引擎是个非常高级的名词,一般的类是没资格用它的。
ScriptEngine
DataQLScriptEngine
C2DEngine
Demo1:审核流引擎,主要用于节点的获取,抽象类。
public interface AuditEngine {
/**
* 对当前审核节点的相关处理人的关联处理,由具体业务实现,
*
* @param appId 当前审核数据
*/
void callback(Long appId);
/**
* 审核成功业务
*
* @param appId 当前审核数据
*/
void success(Long appId, AuditEngineContext auditEngineContext);
/**
* 审核失败业务
*
* @param appId 当前审核数据
*/
void fail(Long appId);
/**
* 撤回
* @param appId 当前审核数据
*/
void revoke(Long appId);
}
Demo2:状态机发送事件接口
public interface IFsmEngine<E extends Enum<?>, S extends Enum<?>, C extends IFsmContext<E, S>, F extends Enum<?> & IFsmField, PT extends Enum<?>> {
/**
* 引擎初始化
*
* @param processorRegisters
* @param pluginRegisters
*/
void initEngine(Collection<ProcessorRegister<E, S, C, F>> processorRegisters,
Collection<PluginRegister<E, S, C, F, PT>> pluginRegisters);
/**
* 发送事件
*
* @param event
* @return
* @throws Exception
*/
FsmResult sendEvent(AbstractEvent<E> event);
}
某个服务。
IntegratorServiceImpl
ISelectionService
PersistenceService
Demo:引入货代财务订单服务
@Slf4j
@Component
public class AgentFinanceOrderInspectionService {
}
某个任务。通常是个Runnable
WorkflowTask
FutureTask
ForkJoinTask
Demo:延时任务
@Getter
@Setter
public abstract class AbstractDelayedTask implements Delayed, Runnable {
protected final static long DELAY = 10 * 1000L;
private long updateTime;
private long objectId;
@Override
public void run() {
process();
}
protected abstract boolean process();
@Override
public long getDelay(TimeUnit unit) {
return 0;
}
@Override
public int compareTo(Delayed o) {
return 0;
}
}
为了完成一些统计类或者全局类的功能,有些参数需要一传到底。传播类的对象就可以通过统一封装的方式进行传递,并在合适的地方进行拷贝或者更新。
如果你的程序执行,有一些变量,需要从函数执行的入口开始,一直传到大量子函数执行完毕之后。这些变量或者集合,如果以参数的形式传递,将会让代码变得冗长无比。这个时候,你就可以把变量统一塞到Context里面,以单个对象的形式进行传递。
在Java中,由于ThreadLocal的存在,Context甚至可以不用在参数之间进行传递
AppContext
ServletContext
ApplicationContext
Demo:业务上下文
@Data
@Accessors(chain = true)
public class BizContext<T> {
/**
* 批次id
*/
public Long batchId;
/**
* 业务最新更新时间
*/
private long bizLastSyncTime;
/**
* 上游业务
*/
private List<T> upstreamBizList;
/**
* 下游业务
*/
private List<T> downstreamBizList;
/**
* 巡检结果
*/
private List<T> checkResultList;
}
传播,繁殖。用来将context中传递的值进行复制,添加,清除,重置,检索,恢复等动作。通常,它会提供一个叫做propagate的方法,实现真正的变量管理。
TextMapPropagator
FilePropagator
TransactionPropagator
使用多核可以增加程序运行的效率,不可避免的引入异步化。我们需要有一定的手段,获取异步任务执行的结果,对任务执行过程中的关键点进行检查。回调类API可以通过监听、通知等形式,获取这些事件。
callback通常是一个接口,用于响应某类消息,进行后续处理;
Handler通常表示持有真正消息处理逻辑的对象,它是有状态的;
trigger 触发器代表某类事件的处理,属于Handler,通常不会出现在类的命名中;
Listener的应用更加局限,通常在观察者模式中用来表示特定的含义。
ChannelHandler
SuccessCallback
CronTrigger
EventListener
CallBack Demo:批量调度子任务
public abstract class AbstractConsumerChildrenCallBack implements MessageCallback, ApplicationRunner {
@Autowired
private BatchJobWriteFacade batchJobWriteFacade;
@Autowired
private mqConsumerManager consumerManager;
public void init() throws MQException {
Map<String, MessageCallback> map = Maps.newHashMap();
map.put(getConsumerId(), ImConsumerEnhanceUtil.enhanceAll(getConsumerId(), this));
consumerManager.listenMessage(map);
}
@Override
public void run(ApplicationArguments args) throws Exception {
init();
}
@Override
public MQConsumeStatus messageArrived(MQMessage mqMessage) {
...
}
}
Handler Demo: 协议变更联动商品变更
public interface BizChangeHandler {
/**
* 联动商品状态
* @param agItems 协议商品
* @param bizChangeContext 业务变更上下文
* @return 联动的结果
*/
Object associateItemChangeV1(List<Item> agItems, BizChangeContext bizChangeContext);
}
Listener Demo:商品更新事件监听
@Slf4j
@Component
public class GoodsUpdateListener {
/**
* 商品更新扩展操作
* 1. 记录涨价免审日志
*/
@Async("asyncApplicationEventThreadPool")
@EventListener
public void onGoodsUpdate(GoodsSkuUpdateEvent event) {
...
}
}
Aware就是感知的意思,一般以该单词结尾的类,都实现了Aware接口。拿Spring来说,Aware 的目的是为了让bean获取Spring容器的服务。具体回调方法由子类实现,比如ApplicationContextAware。它有点回调的意思。
ApplicationContextAware
ApplicationStartupAware
ApplicationEventPublisherAware
Demo:对mq进行trace增强
@Configuration
@Slf4j
public class CommonMqConfig implements ApplicationRunner, ApplicationContextAware {
@Autowired
private mqConsumerManager consumerManager;
private ApplicationContext applicationContext;
@Override
public void run(ApplicationArguments args) throws Exception {
log.info("[MQ 启动消费者加载]");
Map<String, Object> consumerMap = applicationContext.getBeansWithAnnotation(mqConsumer.class);
Map<String, MessageCallback> callbackMap = Maps.newHashMap();
consumerMap.forEach((beanName, callback) -> {
try {
mqConsumer consumer = callback.getClass().getAnnotation(mqConsumer.class);
if (Objects.nonNull(consumer)) {
callbackMap.put(consumer.id(), (MessageCallback) callback);
}
} catch (Exception e) {
log.error("[MQ 启动消费者加载失败], cid: {}, callback: {}", beanName, callback, e);
throw e;
}
});
log.info("[MQ 启动消费者加载成功]消费者集合为:{}", callbackMap);
// 为消费方设置好callback 并启动消费方
try {
log.info("[MQ 启动消费者监听配置]");
consumerManager.listenMessage(callbackMap);
log.info("[MQ 启动消费者监听配置成功]");
} catch (MQException e) {
log.error("[MQ 注册监听器失败], map:{}", callbackMap, e);
throw e;
}
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
}
现在的程序都比较复杂,运行状态监控已经成为居家必备之良品。监控数据的收集往往需要侵入到程序的边边角角,如何有效的与正常业务进行区分,是非常有必要的。
表示监控数据。不要用Monitor了,比较丑。
TimelineMetric
HistogramMetric
Metric
估计,统计。用于计算某一类统计数值的计算器。
ConditionalDensityEstimator
FixedFrameRateEstimator
NestableLoadProfileEstimator
DefaultMessageSizeEstimator // nacos 客户端
累加器的意思。用来缓存累加的中间计算结果,并提供读取通道。
AbstractAccumulator
StatsAccumulator
TopFrequencyAccumulator
一般用于记录日志或者监控值,通常用于apm中。
VelocityTracker
RocketTracker
MediaTracker
如果你的应用用到了自定义的内存管理,那么下面这些名词是绕不开的。比如Netty、lucene,就实现了自己的内存管理机制。
与存储相关,通常表示内存分配器或者管理器。如果你的程序需要申请有规律的大块内存,allocator是你得不二选择。
AbstractByteBufAllocator
ArrayAllocator
RecyclingIntBlockAllocator
表示一块内存。如果你想要对一类存储资源进行抽象,并统一管理,可以采用它。
EncryptedChunk
ChunkFactory
MultiChunk
英文是舞台、竞技场的意思。由于Linux把它用在内存管理上发扬光大,它普遍用于各种存储资源的申请、释放与管理。为不同规格的存储chunk提供舞台,好像也是非常形象的表示。
关键是,这个词很美,作为后缀让类名显得很漂亮。
BookingArena
StandaloneArena
PoolArena
表示池子。内存池,线程池,连接池,池池可用。
ConnectionPool
ObjectPool
MemoryPool
Demo:线程池
public class EventBusThreadPool extends ThreadPoolExecutor {
/**
* 默认线程大小
*/
private static final int DEFAULT_THREAD_SIZE = Runtime.getRuntime().availableProcessors();
private static final AtomicInteger threadNum = new AtomicInteger(1);
/**
* 线程池名称
*/
private static final String NAME = "EventBusThreadPool";
/**
* 线程池实例
*/
public static EventBusThreadPool pool = new EventBusThreadPool();
private EventBusThreadPool() {
super(DEFAULT_THREAD_SIZE, DEFAULT_THREAD_SIZE, 0,
TimeUnit.SECONDS,
new LinkedBlockingDeque<>(20000),
r -> new Thread(r, NAME + "-" + threadNum.getAndDecrement()),
new EventBusThreadPool.RewriteCallerRunsPolicy());
}
@Override
public void execute(Runnable command) {
String traceId = TraceIdUtil.getCurrentTraceId();
try {
// traceId显示传入
super.execute(() -> {
TraceIdUtil.initTraceId(traceId);
try {
command.run();
} finally {
TraceIdUtil.clearTraceId();
}
});
} catch (Exception e) {
log.error("[EventBusThreadPool-execute-error], traceId:{}", traceId, e);
super.execute(command);
}
}
@Override
public Future<?> submit(Runnable task) {
String traceId = TraceIdUtil.getCurrentTraceId();
try {
// traceId显示传入
return super.submit(() -> {
TraceIdUtil.initTraceId(traceId);
try {
task.run();
} finally {
TraceIdUtil.clearTraceId();
}
});
} catch (Exception e) {
log.error("[EventBusThreadPool-submit-error], traceId:{}, cause:", traceId, e);
return super.submit(task);
}
}
/**
* 线程池已经无法处理,重写饱和策略,跟CallerRunsPolicy一样,超出后需要调用者自己的线程处理,多了记录日志
*/
private static class RewriteCallerRunsPolicy implements RejectedExecutionHandler {
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
log.error("线程池:{} 需要处理的任务已经超过任务队列长度(当前队列长度:{}), 需要当前的工作线程自行处理当前任务", NAME, executor.getQueue().size());
if (!executor.isShutdown()) {
r.run();
}
}
}
}
程序收到的事件和信息是非常多的,有些是合法的,有些需要过滤扔掉。根据不同的使用范围和功能性差别,过滤操作也有多种形式。你会在框架类代码中发现大量这样的名词。
一般用在职责链设计模式中。Netty,Spring MVC,Tomcat等都有大量应用。通过将某个处理过程加入到职责链的某个位置中,就可以接收前面处理过程的结果,强制添加或者改变某些功能。就像Linux的管道操作一样,最终构造出想要的结果。
Pipeline
ChildPipeline
DefaultResourceTransformerChain
FilterChain
Demo1:规则执行器注册中心
@Component
class RuleExecutorPipeline {
private final List<RuleExecutor> ruleExecutors = new ArrayList<>();
public List<RuleExecutor> getRuleExecutors() {
return ruleExecutors;
}
/**
* 清空所有的规则
*/
public void clear(){
ruleExecutors.clear();
}
/**
* 批量添加规则
*
* @param ruleExecutors 待添加的规则
*/
protected void addAll(List<RuleExecutor> ruleExecutors){
ruleExecutors.addAll(ruleExecutors);
}
/**
* 单个添加规则
*
* @param ruleExecutor 待添加的规则
*/
protected void add(RuleExecutor ruleExecutor){
ruleExecutors.add(ruleExecutor);
}
}
Demo2:职责链设计模式
public class RuleChain {
@Override
public void configureRuleExecutors(GeneralRuleExecutorRegistry ruleExecutorRegistry) {
/** 业务规则校验逻辑 */
ruleExecutorRegistry.register(GeneralRuleExecutorRegistry.BUSINESS_EXCEUTOR,new BusinessExceutor());
/** 基本规则校验(必填等) */
//平台级基本校验规则
ruleExecutorRegistry.register(GeneralRuleExecutorRegistry.BASE_EXCEUTOR,new BaseExceutor(shopSyncAgent, backCategorySyncAgent, stockReadService,
itemReadService, addressSyncAgent, categoryAttributeSyncAgent,instanceConfigAgent));
//平台禁售规则校验
ruleExecutorRegistry.register(GeneralRuleExecutorRegistry.FORBID_SALE_EXCEUTOR,new ForbidSaleExceutor());
//敏感词规则校验
ruleExecutorRegistry.register(GeneralRuleExecutorRegistry.SENSITIVE_WORD_EXCEUTOR,new SensitiveWordExceutor(sensitiveWordSyncAgent));
/** 唯一性校验 */
//itemCode, skuCode
ruleExecutorRegistry.register(GeneralRuleExecutorRegistry.UNIQUE_CODE_EXCEUTOR,new UniqueCodeExceutor(shopSyncAgent, itemReadService, skuReadService));
//给input对象附上keyAttrs, boundAttrs, backCategory的信息
ruleExecutorRegistry.register(GeneralRuleExecutorRegistry.OTHER_ATTRIBUTE_FILTER_BY_CATEGORY_EXECUTOR,new OtherAttributeFilterByCategoryExecutor(categoryAttributeSyncAgent,
backCategorySyncAgent));
// 商品关键属性校验器(同店铺下,同关键属性的商品只能发布一个)
ruleExecutorRegistry.register(GeneralRuleExecutorRegistry.UNIQUE_KEY_ATTRS_EXCEUTOR,new UniqueKeyAttrsExceutor(categoryAttributeSyncAgent, itemReadService, configComponent,itemAttributeReadService, keyAttrAllowChangeWhiteListConfiguration,spuSyncAgent,brandReadService));
//标准类目下的item的input,直接注入对应SPU的管控的属性值
ruleExecutorRegistry.register(GeneralRuleExecutorRegistry.ITEM_OTHER_ATTRIBUTE_INPUT_RULE_BY_ALI_SPU_EXECUTOR,new ItemOtherAttributeInputRuleByAliSpuExecutor(categoryAttributeSyncAgent, backCategorySyncAgent, spuSyncAgent,shopSyncAgent));
//标准类目下的item的output,向output的规则里注入spu规则
ruleExecutorRegistry.register(GeneralRuleExecutorRegistry.ITEM_OTHER_ATTRIBUTE_OUTPUT_RULE_BY_ALI_SPU_EXECUTOR, new ItemOtherAttributeOutputRuleByAliSpuExecutor(backCategorySyncAgent, spuSyncAgent));
/** 校验商品的 groupedSkuAttributes */
ruleExecutorRegistry.register(GeneralRuleExecutorRegistry.ITEM_SKU_ATTRIBUTE_RULE_BY_CATEGORY_EXECUTOR, new ItemSkuAttributeRuleByCategoryExecutor(categoryAttributeSyncAgent, spuReadServiceFacade, backCategorySyncAgent));
//spu
ruleExecutorRegistry.register(GeneralRuleExecutorRegistry.SPU_OTHER_ATTRIBUTE_RULE_BY_CATEGORY_EXECUTOR, new SpuOtherAttributeRuleByCategoryExecutor(categoryAttributeSyncAgent, outPlatformLinkServiceAgent, addressSyncAgent));
/** 校验商品的 skus
* out 方法 -> 如果 属性值域不合规,就会导致对应的skus为空
*/
ruleExecutorRegistry.register(GeneralRuleExecutorRegistry.SKU_RULE_BY_CATEGORY_EXECUTOR,new SkuRuleByCategoryExecutor(categoryAttributeSyncAgent));
// 增加不受管控的属性处理执行器(回填商品主信息,校验 发布商品时 品牌+型号 在同店铺下是否存在)
ruleExecutorRegistry.register(GeneralRuleExecutorRegistry.OUT_OF_CONTROL_ATTRIBUTE_EXECUTOR,new OutOfControlAttributeExecutor(brandReadService, itemAttributeReadService));
ruleExecutorRegistry.register(GeneralRuleExecutorRegistry.ITEM_OTHER_ATTRIBUTE_RULE_BY_AG_CATEGORYEXECUTOR,new ItemOtherAttributeRuleByAgCategoryExecutor(agCategoryAttributeReadService,
instanceConfigAgent, zcyCategoryInstanceConfigService));
// 格式标题
ruleExecutorRegistry.register(GeneralRuleExecutorRegistry.ITEM_FORMAT_TITLE_EXECUTOR,new ItemFormatTitleExecutor(itemReadService, imCategoryConfigCache, configComponent));
}
}
过滤器,用来筛选某些满足条件的数据集,或者在满足某些条件的时候执行一部分逻辑。如果和职责链连接起来,则通常能够实现多级的过滤。
FilenameFilter
AfterFirstEventTimeFilter
ScanFilter
Demo1:增加dubbo filter
1、需要在resources目录下的META-INF/dubbo/添加com.alibaba.dubbo.rpc.Filter文件
2、然后配置dubboMonitor=cn.gov.zcy.service.filter.DubboCheckMonitorFilter
3、
@Slf4j
@Activate(group = {CommonConstants.PROVIDER})
public class DubboCheckMonitorFilter implements Filter {
@Override
@SuppressWarnings("all")
public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
final Boolean isMonitorEnable = ApolloConfigUtil.getBooleanProperty(
"item.dubbo.monitor.enable", false
);
Stopwatch stopwatch = isMonitorEnable ? Stopwatch.createStarted() : null;
// invoke
Result result = invoker.invoke(invocation);
try {
// consumer 无需拦截,没什么意义
if (isMonitorEnable && !RpcContext.getContext().isConsumerSide()) {
handler(stopwatch, result, invocation);
}
return result;
} catch (Exception e) {
log.error("DubboCheckMonitor发生异常", e);
return result;
}
}
}
Demo2:Spring filter 商品发布前置校验
public interface PublishPreValidatorFilter {
/**
* 执行校验操作
* @param publishFilterContext
* @return true, 校验成功. false, 校验失败
*/
Response<Void> execute(PublishFilterContext publishFilterContext);
}
@Component
public class PublishPreValidatorManager implements InitializingBean {
@Autowired
private PublishItemNumPreValidatorFilter publishItemNumPreValidatorFilter;
private List<PublishPreValidatorFilter> preValidatorFilters = new ArrayList();
public Response<Void> execute(PublishFilterContext publishFilterContext) {
if (CollectionUtils.isEmpty(preValidatorFilters)) {
return Response.ok();
}
for (PublishPreValidatorFilter preValidatorFilter : preValidatorFilters) {
Response<Void> filterResult = preValidatorFilter.execute(publishFilterContext);
if (!filterResult.isSuccess()) {
return Response.fail(filterResult.getCode(), filterResult.getMessage());
}
}
return Response.ok();
}
private void addFilter(PublishPreValidatorFilter filter){
preValidatorFilters.add(filter);
}
// 默认校验器
@Override
public void afterPropertiesSet() throws Exception {
addFilter(publishItemNumPreValidatorFilter);
}
}
拦截器,其实和Filter差不多。不过在Tomcat中,Interceptor可以拿到controller对象,但filter不行。拦截器是被包裹在过滤器中。
HttpRequestInterceptor
Demo1:Mybatis 拦截器
@Intercepts({
@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class})
})
public class MysqlPermissionInterceptor implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
// 执行代码增强逻辑
...
}
@Override
public Object plugin(Object target) {
}
@Override
public void setProperties(Properties properties) {
}
}
Demo2:SpringMVC 防重提交拦截器
@Component
@Slf4j
public class ResubmitCheckInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
...
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
...
}
}
英文里是评估器的意思。可用于判断某些条件是否成立,一般内部方法 evaluate 会返回bool类型。比如你传递进去一个非常复杂的对象,或者字符串,进行正确与否的判断。
ScriptEvaluator
SubtractionExpressionEvaluator
StreamEvaluator
探测器。用来管理一系列探测性事件,并在发生的时候能够进行捕获和响应。比如Android的手势检测,温度检测等。
FileHandlerReloadingDetector
TransformGestureDetector
ScaleGestureDetector
除了基本的数据结构,如数组、链表、队列、栈等,其他更高一层的常见抽象类,能够大量减少大家的交流,并能封装常见的变化。
这个没啥好说的,就是缓存。大块的缓存。常见的缓存算法有LRU(Redis)、LFU(caffeine cache)、FIFO(HashMap)等。
LoadingCache
EhCacheCache
Demo:分布式缓存
@Slf4j
@Component
public class RedisCache {
@Autowired
private RedisClientManager redisClientManager;
/**
* 获取业务线更新时间
* @param redisBizType 业务类型
* @return 最新更新时间
*/
public long getOrderLastSyncTime(String redisBizType) {
try {
String result = redisClientManager.get(redisBizType);
if (StringUtils.isNotBlank(result )) {
return Long.parseLong(result);
}
} catch (Exception e) {
log.error("RedisCommonCache.getOrderLastSyncTime err, param:{}", redisBizType, e);
}
return System.currentTimeMillis() - 5 * 60 * 1000;
}
}
// 通用缓存工具类
public abstract class AbstractCommonCache<T> {
/**
* 根据id信息批量从Redis中获取
* @param paramIds 参数ID
* @param keyPrefix Redis key前缀
* @return 缓存结果
*/
protected List<T> findBatchCacheByIds(List<Long> paramIds, String keyPrefix, LongAdder hitRequest){
...
}
/**
* 根据id信息从Redis中获取
* @param paramId 参数ID
* @param keyPrefix Redis key前缀
* @return 缓存结果
*/
protected T findCacheById(Long paramId, String keyPrefix, LongAdder hitRequest) {
...
}
}
Demo2:fifo cache 简单缓存,单例
public class SimpleCache {
private SimpleCache() {
//do-nothing
}
public static FIFOCache<String, Object> getInstance() {
return SimpleCacheHolder.instance;
}
private static class SimpleCacheHolder {
// 先入先出,一旦缓存满了,先放进去的,先被清空
private static final FIFOCache<String, Object> instance = CacheUtil.newFIFOCache(100);
}
}
buffer是缓冲,不同于缓存,它一般用在数据写入阶段。
ByteBuffer
RingBuffer
DirectByteBuffer
将相似的组件进行组合,并以相同的接口或者功能进行暴露,使用者不知道这到底是一个组合体还是其他个体。
CompositeData
CompositeMap
ScrolledComposite
Demo:组合属性
@Data
public class PropertyComposite implements Serializable{
/**
* 属性
*/
private Property property;
/**
* 所组合的属性
*/
private List<Property> composites;
}
用来包装某个对象,做一些额外的处理,以便增加或者去掉某些功能。
IsoBufferWrapper
ResponseWrapper
MavenWrapperDownloader
Demo:对page类增强
@Getter
@Setter
public class PageParamWrapper extends PageParam {
/**
* 排序方式,asc:正序,desc:倒序(默认)
*/
private String orderType = "desc";
/**
* 排序字段
*/
private String orderField;
}
用来表示配置信息。说实话,它和Properties的区别并不大,但由于Option通常是一个类,所以功能可以扩展的更强大一些。它通常比Config的级别更小,关注的也是单个属性的值。Param一般是作为参数存在,对象生成的速度要快一些。
SpecificationOption
SelectOption
AlarmParam
ModelParam
Demo:param使用场景,作为接口的入参
@Data
@Accessors(chain = true)
public class SlowSqlCreateParam {
...
}
元组的概念。由于Java中缺乏元组结构,我们通常会自定义这样的类。
Tuple2
Tuple3
聚合器,可以做一些聚合计算。比如分库分表中的sum,max,min等聚合函数的汇集。
BigDecimalMaxAggregator
PipelineAggregator
TotalAggregator
迭代器。可以实现Java的迭代器接口,也可以有自己的迭代方式。在数据集很大的时候,需要进行深度遍历,迭代器可以说是必备的。使用迭代器还可以在迭代过程中安全的删除某些元素。
BreakIterator
StringCharacterIterator
可以参考这篇文章:JAVA设计模式第四讲:行为型设计模式
某些可以批量执行的请求或者对象。
SavedObjectBatch
BatchRequest
Demo:批量任务
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class BatchJobDetailDto implements Serializable {
...
}
限流器,使用漏桶算法或者令牌桶(Ratelimiter)来完成平滑的限流。
DefaultTimepointLimiter
RateLimiter
TimeBasedLimiter
Demo1:Ratelimiter 分时段限流
@Slf4j
public class TimeRateLimiter implements RateLimiter {
public static final String PREFIX = "time.rate.limiter.";
private String name;
private String key;
volatile TimeRateLimiterConfig limiterConfig;
/**
* 限流-时段
*/
com.google.common.util.concurrent.RateLimiter timeRateLimiter = null;
/**
* 限流-默认
*/
com.google.common.util.concurrent.RateLimiter defaultRateLimiter = null;
public TimeRateLimiter(String name) {
if (!StringUtils.hasText(name)) {
log.error("RateLimiter, the TimeRateLimiter name is invalid");
throw new IllegalArgumentException("the TimeRateLimiter name is invalid");
}
this.name = name;
this.key = buildKey(this.name);
this.init();
}
private void init() {
refresh();
Config config = ConfigService.getAppConfig();
config.addChangeListener(e -> TimeRateLimiter.this.refresh(), Sets.newHashSet(key));
}
/**
* 限流
*/
@Override
public void acquire(int permits) {
...
}
}
Demo2:商品发布频率限制
@Slf4j
@Component
public class ItemFrequencyLimiter implements ItemRiskControlLimiter {
@Override
public Boolean preLimit(String apolloRuleKey, LoginUser loginUser) {
}
@Override
public void postProcess(String apolloRuleKey, LoginUser loginUser) {
// 异步执行规则校验
asyncExecutor(apolloRuleKey, loginUser);
}
}
将抽象部分与它的实现部分分离,使它们都可以独立地变化。策略模式。相同接口,不同实现类,同一方法结果不同,实现策略不同。比如一个配置文件,是放在xml里,还是放在json文件里,都可以使用不同的provider去命名。
RemoteAddressStrategy
StrategyRegistration
AppStrategy
Demo:经营看板模版
public abstract class AbstractDashboardStrategy<T> {
protected abstract T detail(DashboardParam param);
}
public class BusinessOverviewStrategy extends AbstractDashboardStrategy<BusinessOverviewVO> {
@Override
public BusinessOverviewVO detail(DashboardParam param) {
...
}
}
将一个类的接口转换为客户希望的另一个接口,Adapter模式使得原本由于接口不兼容而不能一起工作的那些类一起工作。
不过,相对于传统的适配器进行api转接,如果你的某个Handler里面方法特别的多,可以使用Adapter实现一些默认的方法进行适配。那么其他类使用的时候,只需要继承Adapter,然后重写他想要重写的方法就可以了。这也是Adapter的常见用法。
ExtendedPropertiesAdapter
ArrayObjectAdapter
CardGridCursorAdapter
Demo:EventBus 适配器,将Event封装到 EventAdapter 对象中
@Slf4j
public abstract class EventAdapter<T extends BaseEvent> {
/**
* 抽象监听
* @param e
* @return
*/
public abstract EventResult process(T e);
/**
* 启动开关
* @return
*/
public abstract boolean enable();
/**
* @return
*/
public Consumer<T> failedHook() {
return (T t) -> log.warn("handle event {} fail", t.getClass().getName());
}
@Subscribe
@AllowConcurrentEvents
public void onEvent(T event) {
if (!enable()) {
return;
}
EventResult result = process(event);
if (!ObjectUtils.isEmpty(result) && !result.getSuccess()) {
failedHook().accept(event);
}
}
/**
* 获取泛型的class name
* @return
*/
@SuppressWarnings("all")
public String getRegisterEventName() {
Class<T> clazz =
(Class<T>)((ParameterizedType)this.getClass().getGenericSuperclass()).getActualTypeArguments()[0];
return clazz.getName();
}
}
将一个请求封装为一个对象,从而使你可用不同的请求对客户进行参数化,对请求排队或记录请求日志,以及支持可撤销的操作。
用来表示一系列动作指令,用来实现命令模式,封装一系列动作或者功能。Action一般用在UI操作上,后端框架可以无差别的使用。
在DDD的概念中,CQRS的Command的C,既为Command。
DeleteAction
BoardCommand
Demo1:命令模式在订单中的使用
public abstract class ClientAction {
public abstract boolean when(OrderInfo orderInfo, Integer orderState);
public abstract ServiceResponse<Long> check(OrderInfo orderInfo, Integer orderState);
public abstract ServiceResponse<Long> then(OrderInfo orderInfo, Long updateUser);
public Integer order() {
return Integer.MIN_VALUE;
}
}
// 流转到已进港规则处理器
@Slf4j
@Service
public class ClientEnterPortAction extends ClientAction {
@Autowired
private OrderFsmEngine orderFsmEngine;
@Override
public boolean when(OrderInfo orderInfo, Integer orderState) {
...
}
@Override
public ServiceResponse<Long> check(OrderInfo orderInfo, Integer orderState) {
}
@Override
public ServiceResponse<Long> then(OrderInfo orderInfo, Long updateUser) {
...
}
@Override
public Integer order() {
return 9;
}
}
Demo2:命令模式在商品扫描中的应用
public interface ScanAction {
/**
* 抽象执行方法
*
* @param scanActionContext 扫描处理上下文
* @return 是否执行完成
*/
Response<Boolean> excute(ScanActionContext scanActionContext);
/**
* 抽象拉取结果方法,请保证此方法不能为空
*
* @param categoryId 类目ID
* @return 通用结果
*/
ScanResult getResult(Long categoryId);
/**
* 补偿逻辑,用于测试、后门接口补偿任务等,按需实现,没有强制逻辑
*
* @param scanActionContext
* @return
*/
Boolean compensate(ScanActionContext scanActionContext);
}
// SPU扫描
@Component
@Slf4j
public class SpuScanAction extends AbstractBaseScanAction {
@Override
public Response<Boolean> excute(ScanActionContext scanActionContext) {
...
}
@Override
public ScanResult getResult(Long categoryId) {
...
}
@Override
public Boolean compensate(ScanActionContext scanActionContext) {
...
}
}
表示一系列事件。一般的,在语义上,Action,Command等,来自于主动触发; Event来自于被动触发。
ObservesProtectedEvent
KeyEvent
Demo:Spring Event 状态机事件
public abstract class AbstractEvent<E extends Enum<?>> {
/**
* 状态机锁ID
*/
private String lockId;
/**
* 获取事件类型
*
* @return
*/
public abstract E getEventType();
/**
* 获取状态机锁ID
*
* @return
*/
public String getLockId() {
return lockId;
}
/**
* 设置状态机锁ID
*
* @param lockId
*/
public void setLockId(String lockId) {
this.lockId = lockId;
}
}
// 创建订单事件
@Setter
@Getter
public class CreateOrderEvent extends AbstractOrderEvent {
private OrderInfo orderInfo;
@Override
public ConvoyOrderEvent getEventType() {
return ConvoyOrderEvent.CREATE;
}
}
代理或者委托模式。委托模式是将一件属于委托者做的事情,交给另外一个被委托者来处理。
LayoutlibDelegate
FragmentDelegate
Demo:
@Slf4j
@Service
public class CustomerDoorsDelegateService {
@Autowired
private CustomerDoorsHService customerDoorsHService;
public Long getDoorsId(OrderInfo orderInfo, DeliveryLoadingInfo deliveryLoadingInfo) {
ServiceResponse<Long> response = customerDoorsHService.add(customerDoorsHParam);
...
}
}
将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。
构建者模式的标准命名。比如StringBuilder。当然StringBuffer是个另类。这也说明了,规则是人定的,人也可以破坏。
JsonBuilder
RequestBuilder
Demo:ES使用构造者模式来生成参数
public class EsParamBuilder {
private BoolQueryBuilder queryBuilder;
public EsParamBuilder() {
queryBuilder = new BoolQueryBuilder();
}
public EsParamBuilder termQuery(String key, Object value) {
...
return this;
}
public EsParamBuilder must(QueryBuilder builder) {
if (Objects.isNull(builder)) {
return this;
}
queryBuilder.must(builder);
return this;
}
public EsParamBuilder should(QueryBuilder builder) {
if (Objects.isNull(builder)) {
return this;
}
queryBuilder.should(builder);
return this;
}
public BoolQueryBuilder buildParam() {
return queryBuilder;
}
}
模板方法类的命名。定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。
JDBCTemplate
Demo:巡检项目模板类
public abstract class AbstractInspectionTemplate<T> implements InspectionManager<T> {
@Autowired
private BizDiffMapper bizDiffMapper;
// 模版方法
public final boolean inspectionMethod() {
Stopwatch stopwatch = Stopwatch.createStarted();
BizContext<T> context;
try {
if (isProcessing()) {
//任务正在执行中
log.warn("当前任务正在执行,本次执行忽略: 唯一键{}", getExecutorKey());
return false;
}
context = fillBizData();
// 获取batchId
initBatchId(context);
// 对账处理
doInspection(context);
} catch (Exception e) {
setStatus(ExecutorStatusEnum.ERROR);
log.warn("获取巡检数据失败, 唯一键{}, 执行器状态为:{},cause:", getExecutorKey(), getCurrentStatus(), e);
return false;
}
// 差错处理
boolean result = handleMistake(context);
setStatus(ExecutorStatusEnum.END);
log.info("巡检任务执行耗时:{}ms, 唯一键{}, 巡检结果:{}", stopwatch.elapsed(TimeUnit.MILLISECONDS), getExecutorKey(), getCurrentStatus());
return result;
}
/**
* 数据获取
* 定时任务按更新时间查询近5s到近5m+5s的数据
* 先获取上游,拿到ids,然后由ids查询下游业务
* @return 上下文
*/
protected abstract BizContext<T> fillBizData() throws SQLException;
/**
* 对账处理
* @param bizContext 上下文
*/
protected abstract void doInspection(BizContext<T> bizContext);
/**
* 对补偿数据异步处理,发送mq,并记录同步时间
* @param bizContext 上下文
*/
protected abstract boolean handleMistake(BizContext<T> bizContext);
}
代理模式。为其他对象提供一种代理以控制对这个对象的访问。
ProxyFactory
SlowQueryProxy
Demo:创建商品云岛动态代理
@Component
public class CloudServiceItemWriteApiProxy {
@Autowired
private CloudServiceItemWriteApi cloudServiceItemWriteApi;
public Response<ItemIdDTO> create(ItemCreateCmd createCmd) {
//岛端创建基础商品,需要将仓库编码置为null
if(Env.isIsland()){
createCmd.clearWarehouseCode();
createCmd.clearTransExpenses();
Object result = ItemPlatformGeneric.invokeParams(CloudServiceItemWriteApi.class, "create", createCmd);
return JSONObject.parseObject(JSON.toJSONString(result), new TypeReference<Response<ItemIdDTO>>() {});
}
return cloudServiceItemWriteApi.create(createCmd);
}
}
写代码要涉及到大量的字符串解析、日期解析、对象转换等。根据语义和使用场合的区别,它们也分为多种。
转换和解析。一般用于不同对象之间的格式转换,把一类对象转换成另一类。注意它们语义上的区别,一般特别复杂的转换或者有加载过程的需求,可以使用Resolver。
DataSetToListConverter
LayoutCommandLineConverter
InitRefResolver
MustacheViewResolver
Demo:解析用户信息Resolver
@Component
public class UserIdLockKeyResolver implements LockKeyGenerator {
@Override
public String resolverLockKey(ResubmitCheck resubmitCheck, HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod) {
AdminServiceParam serviceParam = GatewayContext.getAdminServiceParam();
Long userId = 0L;
if (Objects.nonNull(serviceParam)) {
userId = serviceParam.getUserId();
}
return String.format("%s_%s", userId, request.getRequestURI());
}
}
用来表示非常复杂的解析器,比如解析DSL。
SQLParser
JSONParser
用来表示对某个对象进行特别的配置。由于这些配置过程特别的复杂,值得单独提取出来进行自定义设置。
ContextCustomizer
DeviceFieldCustomizer
格式化类。主要用于字符串、数字或者日期的格式化处理工作。
DateFormatter
StringFormatter
通常用于网络编程中的数据包。
DhcpPacket
PacketBuffer
同样用户网络编程中,用来表示某个协议。
RedisProtocol
HttpProtocol
编码解码器
RedisEncoder
RedisDecoder
RedisCodec
一般用于网络请求的进和出。如果你用在非网络请求的方法上,会显得很怪异。
都表示工具类,Util一般是无状态的,Helper以便需要创建实例才能使用。但是一般没有使用Tool作为后缀的。
HttpUtil
TestKeyFieldHelper
CreationHelper
Demo1:时间转换工具类
public class DateTimeUtil {
private static final String pattern = "yyyy-MM-dd HH:mm:ss";
public static Long dateToTimeStamp(Date date){
if(date == null){
return null;
}
return date.getTime();
}
}
Demo2:店铺前台类目树helper
public class ZcyShopCategoryHelper {
/**
* 构建key(不可随意更改)
*/
public static String makeKeyOfCachedCategoryList(Long shopId, Long parentCategoryId) {
return "shopCategory_shopId_" + shopId + "_parentCategoryId_" + parentCategoryId;
}
/**
* 拆解key(依赖makeKeyOfCachedCategoryList)
*/
public static Long[] parseKeyOfCachedCategoryList(String key) {
String[] split = key.split("_");
Long[] rtn = new Long[2];
rtn[0] = Long.parseLong(split[2]);
rtn[1] = Long.parseLong(split[4]);
return rtn;
}
}
看到mode这个后缀,就能猜到这个类大概率是枚举。它通常把常见的可能性都列到枚举类里面,其他地方就可以引用这个Mode。
OperationMode
BridgeMode
ActionType
Demo1:收付方式
public enum ReceiptPaymentMode {
TRANSFER_ACCOUNTS(1, "转账"),
CASH(2, "现金"),
DRAFT(3, "汇票"),
CHECK(4, "支票");
private final int code;
private final String describe;
}
Demo2:业务类型
public enum BusinessType {
SEA_IMPORT(1, "海运进口"),
SEA_EXPORT(2, "海运出口"),
AIR_IMPORT(3, "空运进口");
private final Integer code;
private final String describe;
}
invoker是一类接口,通常会以反射或者触发的方式,执行一些具体的业务逻辑。通过抽象出invoke方法,可以在invoke执行之前对入参进行记录或者处理;在invoke执行之后对结果和异常进行处理,是AOP中常见的操作方式。
MethodInvoker
Invoker
ConstructorInvocation
Demo:
public class ApplyInvoker {
/**
* 批量执行
*
* @param contexts 执行参数上下文
* @return 执行结果
*/
public static void batchInvoke(List<ApplyContext> contexts) {
if (CollectionUtils.isEmpty(contexts)) {
return;
}
contexts.stream().collect(Collectors.groupingBy(context -> context.getStatusChangeDTO().getApplyType())).
forEach((type, applyContexts) -> {
AbstractApply apply = APPLY_MAP.get(AuditBizCode.from(type));
apply.batchInvoke(applyContexts);
});
return;
}
}
如果你的应用程序,需要经过大量的初始化操作才能启动,那就需要把它独立出来,专门处理初始化动作。
MultiBackgroundInitialize
ApplicationContextInitializer
Demo:数据源初始化
@Slf4j
@Lazy(false)
@Component
public class DataSourceInitializer {
@Autowired(required = false)
Set<DataSource> dataSources;
@PostConstruct
void init() {
if (dataSources == null) {
log.info("not any dataSource");
return;
}
for (DataSource dataSource : dataSources) {
log.info("starting dataSource: {}", dataSource);
try {
dataSource.getConnection();
log.info("started dataSource: {}", dataSource);
} catch (SQLException e) {
log.error("start fail dataSource: {}", dataSource, e);
throw new IllegalStateException("start fail dataSource", e);
}
}
}
}
它们都是用在多线程之间的,进行数据传递。
Feture相当于一个占位符,代表一个操作将来的结果。一般通过get可以直接阻塞得到结果,或者让它异步执行 然后通过callback回调结果。
但如果回调中嵌入了回调呢?如果层次很深,就是回调地狱。Java中的 CompletableFuture 其实就是 Promise,用来解决回调地狱问题。Promise是为了让代码变得优美而存在的。
根据一系列条件,获得相应的同类资源。它比较像Factory,但只处理单项资源。
X509CertSelector
NodeSelector
Demo:双重校验锁获取Spi单例
public class SpiProviderSelector {
private static SpiProviderSelector instance = null;
private SpiProviderSelector(){}
/**
* Double check 获取单例
* @return
*/
public static SpiProviderSelector getInstance(){
if(instance == null){
synchronized (SpiProviderSelector.class){
if(instance == null){
instance = new SpiProviderSelector();
}
}
}
return instance;
}
}
用来汇报某些执行结果。
ExtentHtmlReporter
MetricReporter
一般用于常量列表。
Demo:
public class Constants {
public static final int NO = 0;
public static final int YES = 1;
public static final int FIVE_SECONDS = 3;
public static final int ONE_MINUTE_IN_SECONDS = 60;
public static final int TWO_HOURS_IN_SECONDS = 2 * 60 * 60;
}
封装了一系列get和set方法的类。像lombok就有Accessors注解,生成这些方法。但Accessor类一般是要通过计算来完成get和set,而不是直接操作变量。这适合比较复杂的对象存取服务。
ComponentAccessor
StompHeaderAccessor
生成器,一般用于生成代码,生成id等。
CodeGenerator
CipherKeyGenerator
Demo:生成redis key
public interface LockKeyGenerator {
/**
* 获取处理缓存key
*
* @param resubmitCheck 注解
* @param request 请求
* @param response 响应
* @param handlerMethod 方法
* @return
*/
String resolverLockKey(ResubmitCheck resubmitCheck, HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod);
}