实用指南:【设计模式】门面/外观模式
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
浙公网安备 33010602011771号