├── JCat-web ├── src │ ├── assets │ │ └── logo.png │ ├── plugins │ │ └── vuetify.js │ ├── store │ │ ├── index.js │ │ └── message.js │ ├── main.js │ ├── App.vue │ ├── router │ │ └── index.js │ ├── App2.vue │ ├── components │ │ ├── GlobalMessage.vue │ │ ├── Sidebar.vue │ │ ├── Example.js │ │ ├── ResourceExplorer.vue │ │ ├── GroovySyntax.js │ │ └── Console.vue │ ├── views │ │ └── AppMainView.vue │ └── api │ │ └── index.js ├── babel.config.js ├── .gitignore ├── jsconfig.json ├── README.md ├── public │ ├── index.html │ └── image │ │ ├── ICON_EVENT.svg │ │ └── ICON_SQL.svg ├── vue.config.js └── package.json ├── jCat-agent ├── lib │ └── jd-core-1.1.4.jar ├── src │ ├── main │ │ ├── resources │ │ │ └── lib │ │ │ │ ├── libArthasJniLibrary-x64.so │ │ │ │ ├── libArthasJniLibrary.dylib │ │ │ │ ├── libArthasJniLibrary-x64.dll │ │ │ │ └── libArthasJniLibrary-aarch64.so │ │ ├── groovy │ │ │ └── org │ │ │ │ └── coderead │ │ │ │ └── jcat │ │ │ │ ├── console │ │ │ │ └── ConsoleBase.groovy │ │ │ │ ├── ClassUtil.groovy │ │ │ │ └── Jad.groovy │ │ └── java │ │ │ └── org │ │ │ └── coderead │ │ │ └── jcat │ │ │ ├── common │ │ │ ├── Page.java │ │ │ ├── Maps.java │ │ │ ├── PageUtil.java │ │ │ ├── EncryptUtil.java │ │ │ ├── IOUtils.java │ │ │ ├── WildcardMatcher.java │ │ │ ├── JsonUtil.java │ │ │ ├── JadUtil.java │ │ │ ├── ClassUtil.java │ │ │ ├── UnsafeUtil.java │ │ │ └── Assert.java │ │ │ ├── console │ │ │ ├── ConsoleScript.java │ │ │ └── GroovyConsoleLoader.java │ │ │ ├── groovyLsp │ │ │ ├── CompletionItem.java │ │ │ ├── TextDocumentService.java │ │ │ └── CompletionHandler.java │ │ │ ├── service │ │ │ ├── ResourceExplorerService.java │ │ │ ├── AttachService.java │ │ │ └── DefaultHttpServer.java │ │ │ └── Agent.java │ └── test │ │ ├── java │ │ └── org │ │ │ └── coderead │ │ │ └── jcat │ │ │ └── AgentTest.java │ │ └── groovy │ │ └── test │ │ ├── JarUrlTest.groovy │ │ └── JadTest.groovy ├── .gitignore └── build.gradle ├── img └── Snipaste_2024-08-10_10-14-25.png ├── agent-adapter ├── build.gradle ├── .gitignore └── src │ └── main │ └── java │ └── org │ └── coderead │ └── jcat │ ├── Event.java │ ├── Interceptor.java │ └── InterceptorWrapper.java ├── settings.gradle ├── test-spring ├── src │ ├── main │ │ ├── resources │ │ │ ├── static │ │ │ │ └── index.html │ │ │ └── application.properties │ │ └── java │ │ │ └── cn │ │ │ └── coderead │ │ │ └── testspring │ │ │ ├── TestSpringApplication.java │ │ │ ├── demos │ │ │ └── web │ │ │ │ ├── User.java │ │ │ │ ├── PathVariableController.java │ │ │ │ └── BasicController.java │ │ │ ├── mapper │ │ │ └── UserMapper.java │ │ │ └── bean │ │ │ └── User.java │ └── test │ │ └── java │ │ └── cn │ │ └── coderead │ │ └── testspring │ │ ├── TestSpringApplicationTests.java │ │ └── C3p0Test.java ├── .gitignore └── build.gradle ├── jcat-boot ├── build.gradle ├── .gitignore └── src │ └── main │ └── java │ └── org │ └── coderead │ └── jcat │ ├── JCatLoader.java │ └── BootstrapAgent.java ├── README.md ├── .gitignore └── LICENSE /JCat-web/src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luban-uncle/jCat/HEAD/JCat-web/src/assets/logo.png -------------------------------------------------------------------------------- /JCat-web/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | '@vue/cli-plugin-babel/preset' 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /jCat-agent/lib/jd-core-1.1.4.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luban-uncle/jCat/HEAD/jCat-agent/lib/jd-core-1.1.4.jar -------------------------------------------------------------------------------- /img/Snipaste_2024-08-10_10-14-25.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luban-uncle/jCat/HEAD/img/Snipaste_2024-08-10_10-14-25.png -------------------------------------------------------------------------------- /agent-adapter/build.gradle: -------------------------------------------------------------------------------- 1 | 2 | plugins { 3 | id 'java' 4 | } 5 | dependencies { 6 | // compileOnly 'org.projectlombok:lombok:1.18.26' 7 | } -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'JCat' 2 | include 'jCat-agent' 3 | include 'agent-adapter' 4 | include 'jcat-boot' 5 | include 'test-spring' 6 | -------------------------------------------------------------------------------- /test-spring/src/main/resources/static/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |

hello word!!!

4 |

this is a html page

5 | 6 | -------------------------------------------------------------------------------- /jCat-agent/src/main/resources/lib/libArthasJniLibrary-x64.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luban-uncle/jCat/HEAD/jCat-agent/src/main/resources/lib/libArthasJniLibrary-x64.so -------------------------------------------------------------------------------- /jCat-agent/src/main/resources/lib/libArthasJniLibrary.dylib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luban-uncle/jCat/HEAD/jCat-agent/src/main/resources/lib/libArthasJniLibrary.dylib -------------------------------------------------------------------------------- /jCat-agent/src/main/resources/lib/libArthasJniLibrary-x64.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luban-uncle/jCat/HEAD/jCat-agent/src/main/resources/lib/libArthasJniLibrary-x64.dll -------------------------------------------------------------------------------- /jCat-agent/src/main/resources/lib/libArthasJniLibrary-aarch64.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luban-uncle/jCat/HEAD/jCat-agent/src/main/resources/lib/libArthasJniLibrary-aarch64.so -------------------------------------------------------------------------------- /JCat-web/src/plugins/vuetify.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import Vuetify from 'vuetify/lib/framework'; 3 | 4 | Vue.use(Vuetify); 5 | 6 | export default new Vuetify({ 7 | theme: { dark: false }, 8 | }) 9 | -------------------------------------------------------------------------------- /JCat-web/src/store/index.js: -------------------------------------------------------------------------------- 1 | import Vuex from 'vuex' 2 | import Vue from 'vue' 3 | import message from "@/store/message" 4 | Vue.use(Vuex); 5 | 6 | // 创建一个新的 store 实例 7 | const store = new Vuex.Store({ 8 | modules: { 9 | message: message.store 10 | } 11 | }); 12 | export default store; -------------------------------------------------------------------------------- /jCat-agent/src/main/groovy/org/coderead/jcat/console/ConsoleBase.groovy: -------------------------------------------------------------------------------- 1 | package org.coderead.jcat.console 2 | 3 | class ConsoleBase { 4 | def storage = [:] 5 | def propertyMissing(String name, value) { storage[name] = value } 6 | def propertyMissing(String name) { storage[name] } 7 | } 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /test-spring/src/test/java/cn/coderead/testspring/TestSpringApplicationTests.java: -------------------------------------------------------------------------------- 1 | package cn.coderead.testspring; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.springframework.boot.test.context.SpringBootTest; 5 | 6 | @SpringBootTest 7 | class TestSpringApplicationTests { 8 | 9 | @Test 10 | void contextLoads() { 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /JCat-web/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /dist 4 | 5 | 6 | # local env files 7 | .env.local 8 | .env.*.local 9 | 10 | # Log files 11 | npm-debug.log* 12 | yarn-debug.log* 13 | yarn-error.log* 14 | pnpm-debug.log* 15 | 16 | # Editor directories and files 17 | .idea 18 | .vscode 19 | *.suo 20 | *.ntvs* 21 | *.njsproj 22 | *.sln 23 | *.sw? 24 | /public/download 25 | -------------------------------------------------------------------------------- /JCat-web/jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "module": "esnext", 5 | "baseUrl": "./", 6 | "moduleResolution": "node", 7 | "paths": { 8 | "@/*": [ 9 | "src/*" 10 | ] 11 | }, 12 | "lib": [ 13 | "esnext", 14 | "dom", 15 | "dom.iterable", 16 | "scripthost" 17 | ] 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /JCat-web/src/main.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import App from './App2.vue' 3 | import router from './router' 4 | import vuetify from './plugins/vuetify' 5 | import '@mdi/font/css/materialdesignicons.css' 6 | import store from '@/store' 7 | Vue.config.productionTip = false 8 | 9 | new Vue({ 10 | router, 11 | vuetify, 12 | store, 13 | render: h => h(App) 14 | }).$mount('#app') -------------------------------------------------------------------------------- /jCat-agent/src/test/java/org/coderead/jcat/AgentTest.java: -------------------------------------------------------------------------------- 1 | package org.coderead.jcat; 2 | 3 | import java.io.IOException; 4 | 5 | /** 6 | * @author 鲁班大叔 7 | * @email 27686551@qq.com 8 | * @date 2024/7/19 9 | */ 10 | public class AgentTest { 11 | public static void main(String[] args) throws IOException { 12 | System.out.println("hello agent"); 13 | System.in.read(); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /test-spring/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | server.port=8080 2 | spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver 3 | spring.datasource.username= 4 | spring.datasource.password= 5 | spring.datasource.url= 6 | 7 | 8 | spring.datasource.druid.driver-class-name=com.mysql.cj.jdbc.Driver 9 | spring.datasource.druid.url= 10 | spring.datasource.druid.username= 11 | spring.datasource.druid.password= 12 | -------------------------------------------------------------------------------- /JCat-web/src/App.vue: -------------------------------------------------------------------------------- 1 | 7 | 17 | 18 | 19 | 24 | -------------------------------------------------------------------------------- /JCat-web/README.md: -------------------------------------------------------------------------------- 1 | # algorithm 2 | 3 | ## Project setup 4 | ``` 5 | yarn install 6 | ``` 7 | 8 | ### Compiles and hot-reloads for development 9 | ``` 10 | yarn serve 11 | ``` 12 | 13 | ### Compiles and minifies for production 14 | ``` 15 | yarn build 16 | ``` 17 | 18 | ### Lints and fixes files 19 | ``` 20 | yarn lint 21 | ``` 22 | 23 | ### Customize configuration 24 | See [Configuration Reference](https://cli.vuejs.org/config/). 25 | -------------------------------------------------------------------------------- /jcat-boot/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'java' 3 | } 4 | 5 | group = 'cn.coderead' 6 | jar { 7 | manifest { 8 | attributes 'Premain-Class': 'org.coderead.jcat.BootstrapAgent', 9 | 'Agent-Class': 'org.coderead.jcat.BootstrapAgent', 10 | 'main-class':'org.coderead.jcat.BootstrapAgent', 11 | 'can-redefine-classes': 'true', 12 | 'can-retransform-classes': 'true' 13 | } 14 | } 15 | 16 | -------------------------------------------------------------------------------- /test-spring/src/main/java/cn/coderead/testspring/TestSpringApplication.java: -------------------------------------------------------------------------------- 1 | package cn.coderead.testspring; 2 | 3 | import org.mybatis.spring.annotation.MapperScan; 4 | import org.springframework.boot.SpringApplication; 5 | import org.springframework.boot.autoconfigure.SpringBootApplication; 6 | @SpringBootApplication 7 | @MapperScan 8 | public class TestSpringApplication { 9 | 10 | 11 | public static void main(String[] args) { 12 | SpringApplication.run(TestSpringApplication.class, args); 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /jCat-agent/src/main/java/org/coderead/jcat/common/Page.java: -------------------------------------------------------------------------------- 1 | package org.coderead.jcat.common; 2 | /** 3 | * @Copyright 源码阅读网 http://coderead.cn 4 | */ 5 | 6 | import java.io.Serializable; 7 | import java.util.List; 8 | 9 | /** 10 | * @author 鲁班大叔 11 | * @date 2024 12 | */ 13 | public class Page implements Serializable { 14 | public int total; //总数 15 | public List data; 16 | 17 | public Page(int total, List data) { 18 | this.total = total; 19 | this.data = data; 20 | } 21 | 22 | public Page() { 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /JCat-web/src/router/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import VueRouter from 'vue-router' 3 | import console from "../components/Console.vue" 4 | import resourceExplorer from "../components/ResourceExplorer.vue" 5 | 6 | 7 | Vue.use(VueRouter) 8 | 9 | const routes = [ 10 | { 11 | path: "/console", 12 | component: console, 13 | }, 14 | { 15 | path: "/", 16 | component: console 17 | }, 18 | { 19 | path: "/resource", 20 | component: resourceExplorer 21 | }, 22 | ] 23 | 24 | const router = new VueRouter({ 25 | routes 26 | }) 27 | 28 | export default router 29 | -------------------------------------------------------------------------------- /test-spring/.gitignore: -------------------------------------------------------------------------------- 1 | HELP.md 2 | .gradle 3 | build/ 4 | !gradle/wrapper/gradle-wrapper.jar 5 | !**/src/main/**/build/ 6 | !**/src/test/**/build/ 7 | 8 | ### STS ### 9 | .apt_generated 10 | .classpath 11 | .factorypath 12 | .project 13 | .settings 14 | .springBeans 15 | .sts4-cache 16 | bin/ 17 | !**/src/main/**/bin/ 18 | !**/src/test/**/bin/ 19 | 20 | ### IntelliJ IDEA ### 21 | .idea 22 | *.iws 23 | *.iml 24 | *.ipr 25 | out/ 26 | !**/src/main/**/out/ 27 | !**/src/test/**/out/ 28 | 29 | ### NetBeans ### 30 | /nbproject/private/ 31 | /nbbuild/ 32 | /dist/ 33 | /nbdist/ 34 | /.nb-gradle/ 35 | 36 | ### VS Code ### 37 | .vscode/ 38 | -------------------------------------------------------------------------------- /JCat-web/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | jcat-Java开发者控制台 9 | 10 | 11 | 14 |
15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## 概述 2 | 3 | 这是一个Java开发者控制台,类似前端的Chrome的开发者控制台,可以执行任意java代码。 4 | 5 | 其原理是在jvm进程中基于javaagent方式注入一个WEB管理后台,并接收运行groovy脚本。 6 | 7 | Snipaste_2024-08-10_10-14-25 8 | 9 | ## 启动方式 10 | 11 | 1.启动加载器 12 | 13 | `java -jar jcat-boot.jar` 14 | 15 | 2.选择目标jvm应用 16 | 17 | ## 常见问题 18 | 19 | **找不到tools.jar包** 20 | 21 | `java.lang.NoClassDefFoundError: com/sun/tools/attach/AttachNotSupportedException` 22 | 原因:该问题发生在jdk8,原因是%JAVA_HOME%/lib/tools.jar包不存在 23 | 解决办法:去oracle官网下载一个jdk8并把tools.jar复制到%JAVA_HOME%/lib/ 或%JAVA_HOME%/jre/lib/ext/ 目录中。 24 | 25 | **连接attach目标虚拟机失败** 26 | 27 | 应用权限不够,改用管理员方式启动 `sudo java -jar jcat-boot.jar` 28 | 29 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .gradle 2 | build/ 3 | !gradle/wrapper/gradle-wrapper.jar 4 | !**/src/main/**/build/ 5 | !**/src/test/**/build/ 6 | 7 | ### IntelliJ IDEA ### 8 | .idea/modules.xml 9 | .idea/jarRepositories.xml 10 | .idea/compiler.xml 11 | .idea/libraries/ 12 | *.iws 13 | *.iml 14 | *.ipr 15 | out/ 16 | !**/src/main/**/out/ 17 | !**/src/test/**/out/ 18 | 19 | ### Eclipse ### 20 | .apt_generated 21 | .classpath 22 | .factorypath 23 | .project 24 | .settings 25 | .springBeans 26 | .sts4-cache 27 | bin/ 28 | !**/src/main/**/bin/ 29 | !**/src/test/**/bin/ 30 | 31 | ### NetBeans ### 32 | /nbproject/private/ 33 | /nbbuild/ 34 | /dist/ 35 | /nbdist/ 36 | /.nb-gradle/ 37 | 38 | ### VS Code ### 39 | .vscode/ 40 | 41 | ### Mac OS ### 42 | .DS_Store -------------------------------------------------------------------------------- /jCat-agent/.gitignore: -------------------------------------------------------------------------------- 1 | .gradle 2 | build/ 3 | !gradle/wrapper/gradle-wrapper.jar 4 | !**/src/main/**/build/ 5 | !**/src/test/**/build/ 6 | 7 | ### IntelliJ IDEA ### 8 | .idea/modules.xml 9 | .idea/jarRepositories.xml 10 | .idea/compiler.xml 11 | .idea/libraries/ 12 | *.iws 13 | *.iml 14 | *.ipr 15 | out/ 16 | !**/src/main/**/out/ 17 | !**/src/test/**/out/ 18 | 19 | ### Eclipse ### 20 | .apt_generated 21 | .classpath 22 | .factorypath 23 | .project 24 | .settings 25 | .springBeans 26 | .sts4-cache 27 | bin/ 28 | !**/src/main/**/bin/ 29 | !**/src/test/**/bin/ 30 | 31 | ### NetBeans ### 32 | /nbproject/private/ 33 | /nbbuild/ 34 | /dist/ 35 | /nbdist/ 36 | /.nb-gradle/ 37 | 38 | ### VS Code ### 39 | .vscode/ 40 | 41 | ### Mac OS ### 42 | .DS_Store -------------------------------------------------------------------------------- /jcat-boot/.gitignore: -------------------------------------------------------------------------------- 1 | .gradle 2 | build/ 3 | !gradle/wrapper/gradle-wrapper.jar 4 | !**/src/main/**/build/ 5 | !**/src/test/**/build/ 6 | 7 | ### IntelliJ IDEA ### 8 | .idea/modules.xml 9 | .idea/jarRepositories.xml 10 | .idea/compiler.xml 11 | .idea/libraries/ 12 | *.iws 13 | *.iml 14 | *.ipr 15 | out/ 16 | !**/src/main/**/out/ 17 | !**/src/test/**/out/ 18 | 19 | ### Eclipse ### 20 | .apt_generated 21 | .classpath 22 | .factorypath 23 | .project 24 | .settings 25 | .springBeans 26 | .sts4-cache 27 | bin/ 28 | !**/src/main/**/bin/ 29 | !**/src/test/**/bin/ 30 | 31 | ### NetBeans ### 32 | /nbproject/private/ 33 | /nbbuild/ 34 | /dist/ 35 | /nbdist/ 36 | /.nb-gradle/ 37 | 38 | ### VS Code ### 39 | .vscode/ 40 | 41 | ### Mac OS ### 42 | .DS_Store -------------------------------------------------------------------------------- /agent-adapter/.gitignore: -------------------------------------------------------------------------------- 1 | .gradle 2 | build/ 3 | !gradle/wrapper/gradle-wrapper.jar 4 | !**/src/main/**/build/ 5 | !**/src/test/**/build/ 6 | 7 | ### IntelliJ IDEA ### 8 | .idea/modules.xml 9 | .idea/jarRepositories.xml 10 | .idea/compiler.xml 11 | .idea/libraries/ 12 | *.iws 13 | *.iml 14 | *.ipr 15 | out/ 16 | !**/src/main/**/out/ 17 | !**/src/test/**/out/ 18 | 19 | ### Eclipse ### 20 | .apt_generated 21 | .classpath 22 | .factorypath 23 | .project 24 | .settings 25 | .springBeans 26 | .sts4-cache 27 | bin/ 28 | !**/src/main/**/bin/ 29 | !**/src/test/**/bin/ 30 | 31 | ### NetBeans ### 32 | /nbproject/private/ 33 | /nbbuild/ 34 | /dist/ 35 | /nbdist/ 36 | /.nb-gradle/ 37 | 38 | ### VS Code ### 39 | .vscode/ 40 | 41 | ### Mac OS ### 42 | .DS_Store -------------------------------------------------------------------------------- /JCat-web/public/image/ICON_EVENT.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /jCat-agent/src/main/java/org/coderead/jcat/console/ConsoleScript.java: -------------------------------------------------------------------------------- 1 | package org.coderead.jcat.console; 2 | /** 3 | * @Copyright 源码阅读网 http://coderead.cn 4 | */ 5 | 6 | import arthas.VmTool; 7 | import org.coderead.jcat.common.ClassUtil; 8 | import groovy.lang.Script; 9 | 10 | /** 11 | * @author 鲁班大叔 12 | * @date 2024 13 | */ 14 | public abstract class ConsoleScript extends Script { 15 | // 获取实例 16 | public Object[] get(Class cla, Integer limit) { 17 | limit = limit == null ? 10 : limit; 18 | VmTool vmTool = ClassUtil.getVmTool(); 19 | Object[] instances = vmTool.getInstances(cla, limit); 20 | return instances; 21 | } 22 | 23 | public Object[] get(Class cla) { 24 | return get(cla, 10); 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /JCat-web/vue.config.js: -------------------------------------------------------------------------------- 1 | const { defineConfig } = require('@vue/cli-service') 2 | const vuetify = defineConfig({ 3 | transpileDependencies: [ 4 | 'vuetify' 5 | ] 6 | }) 7 | module.exports = defineConfig({ 8 | publicPath: "/jCat", 9 | transpileDependencies: true, 10 | productionSourceMap: false,// 打包是否显示源码 11 | configureWebpack: { 12 | node:false, 13 | devtool: 'source-map' 14 | }, 15 | devServer: { 16 | allowedHosts: "all", 17 | // client: { 18 | // overlay: { 19 | // errors: false, 20 | // warnings: false, 21 | // }, 22 | // }, 23 | // host:"regex.coderead.cn", 24 | proxy: { 25 | '/jCat/api': { 26 | target: 'http://127.0.0.1:3426', 27 | ws: true, 28 | changeOrigin: true 29 | } 30 | } 31 | }, 32 | ...vuetify 33 | }) 34 | -------------------------------------------------------------------------------- /jCat-agent/src/test/groovy/test/JarUrlTest.groovy: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import com.sun.jndi.toolkit.url.UrlUtil 4 | import org.junit.Test 5 | 6 | import java.util.jar.JarFile 7 | 8 | /** 9 | * @author 鲁班大叔 10 | * @email 27686551@qq.com 11 | * @date 2024/08/ 12 | 5 13 | */ 14 | class JarUrlTest { 15 | @Test 16 | void test() { 17 | URLClassLoader loader = this.class.classLoader as URLClassLoader 18 | loader.getResource("LICENSE-junit.txt") 19 | def url = loader.URLs[0] 20 | JarURLConnection con = new URL("jar:${url}!/").openConnection() 21 | def jarFile = con.getJarFile() 22 | println jarFile.stream().toArray().join("\n") 23 | // new URL("jar://file:////Users/tommy/.gradle/caches/modules-2/files-2.1/junit/junit/4.12/2973d150c0dc1fefe998f834810d68f278ea58ec/junit-4.12.jar") 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /jCat-agent/src/main/java/org/coderead/jcat/common/Maps.java: -------------------------------------------------------------------------------- 1 | package org.coderead.jcat.common; 2 | /** 3 | * @Copyright 源码阅读网 http://coderead.cn 4 | */ 5 | 6 | import java.util.HashMap; 7 | import java.util.Map; 8 | 9 | /** 10 | * @author 鲁班大叔 11 | * @date 2024 12 | */ 13 | public class Maps { 14 | public static Map to(K k, V v) { 15 | Map map = new HashMap<>(); 16 | map.put(k, v); 17 | return map; 18 | } 19 | 20 | public static Map to(K k, V v, K k1, V v1) { 21 | Map map = new HashMap<>(); 22 | map.put(k, v); 23 | map.put(k1, v1); 24 | return map; 25 | } 26 | 27 | public static Map to(Object... p) { 28 | Map map = new HashMap<>(); 29 | for (int i = 0; i < p.length; i += 2) { 30 | map.put(p[i], p[i + 1]); 31 | } 32 | return map; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /agent-adapter/src/main/java/org/coderead/jcat/Event.java: -------------------------------------------------------------------------------- 1 | package org.coderead.jcat; 2 | /** 3 | * @Copyright 源码阅读网 http://coderead.cn 4 | */ 5 | 6 | import lombok.Getter; 7 | import lombok.Setter; 8 | 9 | import java.lang.reflect.InvocationTargetException; 10 | import java.lang.reflect.Method; 11 | 12 | /** 13 | * @author 鲁班大叔 14 | * @date 2024 15 | */ 16 | @Getter 17 | @Setter 18 | public class Event { 19 | Object target; 20 | Object[] args; 21 | Object result; 22 | Method method;// 事件发生时的方法 23 | 24 | public Event() { 25 | } 26 | 27 | public Event(Object target, Object[] args) { 28 | this.target = target; 29 | this.args = args; 30 | } 31 | 32 | public Event(Object target, Object[] args, Object result) { 33 | this.target = target; 34 | this.args = args; 35 | this.result = result; 36 | } 37 | 38 | public Object recall() throws InvocationTargetException, IllegalAccessException { 39 | return method.invoke(target, args); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /jCat-agent/src/test/groovy/test/JadTest.groovy: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import org.coderead.jcat.AgentTest 4 | import org.coderead.jcat.Jad 5 | import org.coderead.jcat.common.JadUtil 6 | import org.junit.Test 7 | 8 | /** 9 | * @author 鲁班大叔 10 | * @email 27686551@qq.com 11 | * @date 2024/08/ 12 | 1 13 | */ 14 | class JadTest { 15 | @Test 16 | void jadTest(){ 17 | def stream = AgentTest.class.getResourceAsStream("AgentTest.class"); 18 | def bytes = new byte[1024 * 1024] 19 | def size= stream.read(bytes) 20 | println JadUtil.decompiler(AgentTest.class.getName(),Arrays.copyOfRange(bytes,0,size)) 21 | } 22 | 23 | 24 | 25 | @Test 26 | void jadTest2(){ 27 | println JadUtil.decompiler(AgentTest.class.getName()) 28 | } 29 | 30 | @Test 31 | void jadTest3(){ 32 | def stream = AgentTest.class.getResourceAsStream("AgentTest.class"); 33 | def bytes = new byte[1024 * 1024] 34 | def size= stream.read(bytes) 35 | println Jad.decompiler(Arrays.copyOfRange(bytes,0,size)) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /test-spring/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'org.springframework.boot' version '2.6.13' 3 | id 'io.spring.dependency-management' version '1.0.15.RELEASE' 4 | id 'java' 5 | } 6 | 7 | group = 'cn.coderead' 8 | version = '0.0.1-SNAPSHOT' 9 | sourceCompatibility = '8' 10 | targetCompatibility = '8' 11 | 12 | configurations { 13 | compileOnly { 14 | extendsFrom annotationProcessor 15 | } 16 | } 17 | 18 | repositories { 19 | mavenCentral() 20 | } 21 | 22 | dependencies { 23 | implementation 'org.springframework.boot:spring-boot-starter-web' 24 | implementation 'org.mybatis.spring.boot:mybatis-spring-boot-starter:2.2.0' 25 | implementation 'mysql:mysql-connector-java:8.0.25' 26 | 27 | testImplementation 'com.mchange:c3p0:0.9.5.5' 28 | testImplementation 'org.apache.commons:commons-dbcp2:2.9.0' 29 | implementation 'com.alibaba:druid-spring-boot-starter:1.2.6' 30 | compileOnly 'org.projectlombok:lombok' 31 | developmentOnly 'org.springframework.boot:spring-boot-devtools' 32 | annotationProcessor 'org.projectlombok:lombok' 33 | testImplementation 'org.springframework.boot:spring-boot-starter-test' 34 | implementation project(path: ':jCat-agent') 35 | } 36 | 37 | tasks.named('test') { 38 | useJUnitPlatform() 39 | } 40 | -------------------------------------------------------------------------------- /test-spring/src/main/java/cn/coderead/testspring/demos/web/User.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013-2018 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package cn.coderead.testspring.demos.web; 18 | 19 | /** 20 | * @author theonefx 21 | */ 22 | public class User { 23 | 24 | private String name; 25 | 26 | private Integer age; 27 | 28 | public String getName() { 29 | return name; 30 | } 31 | 32 | public void setName(String name) { 33 | this.name = name; 34 | } 35 | 36 | public Integer getAge() { 37 | return age; 38 | } 39 | 40 | public void setAge(Integer age) { 41 | this.age = age; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /jCat-agent/src/main/java/org/coderead/jcat/console/GroovyConsoleLoader.java: -------------------------------------------------------------------------------- 1 | package org.coderead.jcat.console; 2 | /** 3 | * @Copyright 源码阅读网 http://coderead.cn 4 | */ 5 | 6 | import java.net.URL; 7 | import java.net.URLClassLoader; 8 | 9 | /** 10 | * @author 鲁班大叔 11 | * @date 2024 12 | */ 13 | public class GroovyConsoleLoader extends URLClassLoader { 14 | private static final URL[] EMPTY_URL_ARRAY = new URL[0]; 15 | private final ClassLoader assistantLoader; 16 | private final String[] groovyPackages; 17 | 18 | public GroovyConsoleLoader(ClassLoader parent) { 19 | super(EMPTY_URL_ARRAY, GroovyConsoleLoader.class.getClassLoader()); 20 | assistantLoader = parent; 21 | groovyPackages = new String[]{ 22 | "groovy.", 23 | "groovyjarjarantlr.", 24 | "groovyjarjarasm.asm.", 25 | "groovyjarjarcommonscli.", 26 | "org.apache.groovy.", 27 | "org.codehaus.groovy.", 28 | }; 29 | } 30 | 31 | 32 | @Override 33 | protected Class loadClass(String name, boolean resolve) throws ClassNotFoundException { 34 | try { 35 | return super.loadClass(name, resolve); 36 | } catch (ClassNotFoundException e) { 37 | return assistantLoader.loadClass(name); 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /jCat-agent/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'java' 3 | id 'com.github.johnrengelman.shadow' version '7.1.2' 4 | id 'groovy' 5 | } 6 | repositories{ 7 | 8 | } 9 | dependencies { 10 | compileOnly files("${System.env.JAVA_HOME}/lib/tools.jar") 11 | implementation 'com.taobao.arthas:arthas-vmtool:3.7.2' 12 | implementation 'org.codehaus.groovy:groovy:3.0.21' 13 | implementation 'com.fasterxml.jackson.core:jackson-databind:2.15.2' 14 | implementation files("${projectDir.path}/lib/jd-core-1.1.4.jar") 15 | implementation project(path: ':agent-adapter') 16 | } 17 | 18 | 19 | compileJava { 20 | options.compilerArgs = ["-Xlint:unchecked"] 21 | classpath += files(sourceSets.main.groovy.classesDirectory) 22 | } 23 | compileGroovy { 24 | classpath = sourceSets.main.compileClasspath 25 | } 26 | 27 | 28 | shadowJar { 29 | mergeServiceFiles() 30 | configurations = [project.configurations.runtimeClasspath] 31 | 32 | from("${parent.projectDir}/JCat-web/dist/") { 33 | into("web/") 34 | } 35 | } 36 | build { 37 | dependsOn shadowJar 38 | } 39 | jar { 40 | manifest { 41 | attributes 'Premain-Class': 'org.coderead.jcat.Agent', 42 | 'Agent-Class': 'org.coderead.jcat.Agent', 43 | 'Main-Class': 'org.coderead.jcat.Agent', 44 | 'can-redefine-classes': 'true', 45 | 'can-retransform-classes': 'true' 46 | } 47 | } 48 | 49 | -------------------------------------------------------------------------------- /jCat-agent/src/main/java/org/coderead/jcat/common/PageUtil.java: -------------------------------------------------------------------------------- 1 | package org.coderead.jcat.common; 2 | /** 3 | * @Copyright 源码阅读网 http://coderead.cn 4 | */ 5 | 6 | import java.util.ArrayList; 7 | import java.util.Collection; 8 | import java.util.List; 9 | import java.util.NavigableMap; 10 | 11 | /** 12 | * @author 鲁班大叔 13 | * @date 2024 14 | */ 15 | public class PageUtil { 16 | // 将NavigableMap中的key进行分页处理 17 | public static Page splitKey(NavigableMap map, int pageIndex, int size) { 18 | return split(map.keySet(), pageIndex, size); 19 | } 20 | 21 | public static Page splitValue(NavigableMap map, int pageIndex, int size) { 22 | return split(map.values(), pageIndex, size); 23 | } 24 | 25 | public static Page split(Collection datas, int pageIndex, int size) { 26 | int begin = pageIndex * size; 27 | int end = (pageIndex + 1) * size; 28 | if (begin >= datas.size()) { 29 | return new Page<>(datas.size(), new ArrayList<>()); // 返回空 30 | } 31 | int index = 0; 32 | List list = new ArrayList<>(size); 33 | for (T t : datas) { 34 | if (index >= begin && index < end) { 35 | list.add(t); 36 | } 37 | if (index >= end) { 38 | break; 39 | } 40 | index++; 41 | } 42 | return new Page<>(datas.size(), list); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /jCat-agent/src/main/java/org/coderead/jcat/common/EncryptUtil.java: -------------------------------------------------------------------------------- 1 | package org.coderead.jcat.common; 2 | 3 | 4 | import java.security.MessageDigest; 5 | 6 | public class EncryptUtil { 7 | 8 | public static String MD5(String s) { 9 | try { 10 | MessageDigest md = MessageDigest.getInstance("MD5"); 11 | byte[] bytes = md.digest(s.getBytes("utf-8")); 12 | return toHex(bytes); 13 | } catch (Exception e) { 14 | throw new RuntimeException(e); 15 | } 16 | } 17 | 18 | public static String MD5(byte[] s) { 19 | try { 20 | MessageDigest md = MessageDigest.getInstance("MD5"); 21 | byte[] bytes = md.digest(s); 22 | return toHex(bytes); 23 | } catch (Exception e) { 24 | throw new RuntimeException(e); 25 | } 26 | } 27 | 28 | public static String md5_32To16(String md5) { 29 | Assert.isTrue(md5.length() == 32, "md5 length must be 32 position "); 30 | return md5.substring(9, 32); 31 | } 32 | 33 | public static String toHex(byte[] bytes) { 34 | final char[] HEX_DIGITS = "0123456789ABCDEF".toCharArray(); 35 | StringBuilder ret = new StringBuilder(bytes.length * 2); 36 | for (int i = 0; i < bytes.length; i++) { 37 | ret.append(HEX_DIGITS[(bytes[i] >> 4) & 0x0f]); 38 | ret.append(HEX_DIGITS[bytes[i] & 0x0f]); 39 | } 40 | return ret.toString(); 41 | } 42 | 43 | 44 | } 45 | -------------------------------------------------------------------------------- /jCat-agent/src/main/java/org/coderead/jcat/common/IOUtils.java: -------------------------------------------------------------------------------- 1 | 2 | package org.coderead.jcat.common; 3 | 4 | import java.io.EOFException; 5 | import java.io.IOException; 6 | import java.io.InputStream; 7 | import java.util.Arrays; 8 | 9 | public class IOUtils { 10 | public IOUtils() { 11 | } 12 | 13 | public static byte[] readFully(InputStream stream, int MaxSize, boolean EofCheck) throws IOException { 14 | byte[] bytes = new byte[0]; 15 | if (MaxSize == -1) { 16 | MaxSize = Integer.MAX_VALUE; 17 | } 18 | 19 | int size; 20 | for(int index = 0; index < MaxSize; index += size) { 21 | int writeIndex; 22 | if (index >= bytes.length) { 23 | writeIndex = Math.min(MaxSize - index, bytes.length + 1024); 24 | if (bytes.length < index + writeIndex) { 25 | bytes = Arrays.copyOf(bytes, index + writeIndex); 26 | } 27 | } else { 28 | writeIndex = bytes.length - index; 29 | } 30 | 31 | size = stream.read(bytes, index, writeIndex); 32 | if (size < 0) { 33 | if (EofCheck && MaxSize != Integer.MAX_VALUE) { 34 | throw new EOFException("Detect premature EOF"); 35 | } 36 | 37 | if (bytes.length != index) { 38 | bytes = Arrays.copyOf(bytes, index); 39 | } 40 | break; 41 | } 42 | } 43 | 44 | return bytes; 45 | } 46 | 47 | 48 | } 49 | -------------------------------------------------------------------------------- /agent-adapter/src/main/java/org/coderead/jcat/Interceptor.java: -------------------------------------------------------------------------------- 1 | package org.coderead.jcat; 2 | /** 3 | * @Copyright 源码阅读网 http://coderead.cn 4 | */ 5 | 6 | import java.util.HashMap; 7 | import java.util.Map; 8 | import java.util.logging.Logger; 9 | 10 | /** 11 | * 事件拦截适配器 该模块将加载至boot加载器中 12 | * 注:该类不能依赖第三包,以及ext扩展加载器中的类 13 | * 14 | * @author 鲁班大叔 15 | * @date 2024 16 | */ 17 | public interface Interceptor { 18 | Logger logger = Logger.getLogger(Interceptor.class.getName()); 19 | 20 | Object invoke(String methodInfo,Object thisObj, Object[] args); 21 | 22 | void invokeEnd(String methodInfo,Object trace, Object thisObj, Object[] args, Object result); 23 | 24 | Map map = new HashMap<>(); 25 | 26 | static Object $begin(int id,String methodInfo, Object thisObj, Object[] args) { 27 | return map.get(id).invoke(methodInfo,thisObj, args); 28 | } 29 | 30 | static void $end(int id,String methodInfo, Object trace, Object thisObj, Object[] args, Object result) { 31 | map.get(id).invokeEnd(methodInfo,trace, thisObj, args, result); 32 | } 33 | 34 | static boolean isRegistered(int key) { 35 | return map.containsKey(key); 36 | } 37 | 38 | static int register(Interceptor interceptor) { 39 | int key = System.identityHashCode(interceptor); 40 | map.put(key, new InterceptorWrapper(interceptor)); 41 | return key; 42 | } 43 | 44 | static void unregister(int key) { 45 | if (!map.containsKey(key)) { 46 | throw new IllegalArgumentException("该拦截器未注册,或已经删除"); 47 | } 48 | map.remove(key); 49 | } 50 | 51 | 52 | } 53 | -------------------------------------------------------------------------------- /test-spring/src/test/java/cn/coderead/testspring/C3p0Test.java: -------------------------------------------------------------------------------- 1 | package cn.coderead.testspring; 2 | 3 | import com.mchange.v2.c3p0.ComboPooledDataSource; 4 | import org.apache.commons.dbcp2.BasicDataSource; 5 | import org.junit.jupiter.api.Test; 6 | import org.springframework.boot.jdbc.DataSourceBuilder; 7 | 8 | /** 9 | * @author 鲁班大叔 10 | * @email 27686551@qq.com 11 | * @date 2024/7/26 12 | */ 13 | public class C3p0Test { 14 | 15 | @Test 16 | public void dbcpTest(){ 17 | BasicDataSource dataSource = new BasicDataSource(); 18 | // 配置数据源 19 | dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver"); 20 | dataSource.setUrl(""); 21 | dataSource.setUsername(""); 22 | dataSource.setPassword(""); 23 | // 其他配置... 24 | // 获取连接池信息 25 | int maxActiveConnections = dataSource.getMaxTotal(); 26 | int maxIdleConnections = dataSource.getMaxIdle(); 27 | int minIdleConnections = dataSource.getMinIdle(); 28 | int numActiveConnections = dataSource.getNumActive(); 29 | int numIdleConnections = dataSource.getNumIdle(); 30 | String log="连接信息 最大激活数:$maxActiveConnections,最大闲置数:$maxIdleConnections,最小闲置数:$minIdleConnections,当前激活数:$numActiveConnections,当前闲置数:$numIdleConnections"; 31 | System.out.println("Max Active Connections: " + maxActiveConnections); 32 | System.out.println("Max Idle Connections: " + maxIdleConnections); 33 | System.out.println("Min Idle Connections: " + minIdleConnections); 34 | System.out.println("Num Active Connections: " + numActiveConnections); 35 | System.out.println("Num Idle Connections: " + numIdleConnections); 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /test-spring/src/main/java/cn/coderead/testspring/mapper/UserMapper.java: -------------------------------------------------------------------------------- 1 | package cn.coderead.testspring.mapper; 2 | 3 | import cn.coderead.testspring.bean.User; 4 | import org.apache.ibatis.annotations.*; 5 | import org.apache.ibatis.mapping.StatementType; 6 | 7 | import java.util.List; 8 | 9 | @Mapper 10 | public interface UserMapper { 11 | 12 | @Select({"select * from users where id=#{userId}"}) 13 | User selectByid(Integer id); 14 | 15 | @Select({"select * from users where id=#{userId}"}) 16 | @Options 17 | User selectByid2(Integer id); 18 | 19 | @Select({"select * from users where name=#{name} or age=#{user.age}"}) 20 | @Options 21 | User selectByNameOrAge(@Param("name") String name, @Param("user") User user); 22 | 23 | @Select({" select * from users where name='${name}'"}) 24 | @Options(statementType = StatementType.PREPARED) 25 | List selectByName(User user); 26 | 27 | 28 | @Insert("INSERT INTO `users`( `name`, `age`, `sex`, `email`, `phone_number`) VALUES ( #{name}, #{age}, #{sex}, #{email}, #{phoneNumber})") 29 | @Options(useGeneratedKeys = true, keyProperty = "id") 30 | int addUser(User user); 31 | 32 | int editUser(User user); 33 | 34 | @Update("update users set name=#{name} where id=#{id}") 35 | // @Options(flushCache = Options.FlushCachePolicy.FALSE) 36 | int setName(@Param("id") Integer id, @Param("name") String name); 37 | 38 | @Update("select * from users where id=#{id} or name=#{name}") 39 | // @Options(flushCache = Options.FlushCachePolicy.FALSE) 40 | int setName2(@Param("id") Integer id, @Param("name") String name); 41 | 42 | @Delete("delete from users where id=#{id}") 43 | int deleteUser(Integer id); 44 | 45 | } 46 | -------------------------------------------------------------------------------- /JCat-web/src/App2.vue: -------------------------------------------------------------------------------- 1 | 40 | 63 | 64 | 65 | 70 | -------------------------------------------------------------------------------- /jCat-agent/src/main/java/org/coderead/jcat/groovyLsp/CompletionItem.java: -------------------------------------------------------------------------------- 1 | package org.coderead.jcat.groovyLsp; 2 | /** 3 | * @Copyright 源码阅读网 http://coderead.cn 4 | */ 5 | 6 | import java.io.Serializable; 7 | import java.util.Objects; 8 | 9 | /** 10 | * @author 鲁班大叔 11 | * @date 2024 12 | */ 13 | public class CompletionItem implements Serializable { 14 | public String label; 15 | public String kind;// 种类 16 | public int modifiers; 17 | public String filterText;// 过滤文本 18 | public boolean deprecated;// 是否弃用 19 | public boolean groovyMethod;// 是否为groovy方法 20 | public String insertText; 21 | public String insertImportText; 22 | public String tipsText;// 提示文本 23 | 24 | @Override 25 | public boolean equals(Object o) { 26 | if (this == o) return true; 27 | if (o == null || getClass() != o.getClass()) return false; 28 | 29 | CompletionItem that = (CompletionItem) o; 30 | 31 | if (!Objects.equals(label, that.label)) return false; 32 | if (!Objects.equals(kind, that.kind)) return false; 33 | if (!Objects.equals(filterText, that.filterText)) return false; 34 | if (!Objects.equals(insertText, that.insertText)) return false; 35 | return Objects.equals(insertImportText, that.insertImportText); 36 | } 37 | 38 | @Override 39 | public int hashCode() { 40 | int result = label != null ? label.hashCode() : 0; 41 | result = 31 * result + (kind != null ? kind.hashCode() : 0); 42 | result = 31 * result + (filterText != null ? filterText.hashCode() : 0); 43 | result = 31 * result + (insertText != null ? insertText.hashCode() : 0); 44 | result = 31 * result + (insertImportText != null ? insertImportText.hashCode() : 0); 45 | return result; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /agent-adapter/src/main/java/org/coderead/jcat/InterceptorWrapper.java: -------------------------------------------------------------------------------- 1 | package org.coderead.jcat; 2 | /** 3 | * @Copyright 源码阅读网 http://coderead.cn 4 | */ 5 | 6 | import java.util.logging.Level; 7 | 8 | /** 9 | * 拦截器包装类,用于避免拦截器对业务逻辑影响 10 | *
    11 | *
  • 避免对拦截器中的逻辑进行拦截从而造成死循环递归
  • 12 | *
  • catch 拦截器中的异常
  • 13 | *
14 | * 15 | * @author 鲁班大叔 16 | * @date 2024 17 | */ 18 | public class InterceptorWrapper implements Interceptor { 19 | Interceptor interceptor; 20 | ThreadLocal lock = ThreadLocal.withInitial(()->false); 21 | 22 | public InterceptorWrapper(Interceptor interceptor) { 23 | this.interceptor = interceptor; 24 | } 25 | 26 | @Override 27 | public Object invoke(String methodInfo,Object thisObj, Object[] args) { 28 | if (lock.get()) { 29 | return null;// 杜绝 $begin内部发生采集事件所造成的死循环递归 30 | } 31 | try { 32 | lock.set(true); 33 | return this.interceptor.invoke(methodInfo,thisObj, args); 34 | } catch (Throwable e) { 35 | logger.log(Level.SEVERE,"JCat内部异常,该异常不影响业务正常执行,请将该异常反馈给开发者", e); 36 | } finally { 37 | lock.set(false); 38 | } 39 | return null; 40 | } 41 | 42 | 43 | @Override 44 | public void invokeEnd(String methodInfo,Object trace, Object thisObj, Object[] args, Object result) { 45 | if (lock.get()) { 46 | return;// 杜绝 内部发生采集事件所造成的死循环递归 47 | } 48 | try { 49 | lock.set(true); 50 | this.interceptor.invokeEnd(methodInfo,trace, thisObj, args, result); 51 | } catch (Throwable e) { 52 | logger.log(Level.SEVERE, "JCat内部异常,该异常不影响业务正常执行,请将该异常反馈给开发者", e); 53 | } finally { 54 | lock.set(false); 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /JCat-web/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "algorithm", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "serve": "vue-cli-service serve --port 8081", 7 | "build": "vue-cli-service build", 8 | "lint": "vue-cli-service lint" 9 | }, 10 | "dependencies": { 11 | "@mdi/font": "^7.0.96", 12 | "axios": "^0.26.0", 13 | "codemirror": "^5.65.2", 14 | "groovy-beautify": "^0.0.17", 15 | "vue": "^2.6.14", 16 | "vue-codemirror": "^4.0.6", 17 | "vue-router": "^3.5.1", 18 | "vuetify": "^2.6.4", 19 | "vuex": "3.6.2" 20 | }, 21 | "devDependencies": { 22 | "@babel/core": "^7.12.16", 23 | "@babel/eslint-parser": "^7.12.16", 24 | "@vue/cli-plugin-babel": "~5.0.0", 25 | "@vue/cli-plugin-eslint": "~5.0.0", 26 | "@vue/cli-plugin-router": "~5.0.0", 27 | "@vue/cli-service": "~5.0.0", 28 | "eslint": "^7.32.0", 29 | "eslint-plugin-vue": "^8.0.3", 30 | "less": "^4.1.3", 31 | "less-loader": "^11.1.0", 32 | "raw-loader": "^4.0.2", 33 | "sass": "~1.32.0", 34 | "sass-loader": "^10.0.0", 35 | "vue-cli-plugin-vuetify": "^2.4.6", 36 | "vue-template-compiler": "^2.6.14", 37 | "vuetify-loader": "^1.7.0", 38 | "webpack-bundle-analyzer": "^4.9.0" 39 | }, 40 | "eslintConfig": { 41 | "root": true, 42 | "env": { 43 | "node": true 44 | }, 45 | "extends": [ 46 | "plugin:vue/essential", 47 | "eslint:recommended" 48 | ], 49 | "parserOptions": { 50 | "parser": "@babel/eslint-parser" 51 | }, 52 | "rules": { 53 | "no-unused-vars": "off", 54 | "no-extra-semi": "off", 55 | "no-useless-escape": "off", 56 | "vue/no-unused-components": "off", 57 | "no-empty": "off" 58 | } 59 | }, 60 | "browserslist": [ 61 | "> 1%", 62 | "last 2 versions", 63 | "not dead" 64 | ] 65 | } 66 | -------------------------------------------------------------------------------- /JCat-web/src/components/GlobalMessage.vue: -------------------------------------------------------------------------------- 1 | 41 | 42 | 73 | 74 | -------------------------------------------------------------------------------- /JCat-web/src/components/Sidebar.vue: -------------------------------------------------------------------------------- 1 | 22 | 23 | 60 | 61 | 63 | -------------------------------------------------------------------------------- /JCat-web/src/views/AppMainView.vue: -------------------------------------------------------------------------------- 1 | 37 | 38 | 69 | 70 | -------------------------------------------------------------------------------- /test-spring/src/main/java/cn/coderead/testspring/demos/web/PathVariableController.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013-2018 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package cn.coderead.testspring.demos.web; 18 | 19 | import cn.coderead.testspring.mapper.UserMapper; 20 | import org.springframework.beans.factory.annotation.Autowired; 21 | import org.springframework.stereotype.Controller; 22 | import org.springframework.web.bind.annotation.PathVariable; 23 | import org.springframework.web.bind.annotation.RequestMapping; 24 | import org.springframework.web.bind.annotation.RequestMethod; 25 | import org.springframework.web.bind.annotation.ResponseBody; 26 | import cn.coderead.testspring.bean.User; 27 | 28 | /** 29 | * @author theonefx 30 | */ 31 | @Controller 32 | public class PathVariableController { 33 | @Autowired 34 | UserMapper userMapper; 35 | 36 | // http://127.0.0.1:8080/user/123/roles/222 37 | @RequestMapping(value = "/user/{userId}/roles/{roleId}", method = RequestMethod.GET) 38 | @ResponseBody 39 | public String getLogin(@PathVariable("userId") String userId, @PathVariable("roleId") String roleId) { 40 | return "User Id : " + userId + " Role Id : " + roleId; 41 | } 42 | 43 | @RequestMapping(value = "/user/{userId}", method = RequestMethod.GET) 44 | @ResponseBody 45 | public User getUser(@PathVariable("userId") String userId) { 46 | User user = userMapper.selectByid(Integer.parseInt(userId)); 47 | return user; 48 | } 49 | 50 | // http://127.0.0.1:8080/javabeat/somewords 51 | @RequestMapping(value = "/javabeat/{regexp1:[a-z-]+}", method = RequestMethod.GET) 52 | @ResponseBody 53 | public String getRegExp(@PathVariable("regexp1") String regexp1) { 54 | return "URI Part : " + regexp1; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /test-spring/src/main/java/cn/coderead/testspring/demos/web/BasicController.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013-2018 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package cn.coderead.testspring.demos.web; 18 | 19 | import org.springframework.stereotype.Controller; 20 | import org.springframework.web.bind.annotation.ModelAttribute; 21 | import org.springframework.web.bind.annotation.RequestMapping; 22 | import org.springframework.web.bind.annotation.RequestParam; 23 | import org.springframework.web.bind.annotation.ResponseBody; 24 | 25 | /** 26 | * @author theonefx 27 | */ 28 | @Controller 29 | public class BasicController { 30 | 31 | // http://127.0.0.1:8080/hello?name=lisi 32 | @RequestMapping("/hello") 33 | @ResponseBody 34 | public String hello(@RequestParam(name = "name", defaultValue = "unknown user") String name) { 35 | return "Hello " + name; 36 | } 37 | 38 | // http://127.0.0.1:8080/user 39 | @RequestMapping("/user") 40 | @ResponseBody 41 | public User user() { 42 | User user = new User(); 43 | user.setName("theonefx"); 44 | user.setAge(666); 45 | return user; 46 | } 47 | 48 | // http://127.0.0.1:8080/save_user?name=newName&age=11 49 | @RequestMapping("/save_user") 50 | @ResponseBody 51 | public String saveUser(User u) { 52 | return "user will save: name=" + u.getName() + ", age=" + u.getAge(); 53 | } 54 | 55 | // http://127.0.0.1:8080/html 56 | @RequestMapping("/html") 57 | public String html() { 58 | return "index.html"; 59 | } 60 | 61 | @ModelAttribute 62 | public void parseUser(@RequestParam(name = "name", defaultValue = "unknown user") String name 63 | , @RequestParam(name = "age", defaultValue = "12") Integer age, User user) { 64 | user.setName("zhangsan"); 65 | user.setAge(18); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /JCat-web/src/components/Example.js: -------------------------------------------------------------------------------- 1 | // 获取所有系统属性 2 | let getProperties = ` 3 | System.getProperties() 4 | `; 5 | 6 | // 获取Spring 上下文 7 | let getSpringContext = ` 8 | import org.springframework.context.ApplicationContext 9 | //get(Class) 获取类的实例 10 | get ApplicationContext.class 11 | `; 12 | 13 | // 获取MyBatis中Mapper实例并调用其中方法 14 | let getMapper = ` 15 | import coderead.tests.dao.UserMapper; 16 | //get(Class) 获取类的实例 17 | UserMapper userMapper=get(UserMapper.class)[0] 18 | userMapper.selectByid(12) 19 | `; 20 | 21 | 22 | // 获取Spring中所有的Bean 23 | let getSpringBeanAll = ` 24 | import org.springframework.context.ApplicationContext 25 | ApplicationContext context=get(ApplicationContext.class)[0] 26 | context.getBeanDefinitionNames().collect {it-> 27 | context.getBean(it) 28 | } 29 | ` 30 | 31 | // 获取数据库连接 32 | let getHikariPool = ` 33 | import com.zaxxer.hikari.HikariPoolMXBean 34 | import com.zaxxer.hikari.HikariDataSource 35 | HikariDataSource source=get(HikariDataSource)[0] 36 | HikariPoolMXBean mxBean= source.getHikariPoolMXBean() 37 | 38 | def total=mxBean.getTotalConnections() 39 | def active=mxBean.getActiveConnections() 40 | def idle=mxBean.getIdleConnections() 41 | def awaiting=mxBean.getThreadsAwaitingConnection() 42 | String log="总数:$total,激活数:$active,闲置数:$idle,等待数:$awaiting}" 43 | ` 44 | 45 | let getC3poPool = ` 46 | import com.mchange.v2.c3p0.PooledDataSource 47 | get(PooledDataSource.class)[0].toString() 48 | ` 49 | let getDruidPool = ` 50 | get(com.alibaba.druid.pool.DruidDataSource)[0].toString()` 51 | let getDbcpPool = ` 52 | import org.apache.commons.dbcp2.BasicDataSource 53 | BasicDataSource dataSource = get(BasicDataSource)[0]; 54 | // 获取连接池信息 55 | int maxActiveConnections = dataSource.getMaxTotal(); 56 | int maxIdleConnections = dataSource.getMaxIdle(); 57 | int minIdleConnections = dataSource.getMinIdle(); 58 | int numActiveConnections = dataSource.getNumActive(); 59 | int numIdleConnections = dataSource.getNumIdle(); 60 | String log="连接信息 最大激活数:$maxActiveConnections,最大闲置数:$maxIdleConnections,最小闲置数:$minIdleConnections,当前激活数:$numActiveConnections,当前闲置数:$numIdleConnections"; 61 | ` 62 | export let items = [ 63 | { title: "获取系统属性", code: getProperties }, 64 | { title: "获取Spring 上下文", code: getSpringContext }, 65 | { title: "调用MyBatis中Mapper实例方法", code: getMapper }, 66 | { title: "获取Spring中所有Bean", code: getSpringBeanAll }, 67 | 68 | { title: "HikariPool-连接池信息", code: getHikariPool }, 69 | { title: "c3p0-连接池信息", code: getC3poPool }, 70 | { title: "Druid-连接池信息", code: getDruidPool }, 71 | { title: "Dbcp-连接池信息", code: getDbcpPool }, 72 | 73 | ] 74 | 75 | export default { 76 | items 77 | } -------------------------------------------------------------------------------- /JCat-web/src/store/message.js: -------------------------------------------------------------------------------- 1 | // import { setTimeout } from "core-js"; 2 | // import vue from "vue"; 3 | 4 | const store = { 5 | namespaced: true, 6 | state: { 7 | msg: "", 8 | icon:"", 9 | pos:"top", 10 | show: false, 11 | type: "info", 12 | timeout: -1, 13 | alert: null, 14 | }, 15 | mutations: { //修改数据 16 | close(state) { 17 | state.show = false; 18 | state.alert = null; 19 | state.msg = null; 20 | state.icon=null; 21 | // state.pos="top"; 22 | } 23 | }, 24 | } 25 | 26 | function configTimeout(timeout) { 27 | if (timeout && timeout > 0) { 28 | store.state.timeout = timeout; 29 | // setTimeout(function(){ 30 | // close(); 31 | // },timeout); 32 | } else { 33 | store.state.timeout = -1; 34 | } 35 | } 36 | function show() { 37 | store.state.show = true; 38 | } 39 | 40 | 41 | export function info(msg, timeout) { 42 | store.state.msg = msg; 43 | store.state.type = "info"; 44 | if (timeout == undefined || timeout == null) { 45 | timeout = 3000; 46 | } 47 | configTimeout(timeout); 48 | show(); 49 | } 50 | 51 | export function success(msg, timeout,pos) { 52 | store.state.msg = msg; 53 | store.state.type = "success"; 54 | if (timeout == undefined || timeout == null) { 55 | timeout = 3000; 56 | } 57 | store.state.icon="mdi-check" 58 | store.state.pos=pos; 59 | configTimeout(timeout); 60 | show(); 61 | } 62 | 63 | export function warn(msg, timeout,pos="br") { 64 | store.state.msg = msg; 65 | store.state.type = "warning"; 66 | store.state.pos=pos; 67 | configTimeout(timeout); 68 | show(); 69 | } 70 | 71 | export function error(msg,pos="tc") { 72 | store.state.msg = msg; 73 | store.state.type = "error"; 74 | store.state.pos=pos; 75 | configTimeout(-1);// 不主动关闭 76 | show(); 77 | } 78 | 79 | 80 | 81 | export function close() { 82 | store.state.show = false; 83 | store.alert = null; 84 | } 85 | export function alert(title, message, affirm) { 86 | if(!affirm||!(affirm instanceof Function)) { 87 | throw new Error("affirm must be a function"); 88 | } 89 | store.state.alert = { 90 | show:true, 91 | title, 92 | message, 93 | affirm, 94 | } 95 | } 96 | 97 | export default { 98 | store, 99 | info, 100 | warn, 101 | error, 102 | success, 103 | close, 104 | alert, 105 | } -------------------------------------------------------------------------------- /test-spring/src/main/java/cn/coderead/testspring/bean/User.java: -------------------------------------------------------------------------------- 1 | package cn.coderead.testspring.bean; 2 | 3 | 4 | import java.util.Date; 5 | 6 | /** 7 | * @author Tommy 8 | * Created by Tommy on 2019/6/27 9 | **/ 10 | public class User implements java.io.Serializable { 11 | private Integer id; 12 | private String name; 13 | private String age; 14 | private String sex; 15 | private String email; 16 | private String phoneNumber; 17 | private Date createTime; 18 | private String[] labels; 19 | 20 | public User() { 21 | } 22 | 23 | public User(Integer id, String name) { 24 | this.id = id; 25 | this.name = name; 26 | } 27 | 28 | public Integer getId() { 29 | return id; 30 | } 31 | 32 | public void setId(Integer id) { 33 | this.id = id; 34 | } 35 | 36 | public String getName() { 37 | return name; 38 | } 39 | 40 | public void setName(String name) { 41 | this.name = name; 42 | } 43 | 44 | public String getAge() { 45 | return age; 46 | } 47 | 48 | public void setAge(String age) { 49 | this.age = age; 50 | } 51 | 52 | public String getSex() { 53 | return sex; 54 | } 55 | 56 | public void setSex(String sex) { 57 | this.sex = sex; 58 | } 59 | 60 | public String getEmail() { 61 | return email; 62 | } 63 | 64 | public void setEmail(String email) { 65 | this.email = email; 66 | } 67 | 68 | public String getPhoneNumber() { 69 | return phoneNumber; 70 | } 71 | 72 | public void setPhoneNumber(String phoneNumber) { 73 | this.phoneNumber = phoneNumber; 74 | } 75 | 76 | public Date getCreateTime() { 77 | return createTime; 78 | } 79 | 80 | public void setCreateTime(Date createTime) { 81 | this.createTime = createTime; 82 | } 83 | 84 | 85 | public String[] getLabels() { 86 | return labels; 87 | } 88 | 89 | public void setLabels(String[] labels) { 90 | this.labels = labels; 91 | } 92 | 93 | @Override 94 | public String toString() { 95 | return "User{" + 96 | "id=" + id + 97 | ", name='" + name + '\'' + 98 | ", age='" + age + '\'' + 99 | ", sex='" + sex + '\'' + 100 | ", emal='" + email + '\'' + 101 | ", phoneNumber='" + phoneNumber + '\'' + 102 | ", createTime=" + createTime + 103 | '}'; 104 | } 105 | } 106 | 107 | -------------------------------------------------------------------------------- /jCat-agent/src/main/java/org/coderead/jcat/common/WildcardMatcher.java: -------------------------------------------------------------------------------- 1 | package org.coderead.jcat.common; 2 | 3 | 4 | import java.util.regex.Pattern; 5 | 6 | /** 7 | * 来自于jacoco 源码 8 | */ 9 | public class WildcardMatcher { 10 | 11 | private final Pattern pattern; 12 | 13 | /** 14 | * Creates a new matcher with the given expression. 15 | * 16 | * @param expression wildcard expressions 17 | */ 18 | public WildcardMatcher(final String expression) { 19 | final String[] parts = expression.split("&"); 20 | final StringBuilder regex = new StringBuilder(expression.length() * 2); 21 | boolean next = false; 22 | for (final String part : parts) { 23 | if (next) { 24 | regex.append('|'); 25 | } 26 | regex.append('(').append(toRegex(part)).append(')'); 27 | next = true; 28 | } 29 | pattern = Pattern.compile(regex.toString()); 30 | } 31 | 32 | private static CharSequence toRegex(final String expression) { 33 | final StringBuilder regex = new StringBuilder(expression.length() * 2); 34 | for (final char c : expression.toCharArray()) { 35 | switch (c) { 36 | case '?': 37 | regex.append(".?"); 38 | break; 39 | case '*': 40 | regex.append(".*"); 41 | break; 42 | default: 43 | regex.append(Pattern.quote(String.valueOf(c))); 44 | break; 45 | } 46 | } 47 | return regex; 48 | } 49 | 50 | /** 51 | * Matches the given string against the expressions of this matcher. 52 | * 53 | * @param s string to test 54 | * @return true, if the expression matches 55 | */ 56 | public boolean matches(final String s) { 57 | return pattern.matcher(s).matches(); 58 | } 59 | 60 | // public static Predicate build(String expression) { 61 | // WildcardMatcher matcher = new WildcardMatcher(expression); 62 | // return a -> matcher.matches(a); 63 | // } 64 | // 65 | // public static Predicate build(String includeExpr, String excludeExpr) { 66 | // Assert.isTrue(includeExpr != null || excludeExpr != null, "参数 includeExpr 或 excludeExpr 至少要有一个不为空"); 67 | // 68 | // WildcardMatcher matcher = new WildcardMatcher(includeExpr == null ? "*" : includeExpr); 69 | // WildcardMatcher excludeMatcher = new WildcardMatcher(excludeExpr == null ? "" : excludeExpr); 70 | // return a -> matcher.matches(a) && !excludeMatcher.matches(a); 71 | // } 72 | } -------------------------------------------------------------------------------- /jCat-agent/src/main/groovy/org/coderead/jcat/ClassUtil.groovy: -------------------------------------------------------------------------------- 1 | package org.coderead.jcat 2 | 3 | import java.lang.instrument.ClassFileTransformer 4 | import java.lang.instrument.IllegalClassFormatException 5 | import java.lang.instrument.Instrumentation 6 | import java.security.ProtectionDomain 7 | 8 | /** 9 | * @author 鲁班大叔 10 | * @email 27686551@qq.com 11 | * @date 2024/08/ 12 | 1 13 | */ 14 | class ClassUtil { 15 | // 获取所有的类,并基于classLoader进行分组 16 | static List getAllClass(Instrumentation instrumentation) { 17 | def items = []; 18 | for (final def c in instrumentation.getAllLoadedClasses()) { 19 | try { 20 | if (c.name.startsWith("[")) { 21 | continue 22 | } 23 | items.add(new ClassItem( 24 | loadId: System.identityHashCode(c.getClassLoader()), 25 | classId: System.identityHashCode(c), 26 | className: c.name 27 | )) 28 | } catch (Throwable e) { 29 | e.printStackTrace(); 30 | } 31 | } 32 | return items 33 | } 34 | 35 | static class ClassItem implements Serializable { 36 | long loadId 37 | long classId 38 | String className 39 | } 40 | // 读取类字节码 41 | static byte[] readClass(Instrumentation instrumentation, long classId) { 42 | def cla = findClass(instrumentation, classId); 43 | final List list = []; 44 | def transformer = new ClassFileTransformer() { 45 | @Override 46 | byte[] transform(ClassLoader loader, String className, Class classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException { 47 | if (classBeingRedefined == cla) { 48 | list << classfileBuffer 49 | } 50 | return null; 51 | } 52 | } 53 | instrumentation.addTransformer(transformer, true); 54 | try { 55 | instrumentation.retransformClasses(cla); 56 | // TODO UnmodifiableClassException 异常处理 如:PropertySerializerMap$TypeAndSerializer类 57 | } finally { 58 | instrumentation.removeTransformer(transformer); 59 | } 60 | return list[0] 61 | } 62 | 63 | static Class findClass(Instrumentation instrumentation, long classId) { 64 | instrumentation.allLoadedClasses.find(c -> System.identityHashCode(c) == classId) 65 | } 66 | 67 | // 反编译类 68 | static String decompilerClass(byte[] bytes) { 69 | Jad.decompiler(bytes) 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /jCat-agent/src/main/java/org/coderead/jcat/common/JsonUtil.java: -------------------------------------------------------------------------------- 1 | package org.coderead.jcat.common; 2 | 3 | 4 | import com.fasterxml.jackson.annotation.JsonInclude; 5 | import com.fasterxml.jackson.core.JsonParser; 6 | import com.fasterxml.jackson.databind.DeserializationFeature; 7 | import com.fasterxml.jackson.databind.ObjectMapper; 8 | import com.fasterxml.jackson.databind.SerializationFeature; 9 | 10 | import java.io.IOException; 11 | import java.util.List; 12 | import java.util.Map; 13 | import java.util.stream.Collectors; 14 | 15 | /** 16 | * Created by Tommy on 2018/3/8. 17 | */ 18 | public class JsonUtil { 19 | public static final ObjectMapper JSON_MAPPER = newObjectMapper(), JSON_MAPPER_WEB = newObjectMapper(); 20 | 21 | private static ObjectMapper newObjectMapper() { 22 | ObjectMapper result = new ObjectMapper(); 23 | result.configure(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES, true); 24 | result.configure(JsonParser.Feature.ALLOW_SINGLE_QUOTES, true); 25 | result.setSerializationInclusion(JsonInclude.Include.NON_NULL); 26 | result.configure(SerializationFeature.WRITE_NULL_MAP_VALUES, false); //不输出value=null的属性 27 | result.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); 28 | result.configure(JsonParser.Feature.ALLOW_UNQUOTED_CONTROL_CHARS, true); 29 | 30 | return result; 31 | } 32 | 33 | public static ObjectMapper getObjectMapper() { 34 | return JSON_MAPPER; 35 | } 36 | 37 | 38 | public static String toJson(Object value) { 39 | try { 40 | return value == null ? null : JSON_MAPPER.writeValueAsString(value); 41 | } catch (IOException e) { 42 | throw new IllegalArgumentException(e); // TIP: 原则上,不对异常包装,这里为什么要包装?因为正常情况不会发生IOException 43 | } 44 | } 45 | 46 | @SuppressWarnings("unchecked") 47 | public static Map toMap(Object value) throws IllegalArgumentException { 48 | return convertValue(value, Map.class); 49 | } 50 | 51 | 52 | public static T convertValue(Object value, Class clazz) throws IllegalArgumentException { 53 | if (StringUtils.isEmpty(value)) return null; 54 | try { 55 | if (value instanceof String) 56 | value = JSON_MAPPER.readTree((String) value); 57 | return JSON_MAPPER.convertValue(value, clazz); 58 | } catch (IOException e) { 59 | throw new IllegalArgumentException(e); 60 | } 61 | } 62 | 63 | public static List convertList(Object value, Class clazz) { 64 | List list = convertValue(value, List.class); 65 | return (List) list.stream().map(o -> convertValue(o, clazz)).collect(Collectors.toList()); 66 | } 67 | 68 | 69 | } 70 | -------------------------------------------------------------------------------- /jCat-agent/src/main/java/org/coderead/jcat/service/ResourceExplorerService.java: -------------------------------------------------------------------------------- 1 | package org.coderead.jcat.service; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Getter; 5 | import lombok.NoArgsConstructor; 6 | import lombok.Setter; 7 | import org.coderead.jcat.Agent; 8 | import org.coderead.jcat.ClassUtil; 9 | import org.coderead.jcat.common.Assert; 10 | 11 | import java.io.IOException; 12 | import java.io.Serializable; 13 | import java.net.JarURLConnection; 14 | import java.net.URL; 15 | import java.net.URLClassLoader; 16 | import java.net.URLConnection; 17 | import java.util.ArrayList; 18 | import java.util.Arrays; 19 | import java.util.List; 20 | import java.util.Map; 21 | import java.util.jar.JarFile; 22 | import java.util.stream.Collectors; 23 | 24 | /** 25 | * @author 鲁班大叔 26 | * @email 27686551@qq.com 27 | * @date 2024/8/25 28 | */ 29 | public class ResourceExplorerService { 30 | // 返回ClassLoader下所有资源路径 31 | public ResourceExplorerService() { 32 | DefaultHttpServer httpServer = DefaultHttpServer.getInstance(); 33 | httpServer.registeGet("/resource/allClass", this::getAllClass); 34 | httpServer.registeGet("/resource/decompilerClass", this::decompilerClass); 35 | } 36 | 37 | 38 | // 获取所有类 39 | private Object getAllClass(Map stringStringMap) { 40 | List items = new ArrayList<>(); 41 | for (Class c : Agent.instrumentation.getAllLoadedClasses()) { 42 | try { 43 | if (c.getName().startsWith("[") || c.getName().contains("$$Lambda$")) { 44 | continue; 45 | } 46 | ClassItem item = new ClassItem( 47 | System.identityHashCode(c.getClassLoader()), 48 | System.identityHashCode(c), 49 | c.getName()); 50 | items.add(item); 51 | } catch (Throwable e) { 52 | e.printStackTrace(); 53 | } 54 | } 55 | return items; 56 | } 57 | 58 | @Getter 59 | @Setter 60 | @NoArgsConstructor 61 | @AllArgsConstructor 62 | static class ClassItem implements Serializable { 63 | long loadId; 64 | long classId; 65 | String className; 66 | } 67 | 68 | 69 | private String decompilerClass(Map stringStringMap) { 70 | long classId = Long.parseLong(stringStringMap.get("classId")); 71 | byte[] bytes = ClassUtil.readClass(Agent.instrumentation, classId); 72 | return ClassUtil.decompilerClass(bytes); 73 | } 74 | 75 | public URL[] getUrls(URLClassLoader urlClassLoader) { 76 | return urlClassLoader.getURLs(); 77 | } 78 | 79 | // 返回资源路径下资源列表 80 | public Resource[] getResource(URL url) throws IOException { 81 | // 打开资源 82 | URLConnection urlConnection = url.openConnection(); 83 | Assert.isTrue(urlConnection instanceof JarURLConnection); 84 | JarURLConnection connection = (JarURLConnection) urlConnection; 85 | JarFile jarFile = connection.getJarFile(); 86 | return null; 87 | } 88 | 89 | 90 | @Getter 91 | @Setter 92 | public static class Resource implements java.io.Serializable { 93 | String url; 94 | String name; 95 | String type; 96 | Resource[] children; 97 | } 98 | // 99 | 100 | 101 | } 102 | -------------------------------------------------------------------------------- /jCat-agent/src/main/groovy/org/coderead/jcat/Jad.groovy: -------------------------------------------------------------------------------- 1 | package org.coderead.jcat 2 | 3 | import org.jd.core.v1.ClassFileToJavaSourceDecompiler 4 | import org.jd.core.v1.api.loader.Loader 5 | import org.jd.core.v1.api.loader.LoaderException 6 | import org.jd.core.v1.api.printer.Printer 7 | 8 | /** 9 | * @author 鲁班大叔 10 | * @email 27686551@qq.com 11 | * @date 2024/08/ 12 | 1 13 | */ 14 | class Jad { 15 | static class classPrinter implements Printer{ 16 | protected static final String TAB = " "; 17 | protected static final String NEWLINE = "\n"; 18 | 19 | protected int indentationCount = 0; 20 | protected StringBuilder sb = new StringBuilder(); 21 | 22 | @Override 23 | public String toString() { 24 | return sb.toString(); 25 | } 26 | 27 | @Override 28 | public void start(int maxLineNumber, int majorVersion, int minorVersion) { 29 | } 30 | 31 | @Override 32 | public void end() { 33 | } 34 | 35 | @Override 36 | public void printText(String text) { 37 | sb.append(text); 38 | } 39 | 40 | @Override 41 | public void printNumericConstant(String constant) { 42 | sb.append(constant); 43 | } 44 | 45 | @Override 46 | public void printStringConstant(String constant, String ownerInternalName) { 47 | sb.append(constant); 48 | } 49 | 50 | @Override 51 | public void printKeyword(String keyword) { 52 | sb.append(keyword); 53 | } 54 | 55 | @Override 56 | public void printDeclaration(int type, String internalTypeName, String name, String descriptor) { 57 | sb.append(name); 58 | } 59 | 60 | @Override 61 | public void printReference(int type, String internalTypeName, String name, String descriptor, String ownerInternalName) { 62 | sb.append(name); 63 | } 64 | 65 | @Override 66 | public void indent() { 67 | this.indentationCount++; 68 | } 69 | 70 | @Override 71 | public void unindent() { 72 | this.indentationCount--; 73 | } 74 | 75 | @Override 76 | public void startLine(int lineNumber) { 77 | for (int i = 0; i < indentationCount; i++) sb.append(TAB); 78 | } 79 | 80 | @Override 81 | public void endLine() { 82 | sb.append(NEWLINE); 83 | } 84 | 85 | @Override 86 | public void extraLine(int count) { 87 | while (count-- > 0) sb.append(NEWLINE); 88 | } 89 | 90 | @Override 91 | public void startMarker(int type) { 92 | } 93 | 94 | @Override 95 | public void endMarker(int type) { 96 | } 97 | }; 98 | 99 | static String decompiler(byte [] bytes) throws Exception { 100 | ClassFileToJavaSourceDecompiler decompiler = new ClassFileToJavaSourceDecompiler(); 101 | def JAD_FIXED_CLASS = UUID.randomUUID().toString().replaceAll("-","") 102 | def printer = new classPrinter() 103 | decompiler.decompile(new Loader() { 104 | @Override 105 | boolean canLoad(String s) { 106 | return JAD_FIXED_CLASS == s 107 | } 108 | 109 | @Override 110 | byte[] load(String s) throws LoaderException { 111 | return bytes 112 | } 113 | }, printer, JAD_FIXED_CLASS); 114 | String source = printer.toString(); 115 | return source; 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /jcat-boot/src/main/java/org/coderead/jcat/JCatLoader.java: -------------------------------------------------------------------------------- 1 | package org.coderead.jcat; 2 | /** 3 | * @Copyright 源码阅读网 http://coderead.cn 4 | */ 5 | 6 | import java.io.File; 7 | import java.net.MalformedURLException; 8 | import java.net.URL; 9 | import java.net.URLClassLoader; 10 | import java.util.Arrays; 11 | import java.util.logging.Logger; 12 | 13 | /** 14 | * @author 鲁班大叔 15 | * @date 2024 16 | */ 17 | public class JCatLoader extends URLClassLoader { 18 | static Logger logger = Logger.getLogger(JCatLoader.class.getName()); 19 | 20 | public JCatLoader(URL[] urls) { 21 | super(attachToolsJar(urls ), JCatLoader.class.getClassLoader().getParent()); 22 | } 23 | 24 | protected static URL[] attachToolsJar(URL[] urls) { 25 | try { 26 | getSystemClassLoader().loadClass("com.sun.tools.attach.VirtualMachine"); 27 | return urls; 28 | } catch (ClassNotFoundException ignored) { 29 | } 30 | 31 | // 查找tools.jar 32 | 33 | File toolsFile = findToolsByCurrentJavaHome(); // 从当前jdk_home中查找 34 | if (toolsFile == null) { 35 | toolsFile = findToolsByCLASSPATH();// 环境变量CLASSPATH中找 36 | } 37 | if (toolsFile == null) { 38 | toolsFile = findToolsByJAVAHOME();// 环境变量JDK_HOME中找 39 | } 40 | if (toolsFile == null) { 41 | throw new IllegalStateException("系统找不到tools.jar"); 42 | } 43 | try { 44 | urls = Arrays.copyOf(urls, urls.length + 1); 45 | urls[urls.length - 1] = toolsFile.toURI().toURL(); 46 | } catch (MalformedURLException e) { 47 | throw new RuntimeException(e); 48 | } 49 | return urls; 50 | } 51 | 52 | static File findToolsByCurrentJavaHome() { 53 | if (!System.getProperty("java.home").endsWith("jre")) { 54 | return null; 55 | } 56 | File file = new File(new File(System.getProperty("java.home")).getParent(), "lib/tools.jar"); 57 | logger.info("find tools.jar by Home:" + file); 58 | return file.exists() ? file : null; 59 | } 60 | 61 | static File findToolsByCLASSPATH() { 62 | String classpath = System.getenv("CLASSPATH"); 63 | if (classpath == null) return null; 64 | logger.info("find tools.jar by CLASSPATH:" + classpath); 65 | return Arrays.stream(classpath.split(System.getProperty("path.separator"))) 66 | .filter(s -> s.endsWith("tools.jar")) 67 | .map(File::new) 68 | .filter(File::exists) 69 | .findFirst().orElse(null); 70 | } 71 | 72 | static File findToolsByJAVAHOME() { 73 | String JAVA_HOME = System.getenv("JAVA_HOME"); 74 | if (JAVA_HOME == null) return null; 75 | File file = new File(JAVA_HOME, "lib/tools.jar"); 76 | logger.info("find tools.jar by JAVA_HOME:" + file); 77 | return file.exists() ? file : null; 78 | } 79 | 80 | 81 | // 82 | @Override 83 | protected synchronized Class loadClass(String name, boolean resolve) throws ClassNotFoundException { 84 | final Class loadedClass = findLoadedClass(name); 85 | if (loadedClass != null) { 86 | return loadedClass; 87 | } 88 | if (name != null && (name.startsWith("sun.") || name.startsWith("java."))) { 89 | return super.loadClass(name, resolve); 90 | } 91 | try { 92 | Class aClass = findClass(name); 93 | if (resolve) { 94 | resolveClass(aClass); 95 | } 96 | return aClass; 97 | } catch (Exception ignored) { 98 | } // 勿略错误,并从父节点加载 99 | return super.loadClass(name, resolve); 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /jCat-agent/src/main/java/org/coderead/jcat/groovyLsp/TextDocumentService.java: -------------------------------------------------------------------------------- 1 | package org.coderead.jcat.groovyLsp; 2 | /** 3 | * @Copyright 源码阅读网 http://coderead.cn 4 | */ 5 | 6 | import org.coderead.jcat.Agent; 7 | import org.coderead.jcat.common.Assert; 8 | import org.coderead.jcat.console.ConsoleScript; 9 | import groovy.lang.GroovyShell; 10 | import lombok.Getter; 11 | import org.codehaus.groovy.control.CompilationFailedException; 12 | import org.codehaus.groovy.control.CompilationUnit; 13 | import org.codehaus.groovy.control.Phases; 14 | import org.codehaus.groovy.control.SourceUnit; 15 | import org.codehaus.groovy.control.messages.SyntaxErrorMessage; 16 | import org.codehaus.groovy.syntax.SyntaxException; 17 | 18 | import java.io.Serializable; 19 | import java.util.ArrayList; 20 | import java.util.List; 21 | 22 | /** 23 | * groovy 语法服务 24 | * 1.同步、编译、获取错误项、 25 | * 2.获取元素定义、 26 | * 3.获取提示项、 27 | * 4.跳转到定义 28 | * 29 | * @author 鲁班大叔 30 | * @date 2024 31 | */ 32 | public class TextDocumentService { 33 | final GroovyShell groovyShell; 34 | private CompilationUnit unit; 35 | private SourceUnit consoleCodeSource; 36 | 37 | public TextDocumentService(GroovyShell groovyShell) { 38 | this.groovyShell = groovyShell; 39 | } 40 | 41 | // 同步文本 42 | // 返回错误、警告等信息 43 | 44 | /** 45 | * 1.同步、编译、获取错误项 46 | * 47 | * @param consoleCode 48 | */ 49 | public List compile(String consoleCode) { 50 | unit = new CompilationUnit(groovyShell.getClassLoader()); 51 | consoleCodeSource = unit.addSource("consoleCode", consoleCode); 52 | ArrayList results = new ArrayList<>(); 53 | try { 54 | unit.compile(Phases.CANONICALIZATION); 55 | } catch (CompilationFailedException e) { 56 | // e.printStackTrace(); // 异常处理 57 | for (Object o : unit.getErrorCollector().getErrors()) { 58 | Assert.isTrue(o instanceof SyntaxErrorMessage, "未对异常作处理:" + o.getClass().getName()); 59 | SyntaxException cause = ((SyntaxErrorMessage) o).getCause(); 60 | CompileError error = new CompileError("error", cause.getOriginalMessage()); 61 | error.range = new int[]{cause.getStartLine(), cause.getStartColumn(), cause.getEndLine(), cause.getEndColumn()}; 62 | results.add(error); 63 | } 64 | if (results.isEmpty()) { 65 | throw new RuntimeException("未知编译异常", e); 66 | } 67 | unit = null; 68 | } 69 | return results; // 70 | } 71 | 72 | // 查看定义 73 | public void hover() { 74 | } 75 | 76 | // 跳转到定义 77 | public void definition() { 78 | } 79 | 80 | 81 | // 获取提示项 82 | public List completionByCursor(int[] position) { 83 | Assert.notNull(unit, "文件未正常编译"); 84 | CompletionHandler handler = new CompletionHandler(groovyShell.getClassLoader(), ConsoleScript.class); 85 | return handler.completionByCursor(unit.getAST(), position); 86 | } 87 | 88 | public List completionByKeyword(String keyword, int maxSize) { 89 | CompletionHandler handler = new CompletionHandler(groovyShell.getClassLoader(), ConsoleScript.class); 90 | handler.setAllClass(() -> Agent.instrumentation.getAllLoadedClasses()); 91 | return handler.completionByKeyword(keyword, maxSize); 92 | } 93 | 94 | @Getter 95 | public static class CompileError implements Serializable { 96 | public String level; //error, warring 97 | public String message; 98 | public int range[] = new int[4]; //范围 line,column,lastLine,lastColumn 99 | 100 | public CompileError() { 101 | } 102 | 103 | public CompileError(String level, String message) { 104 | this.level = level; 105 | this.message = message; 106 | } 107 | } 108 | 109 | 110 | } 111 | -------------------------------------------------------------------------------- /JCat-web/src/api/index.js: -------------------------------------------------------------------------------- 1 | import axios from "axios"; 2 | 3 | export function createBaseApi(baseURL) { 4 | return axios.create({ 5 | baseURL, 6 | timeout: 10000, 7 | headers: { 'Content-Type': "application/json;charset=utf-8" }, 8 | validateStatus: function (status) { 9 | return status < 400; // 状态码为400以下属正常返回 10 | } 11 | }); 12 | } 13 | 14 | function addResponseFilter(api) { 15 | // 服务端响应业务错误: 封装业务异常抛出 16 | // 连接异常:封装请求错误 17 | // 网络异常:封装网络异常 18 | api.interceptors.response.use(function (response) { 19 | return response.data; // 正常直接返回data 20 | }, function (error) { 21 | if (error.response) { 22 | let responseError = error.response.data; 23 | if (error.response.status == 400) { // 请求结果异常,弹窗提示 24 | responseError.type = "requestError"; 25 | } else if (error.response.status == 500) {//服务响应异常,右下角提示 26 | responseError.type = "serverError"; 27 | } else { 28 | responseError.type = "otherError"; 29 | } 30 | console.error(JSON.stringify(responseError)); 31 | return Promise.reject(error.response.data); 32 | } else { 33 | error.type = "networkError" 34 | console.error("连接异常:", error); 35 | return Promise.reject(error); 36 | } 37 | }) 38 | } 39 | 40 | export class ConsoleService { 41 | constructor() { 42 | this.api = createBaseApi(`/jCat/api/console`); 43 | addResponseFilter(this.api); 44 | } 45 | getAllClassLoader() { 46 | return this.api.get(`/allClassLoader`) 47 | } 48 | openSerssion(loaderId) { 49 | let params = new URLSearchParams({ loaderId }); 50 | return this.api.get(`/open?${params.toString()}`) 51 | } 52 | closeSession(sessionId) { 53 | let params = new URLSearchParams({ sessionId }); 54 | return this.api.get(`/close?${params.toString()}`); 55 | } 56 | eval(sessionId, code) { 57 | let params = new URLSearchParams({ sessionId, code }); 58 | return this.api.get(`/eval?${params.toString()}`); 59 | } 60 | getObjectDetail(sessionId, objectPath, begin, size, level = 0) { 61 | let params = new URLSearchParams({ sessionId, objectPath, begin, size, level }); 62 | return this.api.get(`/detail?${params.toString()}`); 63 | } 64 | 65 | completion(sessionId, keywordOrCursor, max = 100) { 66 | let params = new URLSearchParams({ sessionId, max }); 67 | if (typeof keywordOrCursor === 'string') { 68 | params.append("keyword", keywordOrCursor); 69 | } else if (keywordOrCursor instanceof Array) { 70 | params.append("cursor", keywordOrCursor.join()); 71 | } else { 72 | throw Error("非法参数类型:" + keywordOrCursor) 73 | } 74 | return this.api.get(`/completion?${params.toString()}`); 75 | } 76 | 77 | // 语法分析 78 | analysis(sessionId, code) { 79 | let params = new URLSearchParams({ sessionId, code }); 80 | return this.api.get(`/analysis?${params.toString()}`); 81 | } 82 | saveFile(file, text) { 83 | let params = new URLSearchParams({ file, text }); 84 | return this.api.get(`/file/save?${params.toString()}`); 85 | } 86 | openFile(path) { 87 | let params = new URLSearchParams({ path }); 88 | return this.api.get(`/file/open?${params.toString()}`); 89 | } 90 | findFile(parent, suffix) { 91 | let params = new URLSearchParams({ parent }); 92 | if (suffix) { 93 | params.append("suffix", suffix); 94 | } 95 | return this.api.get(`/file/find?${params.toString()}`); 96 | } 97 | } 98 | 99 | export class ResourceExplorerService { 100 | constructor() { 101 | this.api = createBaseApi(`/jCat/api/resource`); 102 | addResponseFilter(this.api); 103 | } 104 | getAllClass(){ 105 | let params = new URLSearchParams({ }); 106 | return this.api.get(`/allClass?${params.toString()}`); 107 | } 108 | decompilerClass(classId){ 109 | let params = new URLSearchParams({ classId }); 110 | return this.api.get(`/decompilerClass?${params.toString()}`); 111 | } 112 | } 113 | export default { 114 | ConsoleService, 115 | ResourceExplorerService 116 | } 117 | 118 | -------------------------------------------------------------------------------- /jcat-boot/src/main/java/org/coderead/jcat/BootstrapAgent.java: -------------------------------------------------------------------------------- 1 | package org.coderead.jcat; 2 | /** 3 | * @Copyright 源码阅读网 http://coderead.cn 4 | */ 5 | 6 | 7 | import java.io.File; 8 | import java.io.IOException; 9 | import java.lang.instrument.Instrumentation; 10 | import java.lang.reflect.Method; 11 | import java.net.MalformedURLException; 12 | import java.net.URL; 13 | import java.util.Arrays; 14 | import java.util.jar.Attributes; 15 | import java.util.jar.JarFile; 16 | import java.util.jar.Manifest; 17 | import java.util.logging.Level; 18 | import java.util.logging.Logger; 19 | 20 | /** 21 | * @author 鲁班大叔 22 | * @date 2024 23 | */ 24 | public class BootstrapAgent { 25 | static final Logger logger = Logger.getLogger(BootstrapAgent.class.getName()); 26 | private static volatile JCatLoader loader = null; 27 | private static String premainClass; 28 | private static String agentmainClass; 29 | private static String mainClass; 30 | 31 | public static void premain(String args, Instrumentation instrumentation) { 32 | try { 33 | initJCatLoader(); 34 | Class aClass = loader.loadClass(premainClass, true); 35 | Method premain = aClass.getDeclaredMethod("premain", String.class, Instrumentation.class); 36 | premain.invoke(aClass, args, instrumentation); 37 | } catch (Throwable e) { 38 | logger.log(Level.SEVERE, "jCat加载失败", e); 39 | } 40 | } 41 | 42 | public static void agentmain(String args, Instrumentation instrumentation) { 43 | if (loader != null) { 44 | logger.warning("jCat已经加载!"); 45 | return; 46 | } 47 | try { 48 | initJCatLoader(); 49 | Class aClass = loader.loadClass(agentmainClass, true); 50 | Method agentmain = aClass.getDeclaredMethod("agentmain", String.class, Instrumentation.class); 51 | agentmain.invoke(aClass, args, instrumentation); 52 | } catch (Throwable e) { 53 | logger.log(Level.SEVERE, "jCat启动失败", e); 54 | } 55 | } 56 | 57 | public static void main(String[] args) { 58 | try { 59 | initJCatLoader(); 60 | Class aClass = loader.loadClass(mainClass, true); 61 | Method main = aClass.getDeclaredMethod("main", String[].class); 62 | if (Arrays.stream(args).noneMatch(s -> s.startsWith("agent.path="))) { 63 | String path = "agent.path=" + BootstrapAgent.class.getProtectionDomain().getCodeSource().getLocation().getPath(); 64 | if (!path.endsWith(".jar")) { 65 | throw new IllegalStateException("agent.path必须是一个.jar文件"); 66 | } 67 | args = Arrays.copyOf(args, args.length + 1); 68 | args[args.length - 1] = path; 69 | } 70 | main.invoke(aClass, (Object) args); 71 | } catch (Throwable e) { 72 | logger.log(Level.SEVERE, "jCat启动失败", e); 73 | } 74 | } 75 | 76 | 77 | private static JCatLoader initJCatLoader() { 78 | File classFile = new File(BootstrapAgent.class.getProtectionDomain().getCodeSource().getLocation().getPath()); 79 | File file = new File(classFile.getParent(), "lib/JCat-agent.jar"); 80 | if (!file.exists()) { 81 | throw new IllegalStateException("找不到文件:" + file); 82 | } 83 | try (JarFile jarFile = new JarFile(file)) { 84 | Manifest manifest = jarFile.getManifest(); 85 | Attributes attributes = manifest.getMainAttributes(); 86 | premainClass = attributes.getValue("premain-class"); 87 | agentmainClass = attributes.getValue("Agent-Class"); 88 | mainClass = attributes.getValue("Main-Class"); 89 | if (premainClass == null || premainClass.trim().equals("")) { 90 | throw new IllegalStateException("找不到premain-class 参数:" + file); 91 | } 92 | if (agentmainClass == null || agentmainClass.trim().equals("")) { 93 | throw new IllegalStateException("找不到Agent-Class 参数:" + file); 94 | } 95 | } catch (IOException e) { 96 | throw new RuntimeException("文件损坏,无法读取" + file, e); 97 | } 98 | try { 99 | loader = new JCatLoader(new URL[]{file.toURI().toURL()}); 100 | } catch (MalformedURLException e) { 101 | throw new IllegalArgumentException(e); 102 | } 103 | return loader; 104 | } 105 | 106 | } 107 | -------------------------------------------------------------------------------- /JCat-web/public/image/ICON_SQL.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /jCat-agent/src/main/java/org/coderead/jcat/Agent.java: -------------------------------------------------------------------------------- 1 | package org.coderead.jcat; 2 | 3 | import org.apache.groovy.util.Maps; 4 | import org.coderead.jcat.common.Assert; 5 | import org.coderead.jcat.service.AttachService; 6 | import org.coderead.jcat.service.ConsoleService; 7 | import org.coderead.jcat.service.DefaultHttpServer; 8 | import org.coderead.jcat.service.ResourceExplorerService; 9 | 10 | import java.awt.*; 11 | import java.io.ByteArrayInputStream; 12 | import java.io.IOException; 13 | import java.io.InputStream; 14 | import java.lang.instrument.Instrumentation; 15 | import java.net.URI; 16 | import java.net.URISyntaxException; 17 | import java.util.Collection; 18 | import java.util.Properties; 19 | import java.util.Scanner; 20 | import java.util.logging.Level; 21 | import java.util.logging.Logger; 22 | 23 | public class Agent { 24 | static final Logger logger = Logger.getLogger(Agent.class.getName()); 25 | 26 | public static Properties configs = new Properties(); 27 | public static Instrumentation instrumentation; 28 | static DefaultHttpServer httpServer = DefaultHttpServer.getInstance(); 29 | static ConsoleService consoleService = new ConsoleService(); // 控制台服务 30 | static ResourceExplorerService resourceService = new ResourceExplorerService(); // 控制台服务 31 | 32 | 33 | public static void premain(String args, Instrumentation instrumentation) { 34 | start(args, instrumentation); 35 | } 36 | 37 | public static void agentmain(String args, Instrumentation instrumentation) { 38 | try { 39 | start(args, instrumentation); 40 | } catch (Throwable e) { 41 | logger.log(Level.SEVERE, "load agentmain fail:" + args, e); 42 | } 43 | } 44 | 45 | public static void start(String args, Instrumentation instrumentation) { 46 | Agent.instrumentation = instrumentation; 47 | configs = getAgentConfigs(args); 48 | try { 49 | httpServer.start(-1, "/jCat"); 50 | } catch (IOException e) { 51 | logger.log(Level.SEVERE, "http服务启动失败", e); 52 | } 53 | } 54 | 55 | // 读取agent 配置 56 | private static Properties getAgentConfigs(String arg) { 57 | // 读取agent 配置 58 | Properties properties = new Properties(); 59 | InputStream resourceAsStream = Agent.class.getResourceAsStream("/agent.properties"); 60 | try { 61 | if (resourceAsStream != null) { 62 | properties.load(resourceAsStream); 63 | resourceAsStream.close(); 64 | } 65 | } catch (IOException e) { 66 | throw new RuntimeException("agent config format is error", e); 67 | } 68 | //装载Debug 调式参数信息,其可直接覆盖上述配置 69 | if (arg != null && !arg.trim().equals("")) { 70 | try { 71 | properties.load(new ByteArrayInputStream( 72 | arg.replaceAll(",", "\n").getBytes())); 73 | } catch (IOException e) { 74 | throw new RuntimeException("agent config format is error", e); 75 | } 76 | } 77 | return properties; 78 | } 79 | 80 | // agentmain装载程序 dd 81 | // -Dagent.path=/Users/tommy/git/JCat/jCat-agent/build/libs/jCat-agent-0.1-SNAPSHOT-all.jar 82 | public static void main(String[] args) throws URISyntaxException, IOException { 83 | Properties properties = new Properties(); 84 | for (String arg : args) { 85 | String[] s = arg.split("="); 86 | properties.put(s[0], s[1]); 87 | } 88 | String agentPath = System.getProperty("agent.path", properties.getProperty("agent.path")); 89 | Assert.hasText(agentPath); 90 | AttachService attachService = new AttachService();// 注册至http服务 91 | Collection jvmItems = attachService.jvmList(); 92 | AttachService.JvmItem[] items = jvmItems.toArray(new AttachService.JvmItem[0]); 93 | for (int i = 0; i < items.length; i++) { 94 | System.out.printf("%s %s\n", i, items[i].getName().replaceFirst("\\s.*$", "")); 95 | } 96 | System.out.println("请输入数字进行选择"); 97 | Scanner scanner = new Scanner(System.in); 98 | int index = scanner.nextInt(); 99 | AttachService.JvmItem attach = attachService.attach(items[index].getId(), agentPath, null); 100 | // 装载成功 101 | System.out.printf("装载成功! 访问地址:http://%s:%s/jCat\n", attach.getTargetIp(), attach.getTargetPort()); 102 | } 103 | 104 | public static boolean isDebug() { 105 | return is("debug", false); 106 | } 107 | 108 | public static boolean is(String key, boolean defaultVal) { 109 | return Boolean.parseBoolean(Agent.configs.getProperty(key, Boolean.toString(defaultVal))); 110 | } 111 | 112 | public static String get(String key, String defaultVal) { 113 | return Agent.configs.getProperty(key, defaultVal); 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /JCat-web/src/components/ResourceExplorer.vue: -------------------------------------------------------------------------------- 1 | 52 | 53 | 54 | 149 | 150 | 151 | 180 | -------------------------------------------------------------------------------- /jCat-agent/src/main/java/org/coderead/jcat/common/JadUtil.java: -------------------------------------------------------------------------------- 1 | package org.coderead.jcat.common; 2 | /** 3 | * @Copyright 源码阅读网 http://coderead.cn 4 | */ 5 | 6 | import org.coderead.jcat.Agent; 7 | import org.jd.core.v1.ClassFileToJavaSourceDecompiler; 8 | import org.jd.core.v1.api.loader.Loader; 9 | import org.jd.core.v1.api.loader.LoaderException; 10 | import org.jd.core.v1.api.printer.Printer; 11 | 12 | import java.io.ByteArrayOutputStream; 13 | import java.io.IOException; 14 | import java.io.InputStream; 15 | 16 | /** 17 | * @author 鲁班大叔 18 | * @date 2021 19 | */ 20 | public class JadUtil { 21 | static Loader loader = new Loader() { 22 | @Override 23 | public byte[] load(String internalName) throws LoaderException { 24 | InputStream is = loadClass(internalName); 25 | if (is == null) { 26 | return null; 27 | } else { 28 | try (InputStream in = is; ByteArrayOutputStream out = new ByteArrayOutputStream()) { 29 | byte[] buffer = new byte[1024]; 30 | int read = in.read(buffer); 31 | 32 | while (read > 0) { 33 | out.write(buffer, 0, read); 34 | read = in.read(buffer); 35 | } 36 | 37 | return out.toByteArray(); 38 | } catch (IOException e) { 39 | throw new LoaderException(e); 40 | } 41 | } 42 | } 43 | 44 | @Override 45 | public boolean canLoad(String internalName) { 46 | return loadClass(internalName) != null; 47 | } 48 | 49 | private InputStream loadClass(String internalName) { 50 | InputStream is = this.getClass().getResourceAsStream("/" + internalName.replaceAll("\\.", "/") + ".class"); 51 | if (is == null && Agent.instrumentation != null) { 52 | for (Class allLoadedClass : Agent.instrumentation.getAllLoadedClasses()) { 53 | if (allLoadedClass.getName().equals(internalName)) { 54 | is = allLoadedClass.getResourceAsStream("/" + internalName.replaceAll("\\.", "/") + ".class"); 55 | break; 56 | } 57 | } 58 | } 59 | return is; 60 | } 61 | }; 62 | 63 | 64 | static Printer printer = new Printer() { 65 | protected static final String TAB = " "; 66 | protected static final String NEWLINE = "\n"; 67 | 68 | protected int indentationCount = 0; 69 | protected StringBuilder sb = new StringBuilder(); 70 | 71 | @Override 72 | public String toString() { 73 | return sb.toString(); 74 | } 75 | 76 | @Override 77 | public void start(int maxLineNumber, int majorVersion, int minorVersion) { 78 | } 79 | 80 | @Override 81 | public void end() { 82 | } 83 | 84 | @Override 85 | public void printText(String text) { 86 | sb.append(text); 87 | } 88 | 89 | @Override 90 | public void printNumericConstant(String constant) { 91 | sb.append(constant); 92 | } 93 | 94 | @Override 95 | public void printStringConstant(String constant, String ownerInternalName) { 96 | sb.append(constant); 97 | } 98 | 99 | @Override 100 | public void printKeyword(String keyword) { 101 | sb.append(keyword); 102 | } 103 | 104 | @Override 105 | public void printDeclaration(int type, String internalTypeName, String name, String descriptor) { 106 | sb.append(name); 107 | } 108 | 109 | @Override 110 | public void printReference(int type, String internalTypeName, String name, String descriptor, String ownerInternalName) { 111 | sb.append(name); 112 | } 113 | 114 | @Override 115 | public void indent() { 116 | this.indentationCount++; 117 | } 118 | 119 | @Override 120 | public void unindent() { 121 | this.indentationCount--; 122 | } 123 | 124 | @Override 125 | public void startLine(int lineNumber) { 126 | for (int i = 0; i < indentationCount; i++) sb.append(TAB); 127 | } 128 | 129 | @Override 130 | public void endLine() { 131 | sb.append(NEWLINE); 132 | } 133 | 134 | @Override 135 | public void extraLine(int count) { 136 | while (count-- > 0) sb.append(NEWLINE); 137 | } 138 | 139 | @Override 140 | public void startMarker(int type) { 141 | } 142 | 143 | @Override 144 | public void endMarker(int type) { 145 | } 146 | }; 147 | 148 | public static String decompiler(String className) throws Exception { 149 | ClassFileToJavaSourceDecompiler decompiler = new ClassFileToJavaSourceDecompiler(); 150 | decompiler.decompile(loader, printer, className); 151 | String source = printer.toString(); 152 | return source; 153 | } 154 | 155 | public static String decompiler(String name,byte [] bytes) throws Exception { 156 | ClassFileToJavaSourceDecompiler decompiler = new ClassFileToJavaSourceDecompiler(); 157 | decompiler.decompile(new Loader() { 158 | @Override 159 | public boolean canLoad(String s) { 160 | return name.equals(s); 161 | } 162 | @Override 163 | public byte[] load(String s) throws LoaderException { 164 | return bytes; 165 | } 166 | }, printer, name); 167 | String source = printer.toString(); 168 | return source; 169 | } 170 | } 171 | -------------------------------------------------------------------------------- /jCat-agent/src/main/java/org/coderead/jcat/service/AttachService.java: -------------------------------------------------------------------------------- 1 | package org.coderead.jcat.service; 2 | /** 3 | * @Copyright 源码阅读网 http://coderead.cn 4 | */ 5 | 6 | import com.sun.tools.attach.*; 7 | import lombok.Getter; 8 | import org.coderead.jcat.Agent; 9 | import org.coderead.jcat.common.Assert; 10 | 11 | import java.io.ByteArrayInputStream; 12 | import java.io.File; 13 | import java.io.IOException; 14 | import java.io.Serializable; 15 | import java.lang.management.ManagementFactory; 16 | import java.lang.management.RuntimeMXBean; 17 | import java.util.*; 18 | import java.util.logging.Level; 19 | import java.util.logging.Logger; 20 | import java.util.stream.Collectors; 21 | 22 | /** 23 | * 利用agentmain 装载至目标jvm进程 24 | * 25 | * @author 鲁班大叔 26 | * @date 2024 27 | */ 28 | public class AttachService { 29 | // 获取jvm 列表 30 | Map attachMap = new HashMap<>(); 31 | static final Logger logger = Logger.getLogger(AttachService.class.getName()); 32 | static final String pid; 33 | 34 | static { 35 | RuntimeMXBean runtimeMXBean = ManagementFactory.getRuntimeMXBean(); 36 | pid = runtimeMXBean.getName().split("@")[0]; 37 | } 38 | 39 | public AttachService() { 40 | DefaultHttpServer httpServer = DefaultHttpServer.getInstance(); 41 | } 42 | 43 | public Collection jvmList() { 44 | List list = VirtualMachine.list(); 45 | Map items = list.stream() 46 | .filter(v -> !v.id().equals(pid)) //排除自身 47 | .map(v -> 48 | attachMap.containsKey(v.id()) ? attachMap.get(v.id()) : new JvmItem(v.id(), v.displayName()) 49 | ).collect(Collectors.toMap(s -> s.id, v -> v)); 50 | checkAndCleanJvm(items); 51 | 52 | return items.values(); 53 | } 54 | 55 | 56 | 57 | 58 | // 判断目标虚拟机是否在线 59 | private void checkAndCleanJvm(Map currentJvmMaps) { 60 | Iterator> iterator = attachMap.entrySet().iterator(); 61 | while (iterator.hasNext()) { 62 | Map.Entry next = iterator.next(); 63 | if (!currentJvmMaps.containsKey(next.getKey())) { 64 | logger.info("目标jvm离线:" + next.getValue()); 65 | iterator.remove(); 66 | } 67 | } 68 | } 69 | 70 | /** 71 | * configs 不允许换行,使用(,)逗号分割 72 | * 73 | * @return 74 | */ 75 | public JvmItem attach(String id,String agentPath, String configs) { 76 | Assert.hasText(id, "参数id不能为空"); 77 | // 参数配置 78 | Properties configsPro = new Properties(); 79 | if (configs != null) { 80 | try { 81 | configsPro.load(new ByteArrayInputStream( 82 | configs.replaceAll(",", "\n").getBytes())); 83 | } catch (IOException e) { 84 | throw new RuntimeException(e); 85 | } 86 | } 87 | if (!configsPro.contains("port")) { 88 | configsPro.put("port", getInitPort()); 89 | } 90 | configs = configsPro.entrySet().stream().map(e -> e.getKey() + "=" + e.getValue()).collect(Collectors.joining(",")); 91 | float currentJvmVersion = 0f, targetJvmVersion = 0f; 92 | VirtualMachine vm = null; 93 | VirtualMachineDescriptor virtualMachineDescriptor; 94 | String httpPort; 95 | String warningMessage = null; 96 | 97 | File file = new File(agentPath); 98 | Assert.isTrue(file.exists(),"找不到方件:"+file); 99 | agentPath=file.toString(); 100 | Assert.isTrue(agentPath.endsWith(".jar") || agentPath.endsWith(".lib"),"找不到agent.path"); ; 101 | virtualMachineDescriptor = VirtualMachine.list().stream().filter(v -> v.id().equals(id)).findFirst().get(); 102 | // 1.attach 103 | try { 104 | vm = VirtualMachine.attach(virtualMachineDescriptor); 105 | Properties targetVmProperties = vm.getSystemProperties(); 106 | // 验证jvm版本信息 107 | currentJvmVersion = getJavaVersion(System.getProperties()); 108 | targetJvmVersion = getJavaVersion(targetVmProperties); 109 | if (targetJvmVersion != currentJvmVersion) { 110 | warningMessage = String.format("与目标JVM版本不一至,可能引发agent装载错误,当前JVM%s,目标JVM%s", currentJvmVersion, targetJvmVersion); 111 | logger.warning(warningMessage); 112 | } 113 | } catch (AttachNotSupportedException e) { 114 | throw new IllegalStateException("目标虚拟机不支持Attach", e); 115 | } catch (IOException e) { 116 | throw new IllegalStateException("连接(attach)目标虚拟机失败", e); 117 | } 118 | // 2.loadAgent 119 | try { 120 | vm.loadAgent(agentPath, configs); 121 | } catch (AgentLoadException e) { 122 | if ("0".equals(e.getMessage())) { 123 | // https://stackoverflow.com/a/54454418 124 | warningMessage = String.format("与目标JVM版本不一至,当前JVM%s 目标JVM%s", currentJvmVersion, targetJvmVersion); 125 | logger.log(Level.WARNING, warningMessage, e); 126 | } else { 127 | throw new IllegalStateException("agent装载失败", e); 128 | } 129 | } catch (AgentInitializationException e) { 130 | throw new IllegalStateException("agent初始化失败", e); 131 | } catch (IOException e) { 132 | if (e.getMessage() != null && e.getMessage().contains("Non-numeric value found")) { 133 | warningMessage = String.format("与目标JVM版本不一至,当前JVM%s 目标JVM%s", currentJvmVersion, targetJvmVersion); 134 | logger.log(Level.WARNING, warningMessage, e); 135 | } else { 136 | throw new IllegalStateException("读取目标虚拟机信息失败", e); 137 | } 138 | } 139 | // 3.验证端口是否顺利打开 140 | try { 141 | httpPort = vm.getSystemProperties().getProperty("jcat.agent.httpPort"); 142 | Assert.hasText(httpPort, "agent打开端口失败:未能获取到目标通信端口"); 143 | vm.detach();// 分离端口 144 | } catch (IOException e) { 145 | throw new IllegalStateException("读取目标虚拟机信息失败", e); 146 | } 147 | 148 | // 4.封装返回结果 149 | JvmItem jvmItem = new JvmItem(vm.id(), virtualMachineDescriptor.displayName()); 150 | jvmItem.targetPort = Integer.parseInt(httpPort); 151 | jvmItem.attachTime = System.currentTimeMillis(); 152 | jvmItem.jvmVersion = targetJvmVersion; 153 | jvmItem.warningMessage = warningMessage; 154 | attachMap.put(jvmItem.id, jvmItem); 155 | return jvmItem; 156 | } 157 | 158 | private float getJavaVersion(Properties systemProperties) { 159 | return Float.parseFloat(systemProperties.getProperty("java.specification.version")); 160 | } 161 | 162 | private int getInitPort() { 163 | return attachMap.values().stream().mapToInt(s -> s.targetPort).max().orElse(3426); 164 | } 165 | 166 | @Getter 167 | public static class JvmItem implements Serializable { 168 | String id; 169 | String name; 170 | long attachTime; // 负载时间 171 | int targetPort;// 目标虚拟机通信端口 172 | String targetIp = "127.0.0.1"; 173 | String warningMessage; 174 | float jvmVersion; 175 | 176 | public JvmItem(String id, String name) { 177 | this.id = id; 178 | this.name = name; 179 | } 180 | 181 | @Override 182 | public String toString() { 183 | return "JvmItem{" + 184 | "id='" + id + '\'' + 185 | ", name='" + name + '\'' + 186 | ", attachTime=" + attachTime + 187 | ", targetPort=" + targetPort + 188 | ", targetIp='" + targetIp + '\'' + 189 | '}'; 190 | } 191 | } 192 | } 193 | -------------------------------------------------------------------------------- /jCat-agent/src/main/java/org/coderead/jcat/common/ClassUtil.java: -------------------------------------------------------------------------------- 1 | package org.coderead.jcat.common; 2 | 3 | import arthas.VmTool; 4 | import org.coderead.jcat.service.ConsoleService; 5 | import com.taobao.arthas.common.VmToolUtils; 6 | //import org.objectweb.asm.Type; 7 | 8 | import java.io.File; 9 | import java.io.IOException; 10 | import java.lang.instrument.Instrumentation; 11 | import java.lang.reflect.Field; 12 | import java.lang.reflect.Modifier; 13 | import java.nio.file.Files; 14 | import java.nio.file.Path; 15 | import java.nio.file.StandardCopyOption; 16 | import java.util.Arrays; 17 | import java.util.LinkedHashMap; 18 | import java.util.Map; 19 | import java.util.Objects; 20 | import java.util.concurrent.ConcurrentHashMap; 21 | import java.util.jar.JarFile; 22 | 23 | /** 24 | * @author tommy 25 | * @title: ClassUtil 26 | * @projectName cbtu-web-ide 27 | * @description: TODO 28 | * @date 2020/3/1110:53 AM 29 | */ 30 | public class ClassUtil { 31 | static VmTool vmTool = null; 32 | static final Map> classMetaCache = new ConcurrentHashMap>(); 33 | 34 | /* public static String getMethodSign(Method method) { 35 | return getMethodSign(Type.getMethodDescriptor(method)); 36 | } 37 | 38 | public static String getMethodSign(String methodDescriptor) { 39 | StringBuilder sb = new StringBuilder(); 40 | Type methodType = Type.getMethodType(methodDescriptor); 41 | Type[] argumentTypes = methodType.getArgumentTypes(); 42 | for (int i = 0; i < argumentTypes.length; i++) { 43 | if (i != 0) { 44 | sb.append(","); 45 | } 46 | sb.append(ClassUtil.convertToJdt(argumentTypes[i])); 47 | } 48 | sb.append(","); 49 | sb.append(ClassUtil.convertToJdt(methodType.getReturnType())); 50 | return EncryptUtil.MD5(sb.toString()).substring(0, 5); 51 | } 52 | 53 | public static String convertToJdt(Type type) { 54 | String descriptor = type.getDescriptor(); 55 | if (descriptor.startsWith("[")) { 56 | return descriptor.replaceAll("/", "."); 57 | } 58 | if (descriptor.startsWith("L")) { 59 | return descriptor.substring(1) 60 | .replaceAll(";", "") 61 | .replaceAll("/", "."); 62 | } 63 | 64 | return descriptor; 65 | }*/ 66 | 67 | 68 | public static VmTool getVmTool() { 69 | if (vmTool == null) { 70 | synchronized (VmTool.JNI_LIBRARY_NAME) { 71 | if (vmTool == null) { 72 | try { 73 | Path tempFile = Files.createTempFile(VmTool.JNI_LIBRARY_NAME, null); 74 | Files.copy(Objects.requireNonNull(ConsoleService.class.getResourceAsStream("/lib/" + VmToolUtils.detectLibName())), tempFile, StandardCopyOption.REPLACE_EXISTING); 75 | tempFile.toFile().deleteOnExit();// 应用正常结束时删除 76 | vmTool = VmTool.getInstance(tempFile.toAbsolutePath().toString()); 77 | } catch (IOException e) { 78 | throw new RuntimeException("加载VmTool失败", e); 79 | } 80 | } 81 | } 82 | } 83 | return vmTool; 84 | } 85 | 86 | public static Class loadAdapterClass(Instrumentation instrumentation) { 87 | try { 88 | Class aClass = ClassLoader.getSystemClassLoader().loadClass("org.coderead.jcat.Interceptor"); 89 | Assert.isTrue(aClass.getClassLoader() == null, "Interceptor必须加载至boot加载器中"); 90 | return aClass; 91 | } catch (ClassNotFoundException e) { 92 | // 没有加载继续执行 93 | } 94 | try { 95 | Path tempFile = Files.createTempFile("agentAdapter", ".zip"); 96 | Files.copy(Objects.requireNonNull(ConsoleService.class.getResourceAsStream("/lib/agentAdapter.jar")), tempFile, StandardCopyOption.REPLACE_EXISTING); 97 | File file = tempFile.toFile(); 98 | System.setProperty("jCat.agentAdapter.path", file.getAbsolutePath()); 99 | file.deleteOnExit();// 应用正常结束时删除 100 | instrumentation.appendToBootstrapClassLoaderSearch(new JarFile(file)); 101 | ClassLoader.getSystemClassLoader().getResource("com/cbtu/agent/Interceptor.class"); 102 | return ClassLoader.getSystemClassLoader().loadClass("org.coderead.jcat.Interceptor"); 103 | } catch (IOException | ClassNotFoundException e) { 104 | throw new IllegalStateException("加载agentAdapter.jar至bootClassLoader失败", e); 105 | } 106 | } 107 | 108 | /** 109 | * 获取当前类以及期父类的所有属性 110 | * 111 | * @param c 112 | * @return 113 | */ 114 | public static Map getDeepDeclaredFields(Class c) { 115 | Map classFields = classMetaCache.get(c); 116 | if (classFields != null) { 117 | return classFields; 118 | } 119 | classFields = new LinkedHashMap<>(); 120 | Class curr = c; 121 | while (curr != null) { 122 | final Field[] local = curr.getDeclaredFields(); 123 | for (Field field : local) { 124 | if ((field.getModifiers() & Modifier.STATIC) == 0) { // speed up: do not process static fields. 125 | if ("metaClass".equals(field.getName()) && "groovy.lang.MetaClass".equals(field.getType().getName())) { // Skip Groovy metaClass field if present 126 | continue; 127 | } 128 | if (classFields.containsKey(field.getName())) { 129 | classFields.put(curr.getName() + '.' + field.getName(), field); 130 | } else { 131 | classFields.put(field.getName(), field); 132 | } 133 | } 134 | } 135 | curr = curr.getSuperclass(); 136 | } 137 | 138 | classMetaCache.put(c, classFields); 139 | return classFields; 140 | } 141 | 142 | 143 | public static boolean isWrapperType(Class cla) { 144 | return cla.equals(Object.class) || 145 | cla.equals(Integer.class) || 146 | cla.equals(Short.class) || 147 | cla.equals(Long.class) || 148 | cla.equals(Double.class) || 149 | cla.equals(Float.class) || 150 | cla.equals(Boolean.class) || 151 | cla.equals(Byte.class) || 152 | cla.equals(Character.class); 153 | } 154 | 155 | static String[] groovyPackages = new String[]{ 156 | "groovy.", 157 | "groovyjarjarantlr.", 158 | "groovyjarjarasm.asm.", 159 | "groovyjarjarcommonscli.", 160 | "org.apache.groovy.", 161 | "org.codehaus.groovy.", 162 | }; 163 | 164 | public static boolean isGroovyClass(String className) { 165 | return Arrays.stream(groovyPackages).anyMatch(p -> className.startsWith(p) || className.startsWith(p.replaceAll("\\.", "/"))); 166 | } 167 | 168 | /* *//** 169 | * 解析方法字符串,以转换成Method 170 | * 171 | * @param methodInfo 类名:方法名:方法签名 172 | * @param loader 该的类加载器 173 | * @return 174 | *//* 175 | public static Method parseMethodInfo(String methodInfo, ClassLoader loader) { 176 | // 类名:方法名:方法签名 177 | String[] splits = methodInfo.split(":"); 178 | Method method; 179 | try { 180 | Class aClass = Class.forName(splits[0], false, loader); 181 | method = Arrays.stream(aClass.getDeclaredMethods()) 182 | .filter(m -> m.getName().equals(splits[1]) 183 | && Type.getMethodDescriptor(m).equals(splits[2])) 184 | .findFirst().orElse(null); 185 | Assert.notNull(method, "解析method失败,找不到类中的方法:" + methodInfo); 186 | } catch (ClassNotFoundException e) { 187 | throw new IllegalArgumentException("解析method失败,找不到类:" + splits[0]); 188 | } 189 | return method; 190 | }*/ 191 | 192 | 193 | 194 | 195 | 196 | } 197 | -------------------------------------------------------------------------------- /JCat-web/src/components/GroovySyntax.js: -------------------------------------------------------------------------------- 1 | // Groovy 语法提示服务 2 | import { windows } from "codemirror/src/util/browser"; 3 | 4 | import CodeMirror, { commands } from 'codemirror' 5 | import groovyBeautify from "groovy-beautify"; 6 | 7 | export class GroovySyntax { 8 | 9 | constructor(cm, consoleService, getSession) { 10 | if (!(getSession instanceof Function)) { 11 | throw new Error('getSession必须是函数') 12 | } 13 | this.cm = cm;// 编辑器 14 | this.getSession = getSession; 15 | this.consoleService = consoleService; 16 | // 添加快捷键 17 | // this.keyMap = adapterKeyMap({ 18 | // "Shift-Cmd-F": (cm) => this.doFormat(), 19 | // "Shift-Alt-F": (cm) => this.doFormat(), 20 | // "Ctrl-.": (cm) => this.showHint(), 21 | // "Cmd-/": (cm) => this.doComment(), 22 | // "Shift-Cmd-/": (cm) => this.doBlockComment(), 23 | // }, true); 24 | 25 | // document.addEventListener('keydown', function (event) { 26 | // if (event.ctrlKey && event.shiftKey && (event.key == 'F' || event.key == 'K')) { 27 | // console.log('触发f快捷键') 28 | // event.preventDefault(); 29 | // // 你的保存操作代码 30 | // } 31 | // if (event.ctrlKey && event.key == '.') { 32 | // console.log('触发.快捷键') 33 | // event.preventDefault(); 34 | // // 你的保存操作代码 35 | // } 36 | // }); 37 | // this.cm.addKeyMap(this.keyMap, false);// 添加快捷键 38 | this.errors = []; //编译错误 39 | } 40 | doFormat() { 41 | // TODO 暂未实现 42 | let cursor = this.cm.getCursor(); 43 | this.cm.setValue(groovyBeautify(this.cm.getValue())); 44 | this.cm.setCursor(cursor); 45 | } 46 | // 行注释 47 | doComment() { 48 | this.cm.toggleComment(); 49 | } 50 | // 快注释 51 | doBlockComment() { 52 | this.cm.getDoc().listSelections().forEach(s => { 53 | if (!this.cm.uncomment(s.from(), s.to(), { fullLines: false })) { 54 | this.cm.blockComment(s.from(), s.to(), { fullLines: false }); 55 | } 56 | }); 57 | } 58 | 59 | // 通过.获取提示项 60 | showHintByInput() { 61 | this._showHintByInput(); 62 | return CodeMirror.Pass; 63 | } 64 | async _showHintByInput() { 65 | //1. 判断提示条件 66 | // 2.编译 67 | let errors = await this.consoleService.analysis(this.getSession().id, this.cm.getValue()); 68 | this.errors = errors; 69 | this.cm.performLint(); // 刷新提示 70 | if (this.errors.length > 0) { 71 | return; 72 | } 73 | // 3.基于光标位置获取提示项 74 | let cursor = this.cm.getCursor(); 75 | let items = await this.consoleService.completion(this.getSession().id, [cursor.line + 1, cursor.ch], 200) 76 | if (items.length == 0) 77 | return; 78 | 79 | // 4.展示结果 80 | this.cm.showHint({ hint: (editor, options) => this.completionHints(editor, options, items) }); 81 | } 82 | 83 | 84 | showHint() { 85 | let editor = this.cm; 86 | let cur = editor.getCursor(); 87 | let curLine = editor.getLine(cur.line); 88 | let end = cur.ch, start = end; 89 | while (start && /[\w$]+/.test(curLine.charAt(start - 1))) --start; 90 | var keyword = start != end ? curLine.slice(start, end) : ""; 91 | if (!keyword) return; 92 | //基于关键字查找提示项 并显示列表 93 | this.consoleService.completion(this.getSession().id, keyword, 200).then(items => { 94 | if (items.length == 0) return; 95 | this.cm.showHint({ 96 | hint: (editor, options) => this.completionHints(editor, options, items), 97 | }); 98 | }) 99 | 100 | } 101 | // 获取完成提示项 102 | completionHints(editor, options, items) { 103 | var cur = editor.getCursor(), curLine = editor.getLine(cur.line); 104 | var end = cur.ch, start = end; 105 | while (start && /[\w$]+/.test(curLine.charAt(start - 1))) --start; 106 | var curWord = start != end ? curLine.slice(start, end) : ""; 107 | 108 | let list = items 109 | .filter(i => { 110 | if (!curWord) { 111 | i.showText = null; 112 | return true; 113 | } 114 | let matchs = doFilter(curWord, i.filterText ? i.filterText : i.label, 'completionMark'); 115 | if (matchs) { 116 | i.showText = matchs.matchText; 117 | i.score = matchs.score; 118 | return true; 119 | } 120 | return false;; 121 | }) 122 | .sort((a1, a2) => a2.score - a1.score) 123 | .map(i => { 124 | let render = (elm, self, data) => this.hintsRender(elm, i); 125 | let hint = (cm, self, data) => this.resolveHints(cm, i, curWord); 126 | return { render, hint }; 127 | }); 128 | 129 | return { 130 | list, 131 | from: { line: cur.line, ch: start }, 132 | to: { line: cur.line, ch: end }, 133 | }; 134 | } 135 | // 样式渲染 136 | hintsRender(elm, item) { 137 | let html = ""; 138 | let items = item.label.split(":"); 139 | html = "" + (item.showText ? item.showText : items[0]) + ""; 140 | if (items.length > 1) 141 | html += "" + subStr(items[1], 50) + ""; 142 | if (items.length > 2) 143 | html += "" + items[2] + ""; 144 | elm.innerHTML = html; 145 | } 146 | // 使用完成项 147 | resolveHints(cm, item, filterToken) { 148 | //.reverse() 149 | var cur = cm.getCursor(); 150 | let newText = item.insertText; 151 | let oldValue = cm.getDoc().getValue(); 152 | 153 | cm.getDoc().replaceRange(newText, { line: cur.line, ch: cur.ch - filterToken.length }, cur); 154 | // 插入tooltip 提示 155 | if (item.tipsText) { 156 | cm.getDoc().markText({ line: cur.line, ch: cur.ch - filterToken.length }, cm.getCursor(), { className: "mark-tooltip", message: item.tipsText }); 157 | this.cm.performLint();// 刷新提示 158 | } 159 | if (item.insertImportText && !oldValue.includes(item.insertImportText)) { 160 | cm.getDoc().replaceRange(item.insertImportText + "\n", { line: 0, ch: 0 }, { line: 0, ch: 0 }); 161 | } 162 | // 163 | if (newText.endsWith(")") || newText.endsWith("}")) { 164 | cur = cm.getCursor(); 165 | cur.ch -= 1; 166 | this.cm.setCursor(cur); 167 | } 168 | 169 | } 170 | 171 | } 172 | 173 | 174 | // windows 下所有Cmd替换成Ctrl 175 | export function adapterKeyMap(keyMap) { 176 | if (windows) { 177 | for (const k of Object.getOwnPropertyNames(keyMap)) { 178 | if (k.indexOf("Cmd") > -1) { 179 | let fun = keyMap[k]; 180 | delete (keyMap[k]); 181 | keyMap[k.replace("Cmd", "Ctrl")] = fun; 182 | } 183 | } 184 | } 185 | return keyMap; 186 | } 187 | 188 | 189 | function doFilter(queryText, itemText, mark, option) { 190 | queryText = queryText.toLocaleLowerCase(); 191 | 192 | // 首字母必须匹配 193 | if (option && option.first && itemText.charAt(0).toLocaleLowerCase() != queryText.charAt(0)) { 194 | return false; 195 | } 196 | let j = 0; 197 | let matchText = ""; 198 | let score = 50; 199 | // 200 | for (let i = 0; i < itemText.length; i++) { 201 | if (itemText.charAt(i).toLocaleLowerCase() == queryText.charAt(j)) { 202 | j++; 203 | matchText += 204 | "" + itemText.charAt(i) + ""; 205 | score += 20 - i * 2; 206 | } else { 207 | score--; 208 | matchText += itemText.charAt(i); 209 | } 210 | } 211 | if (j != queryText.length) { 212 | return false; //匹配失败 213 | } 214 | return { matchText, score };// 匹配成功结果 215 | } 216 | 217 | function subStr(text, maxLength) { 218 | return text.length > maxLength ? (text.substring(0, maxLength) + "...") : text; 219 | } 220 | 221 | export default { 222 | GroovySyntax, 223 | adapterKeyMap 224 | } 225 | 226 | function foundlints(text, options) { 227 | let errors = options.getLint(text); 228 | let errorLint = errors.map(l => { 229 | let r = { 230 | severity: l.level, 231 | from: CodeMirror.Pos(l.range[0] - 1, l.range[1] - 2), 232 | to: CodeMirror.Pos(l.range[2] - 1, l.range[3] - 1), 233 | message: l.message 234 | } 235 | return r; 236 | }) 237 | 238 | let cm = options.getCm(); // 获取实例 239 | let tooltipLints = !cm ? [] : cm.getAllMarks().filter(m => m.className == 'mark-tooltip').map(t => { 240 | let p=t.find(); 241 | let r = { 242 | severity: "tooltip",// 或者 warning 243 | from: p.from, 244 | to: p.to, 245 | message: t.message 246 | } 247 | return r; 248 | }) 249 | 250 | return [...errorLint, ...tooltipLints]; // 合并数组返回 251 | } 252 | // 异常提示 253 | CodeMirror.registerHelper("lint", "text/x-java", foundlints); -------------------------------------------------------------------------------- /jCat-agent/src/main/java/org/coderead/jcat/service/DefaultHttpServer.java: -------------------------------------------------------------------------------- 1 | package org.coderead.jcat.service; 2 | /** 3 | * @Copyright 源码阅读网 http://coderead.cn 4 | */ 5 | 6 | 7 | import org.coderead.jcat.Agent; 8 | import org.coderead.jcat.common.Assert; 9 | import org.coderead.jcat.common.IOUtils; 10 | import org.coderead.jcat.common.JsonUtil; 11 | import org.coderead.jcat.common.Maps; 12 | import com.sun.net.httpserver.HttpExchange; 13 | import com.sun.net.httpserver.HttpHandler; 14 | import com.sun.net.httpserver.HttpServer; 15 | 16 | import java.io.*; 17 | import java.lang.management.ManagementFactory; 18 | import java.lang.management.RuntimeMXBean; 19 | import java.net.*; 20 | import java.nio.charset.StandardCharsets; 21 | import java.util.HashMap; 22 | import java.util.Map; 23 | import java.util.function.Function; 24 | import java.util.logging.Level; 25 | import java.util.logging.Logger; 26 | 27 | /** 28 | * @author 鲁班大叔 29 | * @date 2024 30 | */ 31 | public class DefaultHttpServer { 32 | static final Logger logger = Logger.getLogger(DefaultHttpServer.class.getName()); 33 | 34 | static { 35 | // TODO 通过参数控制强制agent日志输出到控制台 36 | /*logger.setUseParentHandlers(false);// 禁用桥接到slf4j 37 | logger.addHandler(new ConsoleHandler());// 直接输出到控制台*/ 38 | } 39 | 40 | private static DefaultHttpServer instance; 41 | private HttpServer server; 42 | private String context = ""; 43 | private String apiPrefix = "/api"; 44 | 45 | public static DefaultHttpServer getInstance() { 46 | if (instance == null) { 47 | instance = new DefaultHttpServer(); 48 | } 49 | return instance; 50 | } 51 | 52 | 53 | private DefaultHttpServer() { 54 | registeGet("/info", this::getInfo); 55 | } 56 | 57 | Map getInfo(Map urlParams) { 58 | HashMap map = new HashMap<>(); 59 | map.put("port", String.valueOf(server.getAddress().getPort())); 60 | map.put("ip", String.valueOf(server.getAddress().getHostString())); 61 | RuntimeMXBean runtimeMXBean = ManagementFactory.getRuntimeMXBean(); 62 | map.put("processName", runtimeMXBean.getName()); 63 | map.put("pid", runtimeMXBean.getName().split("@")[0]); 64 | map.put("context", context);//attachServer ,agentServer 65 | return map; 66 | } 67 | 68 | public int start(int positivePort, String context) throws IOException { 69 | if (server != null) { 70 | throw new IllegalStateException("The service has started"); 71 | } 72 | Assert.hasText(context); 73 | Assert.isTrue(context.startsWith("/")); 74 | this.context = context; 75 | int port = positivePort == -1 ? 76 | Integer.parseInt(Agent.configs.getProperty("port", "3426")) : 77 | positivePort; 78 | 79 | server = HttpServer.create(); 80 | int retry = positivePort > 0 ? 1 : 5; //自动绑定尝试5次 81 | for (int i = 0; i < retry; i++) { 82 | try { 83 | server.bind(new InetSocketAddress(port), 0); 84 | System.setProperty("jcat.agent.httpPort", String.valueOf(port)); 85 | break; 86 | } catch (BindException e) { 87 | if (i == retry - 1) { 88 | server = null; 89 | throw new IllegalStateException("jcat启动失败,无法打开端口:" + port, e); 90 | } 91 | port += 1; 92 | } 93 | } 94 | server.start(); 95 | logger.info("JCat已启动,访问地址 http://127.0.0.1:" + port+"/jCat"); 96 | server.createContext("/", new DefaultHandler()); 97 | // server.setExecutor(); TODO 是否设置守护进程? 程序结束后服务停止 98 | return port; 99 | } 100 | 101 | public boolean isStart() { 102 | return server != null; 103 | } 104 | 105 | private Map, Object>> actions = new HashMap<>(); 106 | 107 | // // 108 | public void registeGet(String path, Function, Object> function) { 109 | actions.put(path, function); 110 | } 111 | 112 | 113 | public static Map getUrlParams(URI uri) { 114 | Map map = new HashMap<>(); 115 | if (uri.getRawQuery() == null || uri.getRawQuery().trim().equals("")) { 116 | return map; 117 | } 118 | String[] params = uri.getRawQuery().split("&"); 119 | for (String param : params) { 120 | String[] p = param.split("="); 121 | if (p.length == 1) { 122 | map.put(p[0], null); 123 | continue; 124 | } 125 | String key = p[0]; 126 | String value = p[1]; 127 | try { 128 | value = URLDecoder.decode(value, "UTF-8"); 129 | } catch (UnsupportedEncodingException e) { 130 | throw new RuntimeException(e); 131 | } 132 | if (map.containsKey(key)) { // 重复键换行切割 133 | map.put(key, map.get(key) + "\r\n" + value); 134 | } else { 135 | map.put(p[0], value); 136 | } 137 | } 138 | return map; 139 | 140 | } 141 | 142 | 143 | class DefaultHandler implements HttpHandler { 144 | @Override 145 | public void handle(HttpExchange exchange) throws IOException { 146 | try { 147 | doHandle(exchange); 148 | } catch (Throwable e) { 149 | logger.log(Level.SEVERE, "", e); 150 | } 151 | } 152 | 153 | void doHandle(HttpExchange exchange) throws IOException { 154 | if (exchange.getRequestMethod().equals("OPTIONS")) {// 允许CORS 跨域 155 | // exchange.getResponseHeaders().set("Access-Control-Allow-Origin", "*"); 156 | // exchange.getResponseHeaders().set("Access-Control-Allow-Headers", "Content-Type, Authorization"); 157 | writeResponse(200, "", exchange); 158 | return; 159 | } 160 | String responseMsg = null; 161 | int responseCode = 200; 162 | try { 163 | String path = exchange.getRequestURI().getPath(); 164 | if (!path.startsWith(context)) { 165 | writeResponse(404, "找不到路径", exchange); 166 | return; 167 | } 168 | if (!path.startsWith((context + apiPrefix))) {// 静态资源 169 | doStaticHandle(exchange); 170 | return; 171 | } 172 | String realPath = path.substring((context+apiPrefix).length()); 173 | if (!actions.containsKey(realPath)) { 174 | writeResponse(404, "找不到路径", exchange); 175 | return; 176 | } 177 | Function, Object> function = actions.get(realPath); 178 | Map urlParams = getUrlParams(exchange.getRequestURI()); 179 | Object result = function.apply(urlParams); 180 | responseMsg = result != null ? JsonUtil.toJson(result) : "{}"; 181 | } catch (Throwable e) { 182 | responseCode = 500; 183 | responseMsg = JsonUtil.toJson(Maps.to("message", e.getMessage())); 184 | logger.log(Level.SEVERE, "服务异常:" + exchange.getRequestURI(), e); 185 | } 186 | writeResponse(responseCode, responseMsg, exchange); 187 | } 188 | 189 | // 处理静态资源 190 | private void doStaticHandle(HttpExchange exchange) throws IOException { 191 | String path = exchange.getRequestURI().getPath(); 192 | String resourcePath = path.substring(context.length()); 193 | //jCat/ 194 | if (resourcePath.equals("/") || resourcePath.equals("")) { 195 | resourcePath = "/index.html"; 196 | } 197 | InputStream stream = getClass().getResourceAsStream("/web" + resourcePath); 198 | if(stream==null){ 199 | writeResponse(404,"找不到资源",exchange); 200 | return; 201 | } 202 | exchange.getResponseBody(); 203 | byte[] bytes = IOUtils.readFully(stream, -1, false); 204 | exchange.sendResponseHeaders(200, bytes.length); 205 | exchange.getResponseBody().write(bytes); 206 | stream.close(); 207 | exchange.getResponseBody().close(); 208 | } 209 | 210 | public int copyIo(InputStream in, OutputStream out) throws IOException { 211 | byte[] buffer = new byte[1024]; 212 | int len; 213 | int size = 0; 214 | while ((len = in.read(buffer)) != -1) { 215 | size += len; 216 | out.write(buffer, 0, len); 217 | } 218 | return size; 219 | } 220 | 221 | private void writeResponse(int responseCode, String content, HttpExchange exchange) throws IOException { 222 | exchange.getResponseHeaders().set("Content-Type", "application/json; charset=utf-8"); 223 | exchange.getResponseHeaders().set("Access-Control-Allow-Origin", "*"); 224 | exchange.getResponseHeaders().set("Access-Control-Allow-Headers", "Content-Type, Authorization"); 225 | byte[] bytes = content.getBytes(StandardCharsets.UTF_8); 226 | exchange.sendResponseHeaders(responseCode, bytes.length); 227 | // 发送响应内容 228 | OutputStream outputStream = exchange.getResponseBody(); 229 | outputStream.write(bytes); 230 | outputStream.close(); 231 | } 232 | } 233 | 234 | // String toJson(Object obj) { 235 | // WriteOptions options = new WriteOptionsBuilder().showTypeInfoNever().prettyPrint(true).build(); 236 | // return JsonIo.toJson(obj, options); 237 | // } 238 | } 239 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /jCat-agent/src/main/java/org/coderead/jcat/common/UnsafeUtil.java: -------------------------------------------------------------------------------- 1 | package org.coderead.jcat.common; 2 | 3 | import sun.misc.Unsafe; 4 | 5 | import java.lang.reflect.Field; 6 | import java.util.Arrays; 7 | import java.util.List; 8 | import java.util.Map; 9 | 10 | /** 11 | * @Discription: 强制读取和操作对象属性工具类 12 | * @Author Zaki Chen 13 | * @date 2019/9/30 14:15 14 | **/ 15 | public class UnsafeUtil { 16 | public final static Unsafe unsafe; 17 | 18 | static { 19 | try { 20 | Field theUnsafeField = Unsafe.class.getDeclaredField("theUnsafe"); 21 | theUnsafeField.setAccessible(true); 22 | unsafe = (Unsafe) theUnsafeField.get(null); 23 | } catch (NoSuchFieldException | IllegalAccessException e) { 24 | throw new RuntimeException("无法获取unsafe对象", e); 25 | } 26 | } 27 | 28 | /** 29 | * @param field 30 | * @return 31 | */ 32 | public static long toAddress(Field field) { 33 | long offset = unsafe.objectFieldOffset(field); 34 | Type type = getType(field); 35 | return offset * 10 + type.valueIndex(); 36 | } 37 | 38 | public static Object getValue(Object object, Field field) throws NoSuchFieldException { 39 | long offset = unsafe.objectFieldOffset(field); 40 | Type type = getType(field); 41 | return getValue(object, offset, type); 42 | } 43 | 44 | /** 45 | * 取对象数据 46 | * 47 | * @param object 对象:可以是一个实例对象,也可以是一个类对象 48 | * @param offset 内存偏移地址,如果是数组就是其对应坐标 49 | * @param type 返回数据的类型 50 | * @return 51 | */ 52 | public static Object getValue(Object object, long offset, Type type) { 53 | if (object.getClass().getComponentType() != null) {// 数组对象 54 | return getArrayValue(object, (int) offset); 55 | } else if (object instanceof List) { 56 | return ((List) object).get((int) offset); 57 | } else if (object instanceof Map) { 58 | return ((Map) object).entrySet().stream().filter(s -> System.identityHashCode(s.getKey()) == offset).findFirst().orElseThrow(() -> new IllegalArgumentException("找不到entry" + offset)); 59 | } else if (object instanceof Map.Entry) { 60 | Map.Entry entry = (Map.Entry) object; 61 | return offset == 0 ? entry.getKey() : entry.getValue(); 62 | } 63 | switch (type) { 64 | case Char: 65 | return unsafe.getChar(object, offset); 66 | case Byte: 67 | return unsafe.getByte(object, offset); 68 | case Short: 69 | return unsafe.getShort(object, offset); 70 | case Integer: 71 | return unsafe.getInt(object, offset); 72 | case Double: 73 | return unsafe.getDouble(object, offset); 74 | case Float: 75 | return unsafe.getFloat(object, offset); 76 | case Long: 77 | return unsafe.getLong(object, offset); 78 | case Boolean: 79 | return unsafe.getBoolean(object, offset); 80 | default: 81 | return unsafe.getObject(object, offset); 82 | } 83 | } 84 | 85 | public static Object[] toArray(Object object) { 86 | Assert.notNull(object, "object不能为空"); 87 | Assert.notNull(object.getClass().getComponentType(), "object必须为数组类型"); 88 | if (object instanceof int[]) { 89 | return Arrays.stream((int[]) object).boxed().toArray(Integer[]::new); 90 | } else if (object instanceof short[]) { 91 | short[] shortArray = (short[]) object; 92 | Short[] wrapperArray = new Short[shortArray.length]; 93 | for (int i = 0; i < shortArray.length; i++) { 94 | wrapperArray[i] = shortArray[i]; 95 | } 96 | return wrapperArray; 97 | } else if (object instanceof boolean[]) { 98 | boolean[] boolArray = (boolean[]) object; 99 | Boolean[] wrapperArray = new Boolean[boolArray.length]; 100 | for (int i = 0; i < boolArray.length; i++) { 101 | wrapperArray[i] = boolArray[i]; 102 | } 103 | return wrapperArray; 104 | } else if (object instanceof double[]) { 105 | return Arrays.stream((double[]) object).boxed().toArray(Double[]::new); 106 | } else if (object instanceof long[]) { 107 | return Arrays.stream((long[]) object).boxed().toArray(Long[]::new); 108 | } else if (object instanceof char[]) { 109 | char[] charArray = (char[]) object; 110 | Character[] wrapperArray = new Character[charArray.length]; 111 | for (int i = 0; i < charArray.length; i++) { 112 | wrapperArray[i] = charArray[i]; 113 | } 114 | return wrapperArray; 115 | } else if (object instanceof float[]) { 116 | float[] floatArray = (float[]) object; 117 | Float[] wrapperArray = new Float[floatArray.length]; 118 | for (int i = 0; i < floatArray.length; i++) { 119 | wrapperArray[i] = floatArray[i]; 120 | } 121 | return wrapperArray; 122 | } else if (object instanceof byte[]) { 123 | byte[] byteArray = (byte[]) object; 124 | Byte[] wrapperArray = new Byte[byteArray.length]; 125 | for (int i = 0; i < byteArray.length; i++) { 126 | wrapperArray[i] = byteArray[i]; 127 | } 128 | return wrapperArray; 129 | } 130 | return (Object[]) object; 131 | } 132 | 133 | 134 | public static void main(String[] args) { 135 | Object[] array = Arrays.stream(new int[]{1, 2, 3}).boxed().toArray(); 136 | System.out.println(array); 137 | } 138 | 139 | public static Object getArrayValue(Object object, int index) { 140 | if (object instanceof byte[]) { 141 | return ((byte[]) object)[index]; 142 | } else if (object instanceof int[]) { 143 | return ((int[]) object)[index]; 144 | } else if (object instanceof short[]) { 145 | return ((short[]) object)[index]; 146 | } else if (object instanceof float[]) { 147 | return ((float[]) object)[index]; 148 | } else if (object instanceof boolean[]) { 149 | return ((boolean[]) object)[index]; 150 | } else if (object instanceof double[]) { 151 | return ((double[]) object)[index]; 152 | } else if (object instanceof long[]) { 153 | return ((long[]) object)[index]; 154 | } else if (object instanceof char[]) { 155 | return ((char[]) object)[index]; 156 | } else if (object instanceof Object[]) { 157 | return ((Object[]) object)[index]; 158 | } else { 159 | throw new IllegalArgumentException("对象并非数组"); 160 | } 161 | } 162 | 163 | public static int getArrayLength(Object object) { 164 | if (object instanceof byte[]) { 165 | return ((byte[]) object).length; 166 | } else if (object instanceof int[]) { 167 | return ((int[]) object).length; 168 | } else if (object instanceof short[]) { 169 | return ((short[]) object).length; 170 | } else if (object instanceof float[]) { 171 | return ((float[]) object).length; 172 | } else if (object instanceof boolean[]) { 173 | return ((boolean[]) object).length; 174 | } else if (object instanceof double[]) { 175 | return ((double[]) object).length; 176 | } else if (object instanceof long[]) { 177 | return ((long[]) object).length; 178 | } else if (object instanceof char[]) { 179 | return ((char[]) object).length; 180 | } else if (object instanceof Object[]) { 181 | return ((Object[]) object).length; 182 | } else { 183 | throw new IllegalArgumentException("对象并非数组"); 184 | } 185 | } 186 | 187 | 188 | /** 189 | * 深度访问子孙节点信息 190 | * 191 | * @param root 根节点对象 192 | * @param address [offset1,type1,offset2,type2,] 其中数组offset就是其访问下标 193 | * @return 194 | */ 195 | public static Object getChildValue(Object root, long[] address) { 196 | Object parent = root; 197 | for (int i = 0; i < address.length; i++) { 198 | Type type = Type.indexOf((int) Math.abs(address[i] % 10)); 199 | parent = getValue(parent, address[i] / 10, type); 200 | } 201 | return parent; 202 | } 203 | 204 | 205 | /** 206 | * 获取对象的类变量属性值 207 | * 208 | * @param clazz Class对象 209 | * @param propertyName 对象类声明的属性名 210 | * @return 211 | * @throws NoSuchFieldException 212 | */ 213 | public static Object getStaticValue(Class clazz, String propertyName) throws NoSuchFieldException { 214 | Field field = clazz.getDeclaredField(propertyName); 215 | long offset = unsafe.staticFieldOffset(field); 216 | return getValue(clazz, offset, getType(field)); 217 | } 218 | 219 | /** 220 | * 更新对象实例属性的值 221 | * 222 | * @param object 实例对象 223 | * @param propertyName 实例属性名 非static关键字声明的属性 224 | * @param val 要修改的值 225 | * @return 226 | */ 227 | public static void setValue(Object object, String propertyName, Object val) throws NoSuchFieldException { 228 | Field propNameField = object.getClass().getDeclaredField(propertyName); 229 | long offset = unsafe.objectFieldOffset(propNameField); 230 | Type dataType = getType(propNameField); 231 | setValue(object, offset, val, dataType); 232 | 233 | } 234 | 235 | /** 236 | * 修改类属性变量的值 237 | * 238 | * @param clazz 类 239 | * @param propertyName 类属性 static关键字声明的属性 240 | * @param val 值 241 | * @throws NoSuchFieldException 242 | */ 243 | public static void setStaticValue(Class clazz, String propertyName, Object val) throws NoSuchFieldException { 244 | Field propNameField = clazz.getDeclaredField(propertyName); 245 | long offset = unsafe.staticFieldOffset(propNameField); 246 | Type dataType = getType(propNameField); 247 | setValue(clazz, offset, val, dataType); 248 | } 249 | 250 | 251 | /** 252 | * 数据值类型枚举 253 | */ 254 | public enum Type { 255 | Object, 256 | Byte, 257 | Char, 258 | Boolean, 259 | Short, 260 | Integer, 261 | Double, 262 | Float, 263 | Long; // Collection 264 | 265 | public static Type indexOf(int index) { 266 | return Type.values()[index]; 267 | } 268 | 269 | public int valueIndex() { 270 | Type[] values = Type.values(); 271 | for (int i = 0; i < values.length; i++) { 272 | if (values[i] == this) { 273 | return i; 274 | } 275 | } 276 | throw new IllegalArgumentException(); 277 | } 278 | } 279 | 280 | public static Type getType(Field field) { 281 | Class type = field.getType(); 282 | 283 | if (type.equals(Character.TYPE)) { 284 | return Type.Char; 285 | } else if (type.equals(Byte.TYPE)) { 286 | return Type.Byte; 287 | } else if (type.equals(Short.TYPE)) { 288 | return Type.Short; 289 | } else if (type.equals(Integer.TYPE)) { 290 | return Type.Integer; 291 | } else if (type.equals(Double.TYPE)) { 292 | return Type.Double; 293 | } else if (type.equals(Float.TYPE)) { 294 | return Type.Float; 295 | } else if (type.equals(Long.TYPE)) { 296 | return Type.Long; 297 | } else if (type.equals(Boolean.TYPE)) { 298 | return Type.Boolean; 299 | }/* else if (type.getComponentType() != null) { // 数组类 300 | return Type.Array; 301 | }*/ else { 302 | return Type.Object; 303 | } 304 | } 305 | 306 | 307 | /** 308 | * 放对象数据 309 | * 310 | * @param object 对象:可以是一个实例对象,也可以是一个类对象 311 | * @param offset 内存偏移地址 312 | * @param val 修改后的值 313 | * @param type 被修改属性的声明类型 314 | */ 315 | private static void setValue(Object object, long offset, Object val, Type type) { 316 | switch (type) { 317 | case Char: 318 | unsafe.putCharVolatile(object, offset, (Character) val); 319 | break; 320 | case Byte: 321 | unsafe.putByteVolatile(object, offset, (Byte) val); 322 | break; 323 | case Integer: 324 | unsafe.putIntVolatile(object, offset, (Integer) val); 325 | break; 326 | case Double: 327 | unsafe.putDoubleVolatile(object, offset, (Double) val); 328 | break; 329 | case Float: 330 | unsafe.putFloatVolatile(object, offset, (Float) val); 331 | break; 332 | case Long: 333 | unsafe.putLongVolatile(object, offset, (Long) val); 334 | break; 335 | case Boolean: 336 | unsafe.putBooleanVolatile(object, offset, (Boolean) val); 337 | break; 338 | default: 339 | unsafe.putObjectVolatile(object, offset, val); 340 | } 341 | } 342 | 343 | } -------------------------------------------------------------------------------- /jCat-agent/src/main/java/org/coderead/jcat/common/Assert.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2002-2013 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 | * use this file except in compliance with the License. You may obtain a copy of 6 | * the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations under 14 | * the License. 15 | */ 16 | 17 | package org.coderead.jcat.common; 18 | 19 | /** 20 | * Assertion utility class that assists in validating arguments. 21 | * Useful for identifying programmer errors early and clearly at runtime. 22 | *

23 | *

For example, if the contract of a public method states it does not 24 | * allow {@code null} arguments, Assert can be used to validate that 25 | * contract. Doing this clearly indicates a contract violation when it 26 | * occurs and protects the class's invariants. 27 | *

28 | *

Typically used to validate method arguments rather than configuration 29 | * properties, to check for cases that are usually programmer errors rather than 30 | * configuration errors. In contrast to LOCAL_CONFIG initialization code, there is 31 | * usally no point in falling back to defaults in such methods. 32 | *

33 | *

This class is similar to JUnit's assertion library. If an argument value is 34 | * deemed invalid, an {@link IllegalArgumentException} is thrown (typically). 35 | * For example: 36 | *

37 | *

 38 |  * Assert.notNull(clazz, "The class must not be null");
 39 |  * Assert.isTrue(i > 0, "The value must be greater than zero");
40 | *

41 | * Mainly for internal use within the framework; consider Jakarta's Commons Lang 42 | * >= 2.0 for a more comprehensive suite of assertion utilities. 43 | * 44 | * @author Keith Donald 45 | * @author Juergen Hoeller 46 | * @author Colin Sampaleanu 47 | * @author Rob Harrop 48 | * @since 1.1.2 49 | */ 50 | public abstract class Assert { 51 | 52 | /** 53 | * Assert a boolean expression, throwing {@code IllegalArgumentException} 54 | * if the test result is {@code false}. 55 | *

Assert.isTrue(i > 0, "The value must be greater than zero");
56 | * 57 | * @param expression a boolean expression 58 | * @param message the exception message to use if the assertion fails 59 | * @throws IllegalArgumentException if expression is {@code false} 60 | */ 61 | public static void isTrue(boolean expression, String message) { 62 | if (!expression) { 63 | throw new IllegalArgumentException(message); 64 | } 65 | } 66 | 67 | /** 68 | * Assert a boolean expression, throwing {@code IllegalArgumentException} 69 | * if the test result is {@code false}. 70 | *
Assert.isTrue(i > 0);
71 | * 72 | * @param expression a boolean expression 73 | * @throws IllegalArgumentException if expression is {@code false} 74 | */ 75 | public static void isTrue(boolean expression) { 76 | isTrue(expression, "[Assertion failed] - this expression must be true"); 77 | } 78 | 79 | /** 80 | * Assert that an object is {@code null} . 81 | *
Assert.isNull(value, "The value must be null");
82 | * 83 | * @param object the object to check 84 | * @param message the exception message to use if the assertion fails 85 | * @throws IllegalArgumentException if the object is not {@code null} 86 | */ 87 | public static void isNull(Object object, String message) { 88 | if (object != null) { 89 | throw new IllegalArgumentException(message); 90 | } 91 | } 92 | 93 | /** 94 | * Assert that an object is {@code null} . 95 | *
Assert.isNull(value);
96 | * 97 | * @param object the object to check 98 | * @throws IllegalArgumentException if the object is not {@code null} 99 | */ 100 | public static void isNull(Object object) { 101 | isNull(object, "[Assertion failed] - the object argument must be null"); 102 | } 103 | 104 | /** 105 | * Assert that an object is not {@code null} . 106 | *
Assert.notNull(clazz, "The class must not be null");
107 | * 108 | * @param object the object to check 109 | * @param message the exception message to use if the assertion fails 110 | * @throws IllegalArgumentException if the object is {@code null} 111 | */ 112 | public static void notNull(Object object, String message) { 113 | if (object == null) { 114 | throw new IllegalArgumentException(message); 115 | } 116 | } 117 | 118 | /** 119 | * Assert that an object is not {@code null} . 120 | *
Assert.notNull(clazz);
121 | * 122 | * @param object the object to check 123 | * @throws IllegalArgumentException if the object is {@code null} 124 | */ 125 | public static void notNull(Object object) { 126 | notNull(object, "[Assertion failed] - this argument is required; it must not be null"); 127 | } 128 | 129 | /** 130 | * Assert that the given String is not empty; that is, 131 | * it must not be {@code null} and not the empty String. 132 | *
Assert.hasLength(name, "Name must not be empty");
133 | * 134 | * @param text the String to check 135 | * @param message the exception message to use if the assertion fails 136 | * @see StringUtils#hasLength 137 | */ 138 | public static void hasLength(String text, String message) { 139 | if (!StringUtils.hasLength(text)) { 140 | throw new IllegalArgumentException(message); 141 | } 142 | } 143 | 144 | /** 145 | * Assert that the given String is not empty; that is, 146 | * it must not be {@code null} and not the empty String. 147 | *
Assert.hasLength(name);
148 | * 149 | * @param text the String to check 150 | * @see StringUtils#hasLength 151 | */ 152 | public static void hasLength(String text) { 153 | hasLength(text, 154 | "[Assertion failed] - this String argument must have length; it must not be null or empty"); 155 | } 156 | 157 | /** 158 | * Assert that the given String has valid text content; that is, it must not 159 | * be {@code null} and must contain at least one non-whitespace character. 160 | *
Assert.hasText(name, "'name' must not be empty");
161 | * 162 | * @param text the String to check 163 | * @param message the exception message to use if the assertion fails 164 | * @see StringUtils#hasText 165 | */ 166 | public static void hasText(String text, String message) { 167 | if (!StringUtils.hasText(text)) { 168 | throw new IllegalArgumentException(message); 169 | } 170 | } 171 | 172 | /** 173 | * Assert that the given String has valid text content; that is, it must not 174 | * be {@code null} and must contain at least one non-whitespace character. 175 | *
Assert.hasText(name, "'name' must not be empty");
176 | * 177 | * @param text the String to check 178 | * @see StringUtils#hasText 179 | */ 180 | public static void hasText(String text) { 181 | hasText(text, 182 | "[Assertion failed] - this String argument must have text; it must not be null, empty, or blank"); 183 | } 184 | 185 | /** 186 | * Assert that the given text does not contain the given substring. 187 | *
Assert.doesNotContain(name, "rod", "Name must not contain 'rod'");
188 | * 189 | * @param textToSearch the text to search 190 | * @param substring the substring to find within the text 191 | * @param message the exception message to use if the assertion fails 192 | */ 193 | public static void doesNotContain(String textToSearch, String substring, String message) { 194 | if (StringUtils.hasLength(textToSearch) && StringUtils.hasLength(substring) && 195 | textToSearch.contains(substring)) { 196 | throw new IllegalArgumentException(message); 197 | } 198 | } 199 | 200 | /** 201 | * Assert that the given text does not contain the given substring. 202 | *
Assert.doesNotContain(name, "rod");
203 | * 204 | * @param textToSearch the text to search 205 | * @param substring the substring to find within the text 206 | */ 207 | public static void doesNotContain(String textToSearch, String substring) { 208 | doesNotContain(textToSearch, substring, 209 | "[Assertion failed] - this String argument must not contain the substring [" + substring + "]"); 210 | } 211 | 212 | 213 | /** 214 | * Assert that an array has no null elements. 215 | * Note: Does not complain if the array is empty! 216 | *
Assert.noNullElements(array, "The array must have non-null elements");
217 | * 218 | * @param array the array to check 219 | * @param message the exception message to use if the assertion fails 220 | * @throws IllegalArgumentException if the object array contains a {@code null} element 221 | */ 222 | public static void noNullElements(Object[] array, String message) { 223 | if (array != null) { 224 | for (Object element : array) { 225 | if (element == null) { 226 | throw new IllegalArgumentException(message); 227 | } 228 | } 229 | } 230 | } 231 | 232 | /** 233 | * Assert that an array has no null elements. 234 | * Note: Does not complain if the array is empty! 235 | *
Assert.noNullElements(array);
236 | * 237 | * @param array the array to check 238 | * @throws IllegalArgumentException if the object array contains a {@code null} element 239 | */ 240 | public static void noNullElements(Object[] array) { 241 | noNullElements(array, "[Assertion failed] - this array must not contain any null elements"); 242 | } 243 | 244 | 245 | /** 246 | * Assert that the provided object is an instance of the provided class. 247 | *
Assert.instanceOf(Foo.class, foo);
248 | * 249 | * @param clazz the required class 250 | * @param obj the object to check 251 | * @throws IllegalArgumentException if the object is not an instance of clazz 252 | * @see Class#isInstance 253 | */ 254 | public static void isInstanceOf(Class clazz, Object obj) { 255 | isInstanceOf(clazz, obj, ""); 256 | } 257 | 258 | /** 259 | * Assert that the provided object is an instance of the provided class. 260 | *
Assert.instanceOf(Foo.class, foo);
261 | * 262 | * @param type the type to check against 263 | * @param obj the object to check 264 | * @param message a message which will be prepended to the message produced by 265 | * the function itself, and which may be used to provide context. It should 266 | * normally end in a ": " or ". " so that the function generate message looks 267 | * ok when prepended to it. 268 | * @throws IllegalArgumentException if the object is not an instance of clazz 269 | * @see Class#isInstance 270 | */ 271 | public static void isInstanceOf(Class type, Object obj, String message) { 272 | notNull(type, "Type to check against must not be null"); 273 | if (!type.isInstance(obj)) { 274 | throw new IllegalArgumentException( 275 | (StringUtils.hasLength(message) ? message + " " : "") + 276 | "Object of class [" + (obj != null ? obj.getClass().getName() : "null") + 277 | "] must be an instance of " + type); 278 | } 279 | } 280 | 281 | /** 282 | * Assert that {@code superType.isAssignableFrom(subType)} is {@code true}. 283 | *
Assert.isAssignable(Number.class, myClass);
284 | * 285 | * @param superType the super type to check 286 | * @param subType the sub type to check 287 | * @throws IllegalArgumentException if the classes are not assignable 288 | */ 289 | public static void isAssignable(Class superType, Class subType) { 290 | isAssignable(superType, subType, ""); 291 | } 292 | 293 | /** 294 | * Assert that {@code superType.isAssignableFrom(subType)} is {@code true}. 295 | *
Assert.isAssignable(Number.class, myClass);
296 | * 297 | * @param superType the super type to check against 298 | * @param subType the sub type to check 299 | * @param message a message which will be prepended to the message produced by 300 | * the function itself, and which may be used to provide context. It should 301 | * normally end in a ": " or ". " so that the function generate message looks 302 | * ok when prepended to it. 303 | * @throws IllegalArgumentException if the classes are not assignable 304 | */ 305 | public static void isAssignable(Class superType, Class subType, String message) { 306 | notNull(superType, "Type to check against must not be null"); 307 | if (subType == null || !superType.isAssignableFrom(subType)) { 308 | throw new IllegalArgumentException(message + subType + " is not assignable to " + superType); 309 | } 310 | } 311 | 312 | 313 | /** 314 | * Assert a boolean expression, throwing {@code IllegalStateException} 315 | * if the test result is {@code false}. Call isTrue if you wish to 316 | * throw IllegalArgumentException on an assertion failure. 317 | *
Assert.state(id == null, "The id property must not already be initialized");
318 | * 319 | * @param expression a boolean expression 320 | * @param message the exception message to use if the assertion fails 321 | * @throws IllegalStateException if expression is {@code false} 322 | */ 323 | public static void state(boolean expression, String message) { 324 | if (!expression) { 325 | throw new IllegalStateException(message); 326 | } 327 | } 328 | 329 | /** 330 | * Assert a boolean expression, throwing {@link IllegalStateException} 331 | * if the test result is {@code false}. 332 | *

Call {@link #isTrue(boolean)} if you wish to 333 | * throw {@link IllegalArgumentException} on an assertion failure. 334 | *

Assert.state(id == null);
335 | * 336 | * @param expression a boolean expression 337 | * @throws IllegalStateException if the supplied expression is {@code false} 338 | */ 339 | public static void state(boolean expression) { 340 | state(expression, "[Assertion failed] - this state invariant must be true"); 341 | } 342 | 343 | } 344 | -------------------------------------------------------------------------------- /jCat-agent/src/main/java/org/coderead/jcat/groovyLsp/CompletionHandler.java: -------------------------------------------------------------------------------- 1 | package org.coderead.jcat.groovyLsp; 2 | /** 3 | * @Copyright 源码阅读网 http://coderead.cn 4 | */ 5 | 6 | import org.coderead.jcat.common.StringUtils; 7 | import groovy.lang.Closure; 8 | import groovy.lang.Script; 9 | import org.codehaus.groovy.ast.ASTNode; 10 | import org.codehaus.groovy.ast.ClassCodeVisitorSupport; 11 | import org.codehaus.groovy.ast.CompileUnit; 12 | import org.codehaus.groovy.ast.expr.*; 13 | import org.codehaus.groovy.control.SourceUnit; 14 | import org.codehaus.groovy.runtime.DefaultGroovyMethods; 15 | 16 | import java.lang.reflect.Field; 17 | import java.lang.reflect.Method; 18 | import java.lang.reflect.Modifier; 19 | import java.util.*; 20 | import java.util.function.Predicate; 21 | import java.util.function.Supplier; 22 | import java.util.stream.Collectors; 23 | 24 | /** 25 | * @author 鲁班大叔 26 | * @date 2024 27 | */ 28 | public class CompletionHandler { 29 | private ClassLoader userLoader;// 应用使用的classLoader 30 | private Class scriptBaseClass; 31 | Supplier[]> allClass; 32 | public static final String[] defaultImports = { 33 | "java.lang", 34 | "java.util", 35 | "java.io", 36 | "java.net", "groovy.lang", "groovy.util", 37 | "java.math.BigInteger", 38 | "java.math.BigDecimal", 39 | }; 40 | // 是否为默认导入的类 41 | private static Predicate> isDefaultClass = c -> Arrays.stream(defaultImports).anyMatch(p -> p.equals(c.getPackage().getName()) || p.equals(c.getName())); 42 | 43 | public CompletionHandler(ClassLoader baseClassLoader, Class scriptBaseClass) { 44 | this.userLoader = baseClassLoader; 45 | this.scriptBaseClass = scriptBaseClass; 46 | } 47 | 48 | public CompletionHandler() { 49 | this(ClassLoader.getSystemClassLoader(), Script.class); 50 | } 51 | 52 | 53 | public void setAllClass(Supplier[]> allClass) { 54 | this.allClass = allClass; 55 | } 56 | 57 | /** 58 | * 基于光标位置获取提示项 59 | * 60 | * @param position 光标位置 line,column 61 | * @return 62 | */ 63 | public List completionByCursor(CompileUnit unit, int[] position) { 64 | FindCompletionNodeVisitor findNodeVisitor = new FindCompletionNodeVisitor(position); 65 | ASTNode astNode = unit.getModules().stream(). 66 | flatMap(s -> s.getClasses().stream()) 67 | .map(c -> { 68 | c.visitContents(findNodeVisitor); 69 | return findNodeVisitor.node; 70 | }) 71 | .filter(Objects::nonNull) 72 | .findFirst().orElse(null); 73 | 74 | if (astNode instanceof VariableExpression) { 75 | return variableCompletion((VariableExpression) astNode); 76 | } else if (astNode instanceof ConstructorCallExpression) { 77 | return constructorCallCompletion((ConstructorCallExpression) astNode); 78 | } else if (astNode instanceof ClassExpression) { 79 | return classCompletion((ClassExpression) astNode); 80 | } else if (astNode instanceof ListExpression) { 81 | return typeCompletion(List.class); 82 | } else if (astNode instanceof MapExpression) { 83 | return typeCompletion(Map.class); 84 | } 85 | // 返回空 86 | return new ArrayList<>(); 87 | } 88 | 89 | /** 90 | * 基于关键字获取提示: 91 | * 1.关键字及模板关键字 92 | * 2.groovy.lang.Script类中的方法 93 | * 3.当前定义的元素:变量、方法 94 | * 4.类名 95 | *

96 | * 注意: 勿调用class.getSimpleName或isAnonymousClass 将导致NoClassDefFoundError 或 IllegalAccessError 97 | * 98 | * @return 99 | */ 100 | public List completionByKeyword(String keyword, int maxSize) { 101 | //1.关键字过滤 102 | List items = Arrays.stream(JAVA_KEYWORD) 103 | .filter(k -> k.startsWith(keyword)) 104 | .limit(maxSize) 105 | .map(this::keywordCompletion).collect(Collectors.toList()); 106 | if (items.size() >= maxSize) return items; 107 | 108 | //2.脚本方法过滤 109 | List scriptMethodItems = typeCompletion(scriptBaseClass).stream() 110 | .filter(i -> StringUtils.camelSearch(i.filterText, keyword) > 0) 111 | .limit(maxSize - items.size()) 112 | .collect(Collectors.toList()); 113 | items.addAll(scriptMethodItems); 114 | if (items.size() >= maxSize) return items; 115 | //3.当前元素过滤 TODO 116 | // unit.getModules().stream().flatMap(m->m.getClasses().stream()) 117 | 118 | //4.类名过滤 119 | 120 | /*LinkedList loaders=new LinkedList<>(Collections.singletonList(this.userLoader)); 121 | while (loaders.getLast().getParent()!=null){// 获取能访问的loader 122 | loaders.add(loaders.getLast().getParent()); 123 | }*/ 124 | Class[] allClasses = allClass == null ? new Class[0] : allClass.get(); 125 | Map, Float> scores = new HashMap<>(); 126 | 127 | List classItems = Arrays.stream(allClasses) 128 | .filter(a -> Modifier.isPublic(a.getModifiers())) 129 | .filter(a -> !(a.isSynthetic() || a.isArray() || a.getPackage() == null)) 130 | // .filter(a -> a.getClassLoader() == null || loaders.stream().anyMatch(l -> a.getClassLoader() == l))// 必须为当前loader能访问的类 131 | .filter(a -> { 132 | 133 | float score = StringUtils.camelSearch(getSimpleClassName(a), keyword); 134 | // 如果是默认包,增加20%分值 135 | scores.put(a, score * (isDefaultClass.test(a) ? 1.2f : 1f)); 136 | return score > 0; 137 | }) 138 | .sorted((c1, c2) -> (int) (scores.get(c2) * 100 - scores.get(c1) * 100)) 139 | .limit(maxSize - items.size()) // 优先级排序 140 | .map(this::classCompletion) 141 | .collect(Collectors.toList()); 142 | items.addAll(classItems); 143 | 144 | return items; 145 | } 146 | 147 | 148 | //变量提示 149 | private List variableCompletion(VariableExpression variable) { 150 | Class typeClass = variable.getType().getTypeClass(); 151 | return typeCompletion(typeClass); 152 | } 153 | 154 | //构造方法提示 155 | private List constructorCallCompletion(ConstructorCallExpression exp) { 156 | Class typeClass = exp.getType().getTypeClass(); 157 | return typeCompletion(typeClass); 158 | } 159 | 160 | //类提示 161 | private List classCompletion(ClassExpression exp) { 162 | Class typeClass = exp.getType().getTypeClass(); 163 | List completionItems = typeCompletion(typeClass).stream().filter(t -> Modifier.isStatic(t.modifiers)).collect(Collectors.toList()); 164 | completionItems.add(0,keywordCompletion("class")); 165 | return completionItems; 166 | } 167 | 168 | 169 | // TODO 暂不实现 170 | private List methodCallCompletion(MethodCallExpression exp) { 171 | // 获取方法返回结果 172 | return null; 173 | } 174 | 175 | // TODO 暂不实现 176 | private List methodCallCompletion(StaticMethodCallExpression exp) { 177 | return null; 178 | } 179 | 180 | 181 | //通过类型转换定义 182 | public List typeCompletion(Class type) { 183 | List methods = new ArrayList<>(Arrays.asList(type.getMethods())); 184 | methods.addAll(Arrays.asList(type.getDeclaredMethods())); 185 | List items1 = methods.stream() 186 | .filter(m -> Modifier.isPublic(m.getModifiers())) 187 | .distinct() 188 | .map(this::methodCompletion) 189 | .collect(Collectors.toList()); 190 | items1.addAll(groovyCompletion(type));// 添加groovy方法 191 | // 显示 过滤 insertText 192 | List fields = new ArrayList<>(Arrays.asList(type.getFields())); 193 | fields.addAll(Arrays.asList(type.getDeclaredFields())); 194 | List items2 = fields.stream().filter(f -> Modifier.isPublic(f.getModifiers())).map(this::filedCompletion).collect(Collectors.toList()); 195 | items1.addAll(items2); 196 | return items1.stream().distinct().collect(Collectors.toList()); 197 | } 198 | 199 | protected CompletionItem classCompletion(Class cla) { 200 | CompletionItem item = new CompletionItem(); 201 | String simpleName = getSimpleClassName(cla); 202 | item.label = String.format("%s:%s:%s", simpleName, cla.getPackage().getName(), ""); 203 | item.filterText = simpleName; 204 | item.insertText = simpleName; 205 | 206 | if (!isDefaultClass.test(cla)) { 207 | item.insertImportText = "import " + cla.getCanonicalName();//TODO: 在内部类下,存在报找不到类的风险 208 | } 209 | item.kind = "class"; 210 | // 因引发ArrayStoreException异常暂时关闭 211 | // item.deprecated = cla.getDeclaredAnnotation(Deprecated.class) != null; 212 | item.modifiers = cla.getModifiers(); 213 | return item; 214 | } 215 | 216 | private String getSimpleClassName(Class cla) { 217 | if (cla.isArray()) { 218 | return getSimpleClassName(cla.getComponentType())+"[]"; 219 | 220 | } 221 | return new LinkedList<>(Arrays.asList(cla.getName().split("[.|$]"))).getLast();//TODO:当类名中存在 $将导致不准确 222 | } 223 | 224 | private CompletionItem methodCompletion(Method method) { 225 | return methodCompletion(method, false); 226 | } 227 | 228 | /** 229 | * @param method 230 | * @param isGroovy DefaultGroovyMethods中的静态方法 至少包含一个参数 231 | * @return 232 | */ 233 | protected CompletionItem methodCompletion(Method method, boolean isGroovy) { 234 | CompletionItem item = new CompletionItem(); 235 | LinkedList> paramTypes = new LinkedList<>(Arrays.asList(method.getParameterTypes())); 236 | 237 | if (isGroovy && Modifier.isStatic(method.getModifiers())) { 238 | paramTypes.remove();// 第一个参数为当前调用对象 239 | } 240 | String paramText = paramTypes.stream().map(this::getSimpleClassName).collect(Collectors.joining(",")); 241 | String returnText =getSimpleClassName(method.getReturnType()); 242 | item.label = String.format("%s:(%s):%s", method.getName(), paramText, returnText); 243 | item.kind = "method"; 244 | item.deprecated = method.getDeclaredAnnotation(Deprecated.class) != null; 245 | item.filterText = method.getName(); 246 | item.modifiers = method.getModifiers(); 247 | item.groovyMethod = isGroovy; 248 | item.tipsText = paramText; 249 | 250 | if (paramTypes.size() == 1 && paramTypes.getLast().equals(Closure.class)) { 251 | item.insertText = String.format("%s {it-> }", method.getName()); 252 | } else if (paramTypes.size() > 1 && paramTypes.getLast().equals(Closure.class)) { 253 | item.insertText = String.format("%s() {it-> }", method.getName()); 254 | } else { 255 | item.insertText = String.format("%s()", method.getName()); 256 | } 257 | return item; 258 | } 259 | 260 | protected CompletionItem filedCompletion(Field field) { 261 | CompletionItem item = new CompletionItem(); 262 | item.label = String.format("%s:%s:%s", field.getName(), "", getSimpleClassName(field.getType())); 263 | item.filterText = field.getName(); 264 | item.kind = "field"; 265 | item.deprecated = field.getDeclaredAnnotation(Deprecated.class) != null; 266 | item.modifiers = field.getModifiers(); 267 | item.insertText = field.getName(); 268 | return item; 269 | } 270 | 271 | protected List groovyCompletion(Class type) { 272 | return Arrays.stream(DefaultGroovyMethods.class.getDeclaredMethods()) 273 | .filter(m -> Modifier.isPublic(m.getModifiers()) && Modifier.isStatic(m.getModifiers())) 274 | .filter(m -> m.getParameters().length > 0) 275 | .filter(m -> m.getParameters()[0].getType().isAssignableFrom(type)) 276 | .map(m -> methodCompletion(m, true)) 277 | .collect(Collectors.toList()); 278 | } 279 | 280 | protected CompletionItem keywordCompletion(String keyword) { 281 | CompletionItem item = new CompletionItem(); 282 | item.label = keyword; 283 | item.filterText = keyword; 284 | item.kind = "keyword"; 285 | item.insertText = keyword; 286 | 287 | return item; 288 | } 289 | 290 | 291 | // 292 | // 基于光标位置 找到可提示的项目 293 | /* 294 | 基于光标位置 找到可提示的项目,有以下5种节点: 295 | 1.VariableExpression 296 | 2.MethodCallExpression 297 | 3.ConstructorCallExpression 298 | 4.StaticMethodCallExpression 299 | 5.ClassExpression 300 | */ 301 | private class FindCompletionNodeVisitor extends ClassCodeVisitorSupport { 302 | private int[] position; 303 | ASTNode node; 304 | 305 | public FindCompletionNodeVisitor(int[] position) { 306 | this.position = position; 307 | } 308 | 309 | protected SourceUnit getSourceUnit() { 310 | return null; 311 | } 312 | 313 | @Override 314 | public void visitVariableExpression(VariableExpression expression) { 315 | super.visitVariableExpression(expression); 316 | visitNode(expression); 317 | } 318 | 319 | @Override 320 | public void visitMethodCallExpression(MethodCallExpression call) { 321 | super.visitMethodCallExpression(call); 322 | visitNode(call); 323 | } 324 | 325 | @Override 326 | public void visitConstructorCallExpression(ConstructorCallExpression call) { 327 | super.visitConstructorCallExpression(call); 328 | visitNode(call); 329 | } 330 | 331 | @Override 332 | public void visitStaticMethodCallExpression(StaticMethodCallExpression call) { 333 | super.visitStaticMethodCallExpression(call); 334 | visitNode(call); 335 | } 336 | 337 | @Override 338 | public void visitClassExpression(ClassExpression expression) { 339 | super.visitClassExpression(expression); 340 | visitNode(expression); 341 | } 342 | 343 | @Override 344 | public void visitListExpression(ListExpression expression) { 345 | super.visitListExpression(expression); 346 | visitNode(expression); 347 | } 348 | 349 | @Override 350 | public void visitMapExpression(MapExpression expression) { 351 | super.visitMapExpression(expression); 352 | visitNode(expression); 353 | } 354 | 355 | void visitNode(ASTNode node) { 356 | if (node.getLastLineNumber() == position[0] && node.getLastColumnNumber() == position[1]) { 357 | this.node = node; 358 | } 359 | } 360 | } 361 | 362 | 363 | private static final String JAVA_KEYWORD[] = { 364 | "abstract", "assert", "boolean", "break", "byte", "case", "catch", "char", "class", "continue", "default", "do", "double", "else", "enum", "extends", "final", "finally", "float", "for", "if", "implements", "import", "int", "interface", "instanceof", "long", "native", "new", "package", "private", "protected", "public", "return", "short", "static", "strictfp", "super", "switch", "synchronized", "this", "throw", "throws", "transient", "try", "void", "volatile", "while" 365 | }; 366 | } 367 | -------------------------------------------------------------------------------- /JCat-web/src/components/Console.vue: -------------------------------------------------------------------------------- 1 | 225 | 226 | 539 | 540 | --------------------------------------------------------------------------------