1、装饰器模式
含义:
创建了一个装饰类,用来包装原有的类,并在保持类方法签名完整性的前提下,提供了额外的功能。
特征:
1、可以透明动态的扩展一个类的功能或给一个类添加附加职责;
2、动态的给一个对象添加功能,这些功能可以再动态的撤销;
使用场景:
扩展一个类功能; 动态增加功能,动态撤销;
当存在较多复杂的类功能组合的时候,为避免类爆炸,就会采用装饰器模式;
命名规则:
Decorator
现实案例:
咖啡: +冰 + 糖 + 奶
煎饼: +生菜 +鸡蛋 + 火腿肠 + 肉松
蛋糕: +水果 + 巧克力 + 奶油
2、装饰器模式通用实现:
①Shape接口定义,提供 draw() 的画画公用方法
②ShapeDecorator,实现抽象的方法,包含一个Shape属性,通过构造方法传入,实现draw()方法;
③RedShapeDecorator 实现ShapeDecorator,添加自身专属方法setRedBorder() 红边设置;实现增强类实现;
④Circle 和 Rectangle 实现接口Shape, 实现普通的draw方法;
3、在源码中的使用
案例1:InputStream
①InputStream接口定义,提供 draw() 的画画公用方法;
②FilterInputStream,实现抽象装饰角色,包含一个InputStream属性,通过构造方法传入,实现所有规定接口;
③具体装饰角色:BufferedInputStream、DataInputStream、HttpInputStream;
案例2:CachingExecutor(Mybatis)
https://blog.csdn.net/qq_18860653/article/details/80594778
案例3:TransactionAwareCacheDecorator(Spring)
Cache 接口定义
AbstractValueAdaptingCache 缓存的抽象实现
GuavaCache 包装的guava缓存
TransactionAwareCacheDecorator 事务包装缓存
4、真实项目案例(装饰器对请求数据进行复杂的加密、解密)
项目背景 :
在金融项目数据加密是非常常见的,比如前端将加密后的字符串作为参数传给我们, 我们在接口中解密,再比如我们后端返回给前端的结果也是一串加密后的字符串,这是防止爬虫的一种安全手段,那么我们是否需要在每个接口在去进行加密解密?是否有一套机制去控制我想要加密的接口,同学们应该会想到只需在一个请求到达Controller之前能够截获其请求,并且根据其具体情况对 HttpServletRequest 中的参数进行过滤或者修改。然后再放回到该HttpServletRequest 中呢?
流只能读取一次,读取前端传过来的参数具体参数,如果是post请求,那么getInputStream()可以解析到详细的参数
在正式代码之前,我还是先简单介绍下ServletRequest、HttpServletRequest、ServletRequestWrapper以及HttpServletRequestWrapper这几个接口或者类之间的层次关系,并说明“继承HttpServletRequestWrapper类以实现在Filter中修改HttpServletRequest的参数”这种方式的原理是什么
它们之间的层次关系是这样的:
如上图所示,在过滤器中:
//我们会进入这个方法
doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
//通过ServletRequest进来的,之后我们会强制造型如下:
HttpServletRequest req=(HttpServletRequest) request;
''''''
//最后调用
chain.doFilter(req, res);
如果需要对前端加密过来的参数进行解密,那么我们需要对HttpServletRequest进行处理,而上面图关系毫无疑问是一个装饰模式:
以在HttpServletRequestWrapper 做具体的装饰器
public class ParamsWrapperDecorator extends HttpServletRequestWrapper {
private static final Logger logger = LoggerFactory.getLogger(ParamsWrapperDecorator.class);
private HttpServletRequest request;
public ParamsWrapperDecorator(HttpServletRequest request) {
super(request);
this.request = request;
}
@Override
public ServletInputStream getInputStream() throws IOException, RoborderException {
String path = request.getRequestURI();
//if (new Random().ints(0, 4).boxed().findFirst().orElse(-1) == 1) {
LoggerUtil.showInfoLogDetails(logger, "IP:{}, 请求地址:{}",Optional.ofNullable(MDC.get("ip")).orElse("unknown"),path);
// }
final MethodRule rule = MethodRule.getEnum(path);
//处理传入传入参数是否加密
ServletInputStream is = handleWithParamsDecrypt(rule);
return is == null ? request.getInputStream() : is;
}
private ServletInputStream handleWithParamsDecrypt(final MethodRule rule) throws IOException, RoborderException {
if (Optional.ofNullable(rule).isPresent() && rule.getParamsDecrypt()) {
// 大致业务逻辑代码
BufferedReader reader = request.getReader();
String encryptData = String.join("", reader.lines().collect(Collectors.toList()));
reader.close();
.......................................
String result;
EncapDataDTO dto = JSONObject.parseObject(encryptData, EncapDataDTO.class);
try {
result = Optional.of(Des3Util.decode(dto.getEncryptData(), secretKey)).orElse("");
LoggerUtil.showInfoLogDetails(logger, "真实传入参数:{}, 传入参数encaptData:{}, 密钥secretKey:{}", result, encryptData, secretKey);
} catch (Exception e) {
LoggerUtil.showErrorDetails(logger, "参数解密失败,加密的数据为:{}", dto.getEncryptData());
throw new RoborderException(MemberErrorCode.DATA_TRANSFORM_ERROR);
}
ByteArrayInputStream is = new ByteArrayInputStream(result.getBytes(Charset.forName("utf8")));
return new ServletInputStream() {
@Override
public boolean isFinished() {
return false;
}
@Override
public boolean isReady() {
return false;
}
@Override
public void setReadListener(ReadListener listener) {
}
@Override
public int read() throws IOException {
return is.read();
}
};
}
return null;
}
}
定义接口规则枚举类
public enum MethodRule {
start("start", true, true, EncryptType.BASE64, 0),
Member_registerUser(RequestUrl.Member + RequestUrl.Member_registerUser, false, false, EncryptType.NULL, 0),
end("end", true, true, EncryptType.RSA, 0);
private final String url;
/**
* 参数是否需要解密
*/
private final Boolean paramsDecrypt;
/**
* 返回结果是否需要加密
*/
private final Boolean resultEncrypt;
/**
* 加密方式
*/
private final EncryptType signType;
/**
* 返回数据是否需要压缩 0 不压缩, 1 压缩
*/
private final Integer dataCompress;
MethodRule(String url, Boolean paramsDecrypt, Boolean resultEncrypt, EncryptType signType, Integer dataCompress) {
this.url = url;
this.paramsDecrypt = paramsDecrypt;
this.resultEncrypt = resultEncrypt;
this.signType = signType;
this.dataCompress = dataCompress;
}
public static MethodRule getEnum(final String url) {
return Arrays.stream(MethodRule.values()).filter(method -> url.startsWith(method.URL()))
.findFirst().orElse(null);
}
public String URL() {
return url;
}
public Boolean getParamsDecrypt() {
return paramsDecrypt;
}
public Boolean getResultEncrypt() {
return resultEncrypt;
}
public EncryptType getSignType() {
return signType;
}
public Integer getDataCompress() {
return dataCompress;
}
}
controller层
@ApiOperation(value = "用户注册", notes = "userDTO 可选参数:推荐人userToken 推荐码referralCode,其他为必选参数 ", response = ResultVO.class)
@RequestMapping(value = RequestUrl.Member_registerUser, method = RequestMethod.POST)
public ResultObject<ResultVO> registerUser(){
}
最后是dofliter中调用
HttpServletRequestWrapper params = new ParamsWrapperDecorator(req);
ResultWrapperDecorator result = new ResultWrapperDecorator(res);
chain.doFilter(params, result);
https://nicky-chen.github.io/2018/03/28/decorator/
5、优缺点评估:
优点:
1、是继承的有效替代模式,可以动态的扩展一个实现类的功能,比继承灵活;
2、装饰类和被装饰类可以独立发展,不会相互耦合
缺点:
增加系统的复杂性,会实现更多的类和代码; 使得代码复杂