【设计模式】门面/外观模式
MySQL ,MyTomcat 的启动
现在有 MySQL ,MyTomcat 类,需要依次启动。
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()
public interface ServiceFacade {void start();
}
于是你改造了 你的 MySQL,MyTomcat
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()@Overridepublic void start() {initDate();checkLog();unlock();listenPort();}
}public class MyTomcat implements ServiceFacade{void initEngine(){System.out.println("初始化引擎");}void initWeb(){System.out.println("初始化Web应用");}// 实现 start()@Overridepublic 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 的门面。
- 由此我们自己也可以写一个插件,定义自己的门面。
动手写一个插件
@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:
package insight.plugin;public interface MyPlugin {// 再 GetTime 执行前调用void beforeGetTime();
}
于是 RestFul 接口 变成:
@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 ?
- 提供一个接口,用于 加载 插件
// 约定:实现了我的插件的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 "加载失败";}
}
实现 插件的工程:
public class CountPlugin implements MyPlugin{AtomicInteger count = new AtomicInteger(0);@Overridepublic void beforeGetTime() {System.out.println(count.incrementAndGet());}
}
打成 jar 包,在原先的工程中引入。
测试 插件
GET http://localhost:8080/timeGET http://localhost:8080/loadPlug/count_plugin-1.0-SNAPSHOT.jar
测试插件是否加载成功。
让 插件 加载到正在运行的程序中。
总结
定义一个插件,这就是 插件的门面。
第三方去实现插件。
通过一些约定把 插件 加载到正在运行的程序中。
思考
SpringBoot 自动装配 中的 springboot.factory 文件
gradle 的 build.gradle
tomcat 的 web.xml
Java 原生的 spi