Designing Extension Points and Plugin Engines for Business Logic Isolation in Java Backend
The article explains how to avoid tangled if‑else code in a Java backend by using a process engine and a plugin‑based extension point framework, detailing interface definitions, annotations, loading mechanisms, and usage examples drawn from the open‑source MemberClub project.
When a business middle‑platform needs to integrate many heterogeneous business lines, developers often end up with massive if‑else blocks that are hard to maintain and test. The article identifies two core problems: code isolation and extensibility.
Two practical solutions are proposed:
Use a process engine to configure separate execution chains for each business scenario.
Use a plugin extension engine where each business implements its own differentiated logic.
The open‑source MemberClub project demonstrates both approaches. It provides a paid‑membership transaction solution and showcases how to structure extension points.
Defining Extension Points
An interface such as PurchaseExtension abstracts purchase‑related operations. Implementations are marked with @ExtensionProvider, which declares the applicable business line and scenario.
@ExtensionConfig(desc = "购买流程扩展点", type = ExtensionType.PURCHASE, must = true)
public interface PurchaseExtension extends BaseExtension {
public void submit(PurchaseSubmitContext context); // 提交订单
public void reverse(AfterSaleApplyContext context); // 售后逆向
public void cancel(PurchaseCancelContext context); // 取消订单
}ExtensionProvider Annotation
The annotation integrates with Spring's @Service and carries metadata about business lines and scenes.
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Service
public @interface ExtensionProvider {
public Route[] bizScenes();
public String desc();
}Loading Extension Points
During Spring startup, ExtensionManage scans the application context for beans annotated with @ExtensionProvider. It builds a two‑level Table<BizTypeEnum, String, List<Object>> (provided by Guava) that maps business type and scene to the corresponding implementation.
@Getter
private Table<BizTypeEnum, String, List<Object>> bizExtensionMeta = HashBasedTable.create();
@PostConstruct
public void init() {
String[] beanNames = context.getBeanNamesForAnnotation(ExtensionProvider.class);
for (String beanName : beanNames) {
Object bean = context.getBean(beanName);
Set<Class<?>> interfaces = ClassUtils.getAllInterfacesForClassAsSet(bean.getClass());
ExtensionProvider extension = AnnotationUtils.findAnnotation(bean.getClass(), ExtensionProvider.class);
Route[] routes = extension.bizScenes();
for (Class<?> anInterface : interfaces) {
if (BaseExtension.class.isAssignableFrom(anInterface)) {
for (Route route : routes) {
for (SceneEnum scene : route.scenes()) {
String key = buildKey(anInterface, route.bizType().getCode(), scene.getValue());
Object value = extensionBeanMap.put(key, bean);
if (value != null) {
CommonLog.error("注册 Extension key:{}冲突", key);
throw new RuntimeException("注册 Extension 冲突");
}
CommonLog.info("注册 Extension key:{}, 接口:{}, 实现类:{}", key, anInterface.getSimpleName(), bean.getClass().getSimpleName());
List<Object> extensions = bizExtensionMeta.get(route.bizType(), anInterface.getSimpleName());
if (extensions == null) {
bizExtensionMeta.put(route.bizType(), anInterface.getSimpleName(), Lists.newArrayList(bean));
}
}
}
}
}
}
}
private String buildKey(Class<?> anInterface, int bizType, String scene) {
return String.format("%s_%s_%s", anInterface.getSimpleName(), bizType, scene);
}Referencing Extension Points
Clients obtain an implementation via ExtensionManager.getExtension, passing the business scene and the extension interface class.
PurchaseExtension extension = extensionManager.getExtension(context.toDefaultBizScene(), PurchaseExtension.class);
extension.submit(context);The getExtension method builds the lookup key, falls back to a default scene if necessary, and throws an exception when no implementation is found.
public <T> T getExtension(BizScene bizScene, Class<T> tClass) {
if (!tClass.isInterface()) {
throw new RuntimeException(String.format("%s 需要是一个接口", tClass.getSimpleName()));
}
if (!BaseExtension.class.isAssignableFrom(tClass)) {
throw new RuntimeException(String.format("%s 需要继承 BaseExtension 接口", tClass.getSimpleName()));
}
String key = buildKey(tClass, bizScene.getBizType(), bizScene.getScene());
T value = (T) extensionBeanMap.get(key);
if (value == null) {
key = buildKey(tClass, BizTypeEnum.DEFAULT.getCode(), SceneEnum.DEFAULT_SCENE.getValue());
value = (T) extensionBeanMap.get(key);
}
if (value == null) {
throw new RuntimeException(String.format("%s 没有找到实现类%s", tClass.getSimpleName(), bizScene.getKey()));
}
return value;
}All the code referenced above is available in the MemberClub Git repository.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Rare Earth Juejin Tech Community
Juejin, a tech community that helps developers grow.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
