需求背景:根据不同的场景,需要调用多个外部接口,每个外部接口耗时不等,我根据要求做成可动态增加的模式。
优点:用起来很方便,动态地追加功能,可以随意匹配调用接口的数量,且只调用一次方法即可,这会使业务代码非常清晰。配合回调函数和模板,可以实现动态地调用任意组合类型的接口。
缺点:代码有点多。会new出不少对象。因为本来掉接口的类完全可以用注入来的。现在的装饰器必须自己来new,否则无法保证灵活性。
下面是装饰器的一些类:
/**
* 〈一句话功能简述〉<br>
* 〈功能详细描述〉构件接口
*
* @author wangzha
* @see [相关类/方法](可选)
* @since [产品/模块版本] (可选)
*/
public interface IComponent<T> {
/**
* 〈一句话功能简述〉<br>
* 〈功能详细描述〉
*
* @Param t
* @return
* @see [相关类/方法](可选)
* @since [产品/模块版本] (可选)
**/
void operation(T t);
}
/**
* 〈一句话功能简述〉<br>
* 〈功能详细描述〉装饰类
*
* @author wangzha
* @see [相关类/方法](可选)
* @since [产品/模块版本] (可选)
*/
public class Decorator<T> implements IComponent<T> {
public IComponent component;
public Decorator(IComponent component){
this.component = component;
}
@Override
public void operation(T t) {
//noinspection unchecked
this.component.operation(t);
}
}
/**
* 〈一句话功能简述〉<br>
* 〈功能详细描述〉会员属性基本构件
*
* @author wangzha
* @see [相关类/方法](可选)
* @since [产品/模块版本] (可选)
*/
public class MemberAttrComponent implements IComponent<MemberAttrDO> {
public MemberAttrComponent() {
this.esOperator = SpringContextUtils.getBean("esOperatorImpl");
}
@Override
public void operation(MemberAttrDO memberAttrDo) {
//个性化实现
}
}
/**
* 〈一句话功能简述〉<br>
* 〈功能详细描述〉开通任性贷装饰类
*
* @author wangzha
* @see [相关类/方法](可选)
* @since [产品/模块版本] (可选)
*/
public class RxLoanDecorator extends Decorator<MemberAttrDO> {
public RxLoanDecorator(IComponent component) {
super(component);
}
@Override
public void operation(MemberAttrDO memberAttrDO) {
//...省略个性化代码...
super.operation(memberAttrDO);
}
}
/**
* 〈一句话功能简述〉<br>
* 〈功能详细描述〉首次购买理财装饰类
*
* @author wangzha
* @see [相关类/方法](可选)
* @since [产品/模块版本] (可选)
*/
public class LicaiFirstBuyDecorator extends Decorator<MemberAttrDO> {
public LicaiFirstBuyDecorator(IComponent component) {
super(component);
}
@Override
public void operation(MemberAttrDO memberAttrDO) {
//...省略个性化代码...
super.operation(memberAttrDO);
}
}
/**
* 〈一句话功能简述〉<br>
* 〈功能详细描述〉购买基金装饰类
*
* @author wangzha
* @see [相关类/方法](可选)
* @since [产品/模块版本] (可选)
*/
public class FundBuyDecorator extends Decorator<MemberAttrDO> {
public FundBuyDecorator(IComponent component) {
super(component);
}
@Override
public void operation(MemberAttrDO memberAttrDO) {
//...省略个性化代码...
super.operation(memberAttrDO);
}
}
/**
* 〈一句话功能简述〉<br>
* 〈功能详细描述〉首次登陆装饰类
*
* @author wangzha
* @see [相关类/方法](可选)
* @since [产品/模块版本] (可选)
*/
public class FirstLoginDateDecorator extends Decorator<MemberAttrDO> {
public FirstLoginDateDecorator(IComponent component) {
super(component);
}
@Override
public void operation(MemberAttrDO memberAttrDO) {
//...省略个性化代码...
super.operation(memberAttrDO);
}
}
先上业务模板类:
/**
* 〈一句话功能简述〉<br>
* 〈功能详细描述〉查询会员所属用户组摸板类
*
* @author wangzha
* @see [相关类/方法](可选)
* @since [产品/模块版本] (可选)
*/
public abstract class AbstractMemberBelongGroupService {
private static final Logger LOGGER = LoggerFactory.getLogger(QueryMemberBelongGroupForPortraitService.class);
@Autowired
QueryMemberBelongGroupDoService queryMemberBelongGroupDoService;
@Autowired
MemberAttrDoFactory memberAttrDoFactory;
public ObjectResponse<Map<String, Object>> execute(ObjectRequest objectRequest) {
final MemberAttrDO memberAttrDO = (MemberAttrDO) objectRequest.getEntity();
ObjectResponse<Map<String, Object>> objectResponse = new ObjectResponse<>();
// 工厂 + 切面 + 回调 => 创建领域对象
FactoryCallBack<MemberAttrDO> factoryCallBack = () -> queryMemberAttr(memberAttrDO,objectResponse);
memberAttrDoFactory.getInstance(memberAttrDO,factoryCallBack);
LOGGER.info("会员编号:{},会员属性集合:{}", memberAttrDO.getUserNo(), JSON.toJSON(memberAttrDO.getMemberAttr()));
// 构建groovy验证基本属性条件
memberAttrDO.bulidMemberAttrMap();
// 根据groovy规则计算出-会员标签集合
Map<String, Object> memberLabelCodes = queryMemberBelongGroupDoService
.queryMemberLabels(memberAttrDO.getMemberAttr());
// 根据groovy规则计算出-会员所属用户组集合
List<String> memberGroupList = queryMemberBelongGroupDoService.queryMemberBelongGroup(memberLabelCodes);
Map<String, Object> resultMap = Maps.newHashMap();
resultMap.put(MemberAttrDoConstants.LABEL_RESULT_LIST, memberAttrDO.getLabelCodeList(memberLabelCodes));
resultMap.put(MemberAttrDoConstants.RESULT_LIST, memberGroupList);
objectResponse.setObject(resultMap);
LOGGER.info("查询会员所属用户组信息结束,返回报文:{}",JSON.toJSONString(objectResponse));
return objectResponse;
}
protected abstract MemberAttrDO queryMemberAttr(MemberAttrDO memberAttrDO,ObjectResponse objectResponse);
}
业务子类一:
/**
* 〈一句话功能简述〉<br>
* 〈功能详细描述〉会员所属用户组服务(用于会员画像)
*
* @author wangzha
* @see [相关类/方法](可选)
* @since [产品/模块版本] (可选)
*/
@Service
public class QueryMemberBelongGroupForPortraitService extends AbstractMemberBelongGroupService
implements GenericService {
@Override
public ObjectResponse<Map<String, Object>> execute(ObjectRequest objectRequest) {
return super.execute(objectRequest);
}
@Override
protected MemberAttrDO queryMemberAttr(MemberAttrDO memberAttrDO, ObjectResponse objectResponse) {
MemberAttrComponent memberAttrComponent = new MemberAttrComponent();
// 查询会员属性
memberAttrComponent.operation(memberAttrDO);
// 如果不存在该会员直接返回
if (MapUtils.isEmpty(memberAttrDO.getMemberAttr())) {
objectResponse.setResponseEnum(ResponseCodeEnum.MEMBER_NOT_EXIST);
return memberAttrDO;
}
Decorator<MemberAttrDO> decorator = new FirstLoginDateDecorator(new DefaultComponent());
decorator = new LicaiFirstBuyDecorator(decorator);
decorator = new RxLoanDecorator(decorator);
decorator.operation(memberAttrDO);
return memberAttrDO;
}
}
业务子类二:
/**
* 〈一句话功能简述〉<br>
* 〈功能详细描述〉会员所属用户组服务(用于营销中台)
*
* @author wangzha
* @see [相关类/方法](可选)
* @since [产品/模块版本] (可选)
*/
@Service
public class QueryMemberBelongGroupService extends AbstractMemberBelongGroupService implements GenericService {
@Autowired
MemberAttrDoRepository memberAttrRepository;
@Autowired
DecoratorFactory decoratorFactory;
@Override
public ObjectResponse<Map<String, Object>> execute(ObjectRequest objectRequest) {
return super.execute(objectRequest);
}
@Override
protected MemberAttrDO queryMemberAttr(MemberAttrDO memberAttrDO,ObjectResponse objectResponse) {
// 查询现在全量用户组,找到所需要的所有属性
List<AttrDefine> attrDefineList = memberAttrRepository.findMemberAttrInfo();
List<String> attrCodeList = new ArrayList<>();
attrDefineList.forEach(x -> attrCodeList.add(x.getAttrCode()));
//获取属性的装饰器
IComponent<MemberAttrDO> iComponent = decoratorFactory.getMemberAttrDecorator(attrCodeList);
iComponent.operation(memberAttrDO);
return memberAttrDO;
}
}
统一获取和缓存领域对象的工厂:
/**
* 〈一句话功能简述〉<br>
* 〈功能详细描述〉领域对象获取工厂类
*
* @author wangzha
* @see [相关类/方法](可选)
* @since [产品/模块版本] (可选)
*/
@Component
public class MemberAttrDoFactory {
/**
* 〈一句话功能简述〉<br>
* 〈功能详细描述〉获取领域对象,这里只获取了ems_member_index索引里的属性
*
* @Param memberAttrDO 拦截器中会用来拼接缓存key
* @return
* @see [相关类/方法](可选)
* @since [产品/模块版本] (可选)
**/
@Cache(cacheKey = CacheConstants.MEMBER_ATTR_DOMIAN_REDIS_CACHE_KEY)
public MemberAttrDO getInstance(MemberAttrDO memberAttrDO, FactoryCallBack<MemberAttrDO> callBack) {
return callBack.createBySelf();
}
}
领域对象缓存切面:
切面代码省略,即为切开MemberAttrDoFactory
领域对象缓存切面代码:
/**
* 〈一句话功能简述〉<br>
* 〈功能详细描述〉MemberDO缓存推送切面
*
* @author wangzha
* @see [相关类/方法](可选)
* @since [产品/模块版本] (可选)
*/
public class MemberDoCacheAspect extends AbstractDomainCacheAspect<MemberAttrDO> {
private static final Logger LOGGER = LoggerFactory.getLogger(MemberDoCacheAspect.class);
@Autowired
RedisOperator redisOperator;
/**
* 〈一句话功能简述〉<br>
* 〈功能详细描述〉环绕通知
*
* @Param jp
* @return
* @see [相关类/方法](可选)
* @since [产品/模块版本] (可选)
**/
public MemberAttrDO aroundMethod(final ProceedingJoinPoint pjp) {
return super.excute(pjp);
}
@Override
protected MemberAttrDO cacheOperation(Object[] args, String cacheKey, ProceedingJoinPoint pjp) {
Object o = args[0];
// 参数不合法
if (o == null || !(o instanceof MemberAttrDO)) {
LOGGER.error("领域对象缓存不合法!");
return null;
}
MemberAttrDO memberAttrDO = (MemberAttrDO) args[0];
String userNo = memberAttrDO.getUserNo();
String key = cacheKey + userNo;
// 是否从redis读取标志
boolean readFlag = DigitalConstants.STR_ONE
.equals(SwitchScmCfg.getString(ScmConstants.MEMBER_DO_READ_REDIS_SWITCH));
Map<String, String> stringMap = readFlag ? redisOperator.hgetAll(key) : new HashMap<>();
Map<String, Object> map;
if (MapUtils.isNotEmpty(stringMap)) {
// 如果redis里存在属性map,直接构造,跳过原方法体
map = memberAttrDO.getMemberAttr();
stringMap.forEach(map::put);
return memberAttrDO;
}
// 如果redis里不存在属性map,执行原方法体,并放入redis中
try {
Object result = pjp.proceed();
if (result == null || !(result instanceof MemberAttrDO)) {
LOGGER.info("执行结果获取的领域对象不合法!");
return null;
}
map = ((MemberAttrDO) result).getMemberAttr();
memberAttrDO.setMemberAttr(map);
// 是否写入redis标志,默认原方法体执行出来的数据是正确的,不对map做校验
boolean writeFlag = DigitalConstants.STR_ONE
.equals(SwitchScmCfg.getString(ScmConstants.MEMBER_DO_WRITE_REDIS_SWITCH));
if (writeFlag) {
map.forEach((k, v) -> stringMap.put(k, v instanceof String ? (String) v : JSON.toJSONString(v)));
redisOperator.hmset(key, stringMap);
redisOperator.expire(key,
CacheScmCfg.getInteger(CacheConstants.MEMBER_ATTR_DOMIAN_REDIS_CACHE_EXPIRE_TIME));
}
} catch (Throwable throwable) {
LOGGER.error("执行方法出现异常!");
}
return memberAttrDO;
}
}
装饰器工厂:
/**
* 〈一句话功能简述〉<br>
* 〈功能详细描述〉
*
* @author wangzha
* @see [相关类/方法](可选)
* @since [产品/模块版本] (可选)
*/
@Component
public class DecoratorFactory {
Logger LOGGER = LoggerFactory.getLogger(DecoratorFactory.class);
/**
* 会员属性 包含了在es中的ems_member_attr里的会员属性
*/
@Resource(name = "memberAttrList")
private List<String> memberAttrList;
/**
* 基金产品购买标记
*/
private static final String FUND_BUY = "FUND_BUY";
/**
* 理财首购标记
*/
private static final String LICAI_FIRST_BUY = "LICAI_FIRST_BUY";
/**
* 开通任性贷标记
*/
private static final String OPEN_RX_LOAN = "OPEN_RX_LOAN";
/**
* 〈一句话功能简述〉<br>
* 〈功能详细描述〉匹配属性code对应的装饰器
*
* @Param attrCodeList 属性code集合
* @return
* @see [相关类/方法](可选)
* @since [产品/模块版本] (可选)
**/
public IComponent<MemberAttrDO> getMemberAttrDecorator(List<String> attrCodeList) {
LOGGER.debug("匹配会员属性对应的装饰器...");
IComponent<MemberAttrDO> iComponent = new DefaultComponent();
boolean flag = false;
boolean need = true;
for (String attrCode : attrCodeSet) {
if (flag) {
if (memberAttrList.contains(attrCode)) {
iComponent = new MemberAttrComponent();
}
flag = false;
} else {
if (need && memberAttrList.contains(attrCode)) {
flag = true;
need = false;
continue;
}
switch (attrCode) {
case FUND_BUY:
iComponent = new FundBuyDecorator(iComponent);
break;
case LICAI_FIRST_BUY:
iComponent = new LicaiFirstBuyDecorator(iComponent);
break;
case OPEN_RX_LOAN:
iComponent = new RxLoanDecorator(iComponent);
break;
default:
LOGGER.warn("出现未知的会员属性:{},装饰器工厂类无法识别!",attrCode);
}
}
}
LOGGER.debug("匹配会员属性对应的装饰器结束:{}", JSON.toJSON(iComponent));
return iComponent;
}