实用指南:【设计模式】门面/外观模式

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()
@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 的门面。
  • 由此我们自己也可以写一个插件,定义自己的门面。

动手写一个插件

@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
)
;
@Override
public
void beforeGetTime(
) {
System.out.println(count.incrementAndGet(
)
)
;
}
}

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

测试 插件

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

posted @ 2025-07-17 08:04  yjbjingcha  阅读(8)  评论(0)    收藏  举报