【设计模式】门面/外观模式

MySQL ,MyTomcat 的启动

现在有 MySQL ,MyTomcat 类,需要依次启动。

java 复制代码
public class Application {
    public static void main(String[] args) {
        MySQL mySQL = new MySQL();
        mySQL.initDate();
        mySQL.checkLog();
        mySQL.unlock();
        mySQL.listenPort();

        MyTomcat myTomcat = new MyTomcat();
        myTomcat.initEngine();
        myTomcat.initWeb();
    }
}

public class MySQL {
    void initDate(){
        System.out.println("初始化数据库");
    }

    void checkLog(){
        System.out.println("检查日志");
    }

    void unlock(){
        System.out.println("数据库解锁");
    }

    void listenPort(){
        System.out.println("监听端口");
    }
}

public class MyTomcat {
    void initEngine(){
        System.out.println("初始化引擎");
    }

    void initWeb(){
        System.out.println("初始化Web应用");
    }
}

明明只是启动 MySQL,MyTomcat,mian 中却 调用了很多个方法。

于是你 定义了 一个接口 ServiceFacade,实现了这个接口的,必须实现其中的 start()

java 复制代码
public interface ServiceFacade {
    void start();
}

于是你改造了 你的 MySQL,MyTomcat

java 复制代码
public interface ServiceFacade {
    void start();
}

public class MySQL implements ServiceFacade{

    void initDate(){
        System.out.println("初始化数据库");
    }

    void checkLog(){
        System.out.println("检查日志");
    }

    void unlock(){
        System.out.println("数据库解锁");
    }

    void listenPort(){
        System.out.println("监听端口");
    }

    // 实现 start()
    @Override
    public void start() {
        initDate();
        checkLog();
        unlock();
        listenPort();
    }
}


public class MyTomcat implements ServiceFacade{

    void initEngine(){
        System.out.println("初始化引擎");
    }

    void initWeb(){
        System.out.println("初始化Web应用");
    }

    // 实现 start()
    @Override
    public void start() {
        initEngine();
        initWeb();
    }
}

// -------------------------------------------
public class Application {
    public static void main(String[] args) {
        ServiceFacade mySQL = new MySQL();
        mySQL.start();

        ServiceFacade myTomcat = new MyTomcat();
        myTomcat.start();
    }
}

像这样:对外提供统一的接口,调用者不需要关心具体的实现。 这就是门面模式的核心。

插件遵循自己的门面

SLF4j、JDBC 都是设计一个门面,不同的人,有不同的实现方式。

这是比较著名的门面,大家都可以遵循。

而我们自己写的门面,如何让别人遵循,符合我们的规则呢 ?

思考:SpringBoot 打包时,会打包出一个 包含 Tomcat 的 jar 包,这个 jar 包是是谁帮助我们打包的呢?

  • SpringBoot 打包的。

问题:只不过是执行了 Maven 相关的命令,SpringBoot 为什么会打一个 jar 包呢?

  • SpringBoot 依赖 Maven 插件,Maven 插件实现了这个功能。
  • Maven 插件的 API ,就是 Maven 的门面。
  • 由此我们自己也可以写一个插件,定义自己的门面。

动手写一个插件

java 复制代码
@RestController
public class TimeController {
    @GetMapping("/time")
    public String getTime(){
        return LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss SSS"));
    }
}

这是一个 RestFul 接口,现在我要写一个插件,要求插件再 getTime() 前执行。

my_plugin_api 工程 插件的 API:

java 复制代码
package insight.plugin;

public interface MyPlugin {
    // 再 GetTime 执行前调用
    void beforeGetTime();
}

于是 RestFul 接口 变成:

java 复制代码
@RestController
public class TimeController {
    
    MyPlugin myPlugin;
    
    @GetMapping("/time")
    public String getTime(){
        if (myPlugin != null){
            myPlugin.beforeGetTime();
        }
        return LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss SSS"));
    }
}

问题:从哪里加载 myPlugin ?

  • 提供一个接口,用于 加载 插件
java 复制代码
// 约定:实现了我的插件的jar包,必须有一个 wispx.plugin 文件。 
// 里面是实现 MyPlugin 的全类名。
@GetMapping("/loadPlug/{path}")
public String loadPlugin(@PathVariable("path") String path){
    File jarFile = new File(path);
    try (URLClassLoader classLoader = new URLClassLoader(new URL[]{jarFile.toURI().toURL()});
        InputStream wispxStream = classLoader.getResourceAsStream("wispx.plugin");){
        String className = new String(wispxStream.readAllBytes());
        Class<?> aClass = classLoader.loadClass(className);
        Constructor<?> constructor = aClass.getConstructor();
        myPlugin= (MyPlugin)constructor.newInstance();
        return "加载成功" + aClass.getName();
    }catch (Exception e){
        return "加载失败";
    }
}

实现 插件的工程:

java 复制代码
public class CountPlugin implements MyPlugin{

    AtomicInteger count = new AtomicInteger(0);

    @Override
    public void beforeGetTime() {
        System.out.println(count.incrementAndGet());
    }
}

打成 jar 包,在原先的工程中引入。

测试 插件

plain 复制代码
GET http://localhost:8080/time

GET http://localhost:8080/loadPlug/count_plugin-1.0-SNAPSHOT.jar

测试插件是否加载成功。

让 插件 加载到正在运行的程序中。

总结

定义一个插件,这就是 插件的门面。

第三方去实现插件。

通过一些约定把 插件 加载到正在运行的程序中。

思考

SpringBoot 自动装配 中的 springboot.factory 文件

gradle 的 build.gradle

tomcat 的 web.xml

Java 原生的 spi

相关推荐
xyouyuweizhu3 分钟前
字节流与字符流:Java IO 的两条主线该怎么选
java·开发语言·python
代码方舟14 分钟前
零信任架构实战:基于天远人企关联构建自动化供应链核心企业关联穿透与利益冲突审查网关
java·人工智能·架构·自动化
多弗朗皮卡丘44 分钟前
C++的IO流
开发语言·c++·io流·文件io流
liyi_hz20081 小时前
O2OA(翱途) V10.1 即将发布|功能再升级,安全再加固,协同更高效!
java·javascript·windows·开源软件
骑着蜗牛撵大象3271 小时前
AI Agent 工程化开发实战(三):用 StateGraph 给智能体搭一条可回放的状态流水线
java·开发语言·jvm·ai·状态管理·langgraph·stategraph
itmigrate1 小时前
Rust 里的所有权误区:不是不写 clone 就叫高性能
开发语言·后端·rust
Brilliantwxx1 小时前
【Linux】 文件访问与路径解析:目录、文件名、dentry、路径缓冲与 dcache 全解析
linux·运维·服务器·开发语言·spring
鬼手点金2 小时前
KV Cache 未必是一次性的:可编辑、可组合的“笔记”
java·人工智能·笔记·学习·自动化·1024程序员节
摇滚侠2 小时前
《On Java 中文版 基础卷》阅读笔记 初始化和清理 08
java·开发语言·笔记
xiangyun612 小时前
C++ 手撕 shared_ptr:引用计数与控制块到底长什么样?
开发语言·c++·算法·面试·stl