设计模式之业务实战
本文章通过把日常常用的设计模式,就如何结合业务并在整个业务项目中落地,做了总结。有来源于真实的业务场景,有来源大厂案例,有来源于开源框架中的经典案例,只是为了做个笔记,不喜勿喷。
一:代理模式
模式定义
给一个对象创建一个代理对象,通过代理对象可以使用该对象的功能。
模式本质
控制对象访问
使用场景
- 权限控制:用户在执行某个操作的时候,需要有相关的权限
- 方法代理:程序在执行一个方法之前或之后增加一些逻辑处理
- 功能封装:为了屏蔽某些功能的细节,让用户无感知(上传文件代理)
业务场景
代理模式的应用场景除了代码级别,还可以将代理模式迁移到应用以及架构级别,如下图文件上传代理服务,针对一 些图片小文件,我们可以直接把文件存储到FastDFS服务,针对大文件,例如商品视频介绍,我们可以把它存储到第 三方OSS。
用户通过文件上传代理服务可以间接访问OSS和本地FastDFS,这种分布式海量文件管理解决方案,这里不仅在代码 层面充分运用了代理模式,在架构层面也充分运用了代理模式 。
接下来我就使用代理模式实现一个文件上传功能。
功能描述:
根据文件类型不同,选择上传到不同的文件服务器,小文件(jpg/png)上传到自己搭建的服务器-fastdfs,大文件(m p4/avi/vep)上传到oss
代码实现:
1.定义文件上传接口
public interface FileUpload {
/****
* 文件上传
*/
String upload(byte[] buffers , String extName);
}
2.oss实现文件上传
@Component(value = "aliyunOSSFileUpload")
public class AliyunOSSFileUpload implements FileUpload{
@Value("${aliyun.oss.endpoint}")
private String endpoint;
@Value("${aliyun.oss.accessKey}")
private String accessKey;
@Value("${aliyun.oss.accessKeySecret}")
private String accessKeySecret;
@Value("${aliyun.oss.key}")
private String key;
@Value("${aliyun.oss.bucketName}")
private String bucketName;
@Value("${aliyun.oss.backurl}")
private String backurl;
/****
* 文件上传
* 文件类型如果是图片,则上传到本地FastDFS
* 文件类型如果是视频,则上传到aliyun OSS
*/
@Override
public String upload(byte[] buffers,String extName) {
String realName = UUID.randomUUID().toString()+"."+extName ;
// 创建OSSClient实例。
OSS ossClient = new OSSClientBuilder().build(endpoint, accessKey, accessKeySecret);
// <yourObjectName>表示上传文件到OSS时需要指定包含文件后缀在内的完整路径,例如abc/efg/123.jpg。
PutObjectRequest putObjectRequest = new PutObjectRequest(bucketName, key+realName, new ByteArrayInputStream(buffers));
// 上传字符串。
ObjectMetadata objectMetadata = new ObjectMetadata();
objectMetadata.setContentType(FileUtil.getContentType("."+extName));
putObjectRequest.setMetadata(objectMetadata);
ossClient.putObject(putObjectRequest);
// 关闭OSSClient。
ossClient.shutdown();
return backurl+realName;
}
}
3.fastdfs实现文件上传
@Component(value = "fastdfsFileUpoad")
public class FastdfsFileUpoad implements FileUpload{
@Value("${fastdfs.url}")
private String url;
/***
* 文件上传
* @param buffers:文件字节数组
* @param extName:后缀名
* @return
*/
@Override
public String upload(byte[] buffers, String extName) {
/***
* 文件上传后的返回值
* uploadResults[0]:文件上传所存储的组名,例如:group1
* uploadResults[1]:文件存储路径,例如:M00/00/00/wKjThF0DBzaAP23MAAXz2mMp9oM26.jpeg
*/
String[] uploadResults = null;
try {
//获取StorageClient对象
StorageClient storageClient = getStorageClient();
//执行文件上传
uploadResults = storageClient.upload_file(buffers, extName, null);
return url+uploadResults[0]+"/"+uploadResults[1];
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/***
* 初始化tracker信息
*/
static {
try {
//获取tracker的配置文件fdfs_client.conf的位置
String filePath = new ClassPathResource("fdfs_client.conf").getPath();
//加载tracker配置信息
ClientGlobal.init(filePath);
} catch (Exception e) {
e.printStackTrace();
}
}
/***
* 获取StorageClient
* @return
* @throws Exception
*/
public static StorageClient getStorageClient() throws Exception{
//创建TrackerClient对象
TrackerClient trackerClient = new TrackerClient();
//通过TrackerClient获取TrackerServer对象
TrackerServer trackerServer = trackerClient.getConnection();
//通过TrackerServer创建StorageClient
StorageClient storageClient = new StorageClient(trackerServer,null);
return storageClient;
}
}
4.文件类型配置
upload:
filemap:
aliyunOSSFileUpload: mp4,avi
fastdfsFileUpload: png,jpg
5.文件上传服务代理
@Component
@ConfigurationProperties(prefix = "upload")
public class FileUploadProxy implements ApplicationContextAware{
//注入application.yml中配置实例对应处理的文件类型
private Map<String,List<String>> filemap;
//注入Spring的容器对象ApplicationContext
private ApplicationContext act;
/***
* 上传方法
* 接收文件对象:MultipartFile
*/
public String upload(MultipartFile file) throws Exception{
//buffers:文件字节数组
byte[] buffers = file.getBytes();
//extName:后缀名 1.jpg->jpg
String fileName = file.getOriginalFilename();
String extName = StringUtils.getFilenameExtension(fileName);
//循环filemap映射关系对象
for (Map.Entry<String, List<String>> entry : filemap.entrySet()) {
//获取指定的value mp4,avi | png,jpg
List<String> suffixList = entry.getValue();
//匹配当前用户上传的文件扩展名是否匹配
for (String suffix : suffixList) {
if(extName.equalsIgnoreCase(suffix)){
//获取指定key aliyunOSSFileUpload | fastdfsFileUpload
//一旦匹配执行文件上传
String key = entry.getKey();
return act.getBean(key,FileUpload.class).upload(buffers,extName);
}
}
}
return "";
}
//注入映射配置
public void setFilemap(Map<String, List<String>> filemap) {
this.filemap = filemap;
}
//注入容器
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
act = applicationContext;
}
}
5.文件上传controller
@RestController
@RequestMapping(value = "/file")
public class FileController {
@Autowired
private FileUploadProxy fileUploadProxy;
/***
* 文件上传
* @param file
* @return
* @throws IOException
*/
@PostMapping(value = "/upload")
public String upload(MultipartFile file) throws Exception {
return fileUploadProxy.upload(file);
}
}
模式优点
职责清晰,可扩展
模式缺点
增加了业务访问逻辑的复杂性
开源框架
对于开源框架而言,大部分都是使用了动态代理,具体功能可以分为两部分,一部分是为了创建对象,一部分是为了对功能的增强或控制,实现方式有两大类,具体如下:
创建对象
在框架中,一般是通过实现Spring的FactoryBean接口来实现
- Dubbo:ReferenceBean
- Dubbo:ExtensionLoader的Adaptive实现是典型的动态代理实现
- Mybatis:MapperFactoryBean
- Feign:FeignClientFactoryBean
功能增强
在框架中,一般是通过拦截器,比如典型的是spring的aop机制,有两大类,具体如下
- Advice:通知
- Interceptor:拦截器
二:享元模式
模式定义
运用共享技术来有効地支持大量细粒度对象的复用。它通过共享已经存在的对象来大幅度减少需要创建的对象数量、避免大量 相似类的开销,从而提高系统资源的利用率。
模式本质
减少重复对象的创建,共享与分离
使用场景
- 需要创建大量的重复对象
- 创建的对象,需要在整个上下文进行传递
业务场景
会话跟踪,如果是传统项目用Session或者是Cookie,全项目通用,但在微服务项目中,不用Session也不用
Cookie,所以想要在微服务项目中实现会话跟踪,是有一定难度的。 当前微服务项目中,身份识别的主流方法是前端将用户令牌存储到请求头中,每次请求将请求头中的令牌携带到后 台,后台每次从请求头中获取令牌来识别用户身份。 我们在项目操作过程中,很多地方都会用到用户身份信息,比如下订单的时候,要知道当前订单属于哪个用户,记录 下单关键日志的时候,需要记录用户操作的信息以及用户信息,关键日志记录我们一般用AOP进行拦截操作,此时没 法直接把用户身份信息传给AOP。这个时候我们可以利用享元模式实现用户会话信息共享操作
功能描述
用户下单,会话共享
代码实现
1.定义抽象共享会话信息
@Data
@NoArgsConstructor
@AllArgsConstructor
@ToString
public abstract class Session {
private String username;
private String name;
private String sex;
private String role;
private Integer level;
//额外操作
public abstract void handler();
}
2.共享会话实现
public class SessionShare extends Session {
//构造函数
public SessionShare(String username, String name, String sex, String role, Integer level) {
super(username, name, sex, role, level);
}
//额外功能实现
@Override
public void handler() {
System.out.println("要共享用户信息了!");
}
}
3.存储会话共享
@Component
public class SessionThreadLocal {
//1.创建一个ThreadLocal实现存储线程下共享的对象
private static ThreadLocal<Session> sessions = new ThreadLocal<Session>();
//2.添加共享对象
public void add(Session session){
sessions.set(session);
}
//3.获取共享对象
public Session get(){
return sessions.get();
}
//4.移除共享对象
public void remove(){
sessions.remove();
}
}
为什么这里不定义成静态的方法呢
4.共享会话存储
一般是通过拦截器获取到用户信息,然后存储到线程上下文对象中,然后在你想使用的地方通过get获取到
@Component
public class AuthorizationInterceptor implements HandlerInterceptor {
@Autowired
private SessionThreadLocal sessionThreadLocal;
/****
* 将用户会话存储到ThreadLocal中
* @param request
* @param response
* @param handler
* @return
* @throws Exception
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
try {
//获取令牌
String authorization = request.getHeader("token");
//解析令牌
if(!StringUtils.isEmpty(authorization)){
Map<String, Object> tokenMap = JwtTokenUtil.parseToken(authorization);
//封装用户身份信息,存储到ThreadLocal中,供当前线程共享使用
//1.封装需要共享的信息
//2.创建一个对象继承封装信息,每次共享该对象 (不需要共享,则可以创建另外一个对象继承它)
//3.创建共享管理对象,实现共享信息的增加、获取、移除功能
SessionShare session = new SessionShare(
tokenMap.get("username").toString(),
tokenMap.get("name").toString(),
tokenMap.get("sex").toString(),
tokenMap.get("role").toString(),
Integer.valueOf(tokenMap.get("level").toString())
);
sessionThreadLocal.add(session);
return true;
}
} catch (Exception e) {
e.printStackTrace();
}
//输出令牌校验失败
response.setContentType("application/json;charset=utf-8");
response.getWriter().print("身份校验失败!");
response.getWriter().close();
return false;
}
/**
* 移除会话信息
* @throws Exception
*/
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
sessionThreadLocal.remove();
}
}
5.共享会话使用
比如用户在下订单的时候,我们肯定要知道对应订单的用户信息,这个时候就不需要在查询用户信息,可以直接从线程上下文中获取到,具体如下
public int add(Order order) {
order.setUsername("wangwu");
order.setPaymoney(100); //结算价格
order.setMoney(100); //订单价格
//通过享元模式共享获取线程中的对象
order.setUsername(sessionThreadLocal.get().getUsername())
}
模式优点
- 降低大量重复对象的创建
- 降低对内存的使用,提高系统的性能
模式缺点
- 为了使对象可以共享,需要将一些不能共享的状态外部化,这将增加程序的复杂性。
开源框架
jdk也是用了该模式,比如integer,long等对象的创建,具体看一下这个代码
public final class Integer extends Number implements Comparable<Integer> {
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
private static class IntegerCache {
static final int low = -128;
static final int high;
static final Integer cache[];
static {
// high value may be configured by property
int h = 127;
String integerCacheHighPropValue =
sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
if (integerCacheHighPropValue != null) {
try {
int i = parseInt(integerCacheHighPropValue);
i = Math.max(i, 127);
// Maximum array size is Integer.MAX_VALUE
h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
} catch( NumberFormatException nfe) {
// If the property cannot be parsed into an int, ignore it.
}
}
high = h;
cache = new Integer[(high - low) + 1];
int j = low;
for(int k = 0; k < cache.length; k++)
cache[k] = new Integer(j++);
// range [-128, 127] must be interned (JLS7 5.1.7)
assert IntegerCache.high >= 127;
}
private IntegerCache() {}
}
}
Integer.valueOf方法 如果入参是-128到127(high默认是127)的数字,他会从缓存里拿而不是重新创建。我们看IntegerCache类,他在static代码块中预先创建好了这些对象,放到了一个数组里
三:装饰者模式
模式定义
动态的向一个对象添加新的功能,但是却不改变对象的结构
模式本质
动态组合
使用场景
- 在不影响其它对象的情况下,以动态,透明的方式给对象添加职责
- 在不适合使用继承来对业务进行扩展
业务场景
在订单提交的时候,订单价格和结算价格其实是两码事,订单价格是当前商品成交价格,而结算价格是用户最终需要 支付的金额,最终支付的金额并不是一成不变,它也并不是商品成交价格,能改变结算价格的因素很多,比如满100 减10元,VIP用户再减5块。订单结算金额计算我们就可以采用装饰者模式。
功能描述
结算价格计算,根据不同价格嵌套运算
设计思路
1、创建接口(MoneyOperation),定义订单价格计算,因为所有价格波动,都是基于订单价格来波动的。
2、创建订单价格计算类(OrderPayMoneyOperation),实现MoneyOperation接口,实现订单价格计算。
3、创建装饰者对象(Decorator),以供功能扩展。
4、实现优惠券优惠金额计算功能扩展,创建Decorator的扩展类CouponsMoneyOperation,先计算订单金额,再计算优惠 券使用之后的优惠金额。
5、实现金币抵现功能扩展,创建Decorator的扩展类GoldMoneyOperation,先计算订单金额,再实现金币优惠之后的金 额。
代码实现
1.创建订单计算接口
public interface MoneySum {
/***
* 订单价格[结算]运算
*/
void money(Order order);
}
2.创建默认订单计算接口实现
@Component(value = "orderMoneySum")
public class OrderMoneySum implements MoneySum{
@Autowired
private ItemDao itemDao;
/****
* 基础价格计算
* @param order
*/
@Override
public void money(Order order) {
//查询商品
Item item = itemDao.findById(order.getItemId());
//商品价格*购买数量
order.setMoney(item.getPrice()*order.getNum()); //订单价格
order.setPaymoney(item.getPrice()*order.getNum());//结算价格
}
}
3.创建订单计算接口的装饰者
public abstract class DecoratorMoneySum implements MoneySum {
//被扩展的对象
private MoneySum moneySum;
public void setMoneySum(MoneySum moneySum) {
this.moneySum = moneySum;
}
//价格计算
@Override
public void money(Order order) {
moneySum.money(order);
}
}
4.实现满减价格计算,在装饰者上扩展
@Component(value = "fullMoneySum")
public class FullMoneySum extends DecoratorMoneySum{
//原来的功能上进行增强
@Override
public void sum(Order order) {
//原有功能 super.sum(order); //增强 moneySum(order);
}
//满100减5块
public void moneySum(Order order){
} }
5.实现vip价格优惠计算
@Component(value = "vipMoneySum")
public class VipMoneySum extends DecoratorMoneySum {
//原有方法上增强
@Override
public void sum(Order order) {
//原有功能 super.sum(order);
//增强
vipMoneySum(order);
}
//Vip价格优惠-5
public void vipMoneySum(Order order){
order.setPaymoney(order.getPaymoney()-5); }
}
6.订单支付之价格计算
public int add(Order order) {
//通过享元模式共享获取线程中的对象
order.setUsername(sessionThreadLocal.get().getUsername());
//结算价格嵌套运算
//对orderMoneySum进行增强【计算基础价格】,执行满减操作增强
fullMoneySum.setMoneySum(orderMoneySum);
//对fullMoneySum进行增强【满减操作】,执行的增强是Vip价格计算
vipOrderMoney.setMoneySum(fullMoneySum);
vipOrderMoney.money(order);
//添加订单
int addCount = orderDao.add(order);
return addCount;
}
备注:对于业务中使用设计模式,一般我们都会直接去new一个对象
模式优点
- 简化高层定义
- 更容易复用功能
- 比继承更灵活
模式缺点
- 多层的装饰是比较复杂的
开源框架
- Jdk:各种i/o流的支持
- Dubbo:ProtocolFilterWrapper,以Provider提供的调用链为例,EchoFilter -> ClassLoaderFilter -> GenericFilter -> ContextFilter -> ExecuteLimitFilter -> TraceFilter -> TimeoutFilter -> MonitorFilter -> ExceptionFilter
- Dubbo:ProtocolListenerWrapper,ListenerInvokerWrapper,InvokerWrapper
四:策略模式
模式定义
策略模式是对算法的包装,把使用算法的责任和算法本身分隔开,委派给不同的对象管理。策略模式通常把一系列的算法包装 到一系列的策略类里面,作为一个抽象策略类的子类。
模式本质
分离算法,选择实现
使用场景
- 多个类只有在算法或行为上稍有不同的场景
- 算法需要自由切换
业务场景
用户在购买商品的时候,很多时候会根据Vip等级打不同折扣,尤其是在线商城中体现的淋漓尽致。我们这里也基于 真实电商案例来实现VIP等级价格制:
Vip0->普通价格 Vip1->减5元 Vip2->7折 Vip3->5折
功能描述
结算价格计算,根据Vip不同等级进行运算
代码实现
1.创建VIP价格计算接口
public interface VipMoney {
/***
* 金额计算
*/
Integer money(Integer money);
}
2.创建各种不同的vip打折策略
@Component(value = "vipOne")
public class VipOne implements VipMoney {
//Vip1价格计算
@Override
public Integer money(Integer money) {
return money;
}
}
@Component(value = "vipTwo")
public class VipTwo implements VipMoney {
//Vip2价格计算
@Override
public Integer money(Integer money) {
return money-5;
}
}
@Component(value = "vipThree")
public class VipThree implements VipMoney {
//Vip3价格计算
@Override
public Integer money(Integer money) {
return (int)(money*0.5);
}
}
@Component(value = "vipFour")
public class VipFour implements VipMoney {
//Vip4价格计算
@Override
public Integer money(Integer money) {
return (int)(money*0.1);
}
}
3.创建策略工厂
@Component
@ConfigurationProperties(prefix = "strategy")
public class StrategyFactory implements ApplicationContextAware {
//①注入ApplicationContext
//vipOne:VipOneInstance
//vipTwo:VipTwoInstance
//vipThree:VipThreeInstance
//vipFour:VipFourInstance
private ApplicationContext act;
//1:vipOne
//2:vipTwo
//3:vipThree
//4:vipFour
private Map<Integer,String> strategyMap;
/***
* 通过等级获取用户对应的策略实例
*/
public VipMoney get(Integer level){
//获取等级对应的策略实例ID
String id = strategyMap.get(level);
//根据策略实例ID从容器中获取策略实例
return act.getBean(id,VipMoney.class);
}
//注入配置中的映射信息
public void setStrategyMap(Map<Integer, String> strategyMap) {
this.strategyMap = strategyMap;
}
//注入容器
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
act = applicationContext;
}
}
4.策略配置
strategy:
strategyMap:
1: strategyVipOne
2: strategyVipTwo
3: strategyVipThree
4: strategyVipFour
5.策略使用
public void vipMoney(Order order){
//order.setPaymoney(order.getPaymoney()-5);
//获取用户等级
Integer level = sessionThreadLocal.get().getLevel();
//获取价格优惠策略
VipMoney vipMoney = strategyFactory.get(level);
Integer payMoney = vipMoney.money(order.getPaymoney());
order.setPaymoney(payMoney);
}
模式优点
- 算法可以自由切换
- 便于扩展
- 消除多重逻辑判断
模式缺点
- 随着策略不同,策略类可能会增多
- 所有策略类都要对外进行暴露
开源框架
- dubbo:典型的策略模式使用场景是,负载均衡的实现LoadBalance
五:模版模式
模式定义
定义一个操作算法的骨架,而把一些步骤延迟到子类中实现,使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。
模式本质
固定算法骨架
使用场景
- 需要固定算法骨架,实现一个算法不变的部分
- 各个子类具有共有的行为
- 需要控制子类的扩展
业务场景
在做数实时更新的时候,业务人员希望知道,改过之后的值是否正确,在我们的场景中,有单表数据更新,多表数据更新,但是服务端获取到的数据不知道是单表还是多表,因此需要进行数据切割,然后才能做后续的数据更新操作,这里其实就可以使用模版方法
功能描述
针对单表或多表数据做实时更新
代码实现
1.创建表切割接口
/**
* @author hpjia.abcft
* @version 1.0
* @date
* @description
*/
public interface TableSplit {
/**
* cutting data is converted to the corresponding update request object
* @param splitParam splitParam
* @return ModifyRequest
*/
ModifyRequest split(SplitParam splitParam);
}
2.表切割更新模版实现
/**
* @author hpjia.abcft
* @version 1.0
* @date
* @description table split common base class
*/
@Slf4j
public abstract class AbstractTableSplit implements TableSplit {
/**
* cutting data is converted to the corresponding update request object
* @param splitParam splitParam
* @return ModifyRequest
*/
@Override
public ModifyRequest split(SplitParam splitParam) {
DataCacheFacade dataCacheFacade = SpringContextUtils.createBean(DataCacheFacade.class);
if (null!=dataCacheFacade){
String orderId = splitParam.getOrderId();
String fixedKey = splitParam.getFixedKey();
DrvOutput drvOutput = DrvOpUtils.getDpuOutPut(dataCacheFacade, orderId, fixedKey,splitParam.getRuleId()+"");
if (drvOutput!=null){
ModifyRequest modifyRequest = doSplitTable(drvOutput);
log.info("modify request:{}", JSON.toJSONString(modifyRequest));
return modifyRequest;
}
else {
throw new ExternalDataNotValidException("external data not valid");
}
}
return null;
}
/**
* split table data
* @param drvOutput drvOutput
* @return ModifyRequest
*/
protected abstract ModifyRequest doSplitTable(DrvOutput drvOutput);
3.单表数据更新
package com.abcft.phoenix.paas.verify.execution.modify.split;
import com.abcft.phoenix.common.bean.DrvOutput;
import com.abcft.phoenix.common.bean.DrvTableColumn;
import com.abcft.phoenix.common.bean.DrvTableRow;
import com.abcft.phoenix.paas.verify.execution.modify.ModifyRequest;
import com.abcft.phoenix.paas.verify.execution.modify.domain.ModifyRow;
import com.abcft.phoenix.paas.verify.execution.modify.domain.ModifyTable;
import com.google.common.collect.Lists;
import lombok.extern.slf4j.Slf4j;
import java.util.List;
/**
* @author hpjia.abcft
* @version 1.0
* @date
* @description
* for example sql: select a.id,a.name,a.like from user
*/
@Slf4j
public class OneTableSplit extends AbstractTableSplit{
/**
* split table data
* @param drvOutput drvOutput
* @return ModifyRequest
*/
@Override
protected ModifyRequest doSplitTable(DrvOutput drvOutput) {
log.info("come into one table split....");
ModifyRequest modifyRequest = new ModifyRequest();
ModifyTable modifyTable = new ModifyTable();
// split row list
List<ModifyRow> modifyRows = splitRow(drvOutput);
Long dbId = Long.parseLong(drvOutput.getDbId()+"");
List<ModifyTable> modifyTables = Lists.newArrayList();
modifyTable.setTableName(getTableName(drvOutput));
modifyTable.setModifyRows(modifyRows);
modifyTables.add(modifyTable);
modifyRequest.setDbId(dbId);
modifyRequest.setModifyTables(modifyTables);
return modifyRequest;
}
/**
* in this case, do not judge the related set, because it has been judged when getting row
* @param drvOutput drvOutput
* @return String
*/
private String getTableName(DrvOutput drvOutput){
String tableName = drvOutput.getDrvTablesList().get(0).getTableName();
log.info("OneTableSplit table name is:{}",tableName);
return tableName;
}
/**
* traverse all rows to form an updated modify row collection
* @param drvOutput drvOutput
* @return List<ModifyRow>
*/
private List<ModifyRow> splitRow(DrvOutput drvOutput){
List<ModifyRow> modifyRows = Lists.newLinkedList();
checkTableData(drvOutput);
for (DrvTableRow drvTableRow : drvOutput.getDrvTablesList().get(0).getDrvTableRowsList()){
List<DrvTableColumn> drvTableColumnsList = drvTableRow.getDrvTableColumnsList();
ModifyRow modifyRow = setModifyRow(drvTableRow, drvTableColumnsList);
modifyRows.add(modifyRow);
}
return modifyRows;
}
}
4.多表数据更新
package com.abcft.phoenix.paas.verify.execution.modify.split;
import com.abcft.phoenix.common.bean.DrvOutput;
import com.abcft.phoenix.common.bean.DrvTableColumn;
import com.abcft.phoenix.common.bean.DrvTableList;
import com.abcft.phoenix.common.bean.DrvTableRow;
import com.abcft.phoenix.paas.verify.execution.modify.ModifyRequest;
import com.abcft.phoenix.paas.verify.execution.modify.domain.ModifyRow;
import com.abcft.phoenix.paas.verify.execution.modify.domain.ModifyTable;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import lombok.extern.slf4j.Slf4j;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* @author hpjia.abcft
* @version 1.0
* @date
* @description
* for example sql: select a.id as a1,b.id as b1,a.name as a2,b.emp as b2 from a,b where a.id = b.id
*/
@Slf4j
public class MultiTableFieldSplit extends AbstractTableSplit{
/**
* split table data
*
* @param drvOutput drvOutput
* @return ModifyRequest
*/
@Override
protected ModifyRequest doSplitTable(DrvOutput drvOutput) {
log.info("come into multi table field split");
checkTableData(drvOutput);
ModifyRequest modifyRequest = new ModifyRequest();
List<DrvTableList> drvTablesList = drvOutput.getDrvTablesList();
List<DrvTableRow> drvTableRowsList = drvTablesList.get(0).getDrvTableRowsList();
List<Map<String,ModifyRow>> mapRowList = Lists.newLinkedList();
for (DrvTableRow tableRow : drvTableRowsList){
Map<String, ModifyRow> modifyRowMap = splitTableRow(tableRow);
mapRowList.add(modifyRowMap);
}
List<ModifyTable> modifyTables = splitModifyTables(mapRowList);
return modifyRequest.setModifyTables(modifyTables).setDbId(Long.parseLong(drvOutput.getDbId()+""));
}
/**
* according to the name of the table, repeat cut, save as a table corresponding to a modified set
* @param mapList mapList
* @return
*/
private List<ModifyTable> splitModifyTables(List<Map<String,ModifyRow>> mapList){
List<ModifyRow> modifyRows;
// key:table_name ,value :List<ModifyRow>
Map<String ,List<ModifyRow>> tableModifyMap = Maps.newHashMap();
// Map.Entry<String, ModifyRow> for example: key:t1,value:r1 key:t1,value:r1,key:t2,value:r2,key:t2:value:r3
for (Map<String,ModifyRow> map:mapList){
Set<Map.Entry<String, ModifyRow>> entries = map.entrySet();
for (Map.Entry<String, ModifyRow> entry : entries){
String tableName = entry.getKey();
ModifyRow modifyRow = entry.getValue();
modifyRows = tableModifyMap.get(tableName);
if (null==modifyRows){
modifyRows = new ArrayList<>();
modifyRows.add(modifyRow);
}
else {
modifyRows.add(modifyRow);
}
tableModifyMap.put(tableName,modifyRows);
}
}
return splitTables(tableModifyMap);
}
/**
* according to the name of the table, get all modify rows and organize them into corresponding modify tables
* @param tableModifyMap tableModifyMap
* @return List<ModifyTable>
*/
private List<ModifyTable> splitTables(Map<String ,List<ModifyRow>> tableModifyMap){
List<ModifyTable> modifyTables = Lists.newLinkedList();
ModifyTable modifyTable = new ModifyTable();
Set<Map.Entry<String, List<ModifyRow>>> entries = tableModifyMap.entrySet();
for (Map.Entry<String, List<ModifyRow>> entry:entries){
String tableName = entry.getKey();
List<ModifyRow> modifyRows = entry.getValue();
modifyTable.setTableName(tableName);
modifyTable.setModifyRows(modifyRows);
modifyTables.add(modifyTable);
}
return modifyTables;
}
/**
* the value of each field to be modified, the name of the field and the indication it belongs to are assembled into a map container structure
* @param tableRow tableRow
* @return Map<String,ModifyRow>
*/
private Map<String,ModifyRow> splitTableRow(DrvTableRow tableRow){
//key:table_name,value:modify row
Map<String,ModifyRow> tableRowMap = Maps.newHashMap();
ModifyRow modifyRow = new ModifyRow();
Integer rowId = ((Long)tableRow.getRowId()).intValue();
modifyRow.setRowId(rowId);
List<DrvTableColumn> drvTableColumnsList = tableRow.getDrvTableColumnsList();
Map<String,String> modifyValueMap = Maps.newHashMap();
String tableName=null;
for (DrvTableColumn drvTableColumn : drvTableColumnsList){
String updateValue = drvTableColumn.getUpdateValue();
String columnName = drvTableColumn.getColumnName();
tableName = drvTableColumn.getTableName();
modifyValueMap.put(columnName,updateValue);
}
tableRowMap.put(tableName,modifyRow);
return tableRowMap;
}
}
5.数据更新实例封装使用
public class TableSplitFactory {
private TableSplitFactory(){}
private static final Map<String,TableSplit> TABLE_SPLIT_MAP = Maps.newHashMap();
static {
TABLE_SPLIT_MAP.put(SplitType.ONE_TABLE_SPLIT.name(),new OneTableSplit());
TABLE_SPLIT_MAP.put(SplitType.MULTI_TABLE_FIELD_SPLIT.name(),new MultiTableFieldSplit());
TABLE_SPLIT_MAP.put(SplitType.MULTI_TABLE_DETAIL_SPLIT.name(),new MultiTableDetailSplit());
}
private static TableSplit instance(String type){
if (type==null){
throw new NullPointerException("table data split type is null");
}
else if (!TABLE_SPLIT_MAP.containsKey(type)){
throw new IllegalArgumentException("table data split type is not valid");
}
else {
return TABLE_SPLIT_MAP.get(type);
}
}
/**
* call a specific instance of table data cutting to cut the data and assemble it into a modified object
* @param type type
* @param splitParam splitParam
* @return ModifyRequest
*/
public static ModifyRequest split(String type, SplitParam splitParam){
return instance(type).split(splitParam);
}
}
模式优点
- 封装不变部分,扩展可变部分
- 提取公共代码,易于维护
- 行为由父类控制,子类来实现
模式缺点
- 算法骨架不容易升级
开源框架
- Spring:jdbctemplate,transactiontemplate,jmstempalte,jpatemplate
- Dubbo:
六:状态模式
模式定义
对有状态的对象,把复杂的“判断逻辑”提取到不同的状态对象中,允许状态对象在其内部状态发生改变时改变其行为。
模式本质
状态改变其行为
使用场景
- 在运行的时候,通过状态来改变其行为
- 如果一个操作中包含庞大的分支,而分支有依赖于不同的状态来判断的
业务场景
在电商案例中,订单状态每次发生变更,都要执行不同的操作,这里正好可以使用状态模式。当订单完成支付的时 候,我们需要立即通知商家发货,当订单执行取消的时候,我们需要执行库存回滚,如果订单已支付,还需要执行退 款操作,无论是通知商家发货还是执行库存回滚,都是有订单状态决定,因此这里可以使用状态模式来实现
功能描述
订单状态不同,执行不同操作(订单支付通知发货,订单取消通知回滚库存)
代码实现
1.定义状态接口
public interface State {
/***
* 变更状态
* @param order */
void doAction(Order order);
/***
* 执行行为 */
void execute();
}
2.实现订单支付状态处理
@Component("sendMsgBehavior")
public class SendMsgBehavior implements State {
@Override
public void doAction(Order order) {
System.out.println("订单支付");
order.setState(this); }
@Override
public void execute(){
System.out.println("订单变更为已支付,需要通知商家发货!"); }
}
3.实现订单取消状态处理
@Component("resetStoreBehavior")
public class ResetStoreBehavior implements State {
@Override
public void doAction(Order order) {
System.out.println("订单取消");
order.setState(this); }
@Override
public void execute(){
System.out.println("订单取消,执行库存回滚!");
System.out.println("订单取消,执行退款!"); }
}
4.订单状态使用
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xlWMjYUW-1628044193412)(%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%E4%B9%8B%E4%B8%9A%E5%8A%A1%E5%AE%9E%E6%88%98%E5%9F%B9%E8%AE%AD.assets/image-20210801184227880.png)]
模式优点
- 避免一堆的if else
- 封装性非常好
模式缺点
- 状态比较多的时候,会出现类膨胀
- 对设计原则支持的不太友好,修改某个类状态,还要修改对应的代码
开源框架
关于这点,开源框架提供了更好的扩展,就是状态机,Spring Machine Status
七:适配器模式
模式定义
将一个类的接口转换为客户希望的另一个接口
模式本质
转换匹配,复用功能
使用场景
- 想使用一个已经存在的类,但是它的接口不符合你的要求
- 如果你想创建一个可以复用的类,但是它可以和一些不兼容的类一起工作
- 动态给对象添加一些额外的职责功能,就增加功能来说,它比生成子类更灵活
业务场景
第三方支付的时候,一般系统会接多个支付渠道,比如支付宝,微信,百度等,在接这个第三方业务接口的时候,考虑到可能参数接口以及参数的不兼容情况,我们在封装的过程中,可以使用适配器进行转换,对外暴露适配器,而把具体的支付渠道给屏蔽了
功能描述
根据不同的支付渠道,进行支付
业务场景
1.定义支付渠道接口
/**
* @Class PayChannel
* @Author hpjia.abcft & to be a low profile architect
* @CreateDate 2021-08-01 20:46
* @describe
* @Version 1.0
*/
public interface PayChannel {
void pay(Order order);
}
2.各种支付渠道的实现
/**
* @author hpjia.abcft & to be a low profile architect
* @version 1.0
* @date 2021-08-01 20:49
* @describe
*/
public class BaiDuPayChannel implements PayChannel {
@Override
public void pay(Order order) {
System.out.println("use bai du to pay");
}
}
/**
* @author hpjia.abcft & to be a low profile architect
* @version 1.0
* @date 2021-08-01 20:49
* @describe
*/
public class WeiXiPayChannel implements PayChannel {
@Override
public void pay(Order order) {
System.out.println("use wei xin to pay");
}
}
/**
* @author hpjia.abcft & to be a low profile architect
* @version 1.0
* @date 2021-08-01 20:48
* @describe
*/
public class ZhiFuBaoPayChannel implements PayChannel {
@Override
public void pay(Order order) {
System.out.println("use zhi fu bao to pay");
}
}
3.定义支付适配接口
/**
* @Class PayChannelAdapter
* @Author hpjia.abcft & to be a low profile architect
* @CreateDate 2021-08-01 20:52
* @describe
* @Version 1.0
*/
public interface PayChannelAdapter {
PayChannel getPayChannel();
}
4.支付适配接口以及实现
public class BaiDuPayChannelAdapter implements PayChannelAdapter {
@Override
public PayChannel getPayChannel(String channelType) {
return new BaiDuPayChannel();
}
}
public class WeiXiPayChannelAdapter implements PayChannelAdapter {
@Override
public PayChannel getPayChannel() {
return new WeiXiPayChannel();
}
}
public class ZhiFuBaoPayChannelAdapter implements PayChannelAdapter {
@Override
public PayChannel getPayChannel() {
return new ZhiFuBaoPayChannel();
}
}
5.支付渠道工厂
/**
* @author hpjia.abcft & to be a low profile architect
* @version 1.0
* @date 2021-08-01 20:50
* @describe
*/
public class PayChannelFactory {
public static PayChannel getPayChannel(String channelType){
return newInstance(channelType).getPayChannel(channelType);
}
private static PayChannelAdapter newInstance(String channelType){
PayChannelAdapter payChannelAdapter = null;
switch (channelType){
case "zhifubao":
payChannelAdapter = new ZhiFuBaoPayChannelAdapter();
break;
case "weixin":
payChannelAdapter = new WeiXiPayChannelAdapter();
break;
case "baidu":
payChannelAdapter = new BaiDuPayChannelAdapter();
break;
default:
payChannelAdapter = new ZhiFuBaoPayChannelAdapter();
break;
}
return payChannelAdapter;
}
}
模式优点
- 更好的复用性
- 更好的扩展性
模式缺点
- 过多使用适配器,会使整个系统比较凌乱
开源框架
- Dubbo:其中对logger的实现就是使用了典型的适配器,LoggerAdapter
八:链模式-责任链
模式定义
链子上的每一个实例,都有机会参与到对职责的处理
模式本质
逻辑处理和具体的实例分开
使用场景
- 审批流:请假审批&财务审批
业务场景
往上看到很多实例多喜欢用,审批这个功能来描述职责链模式,但是现实情况,我们很少会这么做,因为大多数企业或系统牵涉到审批功能都会使用开源的工作流。再此我分享一下,我实际当中如何用该模式解决实际问题。
业务背景
当时我们的业务主要需求是基于数据和给定规则进行校验(实际情况很复杂),前期开发人员(3000多行代码写在一个类)很难维护,后面为了快速响应业务需求的变化,我亲自看了代码,之后梳理完了以后,并做了重构,通过梳理,我发现校验链路,会经过加载规则->规则分类->错误规则处理->等待规则处理->规则数据量检测->执行正确规则处理,
后面处理步骤还会增多,同时不同的数据校验需求,对上面的链路存在不同的步骤要求.
功能描述
组装不同的数据处理步骤,实现不同的数据校验处理
代码实现
1.抽象数据处理的拦截器接口
/**
* @author hpjia.abcft
* @version 1.0
* @date
* @description
*/
public interface Interceptor {
/**
* do interceptor something,about may classify,or other
* @param context context
* @param entry entry
*/
void interceptor(InterceptorContext context,InterceptorEntry entry);
/**
* next interceptor something,about may classify,or other
* @param context context
* @param entry entry
*/
void nextInterceptor(InterceptorContext context,InterceptorEntry entry);
}
2.定义存储数据处理需要的参数
package com.abcft.phoenix.paas.verify.execution.interceptor;
import com.abcft.pass.phoneix.cache.DataCacheFacade;
import com.abcft.phoenix.paas.verify.common.dict.DataDictClient;
import com.abcft.phoenix.paas.verify.domain.vo.ToolKey;
import com.abcft.phoenix.paas.verify.execution.executor.impl.ExecutorFactory;
import com.abcft.phoenix.paas.verify.execution.send.SendMessageCmp;
import com.abcft.phoenix.paas.verify.execution.trigger.cmp.PreTriggerCheck;
import com.abcft.phoenix.paas.verify.loader.loader.VerificationRuleLoader;
import lombok.Data;
/**
* @author hpjia.abcft
* @version 1.0
* @date
* @description
*/
@Data
public class InterceptorEntry {
/**
* current table id
*/
private String currentTableId;
/**
* current table name
*/
private String currentTableName;
private ToolKey toolKey;
/**
* dataCacheFacade
*/
private DataCacheFacade dataCacheFacade;
/**
* preTriggerCheck
*/
private PreTriggerCheck preTriggerCheck;
/**
* sendMessageCmp
*/
private SendMessageCmp sendMessageCmp;
/**
* verificationRuleLoader
*/
private VerificationRuleLoader verificationRuleLoader;
/**
* executorFactory
*/
private ExecutorFactory executorFactory;
/**
* data dict
*/
private DataDictClient dataDictClient;
/**
* rule search max count
*/
private Integer searchMaxCount;
}
3.定义每个拦截处理的结果
package com.abcft.phoenix.paas.verify.execution.interceptor.context;
import com.abcft.phoenix.paas.verify.domain.dto.SqlRule;
import lombok.Data;
import java.util.List;
/**
* @author hpjia.abcft
* @version 1.0
* @date
* @description
*/
@Data
public class InterceptorContext {
private List<SqlRule> executableRules;
private List<SqlRule> errorRules;
private List<SqlRule> waitingRules;
private Long startTime;
private List<SqlRule> sqlRules;
}
4.定义数据处理拦截器的抽象实现
/**
* @author hpjia.abcft
* @version 1.0
* @date
* @description
*/
public abstract class AbstractInterceptor implements Interceptor {
private AbstractInterceptor next = null;
/**
* next interceptor something,about may classify,or other
*
* @param context context
* @param entry entry
*/
@Override
public void nextInterceptor(InterceptorContext context, InterceptorEntry entry) {
if (null!=next){
next.interceptor(context,entry);
}
}
public AbstractInterceptor getNext() {
return next;
}
public void setNext(AbstractInterceptor next) {
this.next = next;
}
5.定义各种数据处理对应的拦截器实现
/**
* @author hpjia.abcft
* @version 1.0
* @date
* @description
*/
@Slf4j
public class LoadAllRuleInterceptor extends AbstractInterceptor {
/**
* do interceptor something,about may classify,or other
*
* @param context context
* @param entry entry
*/
@Override
public void interceptor(InterceptorContext context, InterceptorEntry entry) {
log.info("do all rule interceptor...");
//send success message to queue
ToolKey toolKey = entry.getToolKey();
String tableId = toolKey.getTableId();
String ruleSetId = toolKey.getRuleSetId();
VerificationRuleLoader verificationRuleLoader = entry.getVerificationRuleLoader();
List<SqlRule> allRules = getAllRules(tableId, ruleSetId, verificationRuleLoader);
if (allRules==null || allRules.size()==0){
this.sendMessage(entry.getSendMessageCmp(), StatusCode.SUCCESS_NO_RULE.getCode(),"当前表ID为["+tableId+"],没有配置任何规则",context.getStartTime(),entry.getToolKey().getMessageContext());
return;
}
else {
log.info("load rule size:{}",allRules.size());
context.setSqlRules(allRules);
nextInterceptor(context,entry);
}
}
/**
* @author hpjia.abcft
* @version 1.0
* @date rule classify interceptor(executable rule,error rule,waiting rule)
*/
@Slf4j
public class RuleClassifyInterceptor extends AbstractInterceptor {
private static final String ERROR_FLAG = "error";
/**
* do interceptor something,about may classify,or other
*
* @param context context
* @param entry entry
*/
@Override
public void interceptor(InterceptorContext context, InterceptorEntry entry) {
log.info("do rule classify interceptor...");
Objects.requireNonNull(entry, "verification executable rule interceptor param is null");
Long startTime = System.currentTimeMillis();
context.setStartTime(startTime);
// executable rules
List<SqlRule> executableRules = Lists.newArrayList();
String currentTableName = entry.getCurrentTableName();
if (StringUtils.isEmpty(currentTableName) || StringUtils.isBlank(currentTableName)) {
throw new NullPointerException("current table name is null");
}
List<SqlRule> sqlRules = context.getSqlRules();
// error rules
List<SqlRule> errorRules = Lists.newArrayList();
// wait rules
List<SqlRule> waitRules = Lists.newArrayList();
for (SqlRule sqlRule : sqlRules) {
String ruleSql = sqlRule.getRuleSql();
int tableSize = ParserSqlUtils.getTableSize(ruleSql);
if (tableSize == 0) {
errorRules.add(sqlRule);
}
// representative current table no relation table,can immediately verification
else if (tableSize == 1) {
executableRules.add(sqlRule);
}
// representative contain relation table
else {
validationMultiExecutableRule(entry, executableRules, waitRules, sqlRule, ruleSql);
}
}
if (CollectionUtils.isNotEmpty(errorRules)) {
log.info("get error rules size:{},table name:{}", errorRules.size(), currentTableName);
context.setErrorRules(errorRules);
}
if (CollectionUtils.isNotEmpty(executableRules)) {
log.info("get executable rules size:{},table name:{}", executableRules.size(), currentTableName);
context.setExecutableRules(executableRules);
}
if (CollectionUtils.isNotEmpty(waitRules)) {
log.info("get waiting rules size:{},table name:{}", waitRules.size(), currentTableName);
context.setWaitingRules(waitRules);
}
nextInterceptor(context, entry);
}
}
@Slf4j
public class ErrorRuleInterceptor extends AbstractInterceptor{
@Override
public void interceptor(InterceptorContext context, InterceptorEntry entry) {
log.info("do error rule interceptor");
List<SqlRule> errorRules = context.getErrorRules();
if (CollectionUtils.isNotEmpty(errorRules)){
List<DrvOutput> drvOutputs = Lists.newLinkedList();
SqlVerificationResult.Builder builder = SqlVerificationResult.newBuilder();
for (SqlRule sqlRule:errorRules){
DrvOutput.Builder drvOutputBuilder = DrvOutput.newBuilder();
drvOutputBuilder.setRuleFlag(true);
drvOutputBuilder.setRuleName(sqlRule.getRuleName());
drvOutputBuilder.setRuleId(sqlRule.getRuleId());
drvOutputs.add(drvOutputBuilder.build());
}
builder.addAllDrvOutputs(drvOutputs);
SqlVerificationResult build = builder.build();
String orderId = entry.getToolKey().getOrderId();
String dataKey = entry.getToolKey().getOutputKey();
boolean b = entry.getDataCacheFacade().setDataContentToCache(orderId, dataKey,build);
log.info("generate rule configuration error,order id:{},data key:{}",orderId,dataKey);
if (b){
sendMessage(entry.getSendMessageCmp(),VerificationConstant.HDPU_CODE_RULE,"当前规则不正确,需要业务人员核查规则",context.getStartTime(),entry.getToolKey().getMessageContext());
}
else{
throw new RuntimeException("写入错误规则到缓存系统失败");
}
}
else {
log.info("no error rule interceptor...");
nextInterceptor(context,entry);
}
}
}
public class WaitingRuleInterceptor extends AbstractInterceptor {
/**
* do interceptor something,about may classify,or other
*
* @param context context
* @param entry entry
*/
@Override
public void interceptor(InterceptorContext context, InterceptorEntry entry) {
log.info("do waiting rule interceptor...");
List<SqlRule> waitingRules = context.getWaitingRules();
List<SqlRule> executableRules = context.getExecutableRules();
ToolKey toolKey = entry.getToolKey();
if (null== waitingRules || waitingRules.size()==0){
toolKey.setTableExecStatus(VerificationConstant.IMMEDIATE_EXECUTION);
log.warn("waiting rule is null,need not interceptor");
entry.setToolKey(toolKey);
nextInterceptor(context,entry);
}
else {
//save waiting rule to db
saveWaitingRules(waitingRules, entry);
//no executable rules,save waiting status to message queue
if (null==executableRules || executableRules.size()==0){
sendMessage(entry.getSendMessageCmp(), VerificationConstant.WAITING_RUNNING_CODE,"当前数据表["+waitingRules.get(0).getTableName()+"],规则不满足执行机制,挂起中",context.getStartTime(),entry.getToolKey().getMessageContext());
return;
}
else {
//Contains partially executable rules,next interceptor
toolKey.setTableExecStatus(VerificationConstant.PARTIAL_RULE_EXECUTED);
entry.setToolKey(toolKey);
nextInterceptor(context, entry);
}
}
}
public class ExecutableRuleInterceptor extends AbstractInterceptor {
/**
* do interceptor something,about may classify,or other
*
* @param context context
* @param entry entry
*/
@Override
public void interceptor(InterceptorContext context, InterceptorEntry entry) {
log.info("do executable rule interceptor...");
List<SqlRule> executableRules = context.getExecutableRules();
if (CollectionUtils.isNotEmpty(executableRules)){
log.info("executable rule size :{}",executableRules.size());
//modify or verification + ignore,for a rule
Type type = getExecutorType(entry.getToolKey(),entry.getDataCacheFacade());
log.info("execution all type is:{}", JSON.toJSONString(type));
// get execution instance
Executor executorInstance = entry.getExecutorFactory().getExecutorInstance(type.getExecutorType());
log.info("executor instance is :{}",executorInstance.getClass().getSimpleName());
// set execution param
ExecutionParam executionParam = setExecutionParam(entry.getToolKey().getRuleSetId(), entry.getToolKey(), entry.getCurrentTableId(), type);
executionParam.setSqlRules(context.getExecutableRules());
// call method execution
executorInstance.execution(executionParam);
}
else {
log.info("no any executable rule exec...");
}
}
public class RuleIndexInterceptor extends AbstractInterceptor {
/**
* do interceptor something,about may classify,or other
*
* @param context context
* @param entry entry
*/
@Override
public void interceptor(InterceptorContext context, InterceptorEntry entry) {
log.info("do rule index interceptor");
}
}
public class RuleSearchCountInterceptor extends AbstractInterceptor {
/**
* do interceptor something,about may classify,or other
* <pre>
* 1.rule search count
* <pre/>
* @param context context
* @param entry entry
*/
@Override
public void interceptor(InterceptorContext context, InterceptorEntry entry) {
log.info("do rule search count interceptor...");
List<SqlRule> executableRules = context.getExecutableRules();
if (CollectionUtils.isNotEmpty(executableRules)){
Long databaseId = executableRules.get(0).getDatabaseId();
DataDictClient dataDictClient = entry.getDataDictClient();
DictDbInfo dataBaseInfo = dataDictClient.getDataBaseInfo(databaseId);
// check each rule for search count
Map<Integer,SearchRuleResult> searchRuleResultMap = Maps.newHashMap();
Integer searchMaxCount = entry.getSearchMaxCount();
for (SqlRule sqlRule : executableRules){
String ruleSql = sqlRule.getRuleSql();
try {
List<Map<String, Object>> mapList = DbOperationTemplate.queryForList(dataBaseInfo, ruleSql);
if (CollectionUtils.isNotEmpty(mapList)){
if (mapList.size()>searchMaxCount){
log.info("current rule id:{},search count:{},super max count:{}",sqlRule.getRuleId(),mapList.size(),searchMaxCount);
SearchRuleResult.SearchRuleResultBuilder searchRuleResultBuilder = SearchRuleResult.builder().ruleId(sqlRule.getRuleId()).ruleName(sqlRule.getRuleName()).tableName(sqlRule.getTableName()).searchCount(mapList.size());
searchRuleResultMap.put(sqlRule.getRuleId(),searchRuleResultBuilder.build());
}
}
} catch (Exception e) {
log.error("search rule count occur exception",e);
}
}
6.定义拦截器链和默认实现
public abstract class InterceptorChain extends AbstractInterceptor {
/**
* use linked list add interceptor
* @param abstractInterceptor abstractInterceptor
*/
public abstract void addLast(AbstractInterceptor abstractInterceptor);
}
public class DefaultInterceptorChain extends InterceptorChain {
AbstractInterceptor first = new AbstractInterceptor() {
@Override
public void interceptor(InterceptorContext context, InterceptorEntry entry) {
super.nextInterceptor(context,entry);
}
};
AbstractInterceptor end = first;
@Override
public void addLast(AbstractInterceptor abstractInterceptor) {
end.setNext(abstractInterceptor);
end = abstractInterceptor;
}
/**
* do interceptor something,about may classify,or other
*
* @param context context
* @param entry entry
*/
@Override
public void interceptor(InterceptorContext context, InterceptorEntry entry) {
first.interceptor(context,entry);
}
@Override
public void setNext(AbstractInterceptor next) {
addLast(next);
}
@Override
public AbstractInterceptor getNext() {
return first.getNext();
}
}
7.实现具体拦截器链的builder
/**
* @author hpjia.abcft
* @version 1.0
* @date
* @description
*/
public interface InterceptorChainBuilder {
/**
* builder interceptor interceptor chain
* @return InterceptorChain
*/
InterceptorChain builder();
}
/**
* @author hpjia.abcft
* @version 1.0
* @date
* @description
*/
public final class DefaultInterceptorChainBuilder implements InterceptorChainBuilder {
/**
* builder interceptor interceptor chain
*
* @return InterceptorChain
*/
@Override
public InterceptorChain builder() {
InterceptorChain interceptorChain = new DefaultInterceptorChain();
interceptorChain.addLast(new LoadAllRuleInterceptor());
interceptorChain.addLast(new RuleClassifyInterceptor());
interceptorChain.addLast(new ErrorRuleInterceptor());
interceptorChain.addLast(new WaitingRuleInterceptor());
//interceptorChain.addLast(new RuleSearchCountInterceptor());
interceptorChain.addLast(new ExecutableRuleInterceptor());
return interceptorChain;
}
}
/**
* @author hpjia.abcft
* @version 1.0
* @date
* @description
*/
public class WaitExecutableInterceptorChainBuilder implements InterceptorChainBuilder {
/**
* builder interceptor interceptor chain
*
* @return InterceptorChain
*/
@Override
public InterceptorChain builder() {
InterceptorChain interceptorChain = new DefaultInterceptorChain();
interceptorChain.addLast(new ExecutableRuleInterceptor());
return interceptorChain;
}
}
8.封装拦截器的提供者
/**
* @author hpjia.abcft
* @version 1.0
* @date
* @description
*/
@Slf4j
public class InterceptorChainProvider {
private InterceptorChainProvider(){}
private static volatile InterceptorChain chain = null;
private static final Object LOCK = new Object();
/**
* new instance interceptor chain
* @return InterceptorChain
*/
public static InterceptorChain newInterceptorChain(InterceptorChainBuilder builder){
if (chain==null){
synchronized (LOCK){
if (chain==null){
if (builder==null){
log.warn("current builder is null,so use default builder");
builder = new DefaultInterceptorChainBuilder();
}
chain = builder.builder();
}
}
}
log.info("init interceptor chain:{}",chain);
return chain;
}
}
9.链使用
// 1 in this case, it may be a data update operation
// 2 in this case, it may be data ignore operation
// 3 in this case, it may be data pure validation
// 4 the order of execution is update > check > ignore. (after the update, check immediately. If there is failure data after verification, ignore it again.)
// 4.1 do some functions before and after verification
// 5 according to the execution type to distinguish
//4 Start processing the business call execution
InterceptorChain interceptorChain = InterceptorChainProvider.newInterceptorChain(null);
// builder entry
InterceptorEntry entry = setInterceptorEntry(toolKey, tableId);
entry.setSearchMaxCount(searchMaxCount);
entry.setCurrentTableName(tableName);
InterceptorContext context = new InterceptorContext();
context.setStartTime(System.currentTimeMillis());
// execute interceptor chain
interceptorChain.interceptor(context,entry);
模式优点
- 责任链模式很好的处理理单⼀一职责和开闭原则,简单了了耦合也使对象关系更更加清晰,⽽而且外部的调⽤用 ⽅方并不不需要关⼼心责任链是如何进⾏行行处理理的*(以上程序中可以把责任链的组合进⾏行行包装,在提供给外 部使⽤用)*
模式缺点
- 避免造成性能以及编排混乱调试测 试疏漏漏问题。
开源框架
- Sentinel:阿里巴巴开源的熔断限流框架,其中关于slot很好的使用了这个模式,解决了核心链路处理的问题,并且也提供了很好的扩展
九:链模式-过滤器链
模式定义
执行某种业务的核心功能逻辑之前,先执行一些列相关的过滤器,这些过滤器放在一起组成过滤器连。
模式本质
过滤功能和核心业务功能的分离
使用场景
- 执行核心逻辑之前,要先执行其他过滤功能(权限,黑白名单,限流等)
业务场景
在这里分享几种常见的案例:比如过滤非法字符和增加一些其它的拦截功能,写法分为两种,一种是有返回值,一种是无返回值:
代码实现
有返回值
public interface Filter {
/**
* Make sure call invoker.invoke() in your implementation.
*/
Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException;
}
无返回值
public interface Filter {
void doFilter(Request request, Response response, FilterChain chain);
}
具体案例
本案例通过filter实现非法字符的过滤
1.定义filter接口
package filter;
import filter.chain.FilterChain;
public interface Filter {
void doFilter(Request request, Response response, FilterChain chain);
}
2.定义各种filter实现
package filter.impl;
import filter.Filter;
import filter.Request;
import filter.Response;
import filter.chain.FilterChain;
public class HTMLFilter implements Filter {
@Override
public void doFilter(Request request, Response response, FilterChain chain) {
//process the html tag <>
request.requestStr = request.requestStr.replace('<', '[')
.replace('>', ']') + "---HTMLFilter()";
chain.doFilter(request, response, chain);
response.responseStr += "---HTMLFilter()";
}
}
package filter.impl;
import filter.Filter;
import filter.Request;
import filter.Response;
import filter.chain.FilterChain;
public class SesitiveFilter implements Filter {
@Override
public void doFilter(Request request, Response response, FilterChain chain) {
request.requestStr = request.requestStr.replace("", "")
.replace("", "") + "---SesitiveFilter()";
chain.doFilter(request, response, chain);
response.responseStr += "---SesitiveFilter()";
}
}
3.定义filter的request
package filter;
public class Request {
public String requestStr;
public String getRequestStr() {
return requestStr;
}
public void setRequestStr(String requestStr) {
this.requestStr = requestStr;
}
}
4.定义filter的response
package filter;
public class Response {
public String responseStr;
public String getResponseStr() {
return responseStr;
}
public void setResponseStr(String responseStr) {
this.responseStr = responseStr;
}
}
5.定义filter chain
package filter.chain;
import filter.Filter;
import filter.Request;
import filter.Response;
import java.util.ArrayList;
import java.util.List;
public class FilterChain implements Filter {
List<Filter> filters = new ArrayList<Filter>();
int index = 0;
public FilterChain addFilter(Filter f) {
this.filters.add(f);
return this;
}
@Override
public void doFilter(Request request, Response response, FilterChain chain) {
if(index == filters.size()) return ;
Filter f = filters.get(index);
index ++;
f.doFilter(request, response, chain);
}
}
模式优点
- 很好的分离业务过滤功能和核心功能,使得非核心业务逻辑功能,很好的扩展
模式缺点
- 过多的过滤功能,会导致类膨胀,从而间接增加系统的复杂性
开源框架
- Servlet:提供了Filter的实现
- Dubbo:通过Filter实现了一些列的功能(非核心业务)
十:插件模式
模式定义
通过可插拔或可扩展的机制,提供很好的扩展功能
模式本质
灵活的扩展机制
实现形式
对于该模式,要理解其核心就是可插拔,可扩展,代码落地有两种形式,一种是基于SPI机制,一种是基于Plugin,具体落地的时候,取决于业务场景,选择何种实现不要过于较真
Plugin
1.定义通用拦截器
public interface Interceptor {
Object intercept(Invocation invocation) throws Throwable;
default Object plugin(Object target) {
return Plugin.wrap(target, this);
}
default void setProperties(Properties properties) {
// NOP
}
}
2.实现日志拦截器
@Intercepts({@Signature(type = Executor.class, method = "query", args = {String.class})})
public class LoggingInterceptor implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
Method method = invocation.getMethod();
Object[] args = invocation.getArgs();
Object target = invocation.getTarget();
System.out.println("method:"+method.getName()+",args:"+args);
return invocation.proceed();
}
@Override
public void setProperties(Properties properties) {
}
@Override
public Object plugin(Object target) {
if (target instanceof Executor) {
return Plugin.wrap(target, this);
}
return target;
}
}
3.定义插件
package interceptor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
/**
* @author Clinton Begin
*/
public class Plugin implements InvocationHandler {
private final Object target;
private final Interceptor interceptor;
private final Map<Class<?>, Set<Method>> signatureMap;
private Plugin(Object target, Interceptor interceptor, Map<Class<?>, Set<Method>> signatureMap) {
this.target = target;
this.interceptor = interceptor;
this.signatureMap = signatureMap;
}
public static Object wrap(Object target, Interceptor interceptor) {
Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
Class<?> type = target.getClass();
Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
if (interfaces.length > 0) {
return Proxy.newProxyInstance(
type.getClassLoader(),
interfaces,
new Plugin(target, interceptor, signatureMap));
}
return target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
Set<Method> methods = signatureMap.get(method.getDeclaringClass());
if (methods != null && methods.contains(method)) {
return interceptor.intercept(new Invocation(target, method, args));
}
return method.invoke(target, args);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
private static Map<Class<?>, Set<Method>> getSignatureMap(Interceptor interceptor) {
Intercepts interceptsAnnotation = interceptor.getClass().getAnnotation(Intercepts.class);
// issue #251
if (interceptsAnnotation == null) {
throw new PluginException("No @Intercepts annotation was found in interceptor " + interceptor.getClass().getName());
}
Signature[] sigs = interceptsAnnotation.value();
Map<Class<?>, Set<Method>> signatureMap = new HashMap<>();
for (Signature sig : sigs) {
Set<Method> methods = signatureMap.computeIfAbsent(sig.type(), k -> new HashSet<>());
try {
Method method = sig.type().getMethod(sig.method(), sig.args());
methods.add(method);
} catch (NoSuchMethodException e) {
throw new PluginException("Could not find method on " + sig.type() + " named " + sig.method() + ". Cause: " + e, e);
}
}
return signatureMap;
}
private static Class<?>[] getAllInterfaces(Class<?> type, Map<Class<?>, Set<Method>> signatureMap) {
Set<Class<?>> interfaces = new HashSet<>();
while (type != null) {
for (Class<?> c : type.getInterfaces()) {
if (signatureMap.containsKey(c)) {
interfaces.add(c);
}
}
type = type.getSuperclass();
}
return interfaces.toArray(new Class<?>[interfaces.size()]);
}
}
3.定义拦截参数封装
public class Invocation {
private final Object target;
private final Method method;
private final Object[] args;
public Invocation(Object target, Method method, Object[] args) {
this.target = target;
this.method = method;
this.args = args;
}
public Object getTarget() {
return target;
}
public Method getMethod() {
return method;
}
public Object[] getArgs() {
return args;
}
public Object proceed() throws InvocationTargetException, IllegalAccessException {
return method.invoke(target, args);
}
}
4.定义拦截器链
package interceptor;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class InterceptorChain {
private final List<Interceptor> interceptors = new ArrayList<>();
public Object pluginAll(Object target) {
for (Interceptor interceptor : interceptors) {
target = interceptor.plugin(target);
}
return target;
}
public void addInterceptor(Interceptor interceptor) {
interceptors.add(interceptor);
}
public List<Interceptor> getInterceptors() {
return Collections.unmodifiableList(interceptors);
}
}
5.定义相关注解
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Intercepts {
Signature[] value();
}
package interceptor;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({})
public @interface Signature {
Class<?> type();
String method();
Class<?>[] args();
}
6.插件使用
通过使用拦截器链
InterceptorChain interceptorChain = new InterceptorChain();
interceptorChain.addInterceptor(new LoggingInterceptor());
interceptorChain.addInterceptor(new TimeInterceptor());
interceptorChain.addInterceptor(new SqlExplainInterceptor());
Executor simpleExecutor = ExecutorFactory.getExecutor("simple", false);
Executor simpleInterceptorExecutor= (Executor)interceptorChain.pluginAll(simpleExecutor);
simpleInterceptorExecutor.query("");
SPI
优点
面向接口的编程,解耦,可拔插,适合于各种组件式开发,商业性开发
缺点
需要遍历所有的实现,并实例化,资源浪费,获取某个实现类的方式不够灵活,多线程使用 ServiceLoader 类的实例不安全。
使用场景
需要提供可扩展或可插拔的功能特性
业务场景
- 需要留有很好的功能扩展,有默认实现,但有其扩展接口
- 在执行核心逻辑的时候,可以执行一些其它的功能,而且可以随时插进或取出
模式优点
- 很好的遵守设计原则
- 灵活的扩展性
- 降低系统修改的成本
模式缺点
- 间接,增加系统的复杂度
开源框架
- Dubbo:扩展整个框架最核心的机制之一就是SPI
- ShardingJdbc:注册中心&配置中心&加解密策略都是通过SPI机制
- Jdbc:数据库驱动,可以支持mysql,sqlserver,db2,oracle的底层机制就是SPI
- Mybatis:通过提供各种interceptor功能,实现分页,sql改写,性能统计,我们也可以实现自定义的拦截器功能来实现各种功能,本质是因为它底层提供了基于plugin机制来实现的
- Spring中大量使用了SPI,比如:对servlet3.0规范对ServletContainerInitializer的实现、自动类型转换Type Conversion SPI(Converter SPI、Formatter SPI)
十一:工厂方法模式
模式定义
定义一个创建产品对象的工厂接口,将产品对象的实际创建工作推迟到具体子工厂类当中。这满足创建型模式中所要求的“创建 与使用相分离”的特点。
模式本质
延迟到子类来选择实现
使用场景
- 创建对象的工作延迟到子类去实现
业务场景
用户每次下单完成后,需要支付订单,支付订单会根据自身情况选择不同支付方式,后台服务会根据用户选中不同创 建不同支付渠道的实例,这里创建支付渠道的实例可以采用工厂方法模式。
- 功能描述
收银案例,根据不同支付方式,选择不同支付渠道
- 代码实现
1.创建支付渠道,定义支付行为
public interface PayChannel {
/***
* 支付 */
void pay(); }
2.定义各种支付渠道实现
@Component("weixinPay")
public class WeixinPay implements PayChannel {
@Override
public void pay() {
System.out.println("微信支付成功!"); }
}
@Component("aliPay")
public class AliPay implements PayChannel {
@Override
public void pay() {
System.out.println("支付宝支付成功!"); }
}
3.实现支付工厂
@ConfigurationProperties(prefix = "pay")
public class PayFactory implements ApplicationContextAware{
//Spring容器
private static ApplicationContext applicationContext;
//支付键值对信息
private Map<String,String> paymap;
/***
* 创建支付通道,从paymap中获取对应通道的实例名字,从applicationContext获取通道实例 */
public PayChannel createChannel(String key){
return applicationContext.getBean(paymap.get(key),PayChannel.class);
}
/***
* 获取容器
* @param applicationContext * @throws BeansException
*/
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws
BeansException {
PayFactory.applicationContext = applicationContext; }
}
4.配置支付渠道配置
pay:
paymap: {"1":"weixinPay","2":"aliPay"}
5.支付渠道实现
@Service
public class PayServiceImpl implements PayService {
@Autowired
private PayChannelFactory payChannelFactory;
/***
* 支付
* @param type
* @param id
*/
@Override
public void pay(String type, String id) {
PayChannel payChannel = payChannelFactory.getPayChannel(type);
payChannel.pay(null);
}
}
模式优点
- 可以在不知具体实现的情况下编程
- 更容易扩展对象的新版本
- 连接平行的类层次
模式缺点
- 具体产品对象和工厂方法是耦合的
开源框架
- Spring:BeanFactory是最典型的代表
- Dubbo:ExtensionFactory
十二:管道模式
模式定义
管道模式(Pipeline Pattern) 是**责任链模式(Chain of Responsibility Pattern)**的常用变体之一。在管道模式中,管道扮演着流水线的角色,将数据传递到一个加工处理序列中,数据在每个步骤中被加工处理后,传递到下一个步骤进行加工处理,直到全部步骤处理完毕。
纯的责任链模式在链上只会有一个处理器用于处理数据,而管道模式上多个处理器都会处理数据
模式本质
数据处理步骤进行合理的分离,然后又可以进行动态的组装
使用场景
任务代码较为复杂,需要拆分为多个子步骤时,尤其是后续可能在任意位置添加新的子步骤、删除旧的子步骤、交换子步骤顺序,可以考虑使用管道模式。
业务场景
最开始做模型平台的时候,创建模型实例的功能,包括:**“输入数据校验 -> 根据输入创建模型实例 -> 保存模型实例到相关 DB 表”**总共三个步骤,也不算复杂,所以当时的代码大概是这样的:
public class ModelServiceImpl implements ModelService {
/**
* 提交模型(构建模型实例)
*/
public CommonReponse<Long> buildModelInstance(InstanceBuildRequest request) {
// 输入数据校验
validateInput(request);
// 根据输入创建模型实例
ModelInstance instance = createModelInstance(request);
// 保存实例到相关 DB 表
saveInstance(instance);
}
}
然而没有过多久,我们发现表单输入数据的格式并不完全符合模型的输入要求,于是我们要加入 “表单数据的预处理”。这功能还没动手呢,又有业务方提出自己也存在需要对数据进行处理的情况(比如根据商家的表单输入,生成一些其他业务数据作为模型输入)。
所以在 “输入数据校验” 之后,还需要加入 “表单输入输出预处理” 和 “业务方自定义数据处理(可选)”
其实在这里就可以使用管道模式,具体看一下代码框架模版
1.创建管道上线文对象
package pipeline.one;
import java.time.LocalDateTime;
/**
* 传递到管道的上下文
*/
public class PipelineContext {
/**
* 处理开始时间
*/
private LocalDateTime startTime;
/**
* 处理结束时间
*/
private LocalDateTime endTime;
/**
* 获取数据名称
*/
public String getName() {
return this.getClass().getSimpleName();
}
}
package pipeline.one;
import java.util.Map;
public class InstanceBuildContext extends PipelineContext {
/**
* 模型 id
*/
private Long modelId;
/**
* 用户 id
*/
private long userId;
/**
* 表单输入
*/
private Map<String, Object> formInput;
public Map<String, Object> getFormInput() {
return formInput;
}
/**
* 保存模型实例完成后,记录下 id
*/
private Long instanceId;
/**
* 模型创建出错时的错误信息
*/
private String errorMsg;
// 其他参数
@Override
public String getName() {
return "模型实例构建上下文";
}
public String getErrorMsg() {
return errorMsg;
}
public void setErrorMsg(String errorMsg) {
this.errorMsg = errorMsg;
}
}
2.创建上下文对象处理器的接口
package pipeline.one;
/**
* @Class ContextHandler
* @Author hpjia.abcft & to be a low profile architect
* @CreateDate 2021-08-03 10:37
* @describe 上线文处理器
* @Version 1.0
*/
public interface ContextHandler<T extends PipelineContext> {
/**
* 处理输入的上下文数据
* @param context 处理时的上下文数据
* @return 返回 true 则表示由下一个 ContextHandler 继续处理,返回 false 则表示处理结束
*/
boolean handle(T context);
}
3.创建具体各个上下文对象处理器的实现
public class FormInputPreprocessor implements ContextHandler<InstanceBuildContext> {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
@Override
public boolean handle(InstanceBuildContext context) {
logger.info("--表单输入数据预处理--");
// 假装进行表单输入数据预处理
return true;
}
}
public class InputDataPreChecker implements ContextHandler<InstanceBuildContext> {
@Override
public boolean handle(InstanceBuildContext context) {
logger.info("--输入数据校验--");
Map<String, Object> formInput = context.getFormInput();
context.setErrorMsg("error");
return true;
}
}
public class ModelInstanceCreator implements ContextHandler<InstanceBuildContext> {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
@Override
public boolean handle(InstanceBuildContext context) {
logger.info("--根据输入数据创建模型实例--");
// 假装创建模型实例
return true;
}
}
public class ModelInstanceSaver implements ContextHandler<InstanceBuildContext> {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
@Override
public boolean handle(InstanceBuildContext context) {
logger.info("--保存模型实例到相关DB表--");
// 假装保存模型实例
return true;
}
}
public class BizSideCustomProcessor implements ContextHandler<InstanceBuildContext> {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
@Override
public boolean handle(InstanceBuildContext context) {
logger.info("--业务方自定义数据处理--");
// 先判断是否存在自定义数据处理,如果没有,直接返回 true
// 调用业务方的自定义的表单数据处理
return true;
}
}
public class CommonHeadHandler implements ContextHandler<PipelineContext> {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
@Override
public boolean handle(PipelineContext context) {
logger.info("管道开始执行:context={}", JSON.toJSONString(context));
// 设置开始时间
context.setStartTime(LocalDateTime.now());
return true;
}
}
public class CommonTailHandler implements ContextHandler<PipelineContext> {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
@Override
public boolean handle(PipelineContext context) {
// 设置处理结束时间
context.setEndTime(LocalDateTime.now());
logger.info("管道执行完毕:context={}", JSON.toJSONString(context));
return true;
}
}
4.定义上下文处理器路由表
package pipeline.one.impl;
public class PipelineRouteConfig implements ApplicationContextAware {
/**
* 数据类型->管道中处理器类型列表 的路由
*/
private static final
Map<Class<? extends PipelineContext>,
List<Class<? extends ContextHandler<? extends PipelineContext>>>> PIPELINE_ROUTE_MAP = new HashMap<>(4);
/*
* 在这里配置各种上下文类型对应的处理管道:键为上下文类型,值为处理器类型的列表
*/
static {
PIPELINE_ROUTE_MAP.put(InstanceBuildContext.class,
Arrays.asList(
InputDataPreChecker.class,
ModelInstanceCreator.class,
ModelInstanceSaver.class
));
// 将来其他 Context 的管道配置
}
/**
* 在 Spring 启动时,根据路由表生成对应的管道映射关系
*/
@Bean("pipelineRouteMap")
public Map<Class<? extends PipelineContext>, List<? extends ContextHandler<? extends PipelineContext>>> getHandlerPipelineMap() {
return PIPELINE_ROUTE_MAP.entrySet()
.stream()
.collect(Collectors.toMap(Map.Entry::getKey, this::toPipeline));
}
/**
* 根据给定的管道中 ContextHandler 的类型的列表,构建管道
*/
private List<? extends ContextHandler<? extends PipelineContext>> toPipeline(
Map.Entry<Class<? extends PipelineContext>, List<Class<? extends ContextHandler<? extends PipelineContext>>>> entry) {
return entry.getValue()
.stream()
.map(appContext::getBean)
.collect(Collectors.toList());
}
private ApplicationContext appContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
appContext = applicationContext;
}
}
5.定义上下文处理执行器
package pipeline.one;
public class PipelineExecutor {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
/**
* 引用 PipelineRouteConfig 中的 pipelineRouteMap
*/
@Resource
private Map<Class<? extends PipelineContext>,
List<? extends ContextHandler<? super PipelineContext>>> pipelineRouteMap;
/**
* 同步处理输入的上下文数据<br/>
* 如果处理时上下文数据流通到最后一个处理器且最后一个处理器返回 true,则返回 true,否则返回 false
*
* @param context 输入的上下文数据
* @return 处理过程中管道是否畅通,畅通返回 true,不畅通返回 false
*/
public boolean acceptSync(PipelineContext context) {
// 【通用头处理器】处理
commonHeadHandler.handle(context);
// 管道是否畅通
boolean lastSuccess = true;
for (ContextHandler<? super PipelineContext> handler : pipeline) {
try {
// 当前处理器处理数据,并返回是否继续向下处理
lastSuccess = handler.handle(context);
} catch (Throwable ex) {
lastSuccess = false;
logger.error("[{}] 处理异常,handler={}", context.getName(), handler.getClass().getSimpleName(), ex);
}
// 不再向下处理
if (!lastSuccess) { break; }
}
// 【通用尾处理器】处理
commonTailHandler.handle(context);
return lastSuccess;
}
}
模式优点
- 很好的把每个要处理的步骤和具体的处理器进行了分离
- 通过组装特定的处理器,可以动态完成多种不同形态业务的处理
- 系统灵活,易于扩展
模式缺点
- 步骤过多的话,对应的处理器也会过多,间接导致类膨胀
- 代码调用层次会变得更加复杂
开源框架
- Sentinel:典型的熔断限流框架,里面的slot算是比较典型的应用
- Netty:里面有个经典的ChannelPipeline模式,穿插了整个输入和输出的处理
十三:Master-Work模式
模式定义
Master对资源进行分配,同时接收每个Work处理的结果,一个master可以对应多个Work,Work是基于mpp运行的。从而充分利用每台机器的cpu资源
模式本质
资源的统一分配和任务的执行进行分离
使用场景
- 一个大任务可以切割成多个小任务,小任务并行计算
- 数据分批快速并行处理
代码实现
1.创建master
package master.work;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;
/**
* @author hpjia.abcft
* @version 1.0
* @date 06-28
* @description 封装任务+worker+写回每个worker执行的结果集
*/
public class Master {
/**
* 封装任务的队列
*/
private ConcurrentLinkedQueue<Product> productConcurrentLinkedQueue = new ConcurrentLinkedQueue<Product>();
/**
* 封装work数量
*/
private Map<String,Thread> workerThread = new HashMap<String, Thread>(100000);
/**
* 保存每个worker执行的结果
*/
private ConcurrentHashMap<String,Object> workerRunResult = new ConcurrentHashMap<String, Object>();
public Master(Worker worker,int workCount){
worker.setProductConcurrentLinkedQueue(productConcurrentLinkedQueue);
worker.setWorkerRunResult(workerRunResult);
for (int i=0;i<workCount;i++){
this.workerThread.put(Integer.toString(i),new Thread(worker));
}
}
/**
* submit 任务提交
*/
public void submit(Product product){
this.productConcurrentLinkedQueue.add(product);
}
/**
* execute 任务执行
*/
public void execute(){
for (Map.Entry<String, Thread> entry:workerThread.entrySet()){
entry.getValue().start();
}
}
/**
* isEnd 线程是否执行结束
*/
public boolean isComplete(){
for (Map.Entry<String, Thread> entry:workerThread.entrySet()){
if (entry.getValue().getState()!=Thread.State.TERMINATED){
return false;
}
}
return true;
}
/**
* getResult 获取执行结果
*/
public int getResult() {
int priceResult = 0;
for(Map.Entry<String, Object> me : this.workerRunResult.entrySet()){
priceResult += (Integer)me.getValue();
}
return priceResult;
}
}
2.定义work
package master.work;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;
/**
* @author hpjia.abcft
* @version 1.0
* @date 06-28
* @description 模拟实现商品定价的单个线程
*/
public abstract class Worker implements Runnable {
/**
* 定义一个并发队列用来存储要处理的商品信息
*/
private ConcurrentLinkedQueue<Product> productConcurrentLinkedQueue;
public void setProductConcurrentLinkedQueue(ConcurrentLinkedQueue<Product> productConcurrentLinkedQueue) {
this.productConcurrentLinkedQueue = productConcurrentLinkedQueue;
}
public void setWorkerRunResult(ConcurrentHashMap<String, Object> workerRunResult) {
this.workerRunResult = workerRunResult;
}
/**
* 定义一个存储每个work执行对应的结果
*/
private ConcurrentHashMap<String,Object> workerRunResult;
/**
* When an object implementing interface <code>Runnable</code> is used
* to create a thread, starting the thread causes the object's
* <code>run</code> method to be called in that separately executing
* thread.
* <p>
* The general contract of the method <code>run</code> is that it may
* take any action whatsoever.
*
* @see Thread#run()
*/
public void run() {
while (true){
Product product = this.productConcurrentLinkedQueue.poll();
if (product==null){
break;
}
Object handle = handle(product);
System.out.println("线程 "+Thread.currentThread().getName()+" , 计算完毕"+ product);
this.workerRunResult.put(Integer.toString(product.getPrdId()),handle);
}
}
/**
* 有真正实现产品定价的业务组件来实现
* @param input
* @return
*/
protected abstract Object handle(Product input) ;
}
3.定义要处理的task
package master.work;
/**
* @author hpjia.abcft
* @version 1.0
* @date 06-29
* @description 产品信息
*/
public class Task {
private int prdId;
private int prdPrice;
public int getPrdId() {
return prdId;
}
public void setPrdId(int prdId) {
this.prdId = prdId;
}
public int getPrdPrice() {
return prdPrice;
}
public void setPrdPrice(int prdPrice) {
this.prdPrice = prdPrice;
}
@Override
public String toString() {
return "商品信息: 商品id " + prdId + ", 商品价格" + prdPrice+"元" ;
}
}
4.定义具体处理task的实现
package master.work;
/**
* @author hpjia.abcft
* @version 1.0
* @date 06-28
* @description 真正实现商品定价的功能的业务逻辑
*/
public class TaskWork extends Worker {
@Override
protected Object handle(Task input) {
Object output = null;
try {
//模拟耗时的操作
Thread.sleep(100);
output = input.getPrdPrice();
} catch (InterruptedException e) {
e.printStackTrace();
}
return output;
}
}
模式优点
- 可以合理榨干每台机器的cpu,使性能达到最佳
- Work可以水平进行扩容
模式缺点
- 系统通信变的复杂和不稳定
- master可能会是瓶颈
开源框架
- Jdk:自带的forkjoin
- Hadoop:mr机制