由一个重构示例引发的对可扩展性的思考
Posted 编程大观园
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了由一个重构示例引发的对可扩展性的思考相关的知识,希望对你有一定的参考价值。
示例
有时,即使使用策略模式来分离差异,也会比较麻烦。 比如保存入侵对象:
@ComponentProperties(purpose = "EntitySaver")
@Component
public class DefaultDetectEntitySaver implements EntitySaver<DefaultAgentEventFlowContext> {
private final Logger logger = LogUtils.getLogger(DefaultDetectEntitySaver.class);
@Autowired
private BounceShellSaverStrategy bounceShellSaverStrategy;
@Autowired
private WebshellCheckRecordSaverStrategy webshellCheckRecordSaverStrategy;
DefaultSaverStrategy defaultDetectEntitySaver = new DefaultSaverStrategy();
@Override
public void save(DefaultAgentEventFlowContext flowContext) {
DetectDO detectDO = flowContext.getDetectDO();
select(detectDO).save(detectDO);
logger.info("DefaultDetectEntitySaver-finished: detectType: {} agentId: {}",
flowContext.getEventData().getDetectType(), flowContext.getAgentId());
}
private DetectEntitySaverStrategy select(DetectDO detectDO) {
if (detectDO instanceof BaseBounceShell) {
return bounceShellSaverStrategy;
}
if (detectDO instanceof WebShellCheckRecordDO) {
return webshellCheckRecordSaverStrategy;
}
return defaultDetectEntitySaver;
}
static class DefaultSaverStrategy implements DetectEntitySaverStrategy {
@Override
public void save(DetectDO detectDO) {}
}
}
@Component
public class BounceShellSaverStrategy implements DetectEntitySaverStrategy<BaseBounceShell> {
@Autowired
private BounceShellService bounceShellService;
@Autowired
private DockerBounceShellService dockerBounceShellService;
@Autowired
private WindowsBounceShellService windowsBounceShellService;
@Override
public void save(BaseBounceShell baseBounceShell) {
select(baseBounceShell).save(baseBounceShell);
}
private BaseService select(BaseBounceShell baseBounceShell) {
if (baseBounceShell instanceof BounceShell) {
return bounceShellService;
}
if (baseBounceShell instanceof DockerBounceShell) {
return dockerBounceShellService;
}
if (baseBounceShell instanceof WindowsBounceShell) {
return windowsBounceShellService;
}
throw new RuntimeException("Not valid bounceShell");
}
}
@Component
public class WebshellCheckRecordSaverStrategy implements DetectEntitySaverStrategy<WebShellCheckRecordDO> {
@Autowired
private WebShellCheckRecordService webShellCheckRecordService;
@Override
public void save(WebShellCheckRecordDO webShellCheckRecordDO) {
webShellCheckRecordService.save(webShellCheckRecordDO);
}
}
不同的 SaverStrategy 的逻辑非常简单,就是选择对应的 Service 来保存对应的 DO 对象。 但即使如此简单的逻辑,还需要写这么多东西,而且还要写 if-else。 有没有更好的办法呢 ?
有的。实际上,对于保存对象来说,差异的部分,无非是 DO 与 Service 的映射关系。 如果能够自动化建立 Map[DO, Service] ,就可以通过 DO 的 class 找到对应的 Service 来保存 DO 对象。
现在来找一下有没有好的办法。最简单的办法,就是直接建立一个枚举,手动指定。 当然,对于我们的工程来说, 更好的是自动化建立。 看看如下 WebShellCheckRecordService 和 WebShellCheckRecordServiceImpl 的 定义,正好第一个泛型参数的类型与 Service 有着对应关系。 现在看看我们有没有办法提取出来。
public interface WebShellCheckRecordService
extends BaseService<WebShellCheckRecordDO, WebShellCheckRecordQuery, String>
public class WebShellCheckRecordServiceImpl extends MongoServiceImpl<WebShellCheckRecordDO,
WebShellCheckRecordQuery, String, WebShellCheckRecordRepository>
implements WebShellCheckRecordService, OnContextReady, OnContextClose {
如下所示: 建立一个 BaseServiceFactory 根据 MongoServiceImpl 的子类自动化解析和建立 DO 与 ServiceImpl 的映射关系。
/**
* @Description 根据 DO 对象找到 Service 对象
* @Date 2021/5/14 10:34 上午
* @Created by qinshu
*/
@Component
public class BaseServiceFactory implements ApplicationContextAware {
private final Logger logger = LogUtils.getLogger(BaseServiceFactory.class);
private ApplicationContext applicationContext;
private Map<String, MongoServiceImpl> doServiceMap = new HashMap<>();
@PostConstruct
public void init() {
Map<String, MongoServiceImpl> baseServiceMap = applicationContext.getBeansOfType(MongoServiceImpl.class);
for (MongoServiceImpl bs: baseServiceMap.values()) {
Class c = bs.getClass();
// 解决cglib动态代理问题
if (bs.getClass().getName().contains("CGLIB")) {
c = bs.getClass().getSuperclass();
}
String typeName = TypeUtils.getParameterizedType(c);
doServiceMap.put(typeName, bs);
}
logger.info("doServiceMap: {}", doServiceMap);
}
public BaseService getService(String doClassName) {
return doServiceMap.get(doClassName);
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
}
public class TypeUtils {
public static String getParameterizedType(Class c) {
Type t = c.getGenericSuperclass();
if (t instanceof ParameterizedType) {
ParameterizedType pt = (ParameterizedType) t;
Type[] types = pt.getActualTypeArguments();
if (types != null && types.length > 0) {
return types[0].getTypeName();
}
}
return null;
}
}
现在, 保存入侵实体对象的组件代码可以写成
@ComponentProperties(purpose = "EntitySaver")
@Component
public class DefaultDetectEntitySaver implements EntitySaver<DefaultAgentEventFlowContext> {
private final Logger logger = LogUtils.getLogger(DefaultDetectEntitySaver.class);
@Autowired
private BaseServiceFactory baseServiceFactory;
@Override
public void save(DefaultAgentEventFlowContext flowContext) {
DetectDO detectDO = flowContext.getDetectDO();
baseServiceFactory.getService(detectDO.getClass().getName()).save(detectDO);
logger.info("DefaultDetectEntitySaver-finished: detectType: {} agentId: {}",
flowContext.getEventData().getDetectType(), flowContext.getAgentId());
}
}
现在,我们再也不需要写任何策略类和选择策略的代码了。一切都是自动化的: 当你建立 XXXServiceImpl 类时,就会自动建立 DO 与 ServiceImpl 之间的映射,而 DefaultDetectEntitySaver 就会从 BaseServiceFactory 里根据 DO 的类名获取对应的 ServiceImpl 来保存对象了。
如果觉得获取泛型参数类型不太安全,可以考虑在 BaseService 添加一个 getEntityClassName 的方法,每个子类都实现该方法,这样会稍微麻烦一点,但是更安全。
小结
如果我们能够进一步思考和建立应用对象之间的关联关系,那么,就很有可能做到自动添加新能力而无需添加新代码。对于本例来说,只要实现了 XXXServiceImpl extends MongoServiceImpl
反过来说,如果对于新功能,我们需要针对每一处都作出改动,这意味着应用对象之间的关联关系还没有建立起来,仍然是孤立状态。
现在假设入侵业务有 A, B, C, D, E, F, G, H 八个子模块。 假设来一个入侵事件类型,都要针对这八个子模块写一些代码改动,这意味着,这些模块之间的关联关系没有建立起来。 理想情况是,这八个模块中有 A, B 两个是基础模块,而其它模块依赖于 A, B 模块建立的关联关系。 那么,如果我们能够根据 A, B 模块建立应用对象之间的关联,并建立模块之间的关联关系,那么,应用对象之间的关联关系就会自动迁移到其它模块上,从而使得除了 A, B 之外的其它模块几乎无需添加新代码就能支持新事件类型。
为什么能够做到无需添加新代码就能支持新事件类型?看上去有点魔幻,实际上并不稀奇。 因为我们可以从 C, D, E, F, G, H 解析出应用对象需要依赖的的关联关系,而这种关联关系可以通过 A, B 两个模块建立起来。这样, 其它模块只要根据这种关联关系来编写代码,就能保证根据 A,B 建立的关联关系自动应用到 C, D, E, F, G, H 上,从而使得 C, D, E, F, G, H 无需添加新代码就能自动支持。
即使达不到理想情况,如果能够建立一些有效的关联,也会使得开发量减少很多,而无需针对每个模块都写相似的代码。
以上是关于由一个重构示例引发的对可扩展性的思考的主要内容,如果未能解决你的问题,请参考以下文章