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