Groovy实现热部署

news2024/12/29 10:47:25

Groovy实现热部署

      • 一、概述
      • 二、准备工作
        • 2.1 规则接口IRule
      • 三、非Spring环境Groovy文件方式
        • 3.1 Groovy文件
        • 3.2 读取并生成实例
        • 3.3 使用这个实现
      • 四、数据库Groovy脚本方式
        • 4.1 Groovy脚本
        • 4.2 读取并生成实例
      • 五、Spring中使用Groovy的方式
        • 5.1 Groovy文件
        • 5.2 读取并生成实例
        • 5.3 使用这个实现
      • 六 原理篇


原文链接

原文中对于Grovvy实现热更新原理没有进行讲解,并且案例存在问题,可能是因为本人版本与作者不同所致,所以本文会进行原理介绍,并纠正原文错误。


一、概述

Groovy是构建在JVM上的一个轻量级却强大的动态语言, 它结合了Python、Ruby和Smalltalk的许多强大的特性.

Groovy就是用Java写的 , Groovy语法与Java语法类似, Groovy 代码能够与 Java 代码很好地结合,也能用于扩展现有代码, 相对于Java, 它在编写代码的灵活性上有非常明显的提升,Groovy 可以使用其他 Java 语言编写的库.

Groovy通常是被用来扩展现有的代码,就是说,当你需要某个实现类动态生成的时候,就可以使用Groovy来完成,比如:

  1. 动态类实现从文件生成,改动后能立即检测到。
  2. 动态类实现从数据库中生成,改动后能立即检测到。
  3. 动态类作为Spring的bean被管理起来,改动后能立即检测到。

这次,我要讲的就是这三种方式。

二、准备工作

本篇的使用场景是:假设有一个规则接口,它的实现可以是本地的JAVA代码实现,也可以是groovy文件实现,也可以通过数据库存储的Groovy脚本实现,也可以是Spring管理的bean。然后这多种不同的规则实现,放到一个列表中,统一执行。

在xxl-job中,所有的任务都要实现IJobHandler接口,它的web Glue方式就是基于Groovy实现的。

2.1 规则接口IRule

IRule定义了这个规则接口。

IRule:

package cn.pomit.skill.groovy.rule;

public interface IRule {
	static final int NORMAL_TYPE = 0;
	static final int GROOVY_FILE_TYPE = 1;
	static final int GROOVY_DB_TYPE = 2;
	static final int GROOVY_SPRING_TYPE = 3;
	
	int getType();
	
	void printInfo();
}

三、非Spring环境Groovy文件方式

3.1 Groovy文件

定义一个GroovyFileRule的Groovy文件,执行自己的规则。

GroovyFileRule:

package cn.pomit.skill.groovy.rule.file;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import cn.pomit.skill.groovy.rule.IRule;

class GroovyFileRule implements IRule {
	private Logger log = LoggerFactory.getLogger(this.getClass());
	
	@Override
	public int getType() {
		return GROOVY_FILE_TYPE;
	}

	@Override
	public void printInfo() {
		log.info("这是一段来自Groovy文件的代码");
	}
}

3.2 读取并生成实例

我这里定义了一个GroovyFactory,用来实现将Groovy文件生成IRule的实现.

GroovyFactory :

package cn.pomit.skill.groovy.rule;

import java.net.URL;

import groovy.lang.GroovyClassLoader;
import groovy.lang.GroovyCodeSource;

public class GroovyFactory {
	private static GroovyFactory groovyFactory = new GroovyFactory();
	private GroovyClassLoader groovyClassLoader = new GroovyClassLoader();

	public static GroovyFactory getInstance() {
		return groovyFactory;
	}

	/**
	 * 根据groovy文件路径生成IRule的实现
	 * @param packagePath
	 * @return
	 * @throws Exception
	 */
	public IRule getIRuleFromPackage(String filePath) throws Exception {
		Class<?> clazz = groovyClassLoader.parseClass(new GroovyCodeSource(new File(filePath)),false);
		if (clazz != null) {
			Object instance = clazz.newInstance();
				if (instance instanceof IRule) {
					return (IRule) instance;
				}
		}
		throw new IllegalArgumentException("读取groovy文件异常");
	}

}

