在使用 Spring、MyBatis-Plus 等框架时,我们经常会看到一种“神奇”的写法:

  • 实现某个接口
  • 重写其中的方法
  • 加一个 @Component 或写在配置类里

然后什么都不调用,框架的行为就被改变了

本文从原理层面解释:这是怎么做到的?


一、先给结论(一句话版)

框架通过“接口 + 默认实现 + 扩展点回调”的方式,把一部分控制权交给用户;
Spring 在启动时扫描并注册用户的实现,在框架的关键执行流程中主动回调这些实现,从而改变默认行为。

这背后,本质是 IoC(控制反转)+ 设计模式 的组合运用。


二、从一个最小模型理解“配置为什么生效”

先不看 Spring,也不看 MyBatis-Plus,先看一个极简的“框架模型”。

1. 框架定义扩展接口

public interface Formatter {
    String format(String input);
}

2. 框架提供默认实现

public class DefaultFormatter implements Formatter {
    @Override
    public String format(String input) {
        return input;
    }
}

3. 框架的核心流程(你通常看不到)

public class FrameworkCore {

    private Formatter formatter = new DefaultFormatter();

    public void run(String data) {
        String result = formatter.format(data);
        System.out.println(result);
    }

    // 给用户预留的扩展点
    public void setFormatter(Formatter formatter) {
        this.formatter = formatter;
    }
}

这里有一个关键点:

框架在自己的核心流程中,依赖的是接口,而不是具体实现。


三、Spring 介入后,发生了什么变化

1. 用户实现接口

@Component
public class UpperCaseFormatter implements Formatter {
    @Override
    public String format(String input) {
        return input.toUpperCase();
    }
}

2. Spring 在启动时做的事

  • 扫描到 UpperCaseFormatter
  • 发现它实现了 Formatter 接口
  • 将其注册为一个 Bean

3. 框架从 Spring 容器中获取实现

Formatter formatter = applicationContext.getBean(Formatter.class);
frameworkCore.setFormatter(formatter);

结果:

  • 你没有主动调用框架
  • 但框架在运行流程中使用了你的实现
  • 默认行为被替换

👉 功能改变了


四、MyBatis-Plus 的真实配置案例

下面以 MyBatis-Plus 中非常常见的自动填充配置为例。

1. MyBatis-Plus 定义扩展接口

public interface MetaObjectHandler {
    void insertFill(MetaObject metaObject);
    void updateFill(MetaObject metaObject);
}

2. 用户实现接口(配置类)

@Component
public class MyMetaObjectHandler implements MetaObjectHandler {

    @Override
    public void insertFill(MetaObject metaObject) {
        this.strictInsertFill(metaObject, "createTime",
                LocalDateTime.class, LocalDateTime.now());
    }

    @Override
    public void updateFill(MetaObject metaObject) {
        this.strictUpdateFill(metaObject, "updateTime",
                LocalDateTime.class, LocalDateTime.now());
    }
}

3. MyBatis-Plus 内部的真实逻辑(伪代码)

MetaObjectHandler handler = applicationContext.getBean(MetaObjectHandler.class);

if (handler != null) {
    handler.insertFill(metaObject);
}

关键点在于:

不是你调用 MyBatis-Plus,而是 MyBatis-Plus 在固定时机回调你。


五、这背后的核心机制

1. IoC(控制反转)

  • 你不再控制“什么时候调用”
  • 框架控制整体流程
  • 你只负责“怎么实现”

控制权从“用户代码”反转到了“框架代码”。


2. 接口 + 依赖倒置

框架代码:

@Autowired
private MetaObjectHandler metaObjectHandler;

框架只关心:

有没有人实现这个接口?

至于实现是谁、怎么实现,完全交给用户。


3. Spring 容器的角色

Spring 在这里充当的是:

扩展点发现器 + 对象管理器

  • 发现你实现的接口
  • 管理生命周期
  • 在运行时提供给框架使用

六、涉及的设计模式(面试加分点)

1. 模板方法模式(Template Method)

框架定义固定流程
  ├─ beforeInsert()  ← 用户实现
  ├─ doInsert()
  └─ afterInsert()   ← 用户实现

流程不可变,细节可扩展。


2. 策略模式(Strategy)

  • 接口:策略抽象
  • 用户实现:具体策略
  • 框架在运行时选择并执行策略

3. 扩展点 / SPI 思想

  • 框架预留扩展接口
  • 用户实现
  • 运行期自动接管行为

七、为什么“实现接口 + 重写方法”就能改功能?

归根结底只有一句话:

框架在关键执行路径中,主动调用接口方法,而接口的实现权被交给了你。

你并没有改变流程,只是替换了“流程中的某个钩子”。


八、面试 / 总结用的一句话

Spring 及其生态框架通常通过接口定义扩展点,结合 IoC 容器在启动时发现并注册用户实现,在框架的关键执行流程中进行回调,从而在不修改框架源码的情况下实现行为扩展。这本质上是 IoC 思想与模板方法、策略模式的结合。


九、结语

当你理解了这一点,就会发现:

  • Spring Boot Starter 为什么“引入即生效”
  • MyBatis-Plus、Spring Security 为什么配置如此灵活
  • 为什么优秀框架都大量使用接口

它们的答案,其实都是同一个:

控制权在框架,定制权在用户。

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