可扩展代码设计(3):多扩展点门面
作者:互联网
可扩展代码设计(1):if-else的痛
可扩展代码设计(2):消除if-else
上一篇文章 可扩展代码设计(2):消除if-else 提到,如果我们有很多的扩展逻辑,那么应该把相关的扩展逻辑归类到一起,形成多个扩展点接口。
本篇文章基于上篇文章的基础上,详细讲解如何组织多个扩展点:
一、归类出多个扩展点
首先,把扩展逻辑归类为多个扩展点。
比如,我们归类出3个扩展点接口:
1、抽象的扩展点接口:
package com.doing.plugin.extension;
/**
* @author Doing on 19/12/2018 19:20
*/
public interface Extension {
}
2、3个归类后的扩展点接口:
package com.doing.plugin.extension;
/**
* 扩展点
*
* @author Doing on 19/12/2018 15:38
*/
public interface PreAndPostExtension extends Extension{
/**
* 前置处理
* @param params 参数
* @param processRecord 处理对象传递
*/
void beforeProcess(String params, StringBuilder processRecord);
/**
* 后置处理
* @param params 参数
* @param processRecord 处理对象传递
*/
void afterProcess(String params, StringBuilder processRecord);
}
package com.doing.plugin.extension;
/**
* @author Doing on 19/12/2018 19:21
*/
public interface ModifyExtension1 extends Extension {
/**
* @param params 参数
* @param processRecord 处理对象传递
*/
void modify(String params, StringBuilder processRecord);
}
package com.doing.plugin.extension;
/**
* @author Doing on 19/12/2018 19:21
*/
public interface ModifyExtension2 extends Extension {
/**
* @param params 参数
* @param processRecord 处理对象传递
*/
void modify(String params, StringBuilder processRecord);
}
二、使用扩展点门面
现在我们有多个扩展点,每个新的活动业务接入进来,可以分别实现这几个扩展点接口。
但是,扩展逻辑分散到多个扩展点接口里后,业务系统共建者(接入新活动业务的开发同学)难免会困惑:现在系统都有哪些扩展点(扩展能力)?
所以,我们可以通过扩展门面去暴露出所有的扩展点:
package com.doing.plugin.extension;
/**
* @author Doing on 19/12/2018 19:39
*/
public interface ExtensionFacade {
PreAndPostExtension getPreAndPostExtension();
ModifyExtension1 getModifyExtension1();
ModifyExtension2 getModifyExtension2();
}
这样,参与活动业务共建的同学,便可以通过这个扩展门面知道都有哪些扩展点,然后根据自己的业务需求,挑选部分/全部扩展点进行实现。
比如,以京东活动为例子:
接入方法1、创建独立扩展类
创建独立的扩展类,实现各个扩展点接口:
package com.doing.plugin.extension.jd;
import com.doing.plugin.extension.PreAndPostExtension;
/**
* @author Doing on 19/12/2018 15:38
*/
public class JdPreAndPostExtension implements PreAndPostExtension {
@Override
public void beforeProcess(String params, StringBuilder processRecord) {
System.out.println("京东活动前置处理流程");
processRecord.append("京东活动前置处理流程 - ");
}
@Override
public void afterProcess(String params, StringBuilder processRecord) {
System.out.println("京东活动后置处理流程");
processRecord.append("京东活动后置处理流程");
}
}
package com.doing.plugin.extension.jd;
import com.doing.plugin.extension.ModifyExtension1;
/**
* @author Doing on 19/12/2018 19:26
*/
public class JdModifyExtension1 implements ModifyExtension1 {
@Override
public void modify(String params, StringBuilder processRecord) {
System.out.println("京东活动优化处理流程1");
processRecord.append("京东活动优化处理流程1 - ");
}
}
package com.doing.plugin.extension.jd;
import com.doing.plugin.extension.ModifyExtension2;
/**
* @author Doing on 19/12/2018 19:26
*/
public class JdModifyExtension2 implements ModifyExtension2 {
@Override
public void modify(String params, StringBuilder processRecord) {
System.out.println("京东活动优化处理流程2");
processRecord.append("京东活动优化处理流程2 - ");
}
}
然后,实现扩展点门面接口:
package com.doing.plugin.extension.jd;
import com.doing.plugin.extension.ExtensionFacade;
import com.doing.plugin.extension.ModifyExtension1;
import com.doing.plugin.extension.ModifyExtension2;
import com.doing.plugin.extension.PreAndPostExtension;
/**
* @author Doing on 19/12/2018 19:41
*/
public class JdExtensionFacade implements ExtensionFacade {
@Override
public PreAndPostExtension getPreAndPostExtension() {
return new JdPreAndPostExtension();
}
@Override
public ModifyExtension1 getModifyExtension1() {
return new JdModifyExtension1();
}
@Override
public ModifyExtension2 getModifyExtension2() {
return new JdModifyExtension2();
}
}
接入方法2、匿名内部类实现
直接在扩展点门面中返回业务扩展的匿名内部类:
package com.doing.plugin.extension.jd;
import com.doing.plugin.extension.ExtensionFacade;
import com.doing.plugin.extension.ModifyExtension1;
import com.doing.plugin.extension.ModifyExtension2;
import com.doing.plugin.extension.PreAndPostExtension;
/**
* @author Doing on 19/12/2018 19:41
*/
@BizCode("jd")
public class JdExtensionFacade implements ExtensionFacade {
@Override
public PreAndPostExtension getPreAndPostExtension() {
return new PreAndPostExtension() {
@Override
public void beforeProcess(String params, StringBuilder processRecord) {
System.out.println("京东活动前置处理流程");
processRecord.append("京东活动前置处理流程 - ");
}
@Override
public void afterProcess(String params, StringBuilder processRecord) {
System.out.println("京东活动后置处理流程");
processRecord.append("京东活动后置处理流程");
}
};
// return new JdPreAndPostExtension();
}
@Override
public ModifyExtension1 getModifyExtension1() {
return new ModifyExtension1() {
@Override
public void modify(String params, StringBuilder processRecord) {
System.out.println("京东活动优化处理流程1");
processRecord.append("京东活动优化处理流程1 - ");
}
};
// return new JdModifyExtension1();
}
@Override
public ModifyExtension2 getModifyExtension2() {
return new ModifyExtension2() {
@Override
public void modify(String params, StringBuilder processRecord) {
System.out.println("京东活动优化处理流程2");
processRecord.append("京东活动优化处理流程2 - ");
}
};
// return new JdModifyExtension2();
}
}
如果业务逻辑不是非常复杂的话,匿名内部类实现会简洁点
三、使用注解标识扩展点
与前篇类似,我们还是要使用注解去标识出业务扩展点,才能被我们的扩展点管理框架自动加载。
注解:
package com.doing.plugin;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface BizCode {
String value() default "taobao";
}
由于业务扩展门面列出了所有的扩展点,所以我们可以只给扩展门面加上注解即可。
比如,京东活动的扩展门面加上@BizCode("jd")
package com.doing.plugin.extension.jd;
import com.doing.plugin.BizCode;
import com.doing.plugin.extension.ExtensionFacade;
import com.doing.plugin.extension.ModifyExtension1;
import com.doing.plugin.extension.ModifyExtension2;
import com.doing.plugin.extension.PreAndPostExtension;
/**
* @author Doing on 19/12/2018 19:41
*/
@BizCode("jd")
public class JdExtensionFacade implements ExtensionFacade {
@Override
public PreAndPostExtension getPreAndPostExtension() {
return new JdPreAndPostExtension();
}
@Override
public ModifyExtension1 getModifyExtension1() {
return new JdModifyExtension1();
}
@Override
public ModifyExtension2 getModifyExtension2() {
return new JdModifyExtension2();
}
}
其他活动类似
四、自动识别和组装扩展点
基于注解和反射,我们可以自动识别和组装扩展点:
package com.doing.plugin;
import com.doing.plugin.extension.Extension;
import com.doing.plugin.extension.ExtensionFacade;
import org.reflections.Reflections;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
/**
* @author Doing on 19/12/2018 16:20
*/
public class ExtensionBuilder {
private ExtensionBuilder() {
}
public static ExtensionBuilder getInstance() {
return ExtensionBuilderInstance.INSTANCE;
}
private static class ExtensionBuilderInstance {
private static final ExtensionBuilder INSTANCE = new ExtensionBuilder();
}
/**
* 扩展类 => (业务编码 => 业务扩展实例)
*/
private Map<Class, ConcurrentHashMap<String, Extension>> multiExtMap = new ConcurrentHashMap<>();
/**
* 注册业务扩展类实例
*/
public void build() {
try {
// 找出所有类型的扩展接口/类(继承或实现了Extension):这里主要是想找出继承于Extension的子接口,并不关心Extension子接口的实现类
Reflections extKindReflections = new Reflections("com.doing.plugin.extension");
Set<Class<? extends Extension>> extKindClasses = extKindReflections.getSubTypesOf(Extension.class);
// 找出所有业务扩展门面类(继承或实现了ExtensionFacade)
Reflections extFacadeReflections = new Reflections("com.doing.plugin.extension");
Set<Class<? extends ExtensionFacade>> extFacadeClasses = extFacadeReflections.getSubTypesOf(ExtensionFacade.class);
// 对每一种类型的扩展
for (Class<? extends Extension> extKindClass : extKindClasses) {
// 过滤Extension子接口的实现类
if (!extKindClass.isInterface()) {
continue;
}
ConcurrentHashMap<String, Extension> extMap = new ConcurrentHashMap<String, Extension>();
// 遍历所有扩展门面类,获取该类型扩展的业务实现
for (Class<? extends ExtensionFacade> extFacade : extFacadeClasses) {
// 构建:业务编码 => 业务扩展实例
BizCode[] annotationsByType = extFacade.getAnnotationsByType(BizCode.class);
if (annotationsByType != null && annotationsByType.length > 0) {
BizCode bizCode = annotationsByType[0];
Extension extension = null;
Object extensionFacade = extFacade.newInstance();
Method[] facadeMethods = extFacade.getDeclaredMethods();
// 遍历扩展门面的所有方法
for (Method facadeMethod : facadeMethods) {
// 找到返回该类型扩展的method
if (extKindClass.isAssignableFrom(facadeMethod.getReturnType())) {
// 从业务扩展门面的方法返回中,获取该类型扩展的业务实现
extension = (Extension) facadeMethod.invoke(extensionFacade);
}
}
if (null != extension) {
extMap.put(bizCode.value(), extension);
}
}
}
multiExtMap.put(extKindClass, extMap);
}
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 根据业务扩展类型、业务编码,获取对应的业务扩展类实例
*
* @param extClass 业务扩展类型
* @param bizCode 业务编码
* @return
*/
public <Ext> Ext getExt(Class<Ext> extClass, String bizCode) {
return (Ext) multiExtMap.get(extClass).get(bizCode);
}
}
五、扩展点执行引擎
执行引擎会使用业务扩展点实现我们的业务逻辑流程:
package com.doing.engine;
import com.doing.plugin.ExtensionBuilder;
import com.doing.plugin.extension.ModifyExtension1;
import com.doing.plugin.extension.ModifyExtension2;
import com.doing.plugin.extension.PreAndPostExtension;
/**
* @author Doing on 19/12/2018 13:20
*/
public class MultiExtensionEngine implements Engine {
public MultiExtensionEngine() {
// 注册扩展
ExtensionBuilder.getInstance().build();
}
@Override
public String process(String bizCode, String params) {
StringBuilder processRecord = new StringBuilder();
PreAndPostExtension preAndPostExtension = ExtensionBuilder.getInstance().getExt(PreAndPostExtension.class, bizCode);
ModifyExtension1 modifyExtension1 = ExtensionBuilder.getInstance().getExt(ModifyExtension1.class, bizCode);
ModifyExtension2 modifyExtension2 = ExtensionBuilder.getInstance().getExt(ModifyExtension2.class, bizCode);
// 1、前置处理
preAndPostExtension.beforeProcess(params, processRecord);
// 2、统一处理1
System.out.println("统一处理流程1");
processRecord.append("统一处理1 - ");
// 3、过程处理1
modifyExtension1.modify(params,processRecord);
// 4、统一处理2
System.out.println("统一处理流程2");
processRecord.append("统一处理2 - ");
// 5、过程处理2
modifyExtension2.modify(params,processRecord);
// 6、统一处理3
System.out.println("统一处理流程3");
processRecord.append("统一处理3 - ");
// 7、后置处理
preAndPostExtension.afterProcess(params, processRecord);
return processRecord.toString();
}
}
包结构
包结构如下:
总结与分析
总结
本系统中,新的活动接入进来,只需要看下扩展点门面里都有哪些扩展点,然后按需实现自己的扩展点逻辑即可,其他工作都交给扩展点引擎框架去实现。
本设计可以提供一个比较清晰的开发框架,不至于由于扩展逻辑的繁杂而造成代码腐烂。
业务响应效率需求
通常来说,常规的开发方式是:每接入一个新的活动业务,我们都要走一遍如下流程:修改代码、编译构建运行调试(使用jrebel等热部署插件可以提效调试修改)、发布日常、发布预发、发布线上。
线上活动逻辑出问题了,也是要如此走一遍流程。
虽然我们的框架已经比较nice,但如果接入新的活动业务很频繁,或更新活动业务扩展逻辑很频繁,我们就要频繁地发布系统。
这样带来的不便在于:
- 频繁发布系统,造成日常/预发/线上环境不稳定
- 每一个小修改都要走一遍发布流程,效率低,不能及时响应BugFix
- 每接入一个活动,都要走一遍发布流程,效率低,不能及时响应业务
这时,很多同学都会想到:能不能来个自动热部署呢?这样每个修改都可以及时生效。
但是,Java不是“脚本语言”,能实现自动热部署吗?
答案当然是肯定的,只是Java动态实现编译部署需要经过比较复杂的流程。
我们接下来可能需要分几篇文章来讲解下~
标签:extension,plugin,doing,代码,扩展,门面,com,public 来源: https://blog.csdn.net/zhousenshan/article/details/117845786