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 getExtension(BizScene bizScene, Class
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.
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.