简介
如何动态更改 代码的运行逻辑? 按照程序=数据结构 + 算法
的说法,无非两个套路
- 更改数据
- 本地配置文件
- 远程配置文件
- 自定义一个dsl + dsl 引擎
- 更改算法,也就是代码
- 远程加载 java/class 文件
- 远程加载 groovy 脚本文件
提神醒脑又浮想联翩的一句话
Groovy 使 Spring 更出色动态语言支持将 Spring 从一个以 Java 为中心的应用程序框架改变成一个以 JVM 为中心的应用程序框架。 现在,Spring 不再只是让 Java 开发变得更容易。它还允许将以静态和动态语言编写的代码轻松地插入到 Spring 支持的分层架构方法中,从而使 JVM 的开发也变得更加容易。
越来越不”本分”的 java 框架
在看到这句话之前,笔者在学习精简代码的利器——lombok 学习 已有所感触,java 框架在功能 上和层次上都在逐渐深化
在功能上
- 提供工具类
- 具备自己的业务抽象,业务只是先回调方法即可
在层次上
- 依托java 语法
- 依托动态代理 动态 改变字节码,行为 和 代码 有了“不一致的感觉”
- 编译期 改变字节码,lombok已经可以 抛开java 语法
反客为主
此外,笔者还负责docker 工作,对docker比较熟悉。在容器领域,docker 和 k8s 虽然是一个容器领域一个是容器编排。spring + groovy + 各种组件 与java的关系,很像kubernetes 与 docker的关系。
为应对docker 一家在容器领域一家独大的情况,google 等制定了一套标准和规范OCI,意在将容器运行时和镜像的实现从Docker 项目中完全剥离出来。然并卵,Docker 是容器领域事实上的标准。为此,google 将战争引向容器之上的平台层(或者说PaaS层),发起了一个名为CNCF的基金会。所谓平台层,就是除容器化、容器编排之外,推动容器监控、存储层、日志手机、微服务(lstio)等项目百花争鸣,与kubernetes 融为一体。
此时,kubernetes 对docker 依赖的只是一个 OCI 接口,docker 仍然是容器化的基础组件,但其 重要性在 平台化的 角度下已经大大下降了。若是kubernetes 说不支持 docker?那么。。。
java 是一个单机版的业务逻辑实现语言,但在微服务架构成为标配的今天,服务发现、日志监控报警、熔断等也成为必备组件。如果这些组件 都可以使用协议来定义,那么最后用不用java 来写业务逻辑就不是那么重要了。
实现
动态加载java 源代码
JavaCompiler javac = ToolProvider.getSystemJavaCompiler();
//方法签名 int run(InputStream in, OutputStream out, OutputStream err, String... arguments);
int compileResult = // 返回0 表示成功,非0表示失败
javac.run(null, // 标准输入流
null, // 标准输出流
null, // 错误流
"-d", distDir.getAbsolutePath(), javaFile.getAbsolutePath());
// 编译好的class 文件在 distDir 中,然后用ClassLoader 加载
从中可以看到,java 可以在运行时 从磁盘、网络 甚至代码自己构造 得到一个java 源文件 并将其 加载到 jvm中。也就意味着,jvm 可以动态更改 程序的行为。
groovy与java 整合
接口
public interface IHello {
String hello();
}
/tmp/hello.groovy
class GroovyHello implements IHello {
@Override
String hello() {
return "hello world"
}
}
主类
public class App {
public static void main(String[] args) throws Exception {
ClassLoader parent = App.class.getClassLoader();
GroovyClassLoader loader = new GroovyClassLoader(parent);
Class groovyClass = loader.parseClass(new File("/tmp/hello.groovy"));
IHello hello = (IHello) groovyClass.newInstance();
System.out.println(hello.hello());
}
}
JAVA中如何与Groovy协作
- 预编译:直接用groovyc将Groovy文件编译成class 文件
- GroovyShell 运行groovy 脚本。类似于在 java 代码中使用Process 运行 linux 命令
- 使用 GroovyClassLoader 加载groovy 源码。从GroovyClassLoader实现上看,大致是将groovy 源码编译为 class 文件 并加载。
- ScriptEngine,JSR-223应该是推荐的一种使用策略.规范化,而且简便。嵌入运行Groovy脚本
从中可以看到,使用groovy 在运行时改变应用程序的行为,是以bean 为基本单位 更新 原有逻辑的。定义一个接口 作为业务代码 和 groovy 的边界。利用Java接口来向调用者隔离,将groovy脚本封装为接口实现类。
groovy与spring 的整合
-
将groovy 创建的bean 纳入到 spring 上下文,Spring 通过 ScriptFactory 和 ScriptSource 接口支持动态语言集成
ScriptSource
<lang:groovy id="pdfGenerator" script-source="classpath:groovierspring/GroovyPdfGenerator.groovy"> <lang:property name="companyName" value="Groovy Bookstore"/> </lang:groovy>
ScriptFactory 是一个ScriptFactory,有点像FactoryBean
<bean id="pdfGenerator" class="org.springframework.scripting.groovy.GroovyScriptFactory"> <constructor-arg value="classpath:groovierspring/GroovyPdfGenerator.groovy"/> <property name="companyName" value="Groovier Bookstore"/> </bean>
- 可刷新的 Groovy bean
- groovy bean 创建前的回调
- 从远端 下载groovy 脚本 执行
笔者个人微信订阅号