3.3 使用这个实现

public class Main {
    public static void main(String[] args) throws Exception {
        dynamicDebug();
        Thread.sleep(10000);
        dynamicDebug();
    }

    private static void dynamicDebug() throws Exception {
        //直接读取Groovy文件生成IRule实现
        IRule groovyFile = GroovyFactory.getInstance()               .getIRuleFromPackage("C:\\Users\\zdh\\IdeaProjects\\leetcode\\src\\main\\java\\com\\GroovyFileRule.groovy");
        groovyFile.printInfo();
    }
}

在睡眠10s内,去修改对应的GroovyFileRule.groovy类输出的内容(记得ctrl+s保存文件),会发现下一次输出时,就会输出最新内容。


四、数据库Groovy脚本方式

4.1 Groovy脚本

定义一个GroovyDbRule 的脚本,执行自己的规则。GroovyDbRule 脚本存储在数据库中,

GroovyDbRule脚本如下:

package cn.pomit.skill.groovy.rule.file;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import cn.pomit.skill.groovy.rule.IRule;

class GroovyDbRule implements IRule {
	private Logger log = LoggerFactory.getLogger(this.getClass());
	
	@Override
	public int getType() {
		return GROOVY_DB_TYPE;
	}

	@Override
	public void printInfo() {
		log.info("这是一段来自数据库的Groovy脚本");
	}
}

建表语句及插入以上脚本:

