Backend Development 8 min read

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.

Rare Earth Juejin Tech Community
Rare Earth Juejin Tech Community
Rare Earth Juejin Tech Community
Designing Extension Points and Plugin Engines for Business Logic Isolation in Java Backend

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.

design patternsJavabackend developmentPlugin ArchitectureSpringExtension Pointscode isolation
Rare Earth Juejin Tech Community
Written by

Rare Earth Juejin Tech Community

Juejin, a tech community that helps developers grow.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.