CREATE TABLE `pomit_rule`  (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL,
  `rule` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci,
  `create_time` timestamp(0) DEFAULT CURRENT_TIMESTAMP,
  `update_time` timestamp(0) DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP(0),
  `visible` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 6 CHARACTER SET = latin1 COLLATE = latin1_swedish_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of pomit_rule
-- ----------------------------
INSERT INTO `pomit_rule` VALUES (5, 'GroovyDbRule', 'package cn.pomit.skill.groovy.rule.file;\r\n\r\nimport org.slf4j.Logger;\r\nimport org.slf4j.LoggerFactory;\r\n\r\nimport cn.pomit.skill.groovy.rule.IRule;\r\n\r\nclass GroovyDbRule implements IRule {\r\n	private Logger log = LoggerFactory.getLogger(this.getClass());\r\n	\r\n	@Override\r\n	public int getType() {\r\n		return GROOVY_DB_TYPE;\r\n	}\r\n\r\n	@Override\r\n	public void printInfo() {\r\n		log.info(\"这是一段来自数据库的Groovy脚本\");\r\n		printInfoHigh();\r\n	}\r\n	\r\n	public void printInfoHigh() {\r\n		log.info(\"这是一段来自数据库的Groovy脚本的代码\");\r\n	}\r\n}\r\n', '2020-01-02 10:36:01', '2020-01-02 10:36:01', NULL);

4.2 读取并生成实例

在GroovyFactory,加入从字符串生成IRule实现的方法.

在上面3.2中的GroovyFactory 中,加入下面的代码:

/**
	 * 根据脚本内容生成IRule的实现
	 * @param code
	 * @return
	 * @throws Exception
	 */
	public IRule getIRuleFromCode(String code) throws Exception {
		Class<?> clazz = groovyClassLoader.parseClass(code);
		if (clazz != null) {
			Object instance = clazz.newInstance();
			if (instance != null) {
				if (instance instanceof IRule) {
					return (IRule) instance;
				}
			}
		}
		throw new IllegalArgumentException("读取groovy脚本异常");
	}

和案例一没啥区别,也就是将源码存储到数据库了,不用从文件读取了。


五、Spring中使用Groovy的方式

5.1 Groovy文件

定义一个SpringGroovyRule 文件,执行自己的规则。这里拿它来测试Spring是如何将Groovy文件作为Bean来使用,不再手动读取Groovy文件。

SpringGroovyRule如下:

package cn.pomit.skill.groovy.rule.spring;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import cn.pomit.skill.groovy.rule.IRule;
import org.springframework.stereotype.Service;

public class SpringGroovyRule implements IRule {
	private Logger log = LoggerFactory.getLogger(this.getClass());
	@Override
	public int getType() {
		return GROOVY_SPRING_TYPE;
	}

	@Override
	public void printInfo() {
		log.info("这是一段Spring的Groovy代码");
	}
}

5.2 读取并生成实例

建立Spring的配置文件(SpringBoot也要这样玩,因为Groovy文件没有JAVA配置的方式生成Bean):spring-groovy.xml。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns:lang="http://www.springframework.org/schema/lang"
	xsi:schemaLocation="http://www.springframework.org/schema/beans 
						http://www.springframework.org/schema/beans/spring-beans.xsd
					    http://www.springframework.org/schema/lang 
					    http://www.springframework.org/schema/lang/spring-lang.xsd">

	<lang:defaults refresh-check-delay="60000" />

	<!-- groovy文件定义为bean -->
	<lang:groovy id="springGroovyRule"
		script-source="classpath:cn/pomit/skill/groovy/rule/spring/SpringGroovyRule.groovy">
	</lang:groovy>
</beans>

5.3 使用这个实现

如果是在SpringBoot下,需要引入上面的xml文件:@ImportResource(locations={"classpath:spring-groovy.xml"})

注入这个bean:

	@Resource(name = "springGroovyRule")
	IRule springGroovyRule;

六 原理篇

java热更新原理参考此篇文章

我们来看看Groovy是如何利用classLoader完成热更新的:

  • parseClass方法
    public Class parseClass(GroovyCodeSource codeSource, boolean shouldCacheSource) throws CompilationFailedException {
        //shouldCacheSource参数用来开启缓存,在应用层面避免了同一个类名被多次加载的问题
        //如果需要实现热更新需要传入参数false,关闭应用缓存
        synchronized (sourceCache) {
            Class answer = sourceCache.get(codeSource.getName());
            if (answer != null) return answer;
            answer = doParseClass(codeSource);
            if (shouldCacheSource) sourceCache.put(codeSource.getName(), answer);
            return answer;
        }
    }
  • doParseClass方法
   private Class doParseClass(GroovyCodeSource codeSource) {
        ...
        Class answer=null;    
        //ClassCollector通过内部的createClass方法调用内部加载器的defineClass方法加载类
        ClassCollector collector = createCollector(unit, su);
        ...
        //将源码文件进行编译操作,变为对应的.class字节流--该方法内部调用ClassCollector的createClass方法加载类    
        unit.compile(goalPhase);
        ...
        //返回加载得到的class对象    
        answer = collector.generatedClass;    
        return answer;
    }
  • createCollector方法
    protected ClassCollector createCollector(CompilationUnit unit, SourceUnit su) {
        //classCollector利用InnerLoader来加载内,这里是热更新的关键
        //因为每次都是new了一个新的类加载器
        InnerLoader loader = new InnerLoader(GroovyClassLoader.this)
        return new ClassCollector(loader, unit, su);
    }
  • createClass方法
       //此时我们的源文件已经被编译为了class字节流 
       protected Class createClass(byte[] code, ClassNode classNode) {
            //拿到上面new出来的loader
            GroovyClassLoader cl = getDefiningClassLoader();
            //利用InnerLoader来加载我们指定的类
            Class theClass = cl.defineClass(classNode.getName(), code, 0, code.length, unit.getAST().getCodeSource());
            this.loadedClasses.add(theClass);
            ...
            this.generatedClass=theClass;    
            return theClass;
        }

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/180026.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

css sprite雪碧图制作,使用以及相关,图文gif

写在前面&#xff1a; 在网页制作中&#xff0c;雪碧图也是前端攻城狮必须掌握的一项小技能。百度词条对雪碧图的解释是&#xff1a;CSS雪碧 即CSS Sprite&#xff0c;也有人叫它CSS精灵&#xff0c;是一种CSS图像合并技术&#xff0c;该方法是将小图标和背景图像合并到一张图…

计算机组成原理 | 第一章:概论 | 冯诺依曼计算机 | 计算机硬件

文章目录&#x1f4da;冯诺依曼计算机的特点&#x1f4da;计算机硬件组成框图&#x1f4da;计算机硬件的主要技术指标&#x1f407;非时间指标&#x1f407;时间指标&#x1f511;计算技巧归纳&#x1f4da;小结&#x1f511;本章掌握要点&#x1f407;补充思考题&#x1f4da;…

[电商实时数仓] 用户行为数据和业务数据采集以及ODS层

文章目录1.数据仓库环境准备1.1 导入依赖1.2 创建相关包2.数据仓库运行环境2.1 Hbase环境2.2 模拟数据3.数仓开发之ODS层3.1 mysql配置修改3.2 FlinkCDC的程序3.3 结果检测1.数据仓库环境准备 1.1 导入依赖 <properties><java.version>1.8</java.version>&l…

为什么你的Facebook广告策略应该包括SEO

最近在看了很多关于 SEO的文章&#xff0c;今天想跟大家分享一些我个人关于 Facebook广告中的 SEO策略&#xff0c;以及它为什么是必要的。虽然在我看来&#xff0c;所有营销手段都需要结合 SEO才能发挥最大作用&#xff0c;但这并不意味着要完全放弃 SEO。如果你对以下问题感兴…

分享147个ASP源码,总有一款适合您

ASP源码 分享147个ASP源码&#xff0c;总有一款适合您 下面是文件的名字&#xff0c;我放了一些图片&#xff0c;文章里不是所有的图主要是放不下...&#xff0c; 147个ASP源码下载链接&#xff1a;https://pan.baidu.com/s/1us1KTsxeaZlosHsqvrkC5Q?pwd81pl 提取码&#x…

Leetcode:51. N 皇后(C++)

目录 问题描述&#xff1a; 实现代码与解析&#xff1a; 回溯&#xff1a; 原理思路&#xff1a; 问题描述&#xff1a; 按照国际象棋的规则&#xff0c;皇后可以攻击与之处在同一行或同一列或同一斜线上的棋子。 n 皇后问题 研究的是如何将 n 个皇后放置在 nn 的棋盘上&a…

数字电位器程控可调电阻ic

一、前言 数字电位器又叫可编程电阻器&#xff0c;是一种替代传统机械电位器的新型CMOS数字、模拟混合信号处理集成电路&#xff0c;不需要搭建复杂的电路环境即可简单的通过CPU数字通讯实现电路调节&#xff0c;数字电位器也不能完全替代传统的机械电位器&#xff0c;在很多场…

Sentinel(限流、熔断、降级)、SpringBoot整合Sentinel、Sentinel的使用-60

一&#xff1a;Sentinel简介 Sentinel就是分布式系统的流量防卫兵 随着微服务的流行&#xff0c;服务和服务之间的稳定性变得越来越重要。Sentinel 以流量为切入点&#xff0c;从流量控制、熔断降级、系统负载保护等多个维度保护服务的稳定性。 1.1 官方文档 官方文档&#…

基于OpenXR,Collabora推开源VI-SLAM AR/VR定位系统

XR最关键的难题之一就是定位&#xff0c;为了定位XR头显在现实世界中的位置和角度&#xff0c;厂商们采用了多种方案&#xff0c;比如机械传感器、惯性传感器、磁传感器、声学传感器等等。这些定位方式有一个共同的问题&#xff0c;那就是传感器不够完善&#xff0c;且会产生噪…

uniapp的父传子,子传父,子组件与父组件数据同步(.sync)的理解

父传子&#xff1a; 父调用 绑定的子组件中state然后 mystate1赋值false 给子组件中的state。并在子组件中显示父中传来的值。 注意要在子组件中设置 props【属性】不然父中的值无法传过去。 <view >开启{{mystate1}}</view> --调用子组件mypop&#xff0c;并传值…

学习记录673@项目管理之进度管理案例

本文主要是进度管理之关键链路法的案例。 案例 Perfect 项目的建设方要求必须按合同规定的期限交付系统&#xff0c;承建方项目经理李某决定严格执行项目进度管理&#xff0c;以保证项目按期完成。他决定使用关键路径法来编制项目进度网络图。在对工作分解结构进行认真分析后&…

05 二叉树前序/中序/后序线索化和找前驱、后继

1. 线索化代码 线索化需要先序/中序/后续遍历的过程&#xff0c;多了访问到节点时指针指向的问题 二叉树形状和运行结果 主函数 #include "func.h"// 二叉树线索化(便于找前驱和后继节点) // 1. 二叉树先序线索化 // 2. 二叉树中序线索化 // 3. 二叉树后序线索化//…

《MySQL》MySQL简单操作

最近开始了新的学习进度 进入MySQL数据库的学习 目录 一、MySQL启动方法 1.使用MySQL启动 2.使用cmd启动 二、数据库的简单操作命令 显示当前服务器上有哪些数据库 创建新的数据库 删除数据库 选中数据库 三、数据表的操作 数据类型 四、表的简单操作 查看数据库中的…

Java多线程-线程的生命周期

Java多线程-线程的生命周期 线程的状态 New 表示线程已创建&#xff0c;没启动的状态此时已经做了一些准备工作&#xff0c;还没有执行run方法中代码 Runnable 调用start方法之后的状态&#xff0c;表示可运行状态(不一定正在运行&#xff0c;因为调用start方法之后不一定立…

分享148个ASP源码,总有一款适合您

ASP源码 分享148个ASP源码&#xff0c;总有一款适合您 下面是文件的名字&#xff0c;我放了一些图片&#xff0c;文章里不是所有的图主要是放不下...&#xff0c; 148个ASP源码下载链接&#xff1a;https://pan.baidu.com/s/1e2PvBmXxZA8C3IelkP8ZtQ?pwdj7lp 提取码&#x…

node.js 安装步骤

1、下载安装包 Node.js 官方网站下载&#xff1a;Node.js 选择操作系统对应的包&#xff1a; 下载完成&#xff0c;安装包如下&#xff1a; 2、安装Node 打开安装&#xff0c;傻瓜式下一步即可&#xff1a; 选择安装位置&#xff0c;我这里装在D盘下&#xff1a; 安装成功&…

图和树基础算法笔记

图的大部分知识在《离散数学》中都已经学习了&#xff0c;所以我主要放一些不知道的知识 常用概念 有很少边或弧&#xff08;如 e < n log n&#xff0c;e指边数&#xff0c;n指顶点数&#xff09;的图称为稀疏图&#xff0c;反之称为稠密图。完全图&#xff1a;每个顶点的…

[引擎开发] 现代图形API - dx12篇

本文将从性能优化的角度去阐述像dx12这样的现代图形API的一些设计理念。 当我们深入优化渲染管线的时候&#xff0c;我们会发现存在的几个瓶颈主要是这样的&#xff1a; ① 线程存在不合理的等待 ② CPU向GPU编码传输数据非常耗时 ③ CPU频繁地切换渲染上下文非常耗时 因此有时…

Python---库的使用

专栏&#xff1a;python 个人主页&#xff1a;HaiFan. 专栏简介&#xff1a;本专栏主要更新一些python的基础知识&#xff0c;也会实现一些小游戏和通讯录&#xff0c;学时管理系统之类的&#xff0c;有兴趣的朋友可以关注一下。 库前言标准库使用import导入模块例1例2例3 文件…

老司机经验分享:生产级中间件系统架构设计实践

目录 1、Master-Slave架构2、异步日志持久化机制3、检查点机制&#xff1a;定时持久化全量数据4、引入检查点节点5、总结 & 思考 这篇文章&#xff0c;给大家来聊一个生产级的中间件系统的架构设计实践&#xff0c;希望给对中间件系统感兴趣的同学一点启发。 1、Master-S…