├── LICENSE ├── README.md ├── gravity-agent ├── pom.xml └── src │ └── main │ └── java │ └── io │ └── manbang │ └── gravity │ └── agent │ ├── GravityAgent.java │ ├── GravityBaseInfo.java │ ├── GravityServiceBoot.java │ ├── PluginDownloader.java │ ├── PluginTransformer.java │ └── PluginVo.java ├── gravity-demo ├── pom.xml └── src │ └── main │ └── java │ └── io │ └── manbang │ └── gravity │ └── trade │ ├── Driver.java │ ├── Order.java │ └── Shippers.java ├── gravity-plugin-api ├── pom.xml └── src │ └── main │ └── java │ └── io │ └── manbang │ └── gravity │ └── plugin │ ├── Advice.java │ ├── AdviceHolder.java │ ├── AdviceTemplate.java │ ├── AgentBuilderCustomizer.java │ ├── AgentOptions.java │ ├── AgentPluginClassLoader.java │ ├── AlwaysWitness.java │ ├── AlwaysWorkingSwitcher.java │ ├── CacheHolder.java │ ├── ClassesWitness.java │ ├── Constants.java │ ├── ConstructorInterceptorFactory.java │ ├── ConstructorInterceptorTemplate.java │ ├── DefaultAgentBuilderCustomizer.java │ ├── DefineMethodInterceptorFactory.java │ ├── DefineMethodInterceptorTemplate.java │ ├── EmptyAdvice.java │ ├── EmptyExtendedExecutor.java │ ├── EmptyInterceptor.java │ ├── ExecuteContext.java │ ├── ExtendedExecutor.java │ ├── GAV.java │ ├── GravityException.java │ ├── GravityHome.java │ ├── GravityLogger.java │ ├── GravityService.java │ ├── GravityUtils.java │ ├── Interceptor.java │ ├── InterceptorFactory.java │ ├── JULLogger.java │ ├── JarType.java │ ├── LifeCycle.java │ ├── LocalPluginLoader.java │ ├── MavenWitness.java │ ├── MetaInfo.java │ ├── MetaInfoWitness.java │ ├── MethodInterceptorFactory.java │ ├── MethodInterceptorTemplate.java │ ├── MorphingCallable.java │ ├── Named.java │ ├── Ordered.java │ ├── Plugin.java │ ├── PluginDefine.java │ ├── PluginJar.java │ ├── PluginLoader.java │ ├── PluginType.java │ ├── ReflectUtil.java │ ├── Services.java │ ├── SkipAdviceTemplate.java │ ├── Switchable.java │ ├── Switcher.java │ ├── SwitcherAdvice.java │ ├── SwitcherInterceptor.java │ ├── SwitcherListener.java │ ├── UnitTestContainer.java │ └── Witness.java ├── gravity-plugin ├── gravity-plugin-monitor │ ├── pom.xml │ └── src │ │ └── main │ │ ├── java │ │ └── io │ │ │ └── manbang │ │ │ └── gravity │ │ │ └── plugin │ │ │ └── monitor │ │ │ ├── AopAdvice.java │ │ │ └── AopPluginDefine.java │ │ └── resources │ │ └── META-INF │ │ └── services │ │ └── io.manbang.gravity.plugin.PluginDefine └── pom.xml └── pom.xml /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 -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # gravity 满帮agent方案 2 | 3 | ## gravity agent 4 | gravity是满帮基于[java agent](https://docs.oracle.com/javase/8/docs/api/java/lang/instrument/package-summary.html#package.description)自研的一款字节码层面AOP框架,目的是基于满帮业务场景,为插件开发同学降低字节码增强难度,目前满帮内部基于gravity开发出了六十余款插件,大致覆盖的业务场景如下: 5 | 6 | |场景名称|简介|业务场景| 7 | | ---- | ---- |---- | 8 | | ironman | 满帮mesh方案 |涵盖rpc,mq,redis,config等多种中间件组件,彻底隔离api与实现,让开发同学无痛无感升级,后续也将开源,敬请期待 | 9 | | venom | 满帮混沌场景 |故障演练,模拟测试,涵盖多种中间件,可模拟多场景异常 | 10 | | mock | 满帮测试框架 |mock演练测试,涵盖多场景,多种组件的模拟 | 11 | | hubble | 满帮APM解决方案 |业务系统性能实时监控,链路埋点 | 12 | | common | 常规增强 |一些常规场景的增强,比如[TTL](https://github.com/alibaba/transmittable-thread-local)集成 | 13 | 14 | ## 1.简单使用 15 | 首先简单模拟一笔发货订单场景 16 | ```java 17 | /** 18 | * 货主 19 | */ 20 | public class Shippers { 21 | /** 22 | * 发布订单 23 | */ 24 | public String postOrder() { 25 | return "南京市 雨花台区 万博科技园 运满满总部 50立方 10吨"; 26 | } 27 | } 28 | 29 | /** 30 | * 司机 31 | */ 32 | public class Driver { 33 | /** 34 | * 接单 35 | */ 36 | public boolean acceptOrder(String address) { 37 | if (Objects.nonNull(address) && address.startsWith("南京市")) { 38 | return true; 39 | } 40 | return false; 41 | } 42 | 43 | /** 44 | * 装货 45 | */ 46 | public String loadCargo() { 47 | return "load cargo success."; 48 | } 49 | 50 | /** 51 | * 运货 52 | */ 53 | public String deliverCargo() { 54 | return "deliver cargo success."; 55 | } 56 | } 57 | 58 | /** 59 | * 发货订单 60 | */ 61 | public class Order { 62 | 63 | public static void main(String[] args) { 64 | new Order().trade(); 65 | } 66 | 67 | public void trade() { 68 | final Shippers shippers = new Shippers(); 69 | final Driver driver = new Driver(); 70 | final String address = shippers.postOrder(); 71 | driver.acceptOrder(address); 72 | driver.loadCargo(); 73 | driver.deliverCargo(); 74 | } 75 | } 76 | ``` 77 | 78 | 这时候,我们希望可以监控该笔订单的行为,决定通过无侵入方式打印出入参
79 | 首先引入pom依賴: 80 | ``` 81 | 82 | io.manbang 83 | gravity-plugin-api 84 | 1.0.0 85 | 86 | ``` 87 | 插件定义,描述目标的织入点: 88 | ```java 89 | /** 90 | * @since 2022/05/19 10:55 91 | */ 92 | public class AopPluginDefine implements PluginDefine { 93 | @Override 94 | public ElementMatcher getTypeMatcher() { 95 | return ElementMatchers.named("io.manbang.gravity.trade.Driver") 96 | .or(ElementMatchers.named("io.manbang.gravity.trade.Shippers")); 97 | } 98 | 99 | @Override 100 | public Plugin[] getPlugins() { 101 | return new Plugin[]{Plugin.advice(ElementMatchers.isMethod(), "io.manbang.gravity.plugin.monitor.AopAdvice").withMethod()}; 102 | } 103 | } 104 | ``` 105 | 具体织入的逻辑: 106 | ```java 107 | /** 108 | * @since 2022/05/19 11:05 109 | */ 110 | public class AopAdvice implements Advice { 111 | private static final java.util.logging.Logger log = java.util.logging.Logger.getLogger(AopAdvice.class.getName()); 112 | 113 | @Override 114 | public void enterMethod(ExecuteContext context) { 115 | final Method method = context.getMethod(); 116 | final Object[] argument = context.getArguments(); 117 | final StringBuilder builder = new StringBuilder(); 118 | builder.append("method enter:").append(method.getName()); 119 | for (int i = 0; i < argument.length; i++) { 120 | builder.append(" arg number:").append(i).append(" arg :").append(argument[i]); 121 | } 122 | log.info(builder.toString()); 123 | } 124 | 125 | @Override 126 | public void exitMethod(ExecuteContext context) { 127 | final Method method = context.getMethod(); 128 | final Object result = context.getResult(); 129 | final StringBuilder builder = new StringBuilder(); 130 | builder.append("method exit:").append(method.getName()); 131 | builder.append("result:").append(result); 132 | log.info(builder.toString()); 133 | } 134 | } 135 | ``` 136 | 新建`SPI`文件:`/META-INF/services/io.manbang.gravity.plugin.PluginDefine`,内容为新创建的插件定义`AopPluginDefine`

137 | 打包该插件,并将打包好的插件放置于`{user.home}/.gravity/cargo-publish-app/agent`目录下(`user.home`路径可以通过执行`java -XshowSettings:properties -version`得到,`cargo-publish-app`为应用名,可以自行定义)

138 | 执行`Order`的`main`方法在启动时,新增`VM`命令:`-javaagent:XXXX/XXXX/gravity-agent.jar=appName=cargo-publish-app`,`gravity-agent.jar`下载路径:[agent](https://github.com/ymm-tech/gravity/blob/27c4399955cc0961c7c2538ea37bb3cdf93bc182/gravity-agent.jar)
139 | 可以观察到控制台输出预期想要的业务出入参: 140 | ``` 141 | 五月 19, 2022 4:49:05 下午 io.manbang.gravity.agent.GravityAgent instrument 142 | 信息: 加载插件:AopPluginDefine 143 | 五月 19, 2022 4:49:05 下午 io.manbang.gravity.agent.PluginTransformer transform 144 | 信息: io.manbang.gravity.trade.Shippers 145 | 五月 19, 2022 4:49:05 下午 io.manbang.gravity.agent.PluginTransformer getClassLoader 146 | 信息: The current classLoader is sun.misc.Launcher$AppClassLoader@18b4aac2 , pluginDefine: AopPluginDefine , transform: io.manbang.gravity.trade.Shippers 147 | 五月 19, 2022 4:49:06 下午 io.manbang.gravity.agent.GravityServiceBoot startServices 148 | 信息: 开始启动重力服务…… 149 | 五月 19, 2022 4:49:06 下午 io.manbang.gravity.agent.PluginTransformer transform 150 | 信息: io.manbang.gravity.trade.Driver 151 | 五月 19, 2022 4:49:06 下午 io.manbang.gravity.agent.PluginTransformer getClassLoader 152 | 信息: The current classLoader is sun.misc.Launcher$AppClassLoader@18b4aac2 , pluginDefine: AopPluginDefine , transform: io.manbang.gravity.trade.Driver 153 | 五月 19, 2022 4:49:06 下午 io.manbang.gravity.plugin.monitor.AopAdvice enterMethod 154 | 信息: method enter:postOrder 155 | 五月 19, 2022 4:49:06 下午 io.manbang.gravity.plugin.monitor.AopAdvice exitMethod 156 | 信息: method exit:postOrderresult:南京市 雨花台区 万博科技园 运满满总部 50立方 10吨 157 | 五月 19, 2022 4:49:06 下午 io.manbang.gravity.plugin.monitor.AopAdvice enterMethod 158 | 信息: method enter:acceptOrder arg number:0 arg :南京市 雨花台区 万博科技园 运满满总部 50立方 10吨 159 | 五月 19, 2022 4:49:06 下午 io.manbang.gravity.plugin.monitor.AopAdvice exitMethod 160 | 信息: method exit:acceptOrderresult:true 161 | 五月 19, 2022 4:49:06 下午 io.manbang.gravity.plugin.monitor.AopAdvice enterMethod 162 | 信息: method enter:loadCargo 163 | 五月 19, 2022 4:49:06 下午 io.manbang.gravity.plugin.monitor.AopAdvice exitMethod 164 | 信息: method exit:loadCargoresult:load cargo success. 165 | 五月 19, 2022 4:49:06 下午 io.manbang.gravity.plugin.monitor.AopAdvice enterMethod 166 | 信息: method enter:deliverCargo 167 | 五月 19, 2022 4:49:06 下午 io.manbang.gravity.plugin.monitor.AopAdvice exitMethod 168 | 信息: method exit:deliverCargoresult:deliver cargo success. 169 | ``` 170 | 具体示例已经放在`gravity-demo`和`gravity-plugin`中 171 | -------------------------------------------------------------------------------- /gravity-agent/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | io.manbang 7 | gravity-parent 8 | 1.0.0 9 | 10 | 4.0.0 11 | 12 | 1.0.0` 13 | gravity-agent 14 | 15 | 16 | UTF-8 17 | 1.8 18 | ${java.version} 19 | ${java.version} 20 | 21 | 1.18.12 22 | 3.14.9 23 | 3.0.2 24 | 25 | 26 | 27 | 28 | io.manbang 29 | gravity-plugin-api 30 | 1.0.0 31 | 32 | 33 | com.alibaba 34 | fastjson 35 | 36 | 37 | com.squareup.okhttp3 38 | okhttp 39 | 40 | 41 | org.projectlombok 42 | lombok 43 | ${lombok.version} 44 | provided 45 | true 46 | 47 | 48 | 49 | 50 | gravity-agent 51 | 52 | 53 | org.apache.maven.plugins 54 | maven-compiler-plugin 55 | 3.8.1 56 | 57 | ${java.version} 58 | ${java.version} 59 | ${java.version} 60 | UTF-8 61 | 62 | 63 | 64 | org.apache.maven.plugins 65 | maven-shade-plugin 66 | 3.2.4 67 | 68 | 69 | package 70 | 71 | shade 72 | 73 | 74 | 75 | 76 | 77 | Gravity 78 | Gravity Cloud Native 79 | ${project.version} 80 | 满帮技术平台 81 | Gravity Agent 82 | ${project.version} 83 | 满帮技术平台中间件 84 | io.manbang.gravity.agent.GravityAgent 85 | io.manbang.gravity.agent.GravityAgent 86 | true 87 | true 88 | 89 | 90 | 91 | false 92 | 93 | 94 | com.alibaba.fastjson 95 | io.manbang.gravity.fastjson 96 | 97 | 98 | okhttp3 99 | io.manbang.gravity.okhttp3 100 | 101 | 102 | okio 103 | io.manbang.gravity.okio 104 | 105 | 106 | net.bytebuddy 107 | io.manbang.gravity.bytebuddy 108 | 109 | 110 | 111 | 112 | * 113 | 114 | **/module-info.class 115 | **/LICENSE 116 | **/NOTICE 117 | **/MANIFEST.MF 118 | 119 | META-INF/** 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | -------------------------------------------------------------------------------- /gravity-agent/src/main/java/io/manbang/gravity/agent/GravityAgent.java: -------------------------------------------------------------------------------- 1 | package io.manbang.gravity.agent; 2 | 3 | 4 | import io.manbang.gravity.plugin.AgentBuilderCustomizer; 5 | import io.manbang.gravity.plugin.AgentOptions; 6 | import io.manbang.gravity.plugin.AgentPluginClassLoader; 7 | import io.manbang.gravity.plugin.GravityUtils; 8 | import io.manbang.gravity.plugin.PluginDefine; 9 | import io.manbang.gravity.plugin.Services; 10 | import lombok.extern.java.Log; 11 | import net.bytebuddy.ByteBuddy; 12 | import net.bytebuddy.agent.builder.AgentBuilder; 13 | import net.bytebuddy.agent.builder.ResettableClassFileTransformer; 14 | 15 | import java.lang.instrument.Instrumentation; 16 | import java.util.List; 17 | import java.util.logging.Level; 18 | 19 | import static net.bytebuddy.matcher.ElementMatchers.isSynthetic; 20 | import static net.bytebuddy.matcher.ElementMatchers.nameContains; 21 | import static net.bytebuddy.matcher.ElementMatchers.nameStartsWith; 22 | 23 | 24 | /** 25 | * 插件代理,以java agent的形式,运行在应用侧 26 | * 27 | * @author duoliang.zhang 28 | * @since 2020/08/11 10:17:04 29 | */ 30 | @Log 31 | public class GravityAgent { 32 | private static ResettableClassFileTransformer transformer; 33 | 34 | private GravityAgent() { 35 | throw new UnsupportedOperationException("插件代理不支持实例化"); 36 | } 37 | 38 | public static void premain(String argument, Instrumentation instrumentation) { 39 | instrument(argument, instrumentation); 40 | } 41 | 42 | public static void agentmain(String argument, Instrumentation instrumentation) { 43 | instrument(argument, instrumentation); 44 | } 45 | 46 | private static void instrument(String argument, Instrumentation instrumentation) { 47 | System.setProperty("agent.options", argument); 48 | GravityUtils.setInstrumentation(instrumentation); 49 | 50 | // 现在全部插件 51 | // PluginDownloader.downloadPlugins(instrumentation, AgentOptions.INSTANCE); 52 | 53 | AgentPluginClassLoader pluginClassLoader = new AgentPluginClassLoader(); 54 | GravityUtils.setAgentPluginClassLoader(pluginClassLoader); 55 | 56 | AgentBuilder builder = getAgentBuilder(pluginClassLoader); 57 | 58 | List pluginDefines = getPluginDefines(pluginClassLoader); 59 | for (PluginDefine plugin : pluginDefines) { 60 | log.info(() -> "加载插件:" + plugin.getName()); 61 | try { 62 | builder = plugin.prepare(builder, instrumentation); 63 | } catch (Exception e) { 64 | log.log(Level.INFO, e, () -> String.format("加载插件:%s出现异常,不影响后续织入", plugin.getName())); 65 | } 66 | builder = builder.type(plugin.getTypeMatcher()).transform(new PluginTransformer(plugin)); 67 | } 68 | 69 | transformer = builder.installOn(instrumentation); 70 | } 71 | 72 | private static AgentBuilder getAgentBuilder(AgentPluginClassLoader classLoader) { 73 | ByteBuddy byteBuddy = new ByteBuddy(); 74 | AgentBuilder builder = new AgentBuilder.Default(byteBuddy) 75 | // .with(AgentBuilder.LambdaInstrumentationStrategy.ENABLED) 76 | .ignore(nameStartsWith("io.manbang.gravity.bytebuddy.") 77 | .or(nameStartsWith("org.slf4j.")) 78 | .or(nameStartsWith("org.groovy.")) 79 | .or(nameContains("javassist")) 80 | .or(nameContains(".asm.")) 81 | .or(nameContains(".reflectasm.")) 82 | .or(nameStartsWith("sun.reflect")) 83 | .or(nameStartsWith("com.intellij")) 84 | .or(isSynthetic())); 85 | 86 | AgentBuilderCustomizer builderCustomizer = AgentBuilderCustomizer.customizer(classLoader); 87 | builder = builderCustomizer.customize(AgentOptions.INSTANCE, builder); 88 | return builder; 89 | } 90 | 91 | private static List getPluginDefines(AgentPluginClassLoader pluginClassLoader) { 92 | List pluginDefines = Services.loadAll(PluginDefine.class, pluginClassLoader); 93 | 94 | if (pluginDefines.isEmpty()) { 95 | log.warning("没有加载到任何插件定义!!!"); 96 | } 97 | 98 | return pluginDefines; 99 | } 100 | 101 | public static boolean reset() { 102 | return transformer.reset(GravityUtils.getInstrumentation(), 103 | AgentBuilder.RedefinitionStrategy.RETRANSFORMATION, 104 | AgentBuilder.RedefinitionStrategy.DiscoveryStrategy.SinglePass.INSTANCE, 105 | AgentBuilder.RedefinitionStrategy.BatchAllocator.ForTotal.INSTANCE, 106 | AgentBuilder.RedefinitionStrategy.Listener.StreamWriting.toSystemError()); 107 | } 108 | 109 | } 110 | -------------------------------------------------------------------------------- /gravity-agent/src/main/java/io/manbang/gravity/agent/GravityBaseInfo.java: -------------------------------------------------------------------------------- 1 | package io.manbang.gravity.agent; 2 | 3 | import lombok.extern.java.Log; 4 | 5 | import java.net.InetAddress; 6 | import java.net.NetworkInterface; 7 | import java.net.SocketException; 8 | import java.util.ArrayList; 9 | import java.util.Collections; 10 | import java.util.Enumeration; 11 | import java.util.HashMap; 12 | import java.util.List; 13 | import java.util.Map; 14 | 15 | /** 16 | * @author weilong.hu 17 | * @since 2021/09/17 11:38 18 | */ 19 | @Log 20 | class GravityBaseInfo { 21 | 22 | public static String baseInfo(String argument) { 23 | Map baseInfo = new HashMap<>(64); 24 | System.getProperties().forEach((k, v) -> baseInfo.put(getString(k), getString(v))); 25 | // System.getenv().forEach((k, v) -> baseInfo.put(getString(k), getString(v))); 26 | baseInfo.put("os.all.ip", getString(getAllLocalIp())); 27 | baseInfo.put("agent.argument", getString(argument)); 28 | baseInfo.put("agent.proxy.version", GravityBaseInfo.class.getPackage().getImplementationVersion()); 29 | return info(baseInfo); 30 | } 31 | 32 | private static String getString(Object o) { 33 | String str = String.valueOf(o); 34 | str = str.replaceAll("\t", ""); 35 | str = str.replaceAll("\r|\n", ""); 36 | str = str.replace("\"", "'"); 37 | str = str.replace("\\", "\\\\"); 38 | return str; 39 | } 40 | 41 | private static String info(Map info) { 42 | StringBuilder sb = new StringBuilder("{"); 43 | info.forEach((k, v) -> { 44 | sb.append('"').append(k).append('"').append(":"); 45 | sb.append('"').append(v).append('"').append(","); 46 | }); 47 | sb.append('"').append("GravityAgentProxy").append('"').append(":"); 48 | sb.append('"').append("满帮技术平台中间件").append('"'); 49 | sb.append("}"); 50 | return sb.toString(); 51 | } 52 | 53 | public static List getAllLocalIp() { 54 | List noLoopbackAddresses = new ArrayList<>(); 55 | List allInetAddresses = getAllLocalAddress(); 56 | 57 | for (InetAddress address : allInetAddresses) { 58 | if (!address.isLoopbackAddress() && !address.isLinkLocalAddress()) { 59 | noLoopbackAddresses.add(address.getHostAddress()); 60 | } 61 | } 62 | 63 | return noLoopbackAddresses; 64 | } 65 | 66 | public static List getAllLocalAddress() { 67 | try { 68 | Enumeration networkInterfaces = NetworkInterface.getNetworkInterfaces(); 69 | List addresses = new ArrayList<>(); 70 | 71 | while (networkInterfaces.hasMoreElements()) { 72 | NetworkInterface networkInterface = networkInterfaces.nextElement(); 73 | Enumeration inetAddresses = networkInterface.getInetAddresses(); 74 | while (inetAddresses.hasMoreElements()) { 75 | InetAddress inetAddress = inetAddresses.nextElement(); 76 | addresses.add(inetAddress); 77 | } 78 | } 79 | 80 | return addresses; 81 | } catch (SocketException e) { 82 | log.warning(e.toString()); 83 | return Collections.emptyList(); 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /gravity-agent/src/main/java/io/manbang/gravity/agent/GravityServiceBoot.java: -------------------------------------------------------------------------------- 1 | package io.manbang.gravity.agent; 2 | 3 | import io.manbang.gravity.plugin.GravityService; 4 | import io.manbang.gravity.plugin.Services; 5 | import lombok.extern.java.Log; 6 | 7 | import java.util.HashSet; 8 | import java.util.LinkedList; 9 | import java.util.List; 10 | import java.util.Set; 11 | 12 | /** 13 | * 重力服务启动器,所有的服务植入完成后执行 14 | * 15 | * @author dzhang 16 | * @since 2020/08/29 11:51:30 17 | */ 18 | @Log 19 | enum GravityServiceBoot { 20 | /** 21 | * instance 22 | */ 23 | INSTANCE; 24 | 25 | /** 26 | * 已经启动了的重力服务 27 | */ 28 | private static final Set STARTED_GRAVITY_SERVICE = new HashSet<>(); 29 | /** 30 | * 已经加载过的类加载器 31 | */ 32 | private static final Set LOADED_CLASS_LOADER = new HashSet<>(); 33 | 34 | /** 35 | * 不同的类加载,有可能会加载出相同的SPI,因为父子关系的存在,会导致重复启动服务,因此,缓存已经存在父 GravityService, 36 | * 已经启动的,就不再执行启动了。 37 | * 38 | * @param classLoader {@link GravityService} 的类加载器 39 | */ 40 | public void startServices(ClassLoader classLoader) { 41 | // 已经加载了,就不在加载了 42 | if (LOADED_CLASS_LOADER.contains(classLoader)) { 43 | return; 44 | } 45 | 46 | // 标记此 ClassLoader 已经被加载过 47 | LOADED_CLASS_LOADER.add(classLoader); 48 | 49 | log.info("开始启动重力服务……"); 50 | List gravityServices = getStartingServices(classLoader); 51 | 52 | doStartServices(classLoader, gravityServices); 53 | addStopServiceHook(gravityServices); 54 | } 55 | 56 | private List getStartingServices(ClassLoader classLoader) { 57 | List gravityServices = new LinkedList<>(); 58 | 59 | for (GravityService gravityService : Services.loadAll(GravityService.class, classLoader)) { 60 | // 已经启动过的服务,直接忽略 61 | if (STARTED_GRAVITY_SERVICE.contains(gravityService)) { 62 | continue; 63 | } 64 | 65 | // 缓存还未加载过的服务 66 | STARTED_GRAVITY_SERVICE.add(gravityService); 67 | // 待启动的服务 68 | gravityServices.add(gravityService); 69 | } 70 | return gravityServices; 71 | } 72 | 73 | private void addStopServiceHook(List gravityServices) { 74 | Runtime.getRuntime().addShutdownHook(new Thread(() -> { 75 | for (GravityService gravityService : gravityServices) { 76 | stopService(gravityService); 77 | } 78 | })); 79 | } 80 | 81 | private void stopService(GravityService gravityService) { 82 | try { 83 | gravityService.stop(); 84 | } catch (Exception e) { 85 | log.warning(gravityService.getName() + " 服务停止失败"); 86 | e.printStackTrace(); 87 | } 88 | } 89 | 90 | private void doStartServices(ClassLoader classLoader, List gravityServices) { 91 | Thread thread = new Thread(() -> { 92 | for (GravityService service : gravityServices) { 93 | boolean prepared = service.prepare(classLoader); 94 | if (prepared) { 95 | try { 96 | service.start(); 97 | } catch (Exception e) { 98 | log.warning(service.getName() + " 服务启动失败"); 99 | e.printStackTrace(); 100 | stopService(service); 101 | } 102 | } 103 | } 104 | }); 105 | thread.setDaemon(true); 106 | thread.setName("重力服务启动线程"); 107 | thread.start(); 108 | } 109 | 110 | } 111 | -------------------------------------------------------------------------------- /gravity-agent/src/main/java/io/manbang/gravity/agent/PluginDownloader.java: -------------------------------------------------------------------------------- 1 | package io.manbang.gravity.agent; 2 | 3 | import com.alibaba.fastjson.JSON; 4 | import io.manbang.gravity.plugin.AgentOptions; 5 | import io.manbang.gravity.plugin.GravityHome; 6 | import io.manbang.gravity.plugin.JarType; 7 | import lombok.extern.java.Log; 8 | import okhttp3.Cache; 9 | import okhttp3.Call; 10 | import okhttp3.MediaType; 11 | import okhttp3.OkHttpClient; 12 | import okhttp3.Request; 13 | import okhttp3.RequestBody; 14 | import okhttp3.Response; 15 | import okhttp3.ResponseBody; 16 | 17 | import java.io.File; 18 | import java.io.IOException; 19 | import java.lang.instrument.Instrumentation; 20 | import java.nio.file.Files; 21 | import java.nio.file.Path; 22 | import java.nio.file.StandardCopyOption; 23 | import java.util.Arrays; 24 | import java.util.HashSet; 25 | import java.util.Optional; 26 | import java.util.Set; 27 | import java.util.concurrent.CountDownLatch; 28 | import java.util.concurrent.ExecutorService; 29 | import java.util.concurrent.LinkedBlockingQueue; 30 | import java.util.concurrent.ThreadFactory; 31 | import java.util.concurrent.ThreadPoolExecutor; 32 | import java.util.concurrent.TimeUnit; 33 | import java.util.concurrent.atomic.AtomicInteger; 34 | import java.util.logging.Level; 35 | 36 | import static io.manbang.gravity.agent.GravityBaseInfo.baseInfo; 37 | 38 | /** 39 | * @author duoliang.zhang 40 | * @since 2020/8/21 17:15 41 | */ 42 | @Log 43 | public class PluginDownloader { 44 | private static final int MAX_RETRIES = 3; 45 | private static final OkHttpClient http; 46 | private static final Set DOWNLOADED_PLUGINS = new HashSet<>(); 47 | 48 | static { 49 | Path cacheDirPath = GravityHome.INSTANCE.resolvePath("cache"); 50 | File cacheDir = cacheDirPath.toFile(); 51 | boolean created = cacheDir.mkdirs(); 52 | if (created) { 53 | log.info("创建插件缓存目录成功"); 54 | } 55 | 56 | Cache cache = new Cache(cacheDir, Integer.MAX_VALUE); 57 | String version = PluginDownloader.class.getPackage().getImplementationVersion(); 58 | final String env = AgentOptions.INSTANCE.getString("env", "None"); 59 | log.info("Gravity Version: " + version); 60 | 61 | http = new OkHttpClient.Builder() 62 | .connectTimeout(3, TimeUnit.SECONDS) 63 | .readTimeout(5, TimeUnit.SECONDS) 64 | .addInterceptor(chain -> { 65 | Request request = chain.request().newBuilder() 66 | .header("User-Agent", "Gravity") 67 | .header("Gravity-Version", version) 68 | .header("Gravity-Env", env) 69 | .header("App-Name", AgentOptions.INSTANCE.getAppName()) 70 | .build(); 71 | return chain.proceed(request); 72 | }) 73 | .cache(cache) 74 | .build(); 75 | } 76 | 77 | private PluginDownloader() { 78 | throw new UnsupportedOperationException(); 79 | } 80 | 81 | /** 82 | * 下载插件 83 | * 84 | * @param instrumentation instrumentation 85 | * @param options 代理的配置参数 86 | */ 87 | public static void downloadPlugins(Instrumentation instrumentation, AgentOptions options) { 88 | PluginVo[] plugins; 89 | try { 90 | plugins = getPlugins(options); 91 | } catch (Exception e) { 92 | log.log(Level.WARNING, e, () -> "获取插件列表URL地址失败,业务不受影响"); 93 | plugins = new PluginVo[0]; 94 | } 95 | downloadPluginsConcurrently(plugins); 96 | for (JarType type : JarType.values()) { 97 | type.appendClassPath(instrumentation, GravityHome.INSTANCE.getHome()); 98 | } 99 | } 100 | 101 | public static boolean checkDownloadedPlugins() { 102 | String checkUrl = String.format("%s/gravity/check", AgentOptions.INSTANCE.getBaseUrl()); 103 | RequestBody body = RequestBody.create(MediaType.parse("application/json; charset=UTF-8"), JSON.toJSONBytes(DOWNLOADED_PLUGINS)); 104 | Request checkRequest = new Request.Builder().url(checkUrl).post(body).build(); 105 | 106 | int retries = 1; 107 | 108 | do { 109 | try (Response response = http.newCall(checkRequest).execute()) { 110 | int code = response.code(); 111 | // 404 服务器访问是正常的 404 112 | // 500 本地访问会出现 113 | if (code == 404 || code == 500) { 114 | return true; 115 | } 116 | 117 | return Optional.ofNullable(response.body()).map(b -> { 118 | try { 119 | return b.string(); 120 | } catch (IOException ignore) { 121 | return "false"; 122 | } 123 | }).map(Boolean::parseBoolean).orElse(false); 124 | } catch (IOException e) { 125 | log.log(Level.WARNING, "检查插件Gravity依赖关系失败,不影响业务。", e); 126 | } 127 | } while (retries++ < MAX_RETRIES); 128 | 129 | return false; 130 | } 131 | 132 | private static void downloadPluginsConcurrently(PluginVo[] plugins) { 133 | CountDownLatch latch = new CountDownLatch(plugins.length); 134 | ExecutorService executor = new ThreadPoolExecutor(10, 10, 0L, 135 | TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>(), new GravityThreadFactory()); 136 | 137 | for (PluginVo plugin : plugins) { 138 | executor.execute(() -> { 139 | downloadPluginWithRetry(plugin); 140 | latch.countDown(); 141 | }); 142 | } 143 | try { 144 | latch.await(); 145 | } catch (InterruptedException e) { 146 | Thread.currentThread().interrupt(); 147 | } catch (Exception e) { 148 | log.log(Level.WARNING, "download plugin latch await", e); 149 | } 150 | // 用完了,直接关停 151 | executor.shutdown(); 152 | } 153 | 154 | private static void downloadPluginWithRetry(PluginVo plugin) { 155 | int retries = 1; 156 | do { 157 | try { 158 | downloadPlugin(plugin); 159 | DOWNLOADED_PLUGINS.add(plugin.getFilename()); 160 | break; 161 | } catch (Exception e) { 162 | log.log(Level.WARNING, e, () -> "下载Jar失败,不影响业务:" + plugin); 163 | } 164 | } while (retries++ < MAX_RETRIES); 165 | } 166 | 167 | private static void downloadPlugin(PluginVo plugin) throws IOException { 168 | String filename = plugin.getFilename(); 169 | // 先放到临时目录 170 | File tempFile = writeToTempFile(plugin); 171 | // 然后得到Jar的配置清单,得到Jar包类型 172 | JarType[] jarTypes = getJarTypes(plugin); 173 | 174 | for (JarType jarType : jarTypes) { 175 | // 然后将Jar放置到指定目标目录 176 | String dir = jarType.name().toLowerCase(); 177 | Path target = GravityHome.INSTANCE.resolvePath(String.format("%s/%s", dir, filename)); 178 | Files.copy(tempFile.toPath(), target, StandardCopyOption.REPLACE_EXISTING); 179 | 180 | log.info(() -> String.format("%s(%s)", filename, jarType)); 181 | } 182 | } 183 | 184 | private static JarType[] getJarTypes(PluginVo plugin) { 185 | if (plugin.getJarTypes().length == 0) { 186 | return new JarType[]{JarType.APP}; 187 | } 188 | return Arrays.stream(plugin.getJarTypes()) 189 | .map(String::trim) 190 | .map(String::toUpperCase) 191 | .map(JarType::valueOf) 192 | .toArray(JarType[]::new); 193 | } 194 | 195 | private static File writeToTempFile(PluginVo plugin) throws IOException { 196 | File tempFile; 197 | String pluginUrl = String.format("%s/gravity%s", AgentOptions.INSTANCE.getBaseUrl(), plugin.getPath()); 198 | Request downloadRequest = new Request.Builder().url(pluginUrl).build(); 199 | 200 | try (Response response = http.newCall(downloadRequest).execute()) { 201 | ResponseBody body = response.body(); 202 | if (body == null) { 203 | throw new IOException("下载插件失败:" + pluginUrl); 204 | } 205 | 206 | tempFile = File.createTempFile(plugin.getFilename(), ".tmp"); 207 | Files.copy(body.byteStream(), tempFile.toPath(), StandardCopyOption.REPLACE_EXISTING); 208 | return tempFile; 209 | } 210 | } 211 | 212 | private static PluginVo[] getPlugins(AgentOptions options) throws IOException { 213 | if (options.isLocalDebug()) { 214 | return new PluginVo[0]; 215 | } 216 | 217 | String appName = options.getAppName(); 218 | String baseUrl = options.getBaseUrl(); 219 | 220 | log.info(() -> String.format("获取【%s】插件列表", appName)); 221 | String url = String.format("%s/gravity/agent/plugins?appName=%s", baseUrl, appName); 222 | 223 | final String argument = System.getProperty("agent.options"); 224 | final String baseInfo = baseInfo(argument); 225 | final Request request = new Request.Builder().post(RequestBody.create(MediaType.get("application/json"), baseInfo)).url(url).build(); 226 | Call call = http.newCall(request); 227 | 228 | try (Response response = call.execute()) { 229 | ResponseBody body = response.body(); 230 | if (body == null) { 231 | return new PluginVo[0]; 232 | } 233 | 234 | return JSON.parseObject(body.byteStream(), PluginVo[].class); 235 | } 236 | } 237 | 238 | private static class GravityThreadFactory implements ThreadFactory { 239 | private final AtomicInteger threadNumber = new AtomicInteger(1); 240 | private final String namePrefix; 241 | 242 | private GravityThreadFactory() { 243 | namePrefix = "gravity-plugin-download-thread-"; 244 | } 245 | 246 | @Override 247 | public Thread newThread(Runnable r) { 248 | Thread t = new Thread(r); 249 | t.setName(namePrefix + threadNumber.getAndIncrement()); 250 | t.setDaemon(true); 251 | return t; 252 | } 253 | } 254 | } 255 | 256 | -------------------------------------------------------------------------------- /gravity-agent/src/main/java/io/manbang/gravity/agent/PluginTransformer.java: -------------------------------------------------------------------------------- 1 | package io.manbang.gravity.agent; 2 | 3 | import io.manbang.gravity.plugin.AdviceTemplate; 4 | import io.manbang.gravity.plugin.AgentPluginClassLoader; 5 | import io.manbang.gravity.plugin.EmptyExtendedExecutor; 6 | import io.manbang.gravity.plugin.ExtendedExecutor; 7 | import io.manbang.gravity.plugin.GravityUtils; 8 | import io.manbang.gravity.plugin.Interceptor; 9 | import io.manbang.gravity.plugin.InterceptorFactory; 10 | import io.manbang.gravity.plugin.MorphingCallable; 11 | import io.manbang.gravity.plugin.Plugin; 12 | import io.manbang.gravity.plugin.PluginDefine; 13 | import io.manbang.gravity.plugin.SkipAdviceTemplate; 14 | import lombok.SneakyThrows; 15 | import lombok.extern.java.Log; 16 | import net.bytebuddy.ByteBuddy; 17 | import net.bytebuddy.agent.builder.AgentBuilder; 18 | import net.bytebuddy.asm.Advice; 19 | import net.bytebuddy.description.field.FieldDescription; 20 | import net.bytebuddy.description.method.MethodDescription; 21 | import net.bytebuddy.description.type.TypeDescription; 22 | import net.bytebuddy.dynamic.ClassFileLocator; 23 | import net.bytebuddy.dynamic.DynamicType; 24 | import net.bytebuddy.dynamic.loading.ClassInjector; 25 | import net.bytebuddy.implementation.MethodDelegation; 26 | import net.bytebuddy.implementation.SuperMethodCall; 27 | import net.bytebuddy.implementation.bind.annotation.Morph; 28 | import net.bytebuddy.matcher.ElementMatcher; 29 | import net.bytebuddy.pool.TypePool; 30 | import net.bytebuddy.utility.JavaModule; 31 | 32 | import java.util.Collections; 33 | import java.util.HashSet; 34 | import java.util.Map; 35 | import java.util.Set; 36 | import java.util.concurrent.ConcurrentHashMap; 37 | import java.util.concurrent.atomic.AtomicBoolean; 38 | 39 | import static net.bytebuddy.jar.asm.Opcodes.ACC_PRIVATE; 40 | import static net.bytebuddy.jar.asm.Opcodes.ACC_VOLATILE; 41 | import static net.bytebuddy.matcher.ElementMatchers.isConstructor; 42 | import static net.bytebuddy.matcher.ElementMatchers.isDeclaredBy; 43 | import static net.bytebuddy.matcher.ElementMatchers.not; 44 | 45 | 46 | @Log 47 | class PluginTransformer implements AgentBuilder.Transformer { 48 | private static final String SET_ADVICE_METHOD_NAME = "setAdvice"; 49 | private static final String SET_EXTENDED_EXECUTOR_METHOD_NAME = "setExtendedExecutor"; 50 | private static final Map CLASS_DELEGATION_LOADER_MAP = new ConcurrentHashMap<>(); 51 | private static final Map CLASS_LOADER_MAP = new ConcurrentHashMap<>(); 52 | private static final Map> EXTEND_CLASS_LOADER_CLASS_FIELD_MAP = new ConcurrentHashMap<>(); 53 | private final PluginDefine pluginDefine; 54 | 55 | PluginTransformer(PluginDefine pluginDefine) { 56 | this.pluginDefine = pluginDefine; 57 | } 58 | 59 | @Override 60 | public DynamicType.Builder transform(DynamicType.Builder builder, TypeDescription typeDescription, ClassLoader classLoader, JavaModule module) { 61 | final String targetClass = typeDescription.getName(); 62 | log.info(targetClass); 63 | 64 | // 一个插件定义,只能支持一类目标(比如MySQL、Redis)的植入 65 | AgentPluginClassLoader agentClassLoader = getClassLoader(classLoader, targetClass); 66 | if (agentClassLoader == null) { 67 | return builder; 68 | } 69 | if (pluginDefine.getWitness().saw(agentClassLoader)) { 70 | // 但每个插件可以有多个不同的植入点 71 | for (Plugin plugin : pluginDefine.getPlugins()) { 72 | switch (plugin.getType()) { 73 | case ADVICE: 74 | builder = instrumentAdvice(builder, agentClassLoader, plugin, classLoader); 75 | break; 76 | case INTERCEPTOR: 77 | builder = instrumentInterceptor(builder, agentClassLoader, plugin); 78 | break; 79 | case INTERCEPTOR_INSTANCE: 80 | builder = instrumentInterceptorInstance(builder, agentClassLoader, plugin); 81 | break; 82 | default: 83 | // do nothing 84 | break; 85 | } 86 | builder = extend(builder, typeDescription, classLoader, targetClass, plugin); 87 | } 88 | } else { 89 | log.warning(() -> "witness: " + pluginDefine.getName()); 90 | } 91 | 92 | // 在这个地方启动,为的是得到应用的 ClassLoader,只会执行一次 93 | GravityServiceBoot.INSTANCE.startServices(agentClassLoader); 94 | 95 | return builder; 96 | } 97 | 98 | private AgentPluginClassLoader getClassLoader(ClassLoader classLoader, String targetClass) { 99 | AgentPluginClassLoader agentClassLoader; 100 | if (classLoader instanceof AgentPluginClassLoader) { 101 | if (pluginDefine.ignoreEnhanceAgentClass()) { 102 | log.info(String.format("The current classLoader is instanceof AgentPluginClassLoader , pluginDefine: %s is user space, ignore transform: %s", pluginDefine.getName(), targetClass)); 103 | agentClassLoader = null; 104 | } else { 105 | log.info(String.format("The current classLoader is instanceof AgentPluginClassLoader , pluginDefine: %s , transform: %s", pluginDefine.getName(), targetClass)); 106 | agentClassLoader = (AgentPluginClassLoader) classLoader; 107 | } 108 | } else { 109 | if (pluginDefine.isDelegated()) { 110 | log.info(String.format("The current classLoader is %s , pluginDefine: %s is delegated , transform: %s", classLoader, pluginDefine.getName(), targetClass)); 111 | agentClassLoader = CLASS_DELEGATION_LOADER_MAP.computeIfAbsent(classLoader, c -> new AgentPluginClassLoader(c, true)); 112 | } else { 113 | log.info(String.format("The current classLoader is %s , pluginDefine: %s , transform: %s", classLoader, pluginDefine.getName(), targetClass)); 114 | agentClassLoader = CLASS_LOADER_MAP.computeIfAbsent(classLoader, c -> new AgentPluginClassLoader(c, false)); 115 | } 116 | } 117 | return agentClassLoader; 118 | } 119 | 120 | /** 121 | * 额外的扩展原有class 122 | */ 123 | private DynamicType.Builder extend(DynamicType.Builder builder, TypeDescription typeDescription, ClassLoader classLoader, String targetClass, Plugin plugin) { 124 | if (plugin.getExtendFields() != null) { 125 | final Map extendedClassFieldMap = EXTEND_CLASS_LOADER_CLASS_FIELD_MAP.computeIfAbsent(classLoader, c -> new ConcurrentHashMap<>()); 126 | final Set declaredFieldNames = getDeclaredFieldNames(typeDescription); 127 | for (String extendField : plugin.getExtendFields()) { 128 | final String classField = String.format("%s.%s", targetClass, extendField); 129 | final AtomicBoolean extended = extendedClassFieldMap.computeIfAbsent(classField, t -> new AtomicBoolean(false)); 130 | //防止重复织入field 131 | if (declaredFieldNames.contains(extendField)) { 132 | log.warning(String.format("The current class already has field,ignore this weaving field : %s", classField)); 133 | } else if (extended.compareAndSet(false, true)) { 134 | builder = builder.defineField(extendField, Object.class, ACC_PRIVATE | ACC_VOLATILE); 135 | log.info(String.format("The current class weaves into the new field successfully,field : %s", classField)); 136 | } else { 137 | log.warning(String.format("The current class has been woven into the extend field : %s", classField)); 138 | } 139 | } 140 | } 141 | return builder; 142 | } 143 | 144 | private Set getDeclaredFieldNames(TypeDescription typeDescription) { 145 | final Set declaredFieldNames = new HashSet<>(); 146 | //不使用stream 防止提前触发一些类的加载 147 | for (FieldDescription.InDefinedShape declaredField : typeDescription.getDeclaredFields()) { 148 | declaredFieldNames.add(declaredField.getName()); 149 | } 150 | return declaredFieldNames; 151 | } 152 | 153 | private DynamicType.Builder instrumentInterceptorInstance(DynamicType.Builder builder, AgentPluginClassLoader acl, Plugin plugin) { 154 | ElementMatcher methodMatcher = excludeObjectMethod(plugin); 155 | Object interceptor = plugin.getInterceptor(); 156 | final ExtendedExecutor extendedExecutor = extendedExecutor(plugin, acl, acl.getParent()); 157 | if (interceptor instanceof Interceptor) { 158 | if (plugin.isDefineMethod()) { 159 | builder = builder.defineMethod(plugin.methodName(), Object.class, plugin.modifiers()) 160 | .withParameters(Object[].class) 161 | .intercept(MethodDelegation.withDefaultConfiguration().to(InterceptorFactory.defineMethod((Interceptor) interceptor, extendedExecutor))); 162 | } else if (plugin.isConstructorAdvised()) { 163 | builder = builder.constructor(methodMatcher).intercept(SuperMethodCall.INSTANCE.andThen( 164 | MethodDelegation.withDefaultConfiguration().to(InterceptorFactory.constructor((Interceptor) interceptor, extendedExecutor)))); 165 | } else { 166 | builder = builder.method(methodMatcher).intercept(MethodDelegation.withDefaultConfiguration() 167 | .withBinders(Morph.Binder.install(MorphingCallable.class)) 168 | .to(InterceptorFactory.method((Interceptor) interceptor, extendedExecutor))); 169 | } 170 | } else { 171 | builder = builder.method(methodMatcher).intercept(MethodDelegation.to(interceptor)); 172 | } 173 | return builder; 174 | } 175 | 176 | @SneakyThrows 177 | private DynamicType.Builder instrumentInterceptor(DynamicType.Builder builder, ClassLoader acl, Plugin plugin) { 178 | ElementMatcher methodMatcher = excludeObjectMethod(plugin); 179 | Class interceptorClass = acl.loadClass(plugin.getInterceptorClassName()); 180 | final ExtendedExecutor extendedExecutor = extendedExecutor(plugin, acl, acl.getParent()); 181 | if (Interceptor.class.isAssignableFrom(interceptorClass)) { 182 | if (plugin.isDefineMethod()) { 183 | builder = builder.defineMethod(plugin.methodName(), Object.class, plugin.modifiers()) 184 | .withParameters(Object[].class) 185 | .intercept(MethodDelegation.withDefaultConfiguration().to(InterceptorFactory.defineMethod(interceptorClass, extendedExecutor))); 186 | } 187 | if (plugin.isConstructorAdvised()) { 188 | builder = builder.constructor(methodMatcher).intercept(SuperMethodCall.INSTANCE.andThen( 189 | MethodDelegation.withDefaultConfiguration().to(InterceptorFactory.constructor(interceptorClass, extendedExecutor)))); 190 | } else { 191 | builder = builder.method(methodMatcher).intercept(MethodDelegation.withDefaultConfiguration() 192 | .withBinders(Morph.Binder.install(MorphingCallable.class)) 193 | .to(InterceptorFactory.method(interceptorClass, extendedExecutor))); 194 | } 195 | } else { 196 | builder = builder.method(methodMatcher).intercept(MethodDelegation.to(interceptorClass)); 197 | } 198 | return builder; 199 | } 200 | 201 | @SneakyThrows 202 | private DynamicType.Builder instrumentAdvice(DynamicType.Builder builder, ClassLoader agentClassLoader, Plugin plugin, ClassLoader classLoader) { 203 | ElementMatcher methodMatcher = excludeObjectMethod(plugin); 204 | String adviceClassName = plugin.getAdviceClassName(); 205 | Class adviceClass = agentClassLoader.loadClass(adviceClassName); 206 | 207 | if (io.manbang.gravity.plugin.Advice.class.isAssignableFrom(adviceClass)) { 208 | // 动态创建的 AdviceTemplate 209 | Class adviceTemplateClass = prepareAdviceTemplateClass(classLoader, plugin, adviceClass); 210 | 211 | // 动态生成的类,通过 ClassLoader#getResource的方式,是找不到,因此要指定 ClassFileLocator 212 | String adviceTemplateClassName = getAdviceTemplateClassName(adviceClassName, classLoader); 213 | ClassFileLocator classFileLocator = ClassFileLocator.Simple.of(adviceTemplateClassName, GravityUtils.getAdviceTemplateByteCodes(adviceTemplateClassName)); 214 | 215 | builder = builder.visit(Advice.to(adviceTemplateClass, classFileLocator).on(methodMatcher)); 216 | } else { 217 | builder = builder.visit(Advice.to(adviceClass).on(methodMatcher)); 218 | } 219 | return builder; 220 | } 221 | 222 | @SneakyThrows 223 | private Class prepareAdviceTemplateClass(ClassLoader classLoader, Plugin plugin, Class adviceClass) { 224 | String adviceTemplateClassName = getAdviceTemplateClassName(plugin.getAdviceClassName(), classLoader); 225 | try { 226 | return classLoader.loadClass(adviceTemplateClassName); 227 | } catch (ClassNotFoundException ignore) { 228 | // 还没有加载过 229 | } 230 | 231 | TypePool typePool = TypePool.Default.of(classLoader); 232 | String baseTemplateClassName = getBaseTemplateClassName(plugin); 233 | 234 | TypeDescription templateTypeDescription = typePool.describe(baseTemplateClassName).resolve(); 235 | 236 | byte[] templateClassBytes = new ByteBuddy().redefine(templateTypeDescription, ClassFileLocator.ForClassLoader 237 | .of(classLoader)) 238 | .name(adviceTemplateClassName) 239 | .make().getBytes(); 240 | 241 | ClassInjector.UsingUnsafe.Factory.resolve(GravityUtils.getInstrumentation()) 242 | .make(classLoader, null) 243 | .injectRaw(Collections.singletonMap(adviceTemplateClassName, templateClassBytes)); 244 | 245 | // byte buddy 的 ClassFileLocator 要用到,它需要指定类的字节码信息,用来解析 246 | GravityUtils.putByteCodes(adviceTemplateClassName, templateClassBytes); 247 | final Class adviceTemplateClass = classLoader.loadClass(adviceTemplateClassName); 248 | adviceTemplateClass.getDeclaredMethod(SET_ADVICE_METHOD_NAME, io.manbang.gravity.plugin.Advice.class).invoke(null, io.manbang.gravity.plugin.Advice.of(adviceClass)); 249 | 250 | final ExtendedExecutor extendedExecutor = extendedExecutor(plugin, adviceClass.getClassLoader(), classLoader); 251 | adviceTemplateClass.getDeclaredMethod(SET_EXTENDED_EXECUTOR_METHOD_NAME, ExtendedExecutor.class).invoke(null, extendedExecutor); 252 | return adviceTemplateClass; 253 | } 254 | 255 | private String getAdviceTemplateClassName(String adviceClass, ClassLoader classLoader) { 256 | final String simpleName = classLoader.getClass().getSimpleName(); 257 | final String hashCode = Integer.toHexString(System.identityHashCode(classLoader)); 258 | return String.format("%s_%s_%s$Template", adviceClass, simpleName, hashCode); 259 | } 260 | 261 | private String getBaseTemplateClassName(Plugin plugin) { 262 | if (plugin.isConstructorAdvised()) { 263 | if (plugin.isWithConstructor()) { 264 | return AdviceTemplate.WITH_CONSTRUCTOR_TEMPLATE; 265 | } else { 266 | return AdviceTemplate.MINI_TEMPLATE; 267 | } 268 | } else if (plugin.isSkipEnabled()) { 269 | // 可以终止业务逻辑执行的Advice,必须捕捉异常,否则有可能导致栈溢出 270 | if (plugin.isWithMethod()) { 271 | return SkipAdviceTemplate.WITH_METHOD_TEMPLATE; 272 | } else { 273 | return SkipAdviceTemplate.WITHOUT_METHOD_TEMPLATE; 274 | } 275 | } else { 276 | if (plugin.isWithThrowable() && plugin.isWithMethod()) { 277 | return AdviceTemplate.FULL_TEMPLATE; 278 | } else if (plugin.isWithThrowable()) { 279 | return AdviceTemplate.WITH_THROWABLE_TEMPLATE; 280 | } else if (plugin.isWithMethod()) { 281 | return AdviceTemplate.WITH_METHOD_TEMPLATE; 282 | } else { 283 | return AdviceTemplate.MINI_TEMPLATE; 284 | } 285 | } 286 | } 287 | 288 | private ElementMatcher excludeObjectMethod(Plugin plugin) { 289 | ElementMatcher methodMatcher = plugin.getMethodMatcher(); 290 | if (pluginDefine.ignoreObjectMethod()) { 291 | methodMatcher = not(isDeclaredBy(Object.class)).and(methodMatcher); 292 | } 293 | if (plugin.isConstructorAdvised()) { 294 | methodMatcher = isConstructor().and(methodMatcher); 295 | } 296 | return methodMatcher; 297 | } 298 | 299 | @SneakyThrows 300 | private ExtendedExecutor extendedExecutor(Plugin plugin, ClassLoader source, ClassLoader target) { 301 | final String extendedExecutor = plugin.getExtendedExecutor(); 302 | if (extendedExecutor == null || extendedExecutor.isEmpty()) { 303 | return EmptyExtendedExecutor.INSTANCE; 304 | } else { 305 | //从source提取extendedExecutor字节码,织入到target里面 306 | ClassInjector.UsingUnsafe.Factory.resolve(GravityUtils.getInstrumentation()) 307 | .make(target, null) 308 | .injectRaw(Collections.singletonMap(extendedExecutor, ClassFileLocator.ForClassLoader 309 | .of(source).locate(extendedExecutor).resolve())); 310 | final Class extendedExecutorClazz = target.loadClass(extendedExecutor); 311 | if (ExtendedExecutor.class.isAssignableFrom(extendedExecutorClazz)) { 312 | return (ExtendedExecutor) extendedExecutorClazz.newInstance(); 313 | } else { 314 | log.warning(String.format("%s is not a subtype of ExtendedExecutor", extendedExecutor)); 315 | return EmptyExtendedExecutor.INSTANCE; 316 | } 317 | } 318 | } 319 | } 320 | -------------------------------------------------------------------------------- /gravity-agent/src/main/java/io/manbang/gravity/agent/PluginVo.java: -------------------------------------------------------------------------------- 1 | package io.manbang.gravity.agent; 2 | 3 | import lombok.Data; 4 | 5 | /** 6 | * 插件VO对象 7 | * 8 | * @author duoliang.zhang 9 | * @since 2020/11/18 11:12 10 | */ 11 | @Data 12 | public class PluginVo { 13 | private String path; 14 | private String[] jarTypes; 15 | private String filename; 16 | } 17 | -------------------------------------------------------------------------------- /gravity-demo/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | gravity-parent 7 | io.manbang 8 | 1.0.0 9 | 10 | 4.0.0 11 | 12 | gravity-demo 13 | 14 | 15 | 8 16 | 8 17 | 18 | 19 | -------------------------------------------------------------------------------- /gravity-demo/src/main/java/io/manbang/gravity/trade/Driver.java: -------------------------------------------------------------------------------- 1 | package io.manbang.gravity.trade; 2 | 3 | import java.util.Objects; 4 | 5 | /** 6 | * 司机 7 | * 8 | * @author weilong.hu 9 | * @since 2022/05/19 10:25 10 | */ 11 | public class Driver { 12 | /** 13 | * 接单 14 | */ 15 | public boolean acceptOrder(String address) { 16 | if (Objects.nonNull(address) && address.startsWith("南京市")) { 17 | return true; 18 | } 19 | return false; 20 | } 21 | 22 | /** 23 | * 装货 24 | */ 25 | public String loadCargo() { 26 | return "load cargo success."; 27 | } 28 | 29 | /** 30 | * 运货 31 | */ 32 | public String deliverCargo() { 33 | return "deliver cargo success."; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /gravity-demo/src/main/java/io/manbang/gravity/trade/Order.java: -------------------------------------------------------------------------------- 1 | package io.manbang.gravity.trade; 2 | 3 | /** 4 | * @author weilong.hu 5 | * @since 2022/05/19 10:48 6 | */ 7 | public class Order { 8 | 9 | public static void main(String[] args) { 10 | new Order().trade(); 11 | } 12 | 13 | public void trade() { 14 | final Shippers shippers = new Shippers(); 15 | final Driver driver = new Driver(); 16 | final String address = shippers.postOrder(); 17 | driver.acceptOrder(address); 18 | driver.loadCargo(); 19 | driver.deliverCargo(); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /gravity-demo/src/main/java/io/manbang/gravity/trade/Shippers.java: -------------------------------------------------------------------------------- 1 | package io.manbang.gravity.trade; 2 | 3 | /** 4 | * 货主 5 | * 6 | * @author weilong.hu 7 | * @since 2022/05/19 10:26 8 | */ 9 | public class Shippers { 10 | /** 11 | * 发布订单 12 | */ 13 | public String postOrder() { 14 | return "南京市 雨花台区 万博科技园 运满满总部 50立方 10吨"; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /gravity-plugin-api/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | io.manbang 7 | gravity-parent 8 | 1.0.0 9 | 10 | 4.0.0 11 | 12 | gravity-plugin-api 13 | 1.0.0 14 | 15 | 16 | UTF-8 17 | 1.8 18 | ${java.version} 19 | ${java.version} 20 | 1.0.2 21 | 1.18.12 22 | 23 | 24 | 25 | 26 | net.bytebuddy 27 | byte-buddy 28 | 29 | 30 | * 31 | * 32 | 33 | 34 | 35 | 36 | org.projectlombok 37 | lombok 38 | ${lombok.version} 39 | provided 40 | true 41 | 42 | 43 | 44 | 45 | 46 | org.apache.maven.plugins 47 | maven-source-plugin 48 | 3.2.0 49 | 50 | 51 | attach-sources 52 | package 53 | 54 | jar-no-fork 55 | 56 | 57 | 58 | 59 | false 60 | 61 | 62 | 63 | org.apache.maven.plugins 64 | maven-compiler-plugin 65 | 3.8.1 66 | 67 | ${java.version} 68 | ${java.version} 69 | ${java.version} 70 | UTF-8 71 | 72 | 73 | 74 | 75 | 76 | -------------------------------------------------------------------------------- /gravity-plugin-api/src/main/java/io/manbang/gravity/plugin/Advice.java: -------------------------------------------------------------------------------- 1 | package io.manbang.gravity.plugin; 2 | 3 | /** 4 | * 切面,这里的回调会直接植入到目标方法的方法体前后 5 | *
 6 |  *     // original class
 7 |  *     public class Foo {
 8 |  *         public void sayHello() {
 9 |  *             System.out.println("Hello Advice);
10 |  *         }
11 |  *     }
12 |  *
13 |  *     // advice class
14 |  *     public class FooAdvice {
15 |  *         public void enterMethod(ExecuteContext context) {
16 |  *              System.err.println("Hello Enter Method.");
17 |  *         }
18 |  *
19 |  *         public void exitMethod(ExecuteContext context) {
20 |  *             System.err.println("Hello Exit Method.);
21 |  *         }
22 |  *     }
23 |  *
24 |  *     // Foo final pseudocode class
25 |  *     public class Foo {
26 |  *         public void sayHello() {
27 |  *             System.err.println("Hello Enter Method.");
28 |  *             System.out.println("Hello Advice);
29 |  *             System.err.println("Hello Exit Method.);
30 |  *         }
31 |  *     }
32 |  * 
33 | * 34 | * @author duoliang.zhang 35 | * @since 2020/8/22 11:25 36 | */ 37 | public interface Advice extends Named, LifeCycle, Switchable { 38 | 39 | static io.manbang.gravity.plugin.Advice of(Class adviceClass) { 40 | return AdviceHolder.getOrCreate(adviceClass); 41 | } 42 | 43 | /** 44 | * 方法体前执行逻辑,如果想提前终止业务方法执行的话,可以执行 {@link ExecuteContext#skip()} 45 | * 想要改变返回值的话,在 {@link #exitMethod(ExecuteContext)} 中设置自定义的返回值 46 | * 47 | * @param context 执行上下文 48 | */ 49 | default void enterMethod(ExecuteContext context) { 50 | } 51 | 52 | /** 53 | * 方法提后逻辑 54 | * 55 | * @param context 执行上下文 56 | */ 57 | default void exitMethod(ExecuteContext context) { 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /gravity-plugin-api/src/main/java/io/manbang/gravity/plugin/AdviceHolder.java: -------------------------------------------------------------------------------- 1 | package io.manbang.gravity.plugin; 2 | 3 | import java.util.Map; 4 | import java.util.concurrent.ConcurrentHashMap; 5 | 6 | /** 7 | * @author duoliang.zhang 8 | * @since 2021/8/26 15:02 9 | */ 10 | class AdviceHolder { 11 | private static final Map, Advice> ADVICES = new ConcurrentHashMap<>(); 12 | 13 | private AdviceHolder() { 14 | throw new UnsupportedOperationException(); 15 | } 16 | 17 | /** 18 | * 将原始 {@link Advice} 做一次包装,如果Advice支持开关的话,会封装成 {@link SwitcherAdvice},否则直接返回 19 | * 20 | * @param advice 切面插件 21 | * @return 如果是可以开关控制的 {@link Advice},返回包装后的 {@link SwitcherAdvice} 22 | */ 23 | private static Advice switcher(Advice advice) { 24 | return advice.isSwitchable() ? new SwitcherAdvice(advice) : advice; 25 | } 26 | 27 | public static Advice getOrCreate(Class adviceClass) { 28 | return ADVICES.computeIfAbsent(adviceClass, clazz -> { 29 | try { 30 | return switcher((Advice) clazz.getConstructor().newInstance()); 31 | } catch (Exception e) { 32 | throw new GravityException(e); 33 | } 34 | }); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /gravity-plugin-api/src/main/java/io/manbang/gravity/plugin/AdviceTemplate.java: -------------------------------------------------------------------------------- 1 | package io.manbang.gravity.plugin; 2 | 3 | import net.bytebuddy.asm.Advice; 4 | import net.bytebuddy.implementation.bytecode.assign.Assigner; 5 | 6 | import java.lang.reflect.Constructor; 7 | import java.lang.reflect.Method; 8 | import java.util.logging.Level; 9 | import java.util.logging.Logger; 10 | 11 | import static io.manbang.gravity.plugin.Constants.ADVICE_LOGGER_NAME; 12 | import static io.manbang.gravity.plugin.Constants.ENTER_METHOD_ERROR; 13 | import static io.manbang.gravity.plugin.Constants.EXIT_METHOD_ERROR; 14 | 15 | /** 16 | * Advice的执行模板,因为如果Advice需要捕捉异常的话,对性能损失比较大,所以分两种情况,一种是不捕捉异常,一种捕捉异常,大家酌情使用 17 | * 之所以要动态生成模板,是因为Advice方式的拦截,需要静态方法拦截,同时我们又系统将自定义的注入逻辑加到被拦截的方法上,只能通过静态属性, 18 | * 才能让静态方法访问,如果变成了静态属性,那么就会变成一个共享变量,但不同的Advice实现,肯定是不能冲突的,所以只能通过动态创建Advice模板类型的方式, 19 | * 让Advice属性不共享,每个Advice单独生成一个模板类,类名是 Advice#fqcn$Template,也即是每个Advice的一个 Template内部类。 20 | * 相当于每个Advice会变成如下代码,内部多了个 Template 静态内部类: 21 | *
 22 |  * public class TimingAdvice implements Advice {
 23 |  *     public static final class Template {
 24 |  *     // Advice Template 中的代码
 25 |  *     }
 26 |  *
 27 |  *     // enterMethod 逻辑
 28 |  *     // exitMethod 逻辑
 29 |  * }
 30 |  * 
31 | * 32 | * @author duoliang.zhang 33 | * @since 2020/8/26 9:36 34 | */ 35 | public class AdviceTemplate { 36 | public static final String WITH_THROWABLE_TEMPLATE = "io.manbang.gravity.plugin.AdviceTemplate$WithThrowableTemplate"; 37 | public static final String WITH_METHOD_TEMPLATE = "io.manbang.gravity.plugin.AdviceTemplate$WithMethodTemplate"; 38 | public static final String WITH_CONSTRUCTOR_TEMPLATE = "io.manbang.gravity.plugin.AdviceTemplate$WithConstructorTemplate"; 39 | public static final String MINI_TEMPLATE = "io.manbang.gravity.plugin.AdviceTemplate$MiniTemplate"; 40 | public static final String FULL_TEMPLATE = "io.manbang.gravity.plugin.AdviceTemplate$FullTemplate"; 41 | public static final Logger LOGGER = Logger.getLogger(ADVICE_LOGGER_NAME); 42 | 43 | private AdviceTemplate() { 44 | throw new UnsupportedOperationException(); 45 | } 46 | 47 | public static ExecuteContext getContext(Class targetClass, Object target, Method method, Object[] arguments, Constructor constructor, ExtendedExecutor extendedExecutor) { 48 | return ExecuteContext.builder() 49 | .targetClass(targetClass) 50 | .target(target) 51 | .method(method) 52 | .arguments(arguments) 53 | .constructor(constructor) 54 | .extraExecutor(extendedExecutor) 55 | .build(); 56 | } 57 | 58 | public static class MiniTemplate { 59 | private static io.manbang.gravity.plugin.Advice advice; 60 | private static ExtendedExecutor extendedExecutor; 61 | 62 | private MiniTemplate() { 63 | throw new UnsupportedOperationException(); 64 | } 65 | 66 | public static io.manbang.gravity.plugin.Advice getAdvice() { 67 | return advice; 68 | } 69 | 70 | public static void setAdvice(io.manbang.gravity.plugin.Advice advice) { 71 | MiniTemplate.advice = advice; 72 | } 73 | 74 | public static ExtendedExecutor getExtendedExecutor() { 75 | return extendedExecutor; 76 | } 77 | 78 | public static void setExtendedExecutor(ExtendedExecutor extendedExecutor) { 79 | MiniTemplate.extendedExecutor = extendedExecutor; 80 | } 81 | 82 | @Advice.OnMethodEnter 83 | public static ExecuteContext enterMethod(@Advice.Origin Class targetClass, 84 | @Advice.This(optional = true) Object target, 85 | @Advice.AllArguments(readOnly = false, typing = Assigner.Typing.DYNAMIC) Object[] arguments) { 86 | ExecuteContext context = getContext(targetClass, target, null, arguments, null, getExtendedExecutor()); 87 | try { 88 | getAdvice().enterMethod(context); 89 | arguments = context.getArguments(); 90 | } catch (Throwable e) { 91 | LOGGER.log(Level.WARNING, ENTER_METHOD_ERROR, e); 92 | } 93 | return context; 94 | } 95 | 96 | @Advice.OnMethodExit 97 | public static void exitMethod(@Advice.Enter ExecuteContext context, 98 | @Advice.This(optional = true) Object target, 99 | @Advice.Return(readOnly = false, typing = Assigner.Typing.DYNAMIC) Object result) { 100 | context.setTarget(target); 101 | context.setResult(result); 102 | 103 | try { 104 | getAdvice().exitMethod(context); 105 | result = context.getResult(); 106 | } catch (Throwable e) { 107 | LOGGER.log(Level.WARNING, EXIT_METHOD_ERROR, e); 108 | } 109 | } 110 | } 111 | 112 | public static class WithMethodTemplate { 113 | private static io.manbang.gravity.plugin.Advice advice; 114 | private static ExtendedExecutor extendedExecutor; 115 | 116 | private WithMethodTemplate() { 117 | throw new UnsupportedOperationException(); 118 | } 119 | 120 | public static io.manbang.gravity.plugin.Advice getAdvice() { 121 | return advice; 122 | } 123 | 124 | public static void setAdvice(io.manbang.gravity.plugin.Advice advice) { 125 | WithMethodTemplate.advice = advice; 126 | } 127 | 128 | public static ExtendedExecutor getExtendedExecutor() { 129 | return extendedExecutor; 130 | } 131 | 132 | public static void setExtendedExecutor(ExtendedExecutor extendedExecutor) { 133 | WithMethodTemplate.extendedExecutor = extendedExecutor; 134 | } 135 | 136 | @Advice.OnMethodEnter 137 | public static ExecuteContext enterMethod(@Advice.Origin Class targetClass, 138 | @Advice.This(optional = true) Object target, 139 | @Advice.Origin Method method, 140 | @Advice.AllArguments(readOnly = false, typing = Assigner.Typing.DYNAMIC) Object[] arguments) { 141 | ExecuteContext context = getContext(targetClass, target, method, arguments, null, getExtendedExecutor()); 142 | try { 143 | getAdvice().enterMethod(context); 144 | arguments = context.getArguments(); 145 | } catch (Throwable e) { 146 | LOGGER.log(Level.WARNING, ENTER_METHOD_ERROR, e); 147 | } 148 | return context; 149 | } 150 | 151 | @Advice.OnMethodExit 152 | public static void exitMethod(@Advice.Enter ExecuteContext context, 153 | @Advice.This(optional = true) Object target, 154 | @Advice.Return(readOnly = false, typing = Assigner.Typing.DYNAMIC) Object result) { 155 | context.setTarget(target); 156 | context.setResult(result); 157 | 158 | try { 159 | getAdvice().exitMethod(context); 160 | result = context.getResult(); 161 | } catch (Throwable e) { 162 | LOGGER.log(Level.WARNING, EXIT_METHOD_ERROR, e); 163 | } 164 | } 165 | } 166 | 167 | public static class WithConstructorTemplate { 168 | private static io.manbang.gravity.plugin.Advice advice; 169 | private static ExtendedExecutor extendedExecutor; 170 | 171 | private WithConstructorTemplate() { 172 | throw new UnsupportedOperationException(); 173 | } 174 | 175 | public static io.manbang.gravity.plugin.Advice getAdvice() { 176 | return advice; 177 | } 178 | 179 | public static void setAdvice(io.manbang.gravity.plugin.Advice advice) { 180 | WithConstructorTemplate.advice = advice; 181 | } 182 | 183 | public static ExtendedExecutor getExtendedExecutor() { 184 | return extendedExecutor; 185 | } 186 | 187 | public static void setExtendedExecutor(ExtendedExecutor extendedExecutor) { 188 | WithConstructorTemplate.extendedExecutor = extendedExecutor; 189 | } 190 | 191 | @Advice.OnMethodEnter 192 | public static ExecuteContext enterMethod(@Advice.Origin Class targetClass, 193 | @Advice.This(optional = true) Object target, 194 | @Advice.Origin Constructor constructor, 195 | @Advice.AllArguments(readOnly = false, typing = Assigner.Typing.DYNAMIC) Object[] arguments) { 196 | ExecuteContext context = getContext(targetClass, target, null, arguments, constructor, getExtendedExecutor()); 197 | try { 198 | getAdvice().enterMethod(context); 199 | arguments = context.getArguments(); 200 | } catch (Throwable e) { 201 | LOGGER.log(Level.WARNING, ENTER_METHOD_ERROR, e); 202 | } 203 | return context; 204 | } 205 | 206 | @Advice.OnMethodExit 207 | public static void exitMethod(@Advice.Enter ExecuteContext context, 208 | @Advice.This(optional = true) Object target, 209 | @Advice.Return(readOnly = false, typing = Assigner.Typing.DYNAMIC) Object result) { 210 | context.setTarget(target); 211 | context.setResult(result); 212 | 213 | try { 214 | getAdvice().exitMethod(context); 215 | result = context.getResult(); 216 | } catch (Throwable e) { 217 | LOGGER.log(Level.WARNING, EXIT_METHOD_ERROR, e); 218 | } 219 | } 220 | } 221 | 222 | public static class WithThrowableTemplate { 223 | private static io.manbang.gravity.plugin.Advice advice; 224 | private static ExtendedExecutor extendedExecutor; 225 | 226 | private WithThrowableTemplate() { 227 | throw new UnsupportedOperationException(); 228 | } 229 | 230 | public static io.manbang.gravity.plugin.Advice getAdvice() { 231 | return advice; 232 | } 233 | 234 | public static void setAdvice(io.manbang.gravity.plugin.Advice advice) { 235 | WithThrowableTemplate.advice = advice; 236 | } 237 | 238 | public static ExtendedExecutor getExtendedExecutor() { 239 | return extendedExecutor; 240 | } 241 | 242 | public static void setExtendedExecutor(ExtendedExecutor extendedExecutor) { 243 | WithThrowableTemplate.extendedExecutor = extendedExecutor; 244 | } 245 | 246 | @Advice.OnMethodEnter 247 | public static ExecuteContext enterMethod(@Advice.Origin Class targetClass, 248 | @Advice.This(optional = true) Object target, 249 | @Advice.AllArguments(readOnly = false, typing = Assigner.Typing.DYNAMIC) Object[] arguments) { 250 | ExecuteContext context = getContext(targetClass, target, null, arguments, null, getExtendedExecutor()); 251 | try { 252 | getAdvice().enterMethod(context); 253 | arguments = context.getArguments(); 254 | } catch (Throwable e) { 255 | LOGGER.log(Level.WARNING, ENTER_METHOD_ERROR, e); 256 | } 257 | return context; 258 | } 259 | 260 | @Advice.OnMethodExit(onThrowable = Throwable.class) 261 | public static void exitMethod(@Advice.Enter ExecuteContext context, 262 | @Advice.This(optional = true) Object target, 263 | @Advice.Return(readOnly = false, typing = Assigner.Typing.DYNAMIC) Object result, 264 | @Advice.Thrown(readOnly = false) Throwable throwable) { 265 | context.setTarget(target); 266 | context.setResult(result); 267 | context.setThrowable(throwable); 268 | 269 | try { 270 | getAdvice().exitMethod(context); 271 | result = context.getResult(); 272 | throwable = context.getThrowable(); 273 | } catch (Throwable e) { 274 | LOGGER.log(Level.WARNING, EXIT_METHOD_ERROR, e); 275 | } 276 | } 277 | } 278 | 279 | public static class FullTemplate { 280 | private static io.manbang.gravity.plugin.Advice advice; 281 | private static ExtendedExecutor extendedExecutor; 282 | 283 | private FullTemplate() { 284 | throw new UnsupportedOperationException(); 285 | } 286 | 287 | public static io.manbang.gravity.plugin.Advice getAdvice() { 288 | return advice; 289 | } 290 | 291 | public static void setAdvice(io.manbang.gravity.plugin.Advice advice) { 292 | FullTemplate.advice = advice; 293 | } 294 | 295 | public static ExtendedExecutor getExtendedExecutor() { 296 | return extendedExecutor; 297 | } 298 | 299 | public static void setExtendedExecutor(ExtendedExecutor extendedExecutor) { 300 | FullTemplate.extendedExecutor = extendedExecutor; 301 | } 302 | 303 | @Advice.OnMethodEnter 304 | public static ExecuteContext enterMethod(@Advice.Origin Class targetClass, 305 | @Advice.This(optional = true) Object target, 306 | @Advice.Origin Method method, 307 | @Advice.AllArguments(readOnly = false, typing = Assigner.Typing.DYNAMIC) Object[] arguments) { 308 | ExecuteContext context = getContext(targetClass, target, method, arguments, null, getExtendedExecutor()); 309 | try { 310 | getAdvice().enterMethod(context); 311 | arguments = context.getArguments(); 312 | } catch (Throwable e) { 313 | LOGGER.log(Level.WARNING, ENTER_METHOD_ERROR, e); 314 | } 315 | return context; 316 | } 317 | 318 | @Advice.OnMethodExit(onThrowable = Throwable.class) 319 | public static void exitMethod(@Advice.Enter ExecuteContext context, 320 | @Advice.This(optional = true) Object target, 321 | @Advice.Return(readOnly = false, typing = Assigner.Typing.DYNAMIC) Object result, 322 | @Advice.Thrown(readOnly = false) Throwable throwable) { 323 | context.setTarget(target); 324 | context.setResult(result); 325 | context.setThrowable(throwable); 326 | 327 | try { 328 | getAdvice().exitMethod(context); 329 | result = context.getResult(); 330 | throwable = context.getThrowable(); 331 | } catch (Throwable e) { 332 | LOGGER.log(Level.WARNING, EXIT_METHOD_ERROR, e); 333 | } 334 | } 335 | } 336 | } 337 | 338 | -------------------------------------------------------------------------------- /gravity-plugin-api/src/main/java/io/manbang/gravity/plugin/AgentBuilderCustomizer.java: -------------------------------------------------------------------------------- 1 | package io.manbang.gravity.plugin; 2 | 3 | 4 | import net.bytebuddy.agent.builder.AgentBuilder; 5 | 6 | /** 7 | * 代理构建器定制器SPI,用户可以自定义,如果没有自定义需求,则利用默认定制器 {@link DefaultAgentBuilderCustomizer} 8 | * 9 | * @author 章多亮 10 | * @since 2020-08-07 11 | */ 12 | public interface AgentBuilderCustomizer { 13 | /** 14 | * 当没有用户自定义的定制器时,用默认的定制器 15 | * 16 | * @param pluginClassLoader 插件加载器 17 | * @return 代理构建定制器 18 | */ 19 | static AgentBuilderCustomizer customizer(AgentPluginClassLoader pluginClassLoader) { 20 | return Services.loadFirst(AgentBuilderCustomizer.class, pluginClassLoader, DefaultAgentBuilderCustomizer.INSTANCE); 21 | } 22 | 23 | /** 24 | * 用户自定义代理构建器 25 | * 26 | * @param options Agent入参 27 | * @param builder 代理构建器 28 | * @return 配置后的代理构建器 29 | */ 30 | AgentBuilder customize(final AgentOptions options, final AgentBuilder builder); 31 | } 32 | -------------------------------------------------------------------------------- /gravity-plugin-api/src/main/java/io/manbang/gravity/plugin/AgentOptions.java: -------------------------------------------------------------------------------- 1 | package io.manbang.gravity.plugin; 2 | 3 | import java.util.Collections; 4 | import java.util.HashMap; 5 | import java.util.Map; 6 | 7 | /** 8 | * 代理配置选项 9 | * 10 | * @author dzhang 11 | * @since 2020/08/18 12 | */ 13 | public enum AgentOptions { 14 | /** 15 | * instance 16 | */ 17 | INSTANCE; 18 | 19 | private final String argument; 20 | private final Map options; 21 | 22 | AgentOptions() { 23 | this.argument = System.getProperty("agent.options"); 24 | this.options = doParse(argument); 25 | } 26 | 27 | private Map doParse(String argument) { 28 | if (argument == null || argument.trim().isEmpty()) { 29 | return Collections.emptyMap(); 30 | } 31 | 32 | Map optionMap = new HashMap<>(8); 33 | 34 | String[] arguments = argument.split("\\s*,\\s*"); 35 | for (String arg : arguments) { 36 | String[] keyValue = arg.split("\\s*=\\s*"); 37 | 38 | if (keyValue.length == 0) { 39 | continue; 40 | } 41 | 42 | if (keyValue.length == 1) { 43 | optionMap.put(keyValue[0], null); 44 | } 45 | 46 | if (keyValue.length == 2) { 47 | optionMap.put(keyValue[0], keyValue[1]); 48 | } 49 | } 50 | optionMap.forEach((k, v) -> System.setProperty("gravity." + k, v)); 51 | return optionMap; 52 | } 53 | 54 | public boolean isLocalDebug() { 55 | return getBoolean("localDebug", false); 56 | } 57 | 58 | public String getString(String key, String defaultValue) { 59 | return options.getOrDefault(key, defaultValue); 60 | } 61 | 62 | public boolean getBoolean(String key, boolean defaultValue) { 63 | return options.containsKey(key) ? Boolean.parseBoolean(options.get(key)) : defaultValue; 64 | } 65 | 66 | public int getInt(String key, int defaultValue) { 67 | return options.containsKey(key) ? Integer.parseInt(options.get(key)) : defaultValue; 68 | } 69 | 70 | public long getLong(String key, long defaultValue) { 71 | return options.containsKey(key) ? Long.parseLong(options.get(key)) : defaultValue; 72 | } 73 | 74 | public String getString(String key) { 75 | return options.get(key); 76 | } 77 | 78 | public boolean getBoolean(String key) { 79 | return Boolean.parseBoolean(options.get(key)); 80 | } 81 | 82 | public int getInt(String key) { 83 | return Integer.parseInt(options.get(key)); 84 | } 85 | 86 | public long getLong(String key) { 87 | return Long.parseLong(options.get(key)); 88 | } 89 | 90 | public String getArgument() { 91 | return argument; 92 | } 93 | 94 | public String getAppName() { 95 | return getString("appName"); 96 | } 97 | 98 | public String getAppType() { 99 | return getString("appType"); 100 | } 101 | 102 | public String getBaseUrl() { 103 | return getString("baseUrl"); 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /gravity-plugin-api/src/main/java/io/manbang/gravity/plugin/AgentPluginClassLoader.java: -------------------------------------------------------------------------------- 1 | package io.manbang.gravity.plugin; 2 | 3 | import java.io.IOException; 4 | import java.net.URL; 5 | import java.net.URLClassLoader; 6 | import java.util.Arrays; 7 | import java.util.Enumeration; 8 | import java.util.HashSet; 9 | import java.util.Set; 10 | import java.util.concurrent.atomic.AtomicReference; 11 | 12 | /** 13 | * 调整类加载器加载机制,优先从当前类加载器加载资源,针对guava等容易依赖冲突得第三方包,打包时不用再shade修改包名等处理 14 | * 可能出现得问题: 15 | * 如果插件打包时,引入了多余得jar包可能出现如下问题 16 | * 1.spring等框架根据注解扫描得方式失效 17 | * 2.SPI初始化异常 18 | * 3.常量获取不对,常见用ThreadLocal传递上下文 19 | * 4.。。。。 20 | * 原因主要是,目标业务使用得class是由业务自身得类加载器加载,在gravity内部得class是由AgentPluginClassLoader加载,相同得class加载两次导致异常,所以插件打包时,要排除多余得jar 21 | * 22 | * @author duoliang.zhang 23 | */ 24 | public class AgentPluginClassLoader extends URLClassLoader { 25 | private static final AtomicReference AGENT_JAR_URLS = new AtomicReference<>(); 26 | private static Set FILTER_RESOURCES = new HashSet<>(); 27 | 28 | static { 29 | ClassLoader.registerAsParallelCapable(); 30 | } 31 | 32 | private boolean useParent = true; 33 | 34 | /** 35 | * 默认类加载器useParent = true,因为影响io.manbang.gravity.plugin.ttl.TtlPluginDefine#prepare等需要对jdk源码织入的插件 36 | */ 37 | public AgentPluginClassLoader() { 38 | super(getAgentJarUrls()); 39 | } 40 | 41 | public AgentPluginClassLoader(ClassLoader parent) { 42 | this(parent, false); 43 | } 44 | 45 | public AgentPluginClassLoader(ClassLoader parent, boolean useParent) { 46 | super(getAgentJarUrls(), parent); 47 | this.useParent = useParent; 48 | } 49 | 50 | public static void addFilterResources(String... names) { 51 | FILTER_RESOURCES.addAll(Arrays.asList(names)); 52 | } 53 | 54 | private static URL[] getAgentJarUrls() { 55 | URL[] urls = AGENT_JAR_URLS.get(); 56 | if (urls != null) { 57 | return urls; 58 | } 59 | 60 | urls = GravityHome.INSTANCE.listFileUrls(JarType.AGENT); 61 | AGENT_JAR_URLS.compareAndSet(null, urls); 62 | return urls; 63 | } 64 | 65 | @Override 66 | protected Class loadClass(String name, boolean resolve) throws ClassNotFoundException { 67 | if (useParent) { 68 | return super.loadClass(name, resolve); 69 | } 70 | synchronized (getClassLoadingLock(name)) { 71 | Class c = findLoadedClass(name); 72 | if (c == null) { 73 | try { 74 | c = findClass(name); 75 | } catch (ClassNotFoundException ignore) { 76 | // 77 | } 78 | if (c == null) { 79 | c = super.loadClass(name, resolve); 80 | } else if (resolve) { 81 | resolveClass(c); 82 | } 83 | 84 | } 85 | return c; 86 | } 87 | } 88 | 89 | @Override 90 | public URL getResource(String name) { 91 | if (!FILTER_RESOURCES.contains(name)) { 92 | return super.getResource(name); 93 | } 94 | return findResource(name); 95 | } 96 | 97 | @Override 98 | public Enumeration getResources(String name) throws IOException { 99 | if (!FILTER_RESOURCES.contains(name)) { 100 | return super.getResources(name); 101 | } 102 | return findResources(name); 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /gravity-plugin-api/src/main/java/io/manbang/gravity/plugin/AlwaysWitness.java: -------------------------------------------------------------------------------- 1 | package io.manbang.gravity.plugin; 2 | 3 | enum AlwaysWitness implements Witness { 4 | INSTANCE; 5 | 6 | @Override 7 | public boolean saw(ClassLoader classLoader) { 8 | return true; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /gravity-plugin-api/src/main/java/io/manbang/gravity/plugin/AlwaysWorkingSwitcher.java: -------------------------------------------------------------------------------- 1 | package io.manbang.gravity.plugin; 2 | 3 | /** 4 | * 总是起作用的开关 5 | * 6 | * @author duoliang.zhang 7 | * @since 2020/11/5 9:51 8 | */ 9 | enum AlwaysWorkingSwitcher implements Switcher { 10 | INSTANCE; 11 | 12 | @Override 13 | public void addListener(SwitcherListener listener) { 14 | listener.on(); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /gravity-plugin-api/src/main/java/io/manbang/gravity/plugin/CacheHolder.java: -------------------------------------------------------------------------------- 1 | package io.manbang.gravity.plugin; 2 | 3 | import java.util.Map; 4 | import java.util.concurrent.ConcurrentHashMap; 5 | 6 | /** 7 | * 开关缓存容器 8 | * 9 | * @author duoliang.zhang 10 | * @since 2020/11/5 10:02 11 | */ 12 | enum CacheHolder { 13 | /** 14 | * instance 15 | */ 16 | INSTANCE; 17 | private static final Map, Map> HOLDER = new ConcurrentHashMap<>(); 18 | 19 | /** 20 | * 载入开关服务,如果缓存中,不存在,则从SPI中加载实现 21 | * 22 | * @param classLoader 开关SPI所在的类加载器 23 | * @return 开关 24 | */ 25 | @SuppressWarnings("unchecked") 26 | public T loadIfAbsent(ClassLoader classLoader, Class clazz, T defaultValue) { 27 | final Map cacheMap = HOLDER.computeIfAbsent(clazz, c -> new ConcurrentHashMap<>()); 28 | return (T) cacheMap.computeIfAbsent(classLoader, cl -> Services.loadFirst(clazz, cl, defaultValue)); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /gravity-plugin-api/src/main/java/io/manbang/gravity/plugin/ClassesWitness.java: -------------------------------------------------------------------------------- 1 | package io.manbang.gravity.plugin; 2 | 3 | import lombok.extern.java.Log; 4 | 5 | import java.net.URL; 6 | import java.util.stream.Stream; 7 | 8 | @Log 9 | class ClassesWitness implements Witness { 10 | private final String[] classNames; 11 | 12 | ClassesWitness(String... classNames) { 13 | this.classNames = Stream.of(classNames) 14 | .map(n -> n.replace(".", "/")) 15 | .map(n -> n.concat(".class")) 16 | .toArray(String[]::new); 17 | } 18 | 19 | @Override 20 | public boolean saw(ClassLoader classLoader) { 21 | for (String name : classNames) { 22 | URL resource = classLoader.getResource(name); 23 | if (resource == null) { 24 | return false; 25 | } 26 | } 27 | 28 | return true; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /gravity-plugin-api/src/main/java/io/manbang/gravity/plugin/Constants.java: -------------------------------------------------------------------------------- 1 | package io.manbang.gravity.plugin; 2 | 3 | /** 4 | * @author duoliang.zhang 5 | * @since 2021/8/26 17:46 6 | */ 7 | public class Constants { 8 | public static final String EXIT_METHOD_ERROR = "重力退出方法回调异常,不影响业务,如出现次数较多,可联系gravity负责人定位。"; 9 | public static final String ENTER_METHOD_ERROR = "重力进入方法回调异常,不影响业务,如出现次数较多,可联系gravity负责人定位。"; 10 | public static final String ADVICE_LOGGER_NAME = "G:Advice"; 11 | 12 | private Constants() { 13 | throw new UnsupportedOperationException(); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /gravity-plugin-api/src/main/java/io/manbang/gravity/plugin/ConstructorInterceptorFactory.java: -------------------------------------------------------------------------------- 1 | package io.manbang.gravity.plugin; 2 | 3 | import java.util.Map; 4 | import java.util.concurrent.ConcurrentHashMap; 5 | 6 | /** 7 | * @author weilong.hu 8 | * @since 2022/01/22 14:27 9 | */ 10 | class ConstructorInterceptorFactory implements InterceptorFactory { 11 | static final InterceptorFactory INSTANCE = new ConstructorInterceptorFactory(); 12 | private static final Map TEMPLATES = new ConcurrentHashMap<>(); 13 | 14 | private ConstructorInterceptorFactory() { 15 | } 16 | 17 | @Override 18 | public Object create(Class interceptorClass, ExtendedExecutor extendedExecutor) { 19 | return TEMPLATES.computeIfAbsent(interceptorClass, c -> new ConstructorInterceptorTemplate(interceptorClass, extendedExecutor)); 20 | } 21 | 22 | @Override 23 | public Object create(Interceptor interceptor, ExtendedExecutor extendedExecutor) { 24 | return TEMPLATES.computeIfAbsent(interceptor, k -> new ConstructorInterceptorTemplate(interceptor, extendedExecutor)); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /gravity-plugin-api/src/main/java/io/manbang/gravity/plugin/ConstructorInterceptorTemplate.java: -------------------------------------------------------------------------------- 1 | package io.manbang.gravity.plugin; 2 | 3 | 4 | import net.bytebuddy.implementation.bind.annotation.AllArguments; 5 | import net.bytebuddy.implementation.bind.annotation.Origin; 6 | import net.bytebuddy.implementation.bind.annotation.RuntimeType; 7 | import net.bytebuddy.implementation.bind.annotation.This; 8 | 9 | import java.lang.reflect.Constructor; 10 | 11 | import static io.manbang.gravity.plugin.ReflectUtil.newInstance; 12 | 13 | /** 14 | * @author weilong.hu 15 | * @since 2021/09/13 10:54 16 | */ 17 | public class ConstructorInterceptorTemplate { 18 | private final Interceptor interceptor; 19 | private final ExtendedExecutor extendedExecutor; 20 | 21 | ConstructorInterceptorTemplate(Class interceptorClass, ExtendedExecutor extendedExecutor) { 22 | this.interceptor = Interceptor.switcher(newInstance(interceptorClass)); 23 | this.extendedExecutor = extendedExecutor; 24 | } 25 | 26 | ConstructorInterceptorTemplate(Interceptor interceptor, ExtendedExecutor extendedExecutor) { 27 | this.interceptor = Interceptor.switcher(interceptor); 28 | this.extendedExecutor = extendedExecutor; 29 | } 30 | 31 | @RuntimeType 32 | public void intercept(@Origin Class targetClass, 33 | @This(optional = true) Object target, 34 | @Origin Constructor constructor, 35 | @AllArguments Object[] arguments) { 36 | 37 | ExecuteContext context = ExecuteContext.builder() 38 | .targetClass(targetClass) 39 | .target(target) 40 | .constructor(constructor) 41 | .arguments(arguments) 42 | .extraExecutor(extendedExecutor) 43 | .build(); 44 | 45 | interceptor.onCompleted(context); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /gravity-plugin-api/src/main/java/io/manbang/gravity/plugin/DefaultAgentBuilderCustomizer.java: -------------------------------------------------------------------------------- 1 | package io.manbang.gravity.plugin; 2 | 3 | 4 | import net.bytebuddy.agent.builder.AgentBuilder; 5 | 6 | enum DefaultAgentBuilderCustomizer implements AgentBuilderCustomizer { 7 | INSTANCE; 8 | 9 | @Override 10 | public AgentBuilder customize(AgentOptions options, AgentBuilder builder) { 11 | return builder.with(AgentBuilder.RedefinitionStrategy.RETRANSFORMATION); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /gravity-plugin-api/src/main/java/io/manbang/gravity/plugin/DefineMethodInterceptorFactory.java: -------------------------------------------------------------------------------- 1 | package io.manbang.gravity.plugin; 2 | 3 | import java.util.Map; 4 | import java.util.concurrent.ConcurrentHashMap; 5 | 6 | /** 7 | * @author weilong.hu 8 | * @since 2022/01/22 14:34 9 | */ 10 | 11 | class DefineMethodInterceptorFactory implements InterceptorFactory { 12 | static final InterceptorFactory INSTANCE = new DefineMethodInterceptorFactory(); 13 | static final Map TEMPLATES = new ConcurrentHashMap<>(); 14 | 15 | private DefineMethodInterceptorFactory() { 16 | } 17 | 18 | @Override 19 | public Object create(Class interceptorClass, ExtendedExecutor extendedExecutor) { 20 | return TEMPLATES.computeIfAbsent(interceptorClass, c -> new DefineMethodInterceptorTemplate(interceptorClass, extendedExecutor)); 21 | } 22 | 23 | @Override 24 | public Object create(Interceptor interceptor, ExtendedExecutor extendedExecutor) { 25 | return TEMPLATES.computeIfAbsent(interceptor, k -> new DefineMethodInterceptorTemplate(interceptor, extendedExecutor)); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /gravity-plugin-api/src/main/java/io/manbang/gravity/plugin/DefineMethodInterceptorTemplate.java: -------------------------------------------------------------------------------- 1 | package io.manbang.gravity.plugin; 2 | 3 | import net.bytebuddy.implementation.bind.annotation.AllArguments; 4 | import net.bytebuddy.implementation.bind.annotation.Origin; 5 | import net.bytebuddy.implementation.bind.annotation.RuntimeType; 6 | import net.bytebuddy.implementation.bind.annotation.This; 7 | 8 | import java.lang.reflect.Method; 9 | 10 | import static io.manbang.gravity.plugin.ReflectUtil.newInstance; 11 | 12 | /** 13 | * @author weilong.hu 14 | * @since 2022/01/21 15:43 15 | */ 16 | public class DefineMethodInterceptorTemplate { 17 | 18 | private final Interceptor interceptor; 19 | private final ExtendedExecutor extendedExecutor; 20 | 21 | 22 | DefineMethodInterceptorTemplate(Class interceptorClass, ExtendedExecutor extendedExecutor) { 23 | this.interceptor = Interceptor.switcher(newInstance(interceptorClass)); 24 | this.extendedExecutor = extendedExecutor; 25 | } 26 | 27 | DefineMethodInterceptorTemplate(Interceptor interceptor, ExtendedExecutor extendedExecutor) { 28 | this.interceptor = Interceptor.switcher(interceptor); 29 | this.extendedExecutor = extendedExecutor; 30 | } 31 | 32 | @RuntimeType 33 | public Object intercept(@Origin Class targetClass, 34 | @This(optional = true) Object target, 35 | @Origin Method method, 36 | @AllArguments Object[] arguments) throws Throwable { 37 | 38 | ExecuteContext context = ExecuteContext.builder() 39 | .targetClass(targetClass) 40 | .target(target) 41 | .method(method) 42 | .arguments(arguments) 43 | .extraExecutor(extendedExecutor) 44 | .build(); 45 | 46 | interceptor.onCompleted(context); 47 | Throwable throwable = context.getThrowable(); 48 | if (throwable == null) { 49 | return context.getResult(); 50 | } else { 51 | throw throwable; 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /gravity-plugin-api/src/main/java/io/manbang/gravity/plugin/EmptyAdvice.java: -------------------------------------------------------------------------------- 1 | package io.manbang.gravity.plugin; 2 | 3 | /** 4 | * @author duoliang.zhang 5 | * @since 2020/11/5 13:57 6 | */ 7 | enum EmptyAdvice implements Advice { 8 | INSTANCE 9 | } 10 | -------------------------------------------------------------------------------- /gravity-plugin-api/src/main/java/io/manbang/gravity/plugin/EmptyExtendedExecutor.java: -------------------------------------------------------------------------------- 1 | package io.manbang.gravity.plugin; 2 | 3 | /** 4 | * @author weilong.hu 5 | * @since 2021/09/15 16:56 6 | */ 7 | public enum EmptyExtendedExecutor implements ExtendedExecutor { 8 | /** 9 | * 10 | */ 11 | INSTANCE 12 | } 13 | -------------------------------------------------------------------------------- /gravity-plugin-api/src/main/java/io/manbang/gravity/plugin/EmptyInterceptor.java: -------------------------------------------------------------------------------- 1 | package io.manbang.gravity.plugin; 2 | 3 | /** 4 | * @author duoliang.zhang 5 | * @since 2020/11/5 13:58 6 | */ 7 | enum EmptyInterceptor implements Interceptor { 8 | INSTANCE 9 | } 10 | -------------------------------------------------------------------------------- /gravity-plugin-api/src/main/java/io/manbang/gravity/plugin/ExecuteContext.java: -------------------------------------------------------------------------------- 1 | package io.manbang.gravity.plugin; 2 | 3 | import lombok.Builder; 4 | import lombok.ToString; 5 | 6 | import java.lang.reflect.Constructor; 7 | import java.lang.reflect.Field; 8 | import java.lang.reflect.InvocationTargetException; 9 | import java.lang.reflect.Method; 10 | import java.util.Collections; 11 | import java.util.HashMap; 12 | import java.util.Map; 13 | import java.util.Objects; 14 | import java.util.concurrent.ConcurrentHashMap; 15 | import java.util.function.Consumer; 16 | 17 | 18 | /** 19 | * 方法执行上下文,用于在 {@link Advice} 和 {@link Interceptor}的回调方法间传递上下文信息 20 | * 21 | * @author duoliang.zhang 22 | * @since 2020/8/25 16:46 23 | */ 24 | @Builder 25 | @SuppressWarnings("unchecked") 26 | @ToString(of = {"targetClass", "method"}) 27 | public class ExecuteContext { 28 | private static final Map, Map> FIELD_CACHE = new ConcurrentHashMap<>(); 29 | private static final Map METHOD_CACHE = new ConcurrentHashMap<>(); 30 | /** 31 | * 附件信息 32 | */ 33 | private final Map attachments = new HashMap<>(); 34 | /** 35 | * 拦截对象的类型 36 | */ 37 | private final Class targetClass; 38 | /** 39 | * 被拦截的方法 40 | */ 41 | private final Method method; 42 | /** 43 | * 被拦截的构造器 44 | */ 45 | private final Constructor constructor; 46 | /** 47 | * 实参列表 48 | */ 49 | private Object[] arguments; 50 | /** 51 | * 拦截的对象实例 52 | */ 53 | private Object target; 54 | /** 55 | * 返回值结果,可以用来存储原方法返回的结果,或者用户重定向的覆盖结果 56 | */ 57 | private Object result; 58 | /** 59 | * 异常,可以用来存储原方法抛出的异常,或者用户自定义重新抛出去的异常 60 | */ 61 | private Throwable throwable; 62 | /** 63 | * 跳过业务逻辑执行 64 | */ 65 | private boolean skipped; 66 | 67 | /** 68 | * 扩展执行器 69 | */ 70 | private ExtendedExecutor extraExecutor; 71 | 72 | /** 73 | * 业务跳过执行的话,回调处理,{@link Advice} 插件有效,别用在 {@link Interceptor} 74 | * 75 | * @param consumer 回调函数 76 | * @return 上下文 77 | */ 78 | public ExecuteContext onSkip(Consumer consumer) { 79 | if (skipped) { 80 | consumer.accept(this); 81 | } 82 | return this; 83 | } 84 | 85 | /** 86 | * 业务继续执行的话,回调处理,{@link Advice} 插件有效,别用在 {@link Interceptor} 87 | * 88 | * @param consumer 回调函数 89 | * @return 上下文 90 | */ 91 | public ExecuteContext onContinue(Consumer consumer) { 92 | if (!skipped) { 93 | consumer.accept(this); 94 | } 95 | return this; 96 | } 97 | 98 | public void extraExecute() { 99 | extraExecutor.execute(this); 100 | } 101 | 102 | public ExtendedExecutor getExtraExecutor() { 103 | return extraExecutor; 104 | } 105 | 106 | /** 107 | * 获取被植入的方法 108 | * 109 | * @return 被植入方法 110 | */ 111 | public Method getMethod() { 112 | return method; 113 | } 114 | 115 | /** 116 | * 获取被植入的构造器 117 | * 118 | * @return 被植入的构造器 119 | */ 120 | public Constructor getConstructor() { 121 | return (Constructor) constructor; 122 | } 123 | 124 | /** 125 | * 获取被植入代码类 126 | * 127 | * @param 实际类类型 128 | * @return 被植入方法所在的类 129 | */ 130 | public Class getTargetClass() { 131 | return (Class) targetClass; 132 | } 133 | 134 | /** 135 | * 判断是否需要跳过业务逻辑执行 136 | * 137 | * @return 如果跳过业务逻辑执行,返回true,否则,返回false 138 | */ 139 | public boolean isSkipped() { 140 | return skipped; 141 | } 142 | 143 | public void setSkipped(boolean skipped) { 144 | this.skipped = skipped; 145 | } 146 | 147 | /** 148 | * 跳过业务逻辑执行 149 | */ 150 | public void skip() { 151 | this.skipped = true; 152 | } 153 | 154 | /** 155 | * 获取被拦截对象的全限定名 156 | * 157 | * @return 被拦截类的全限定名 158 | */ 159 | public String getTargetClassName() { 160 | return targetClass.getName(); 161 | } 162 | 163 | public ClassLoader getClassLoader() { 164 | return targetClass.getClassLoader(); 165 | } 166 | 167 | /** 168 | * 设置新的异常实例 169 | * 170 | * @param throwable 新异常 171 | */ 172 | public void newThrowable(Throwable throwable) { 173 | this.throwable = throwable; 174 | } 175 | 176 | /** 177 | * 这是新的返回值,改变原方法的返回值 178 | * 179 | * @param result 返回值 180 | */ 181 | public void newResult(Object result) { 182 | this.result = result; 183 | this.throwable = null; 184 | } 185 | 186 | /** 187 | * 获取附件列表,不能修改 188 | * 189 | * @return 附件列表 190 | */ 191 | public Map getAttachments() { 192 | return Collections.unmodifiableMap(attachments); 193 | } 194 | 195 | /** 196 | * 像上下文中添加附件信息,可用来在拦截器重传递信息 197 | * 198 | * @param name 附件名称 199 | * @param value 附件 200 | * @return 执行上下文,方便连续添加附件 201 | */ 202 | public ExecuteContext addAttachment(String name, Object value) { 203 | attachments.put(name, value); 204 | return this; 205 | } 206 | 207 | /** 208 | * 清空附件信息 209 | */ 210 | public void clearAttachments() { 211 | attachments.clear(); 212 | } 213 | 214 | /** 215 | * 获取指定名称的附件,如果不存在,是可能为空值的 216 | * 217 | * @param name 附件名 218 | * @param 附件类型 219 | * @return 附件 220 | */ 221 | public T getAttachment(String name) { 222 | return (T) attachments.get(name); 223 | } 224 | 225 | /** 226 | * 获取返回值,可能是修改之后的 227 | * 228 | * @param 返回值类型 229 | * @return 返回值 230 | */ 231 | public T getResult() { 232 | return (T) result; 233 | } 234 | 235 | /** 236 | * 设置方法返回结果,内部用,外不用请用 {@link ExecuteContext#newResult(Object)} 237 | * 238 | * @param result 返回结果 239 | */ 240 | public void setResult(Object result) { 241 | this.result = result; 242 | } 243 | 244 | /** 245 | * 获取指定位置实参 246 | * 247 | * @param index 实参索引 248 | * @param 实参类型 249 | * @return 实参 250 | */ 251 | public T getArgument(int index) { 252 | return (T) arguments[index]; 253 | } 254 | 255 | /** 256 | * 获取第一个实参 257 | * 258 | * @param 参数类型 259 | * @return 实参 260 | */ 261 | public T getArgument() { 262 | return getArgument(0); 263 | } 264 | 265 | /** 266 | * 修改指定索引位置的实参 267 | * 268 | * @param index 实参索引 269 | * @param arg 新参数 270 | */ 271 | public void newArgument(int index, Object arg) { 272 | arguments[index] = arg; 273 | } 274 | 275 | /** 276 | * 修改实参列表 277 | * 278 | * @param arguments 新的实参列表 279 | */ 280 | public void newArguments(Object... arguments) { 281 | this.arguments = arguments; 282 | } 283 | 284 | /** 285 | * 获取当前类指定名称字段值,字段反射会被缓存 286 | * 287 | * @param name 字段名 288 | * @param 字段类型 289 | * @return 字段值 290 | */ 291 | public T getFieldValue(String name) { 292 | Field field = getField(name); 293 | try { 294 | return (T) field.get(target); 295 | } catch (IllegalAccessException e) { 296 | throw new GravityException(e); 297 | } 298 | } 299 | 300 | /** 301 | * 强行设置字段值 302 | * 303 | * @param name 字段名称 304 | * @param value 要设置的字段值 305 | */ 306 | public void setFieldValue(String name, Object value) { 307 | Field field = getField(name); 308 | boolean accessible = field.isAccessible(); 309 | if (!accessible) { 310 | field.setAccessible(true); // NOSONAR 311 | } 312 | 313 | try { 314 | field.set(getTarget(), value); // NOSONAR 315 | } catch (IllegalAccessException e) { 316 | throw new GravityException(e); 317 | } 318 | } 319 | 320 | private Field getField(String name) { 321 | Class clazz; 322 | //静态方法无target 323 | if (Objects.nonNull(target)) { 324 | //优先从当前target检索field 避免field重名情况 325 | clazz = target.getClass(); 326 | } else { 327 | clazz = this.targetClass; 328 | } 329 | final Map fieldMap = FIELD_CACHE.computeIfAbsent(clazz, t -> new ConcurrentHashMap<>()); 330 | return fieldMap.computeIfAbsent(name, n -> { 331 | Class c = clazz; 332 | do { 333 | try { 334 | Field field = c.getDeclaredField(n); 335 | field.setAccessible(true);// NOSONAR 336 | return field; 337 | } catch (NoSuchFieldException e) { 338 | c = c.getSuperclass(); 339 | } 340 | } 341 | while (c != null && c != Object.class); 342 | 343 | throw new GravityException(new NoSuchFieldException(n)); 344 | }); 345 | } 346 | 347 | /** 348 | * 调用指定名称方法 349 | * 350 | * @param name 方法名 351 | * @param parameterTypes 形参类型列表 352 | * @param args 实参列表 353 | * @param 返回值类型 354 | * @return 返回值 355 | */ 356 | public T invokeMethod(String name, Class[] parameterTypes, Object[] args) { 357 | Method m = getMethod(name, parameterTypes); 358 | 359 | try { 360 | return (T) m.invoke(target, args); 361 | } catch (IllegalAccessException e) { 362 | throw new GravityException(e); 363 | } catch (InvocationTargetException e) { 364 | throw new GravityException(e.getCause()); 365 | } 366 | } 367 | 368 | private Method getMethod(String name, Class[] parameterTypes) { 369 | StringBuilder sb = new StringBuilder(); 370 | for (Class parameterType : parameterTypes) { 371 | sb.append(parameterType.getName()).append("|"); 372 | } 373 | return METHOD_CACHE.computeIfAbsent(String.format("%s#%s(%s)", targetClass.getName(), name, sb), n -> { 374 | Class clazz = targetClass; 375 | do { 376 | try { 377 | Method m = clazz.getDeclaredMethod(name, parameterTypes); 378 | m.setAccessible(true); // NOSONAR 379 | return m; 380 | } catch (NoSuchMethodException e) { 381 | clazz = clazz.getSuperclass(); 382 | } 383 | } while (clazz != null && clazz != Object.class); 384 | throw new GravityException(new NoSuchMethodException(name)); 385 | }); 386 | } 387 | 388 | public T getTarget() { 389 | return (T) target; 390 | } 391 | 392 | /** 393 | * 设置被植入对象,如果被植入的方式是静态方法,是null 394 | * 395 | * @param target 被植入对象 396 | */ 397 | public void setTarget(Object target) { 398 | this.target = target; 399 | } 400 | 401 | public T getThrowable() { 402 | return (T) throwable; 403 | } 404 | 405 | /** 406 | * 设置方法抛出的异常,如果要修改下异常的,请使用:{@link ExecuteContext#newThrowable(Throwable)} 407 | * 408 | * @param throwable 异常 409 | * @see ExecuteContext#newThrowable(Throwable) 410 | */ 411 | public void setThrowable(Throwable throwable) { 412 | this.throwable = throwable; 413 | } 414 | 415 | public Object[] getArguments() { 416 | return arguments; 417 | } 418 | } 419 | 420 | -------------------------------------------------------------------------------- /gravity-plugin-api/src/main/java/io/manbang/gravity/plugin/ExtendedExecutor.java: -------------------------------------------------------------------------------- 1 | package io.manbang.gravity.plugin; 2 | 3 | /** 4 | * 扩展执行器 用于在目标环境下执行 5 | * 6 | * @author weilong.hu 7 | * @since 2021/09/03 18:10 8 | */ 9 | public interface ExtendedExecutor { 10 | default void execute(ExecuteContext context) { 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /gravity-plugin-api/src/main/java/io/manbang/gravity/plugin/GAV.java: -------------------------------------------------------------------------------- 1 | package io.manbang.gravity.plugin; 2 | 3 | import lombok.Builder; 4 | import lombok.Data; 5 | 6 | /** 7 | * Maven jar POM GAV(groupId:artifactId:version) 8 | * 9 | * @author duoliang.zhang 10 | * @since 2020/8/25 12:16 11 | */ 12 | @Data 13 | @Builder 14 | public class GAV { 15 | private final String groupId; 16 | private final String artifactId; 17 | private final String version; 18 | 19 | public boolean match(GAV gav) { 20 | if (gav == null) { 21 | return true; 22 | } 23 | 24 | return (gav.groupId == null || this.groupId.equals(gav.groupId)) 25 | && (gav.artifactId == null || this.artifactId.equals(gav.artifactId)) 26 | && (gav.version == null || this.version.startsWith(gav.version)); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /gravity-plugin-api/src/main/java/io/manbang/gravity/plugin/GravityException.java: -------------------------------------------------------------------------------- 1 | package io.manbang.gravity.plugin; 2 | 3 | /** 4 | * 重力异常 5 | * 6 | * @author duoliang.zhang 7 | * @since 2020/8/28 15:42 8 | */ 9 | public class GravityException extends RuntimeException { 10 | private static final long serialVersionUID = -8891459650895985754L; 11 | 12 | public GravityException() { 13 | super(); 14 | } 15 | 16 | public GravityException(String message) { 17 | super(message); 18 | } 19 | 20 | public GravityException(String message, Throwable cause) { 21 | super(message, cause); 22 | } 23 | 24 | public GravityException(Throwable cause) { 25 | super(cause); 26 | } 27 | 28 | @Override 29 | public synchronized Throwable fillInStackTrace() { 30 | if (isWritableStackTrace()) { 31 | return super.fillInStackTrace(); 32 | } 33 | 34 | return this; 35 | } 36 | 37 | protected boolean isWritableStackTrace() { 38 | return false; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /gravity-plugin-api/src/main/java/io/manbang/gravity/plugin/GravityHome.java: -------------------------------------------------------------------------------- 1 | package io.manbang.gravity.plugin; 2 | 3 | import java.io.File; 4 | import java.net.URL; 5 | import java.nio.file.Path; 6 | import java.nio.file.Paths; 7 | import java.util.Objects; 8 | import java.util.Optional; 9 | import java.util.logging.Logger; 10 | import java.util.stream.Stream; 11 | 12 | /** 13 | * 重力系统家目录,存放依赖的Jar或者配置文件,枚举类型单例 14 | * 15 | * @author duoliang.zhang 16 | * @since 2020/9/1 9:13 17 | */ 18 | public enum GravityHome { 19 | /** 20 | * instance 21 | */ 22 | INSTANCE; 23 | 24 | private static final String HOME_DIR_NAME = ".gravity"; 25 | private static final String JAR_FILE_EXTENSION = ".jar"; 26 | private final Logger log; 27 | private final Path home; 28 | 29 | GravityHome() { 30 | log = Logger.getLogger(GravityHome.class.getName()); 31 | home = createHomeIfNecessary(); 32 | createJarDirectories(home); 33 | } 34 | 35 | public Path getHome() { 36 | return home; 37 | } 38 | 39 | public File toFile(JarType jarType) { 40 | return resolvePath(jarType).toFile(); 41 | } 42 | 43 | public URL[] listFileUrls(JarType jarType) { 44 | return Stream.of(listFiles(jarType)) 45 | .map(File::toURI) 46 | .map(GravityUtils::toUrl) 47 | .filter(Objects::nonNull) 48 | .toArray(URL[]::new); 49 | } 50 | 51 | public File[] listFiles(JarType jarType) { 52 | File file = home.resolve(jarType.name().toLowerCase()).toFile(); 53 | if (file.exists()) { 54 | File[] jars = file.listFiles(pathname -> pathname.getName().endsWith(JAR_FILE_EXTENSION)); 55 | return jars == null ? new File[0] : jars; 56 | } 57 | 58 | return new File[0]; 59 | } 60 | 61 | public String[] listFileNames(JarType jarType) { 62 | File file = home.resolve(jarType.name().toLowerCase()).toFile(); 63 | if (file.exists()) { 64 | String[] jarNames = file.list((dir, name) -> name.endsWith(JAR_FILE_EXTENSION)); 65 | return jarNames == null ? new String[0] : jarNames; 66 | } 67 | 68 | return new String[0]; 69 | } 70 | 71 | public Path resolvePath(JarType type) { 72 | return resolvePath(type.name().toLowerCase()); 73 | } 74 | 75 | public Path resolvePath(String path) { 76 | return home.resolve(path); 77 | } 78 | 79 | private Path createHomeIfNecessary() { 80 | Path path = Paths.get(System.getProperty("user.home"), HOME_DIR_NAME, AgentOptions.INSTANCE.getAppName()); 81 | File file = path.toFile(); 82 | 83 | if (!file.exists()) { 84 | boolean created = file.mkdirs(); 85 | if (created) { 86 | log.info(() -> String.format("创建重力家目录成功:%s", path)); 87 | } 88 | } 89 | 90 | return path; 91 | } 92 | 93 | private void createJarDirectories(Path home) { 94 | for (JarType jarType : JarType.values()) { 95 | File file = home.resolve(jarType.name().toLowerCase()).toFile(); 96 | 97 | if (file.exists()) { 98 | if (AgentOptions.INSTANCE.isLocalDebug()) { 99 | return; 100 | } 101 | 102 | // Optional.ofNullable(file.listFiles(this::isJarFile)) 103 | // .map(Stream::of) 104 | // .orElse(Stream.empty()) 105 | // .forEach(File::delete); 106 | 107 | } else { 108 | boolean created = file.mkdirs(); 109 | 110 | if (created) { 111 | log.info(() -> String.format("创建Jar包目录成功:%s", file)); 112 | } 113 | } 114 | } 115 | } 116 | 117 | private boolean isJarFile(File file) { 118 | return file.getName().toLowerCase().endsWith(JAR_FILE_EXTENSION); 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /gravity-plugin-api/src/main/java/io/manbang/gravity/plugin/GravityLogger.java: -------------------------------------------------------------------------------- 1 | package io.manbang.gravity.plugin; 2 | 3 | /** 4 | * @author weilong.hu 5 | * @since 2020/12/1 15:17 6 | */ 7 | public interface GravityLogger { 8 | /** 9 | * 从SPI中获取一个logger 10 | * 11 | * @return 开关 12 | */ 13 | static GravityLogger of(ClassLoader classLoader) { 14 | return CacheHolder.INSTANCE.loadIfAbsent(classLoader, GravityLogger.class, JULLogger.INSTANCE); 15 | } 16 | 17 | /** 18 | * 从SPI中获取一个logger 19 | * 20 | * @return 开关 21 | */ 22 | static GravityLogger jul() { 23 | return JULLogger.INSTANCE; 24 | } 25 | 26 | void info(String logName, String msg); 27 | 28 | void info(String logName, String format, Object arg); 29 | 30 | void info(String logName, String format, Object arg1, Object arg2); 31 | 32 | void info(String logName, String format, Object... arguments); 33 | 34 | void info(String logName, String msg, Throwable t); 35 | 36 | void warn(String logName, String msg); 37 | 38 | void warn(String logName, String format, Object arg); 39 | 40 | void warn(String logName, String format, Object arg1, Object arg2); 41 | 42 | void warn(String logName, String format, Object... arguments); 43 | 44 | void warn(String logName, String msg, Throwable t); 45 | 46 | void error(String logName, String msg); 47 | 48 | void error(String logName, String format, Object arg); 49 | 50 | void error(String logName, String format, Object arg1, Object arg2); 51 | 52 | void error(String logName, String format, Object... arguments); 53 | 54 | void error(String logName, String msg, Throwable t); 55 | } 56 | -------------------------------------------------------------------------------- /gravity-plugin-api/src/main/java/io/manbang/gravity/plugin/GravityService.java: -------------------------------------------------------------------------------- 1 | package io.manbang.gravity.plugin; 2 | 3 | /** 4 | * 精灵服务,除了插件机制之外,重力系统还支持后台服务,SPI方式自定义 5 | * 6 | * @author dzhang 7 | */ 8 | public interface GravityService extends Ordered, Named { 9 | /** 10 | * 服务准备,默认什么都不干,直接返回true,然后继续执行 start 方法,此接口,不允许抛出异常 11 | * 12 | * @param classLoader 当前应用的类加载器 13 | * @return 如果,继续执行 start 接口,则返回true,否则,精灵服务将不会执行 14 | */ 15 | default boolean prepare(ClassLoader classLoader) { 16 | return true; 17 | } 18 | 19 | /** 20 | * 启动服务 21 | */ 22 | void start(); 23 | 24 | /** 25 | * 停止服务,如果需要清理资源,请覆盖此接口,默认什么都不干 26 | */ 27 | default void stop() { 28 | // 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /gravity-plugin-api/src/main/java/io/manbang/gravity/plugin/GravityUtils.java: -------------------------------------------------------------------------------- 1 | package io.manbang.gravity.plugin; 2 | 3 | import lombok.extern.java.Log; 4 | 5 | import java.io.ByteArrayOutputStream; 6 | import java.io.IOException; 7 | import java.io.InputStream; 8 | import java.io.PrintWriter; 9 | import java.io.StringWriter; 10 | import java.lang.instrument.Instrumentation; 11 | import java.net.MalformedURLException; 12 | import java.net.URI; 13 | import java.net.URL; 14 | import java.util.Collections; 15 | import java.util.HashMap; 16 | import java.util.Map; 17 | import java.util.concurrent.atomic.AtomicReference; 18 | 19 | /** 20 | * @author dzhang 21 | */ 22 | @Log 23 | public class GravityUtils { 24 | private static final Map BYTE_CODES = new HashMap<>(); 25 | private static final AtomicReference INSTRUMENTATION_REF = new AtomicReference<>(); 26 | private static final AtomicReference AGENT_PLUGIN_CLASS_LOADER_REF = new AtomicReference<>(); 27 | 28 | private GravityUtils() { 29 | throw new UnsupportedOperationException(); 30 | } 31 | 32 | public static Instrumentation getInstrumentation() { 33 | return INSTRUMENTATION_REF.get(); 34 | } 35 | 36 | public static void setInstrumentation(Instrumentation instrumentation) { 37 | INSTRUMENTATION_REF.compareAndSet(null, instrumentation); 38 | } 39 | 40 | public static void setAgentPluginClassLoader(AgentPluginClassLoader classLoader) { 41 | AGENT_PLUGIN_CLASS_LOADER_REF.compareAndSet(null, classLoader); 42 | } 43 | 44 | public static AgentPluginClassLoader getAgentPluginClassLoader() { 45 | return AGENT_PLUGIN_CLASS_LOADER_REF.get(); 46 | } 47 | 48 | public static void putByteCodes(String typeName, byte[] bytes) { 49 | BYTE_CODES.put(typeName, bytes); 50 | } 51 | 52 | public static byte[] getByteCodes(String typeName) { 53 | return BYTE_CODES.get(typeName); 54 | } 55 | 56 | public static void clearByteCodes() { 57 | BYTE_CODES.clear(); 58 | } 59 | 60 | public static Map getAllByteCodes() { 61 | return Collections.unmodifiableMap(BYTE_CODES); 62 | } 63 | 64 | public static byte[] getAdviceTemplateByteCodes(String adviceClassName) { 65 | return getByteCodes(adviceClassName); 66 | } 67 | 68 | public static URL toUrl(URI uri) { 69 | try { 70 | return uri.toURL(); 71 | } catch (MalformedURLException e) { 72 | log.severe("uri地址转换异常:" + uri); 73 | e.printStackTrace(); 74 | return null; 75 | } 76 | } 77 | 78 | public static URL toUrl(String uri) { 79 | try { 80 | return URI.create(uri).toURL(); 81 | } catch (MalformedURLException e) { 82 | log.severe("uri地址转换异常:" + uri); 83 | throw new IllegalArgumentException(uri, e); 84 | } 85 | } 86 | 87 | public static byte[] getByteCodes(String className, ClassLoader classLoader) { 88 | InputStream stream = classLoader.getResourceAsStream(className.replace(".", "/").concat(".class")); 89 | if (stream == null) { 90 | return new byte[0]; 91 | } 92 | ByteArrayOutputStream out = new ByteArrayOutputStream(); 93 | byte[] buffer = new byte[1024]; 94 | while (true) { 95 | try { 96 | int r = stream.read(buffer); 97 | if (r == -1) { 98 | break; 99 | } 100 | out.write(buffer, 0, r); 101 | } catch (IOException e) { 102 | throw new GravityException(e); 103 | } 104 | } 105 | 106 | return out.toByteArray(); 107 | } 108 | 109 | public static String getUriQueryParamValue(String uri, String paramName) { 110 | URL url = toUrl(uri); 111 | 112 | String query = url.getQuery(); 113 | if (query == null) { 114 | return url.getPath().substring(url.getPath().lastIndexOf('/')); 115 | } 116 | 117 | String[] parameters = query.split("&"); 118 | for (String parameter : parameters) { 119 | String[] nameValue = parameter.split("="); 120 | if (nameValue.length == 2 && paramName.equals(nameValue[0])) { 121 | return nameValue[1]; 122 | } 123 | } 124 | 125 | return url.getPath().substring(url.getPath().lastIndexOf('/')); 126 | } 127 | 128 | public static String getStackTrace(final Throwable t) { 129 | final StringWriter sw = new StringWriter(); 130 | final PrintWriter pw = new PrintWriter(sw, true); 131 | t.printStackTrace(pw); 132 | return sw.getBuffer().toString(); 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /gravity-plugin-api/src/main/java/io/manbang/gravity/plugin/Interceptor.java: -------------------------------------------------------------------------------- 1 | package io.manbang.gravity.plugin; 2 | 3 | /** 4 | * 方法拦截器,拦截器是不能修改方法的body内容的,原方法会被包装成一个新的方法 5 | *
  6 |  *     // original class
  7 |  *     public class Foo {
  8 |  *         public String sayHello() {
  9 |  *             return "Hello Interceptor.";
 10 |  *         }
 11 |  *     }
 12 |  *
 13 |  *     // interceptor class
 14 |  *     public FooInterceptor implements Interceptor {
 15 |  *         public boolean beforeMethod(ExecuteContext context) {
 16 |  *             System.err.println("Hello Before Method.");
 17 |  *             return true;
 18 |  *         }
 19 |  *
 20 |  *         public void handleException(ExecuteContext context) {
 21 |  *             System.err.println("Hello Handle Exception.");
 22 |  *         }
 23 |  *
 24 |  *         public void afterMethod(ExecuteContext context) {
 25 |  *             System.err.println("Hello After Method.");
 26 |  *         }
 27 |  *
 28 |  *         public void onCompleted(ExecuteContext context) {
 29 |  *             System.err.println("Hello on Completed.);
 30 |  *         }
 31 |  *     }
 32 |  *
 33 |  *     // Foo final pseudocode class
 34 |  *     public class Foo {
 35 |  *         public String sayHello() {
 36 |  *             // ......
 37 |  *             ExecuteContext context = getContext();
 38 |  *             boolean continued = interceptor.beforeMethod(context);
 39 |  *             if (continued) {
 40 |  *                 try {
 41 |  *                     String result = sayHelloWrapped();
 42 |  *                     context.setResult(result);
 43 |  *                     interceptor.afterMethod(context);
 44 |  *                 } catch(Throwable throwable) {
 45 |  *                     context.setThrowable(throwable);
 46 |  *                     interceptor.handleException(context);
 47 |  *                 } finally {
 48 |  *                     interceptor.onCompleted(context);
 49 |  *                 }
 50 |  *             }
 51 |  *
 52 |  *             if (context.isRethrown()) {
 53 |  *                 throw context.getThrowable();
 54 |  *             }
 55 |  *
 56 |  *             return context.getResult();
 57 |  *         }
 58 |  *
 59 |  *         private String sayHelloWrapped() {
 60 |  *             return "Hello Interceptor.";
 61 |  *         }
 62 |  *     }
 63 |  * 
64 | * 65 | * @author duoliang.zhang 66 | * @since 2020/8/22 11:27 67 | */ 68 | public interface Interceptor extends Named, LifeCycle, Switchable { 69 | 70 | /** 71 | * 将原始 {@link Interceptor} 做一次包装,如果 {@link Interceptor} 支持开关的话,会封装成 {@link SwitcherInterceptor},否则直接返回 72 | * 73 | * @param interceptor 拦截器插件 74 | * @return 如果是可以开关控制的 {@link Interceptor},返回包装后的 {@link SwitcherInterceptor} 75 | */ 76 | static Interceptor switcher(Interceptor interceptor) { 77 | return interceptor.isSwitchable() ? new SwitcherInterceptor(interceptor) : interceptor; 78 | } 79 | 80 | /** 81 | * 原方法执行前,回调,可以通过 {@link ExecuteContext#setResult(Object)} 设置新的返回值 82 | * 83 | * @param context 执行上下文 84 | * @return 如果继续希望继续执行,返回true,否则,原方法不再执行 85 | */ 86 | default boolean beforeMethod(ExecuteContext context) { 87 | return true; 88 | } 89 | 90 | /** 91 | * 如果原方法执行异常,回调此方法处理,{@link ExecuteContext#getThrowable()} 获取异常信息,默认直接抛出异常 92 | * 93 | * @param context 执行上下文 94 | */ 95 | default void handleException(ExecuteContext context) throws Throwable { 96 | if (context.getThrowable() != null) { 97 | throw context.getThrowable(); 98 | } 99 | } 100 | 101 | /** 102 | * 远方执行之后,回调, 103 | * 104 | * @param context 执行上下文 105 | */ 106 | default void afterMethod(ExecuteContext context) { 107 | 108 | } 109 | 110 | /** 111 | * @param context 执行上下文 112 | */ 113 | default void onCompleted(ExecuteContext context) { 114 | 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /gravity-plugin-api/src/main/java/io/manbang/gravity/plugin/InterceptorFactory.java: -------------------------------------------------------------------------------- 1 | package io.manbang.gravity.plugin; 2 | 3 | /** 4 | * @author weilong.hu 5 | * @since 2022/01/22 14:08 6 | */ 7 | public interface InterceptorFactory { 8 | /** 9 | * 拦截方法 支持静态和实例方法 10 | * 11 | * @param interceptor 拦截器 12 | * @param extendedExecutor 扩展执行器 13 | * @return 拦截模板实例 14 | */ 15 | static Object method(Interceptor interceptor, ExtendedExecutor extendedExecutor) { 16 | return MethodInterceptorFactory.INSTANCE.create(interceptor, extendedExecutor); 17 | } 18 | 19 | /** 20 | * 拦截方法 支持静态和实例方法 21 | * 22 | * @param interceptorClass 拦截器class 23 | * @param extendedExecutor 扩展执行器 24 | * @return 拦截模板实例 25 | */ 26 | static Object method(Class interceptorClass, ExtendedExecutor extendedExecutor) { 27 | return MethodInterceptorFactory.INSTANCE.create(interceptorClass, extendedExecutor); 28 | } 29 | 30 | /** 31 | * 拦截构造器 32 | * 33 | * @param interceptor 拦截器 34 | * @param extendedExecutor 扩展执行器 35 | * @return 拦截模板实例 36 | */ 37 | static Object constructor(Interceptor interceptor, ExtendedExecutor extendedExecutor) { 38 | return ConstructorInterceptorFactory.INSTANCE.create(interceptor, extendedExecutor); 39 | } 40 | 41 | /** 42 | * 拦截构造器 43 | * 44 | * @param interceptorClass 拦截器class 45 | * @param extendedExecutor 扩展执行器 46 | * @return 拦截模板实例 47 | */ 48 | static Object constructor(Class interceptorClass, ExtendedExecutor extendedExecutor) { 49 | return ConstructorInterceptorFactory.INSTANCE.create(interceptorClass, extendedExecutor); 50 | } 51 | 52 | /** 53 | * 增加新方法 54 | * 55 | * @param interceptor 拦截器 56 | * @param extendedExecutor 扩展执行器 57 | * @return 拦截模板实例 58 | */ 59 | static Object defineMethod(Interceptor interceptor, ExtendedExecutor extendedExecutor) { 60 | return DefineMethodInterceptorFactory.INSTANCE.create(interceptor, extendedExecutor); 61 | } 62 | 63 | /** 64 | * 增加新方法 65 | * 66 | * @param interceptorClass 拦截器class 67 | * @param extendedExecutor 扩展执行器 68 | * @return 拦截模板实例 69 | */ 70 | static Object defineMethod(Class interceptorClass, ExtendedExecutor extendedExecutor) { 71 | return DefineMethodInterceptorFactory.INSTANCE.create(interceptorClass, extendedExecutor); 72 | } 73 | 74 | 75 | Object create(Class interceptorClass, ExtendedExecutor extendedExecutor); 76 | 77 | Object create(Interceptor interceptor, ExtendedExecutor extendedExecutor); 78 | } 79 | -------------------------------------------------------------------------------- /gravity-plugin-api/src/main/java/io/manbang/gravity/plugin/JULLogger.java: -------------------------------------------------------------------------------- 1 | package io.manbang.gravity.plugin; 2 | 3 | import java.util.logging.Logger; 4 | 5 | /** 6 | * @author weilong.hu 7 | * @since 2020/12/1 15:57 8 | */ 9 | enum JULLogger implements GravityLogger { 10 | /** 11 | * jul logger 12 | */ 13 | INSTANCE; 14 | 15 | @Override 16 | public void info(String logName, String msg) { 17 | Logger.getLogger(logName).info(() -> msg); 18 | } 19 | 20 | @Override 21 | public void info(String logName, String format, Object arg) { 22 | Logger.getLogger(logName).info(() -> String.format(format, arg)); 23 | } 24 | 25 | @Override 26 | public void info(String logName, String format, Object arg1, Object arg2) { 27 | Logger.getLogger(logName).info(() -> String.format(format, arg1, arg2)); 28 | } 29 | 30 | @Override 31 | public void info(String logName, String format, Object... arguments) { 32 | Logger.getLogger(logName).info(() -> String.format(format, arguments)); 33 | } 34 | 35 | @Override 36 | public void info(String logName, String msg, Throwable t) { 37 | Logger.getLogger(logName).info(() -> String.format(msg, GravityUtils.getStackTrace(t))); 38 | } 39 | 40 | @Override 41 | public void warn(String logName, String msg) { 42 | Logger.getLogger(logName).warning(() -> msg); 43 | } 44 | 45 | @Override 46 | public void warn(String logName, String format, Object arg) { 47 | Logger.getLogger(logName).warning(() -> String.format(format, arg)); 48 | } 49 | 50 | @Override 51 | public void warn(String logName, String format, Object arg1, Object arg2) { 52 | Logger.getLogger(logName).warning(() -> String.format(format, arg1, arg2)); 53 | } 54 | 55 | @Override 56 | public void warn(String logName, String format, Object... arguments) { 57 | Logger.getLogger(logName).warning(() -> String.format(format, arguments)); 58 | } 59 | 60 | @Override 61 | public void warn(String logName, String msg, Throwable t) { 62 | Logger.getLogger(logName).warning(() -> String.format(msg, GravityUtils.getStackTrace(t))); 63 | } 64 | 65 | @Override 66 | public void error(String logName, String msg) { 67 | Logger.getLogger(logName).warning(() -> msg); 68 | } 69 | 70 | @Override 71 | public void error(String logName, String format, Object arg) { 72 | Logger.getLogger(logName).warning(() -> String.format(format, arg)); 73 | } 74 | 75 | @Override 76 | public void error(String logName, String format, Object arg1, Object arg2) { 77 | Logger.getLogger(logName).warning(() -> String.format(format, arg1, arg2)); 78 | } 79 | 80 | @Override 81 | public void error(String logName, String format, Object... arguments) { 82 | Logger.getLogger(logName).warning(() -> String.format(format, arguments)); 83 | } 84 | 85 | @Override 86 | public void error(String logName, String msg, Throwable t) { 87 | Logger.getLogger(logName).warning(() -> String.format("msg:%s exception:%s", msg, GravityUtils.getStackTrace(t))); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /gravity-plugin-api/src/main/java/io/manbang/gravity/plugin/JarType.java: -------------------------------------------------------------------------------- 1 | package io.manbang.gravity.plugin; 2 | 3 | import java.io.File; 4 | import java.io.IOException; 5 | import java.lang.instrument.Instrumentation; 6 | import java.nio.file.Path; 7 | import java.util.Objects; 8 | import java.util.Optional; 9 | import java.util.function.Consumer; 10 | import java.util.jar.JarFile; 11 | import java.util.stream.Stream; 12 | 13 | /** 14 | * Jar包类型,不同类型Jar会被放到不同的目录中 15 | * 16 | * @author duoliang.zhang 17 | * @since 2020/8/12 13:46 18 | */ 19 | public enum JarType { 20 | /** 21 | * boot class 22 | */ 23 | BOOT { 24 | @Override 25 | public void appendClassPath(Instrumentation instrumentation, Path gravityHome) { 26 | doAppendClassPath(gravityHome.resolve(name().toLowerCase()).toFile(), instrumentation::appendToBootstrapClassLoaderSearch); 27 | } 28 | }, 29 | /** 30 | * application class 31 | */ 32 | APP { 33 | @Override 34 | public void appendClassPath(Instrumentation instrumentation, Path gravityHome) { 35 | doAppendClassPath(gravityHome.resolve(name().toLowerCase()).toFile(), instrumentation::appendToSystemClassLoaderSearch); 36 | } 37 | }, 38 | /** 39 | * agent class 40 | */ 41 | AGENT, 42 | /** 43 | * Tomcat WEB-INF/lib 44 | */ 45 | TOMCAT; 46 | 47 | private static JarFile newJarFile(File file) { 48 | try { 49 | return new JarFile(file); 50 | } catch (IOException e) { 51 | e.printStackTrace(); 52 | return null; 53 | } 54 | } 55 | 56 | private static void doAppendClassPath(File classpath, Consumer appendConsumer) { 57 | Optional.ofNullable(classpath.listFiles()) 58 | .map(Stream::of) 59 | .orElse(Stream.empty()) 60 | .filter(file -> file.canRead() && file.getName().endsWith(".jar")) 61 | .map(JarType::newJarFile) 62 | .filter(Objects::nonNull) 63 | .forEach(appendConsumer); 64 | } 65 | 66 | public void appendClassPath(Instrumentation instrumentation, Path gravityHome) { 67 | // do nothing 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /gravity-plugin-api/src/main/java/io/manbang/gravity/plugin/LifeCycle.java: -------------------------------------------------------------------------------- 1 | package io.manbang.gravity.plugin; 2 | 3 | /** 4 | * 生命周期 5 | * 6 | * @author duoliang.zhang 7 | * @see Advice 切面 8 | * @see Interceptor 拦截器 9 | * @since 2020/11/25 16:05 10 | */ 11 | public interface LifeCycle { 12 | /** 13 | * 插件启用的时候,会回调此方法 14 | */ 15 | default void resume() { 16 | // do nothing 17 | } 18 | 19 | /** 20 | * 插件禁用的时候,会回调方法 21 | */ 22 | default void suspend() { 23 | // do nothing 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /gravity-plugin-api/src/main/java/io/manbang/gravity/plugin/LocalPluginLoader.java: -------------------------------------------------------------------------------- 1 | package io.manbang.gravity.plugin; 2 | 3 | import java.util.Collections; 4 | import java.util.List; 5 | 6 | /** 7 | * @author duoliang.zhang 8 | * @since 2020/9/10 16:40 9 | */ 10 | enum LocalPluginLoader implements PluginLoader { 11 | INSTANCE; 12 | 13 | @Override 14 | public List loadPluginJars(AgentOptions options) { 15 | return Collections.emptyList(); 16 | } 17 | 18 | @Override 19 | public int getOrder() { 20 | return 0; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /gravity-plugin-api/src/main/java/io/manbang/gravity/plugin/MavenWitness.java: -------------------------------------------------------------------------------- 1 | package io.manbang.gravity.plugin; 2 | 3 | import java.io.IOException; 4 | import java.io.InputStream; 5 | import java.util.Properties; 6 | 7 | class MavenWitness implements Witness { 8 | private final GAV gav; 9 | 10 | MavenWitness(GAV gav) { 11 | this.gav = gav; 12 | } 13 | 14 | @Override 15 | public boolean saw(ClassLoader classLoader) { 16 | InputStream pomStream = classLoader.getResourceAsStream(String.format("META-INF/maven/%s/%s/pom.properties", 17 | gav.getGroupId(), gav.getArtifactId())); 18 | if (pomStream == null) { 19 | return false; 20 | } 21 | 22 | Properties pomProperties = new Properties(); 23 | try { 24 | pomProperties.load(pomStream); 25 | } catch (IOException e) { 26 | return false; 27 | } 28 | 29 | return GAV.builder() 30 | .groupId(pomProperties.getProperty("groupId", "")) 31 | .artifactId(pomProperties.getProperty("artifactId", "")) 32 | .version(pomProperties.getProperty("version", "")) 33 | .build() 34 | .match(gav); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /gravity-plugin-api/src/main/java/io/manbang/gravity/plugin/MetaInfo.java: -------------------------------------------------------------------------------- 1 | package io.manbang.gravity.plugin; 2 | 3 | import lombok.Builder; 4 | import lombok.Data; 5 | 6 | /** 7 | * @author duoliang.zhang 8 | * @since 2020/9/2 15:56 9 | */ 10 | @Data 11 | @Builder 12 | public class MetaInfo { 13 | private final String specificationTitle; 14 | private final String specificationVersion; 15 | private final String implementationTitle; 16 | private final String implementationVersion; 17 | 18 | public boolean match(MetaInfo metaInfo) { 19 | if (metaInfo == null) { 20 | return true; 21 | } 22 | 23 | return (metaInfo.specificationTitle == null || this.specificationTitle.equals(metaInfo.specificationTitle)) 24 | && (metaInfo.specificationVersion == null || this.specificationVersion.startsWith(metaInfo.specificationVersion)) 25 | && (metaInfo.implementationTitle == null || this.implementationTitle.equals(metaInfo.implementationTitle)) 26 | && (metaInfo.implementationVersion == null || this.implementationVersion.startsWith(metaInfo.implementationVersion)); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /gravity-plugin-api/src/main/java/io/manbang/gravity/plugin/MetaInfoWitness.java: -------------------------------------------------------------------------------- 1 | package io.manbang.gravity.plugin; 2 | 3 | import java.io.IOException; 4 | import java.io.InputStream; 5 | import java.net.URL; 6 | import java.util.Enumeration; 7 | import java.util.Properties; 8 | 9 | /** 10 | * @author duoliang.zhang 11 | * @since 2020/9/2 15:53 12 | */ 13 | class MetaInfoWitness implements Witness { 14 | private final MetaInfo metaInfo; 15 | 16 | public MetaInfoWitness(MetaInfo metaInfo) { 17 | this.metaInfo = metaInfo; 18 | } 19 | 20 | @Override 21 | public boolean saw(ClassLoader classLoader) { 22 | Enumeration metaInfos; 23 | try { 24 | metaInfos = classLoader.getResources("META-INF/MANIFEST.MF"); 25 | } catch (IOException e) { 26 | return false; 27 | } 28 | 29 | while (metaInfos.hasMoreElements()) { 30 | URL url = metaInfos.nextElement(); 31 | Properties properties = new Properties(); 32 | try (InputStream metaInfoStream = url.openStream()) { 33 | properties.load(metaInfoStream); 34 | } catch (IOException e) { 35 | continue; 36 | } 37 | 38 | boolean matched = MetaInfo.builder() 39 | .specificationTitle(properties.getProperty("Specification-Title", "")) 40 | .specificationVersion(properties.getProperty("Specification-Version", "")) 41 | .implementationTitle(properties.getProperty("Implementation-Title", "")) 42 | .implementationVersion(properties.getProperty("Implementation-Version", "")) 43 | .build().match(metaInfo); 44 | 45 | if (matched) { 46 | return true; 47 | } 48 | } 49 | 50 | return false; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /gravity-plugin-api/src/main/java/io/manbang/gravity/plugin/MethodInterceptorFactory.java: -------------------------------------------------------------------------------- 1 | package io.manbang.gravity.plugin; 2 | 3 | import java.util.Map; 4 | import java.util.concurrent.ConcurrentHashMap; 5 | 6 | /** 7 | * @author weilong.hu 8 | * @since 2022/01/22 14:11 9 | */ 10 | class MethodInterceptorFactory implements InterceptorFactory { 11 | static final InterceptorFactory INSTANCE = new MethodInterceptorFactory(); 12 | private static final Map TEMPLATES = new ConcurrentHashMap<>(); 13 | 14 | private MethodInterceptorFactory() { 15 | } 16 | 17 | @Override 18 | public Object create(Class interceptorClass, ExtendedExecutor extendedExecutor) { 19 | return TEMPLATES.computeIfAbsent(interceptorClass, c -> new MethodInterceptorTemplate(interceptorClass, extendedExecutor)); 20 | } 21 | 22 | @Override 23 | public Object create(Interceptor interceptor, ExtendedExecutor extendedExecutor) { 24 | return TEMPLATES.computeIfAbsent(interceptor, k -> new MethodInterceptorTemplate(interceptor, extendedExecutor)); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /gravity-plugin-api/src/main/java/io/manbang/gravity/plugin/MethodInterceptorTemplate.java: -------------------------------------------------------------------------------- 1 | package io.manbang.gravity.plugin; 2 | 3 | 4 | import net.bytebuddy.implementation.bind.annotation.AllArguments; 5 | import net.bytebuddy.implementation.bind.annotation.Morph; 6 | import net.bytebuddy.implementation.bind.annotation.Origin; 7 | import net.bytebuddy.implementation.bind.annotation.RuntimeType; 8 | import net.bytebuddy.implementation.bind.annotation.This; 9 | 10 | import java.lang.reflect.Method; 11 | 12 | import static io.manbang.gravity.plugin.ReflectUtil.newInstance; 13 | 14 | /** 15 | * @author duoliang.zhang 16 | * @since 2020/8/26 9:35 17 | */ 18 | public class MethodInterceptorTemplate { 19 | 20 | private final Interceptor interceptor; 21 | private final ExtendedExecutor extendedExecutor; 22 | 23 | MethodInterceptorTemplate(Class interceptorClass, ExtendedExecutor extendedExecutor) { 24 | this.interceptor = Interceptor.switcher(newInstance(interceptorClass)); 25 | this.extendedExecutor = extendedExecutor; 26 | } 27 | 28 | MethodInterceptorTemplate(Interceptor interceptor, ExtendedExecutor extendedExecutor) { 29 | this.interceptor = Interceptor.switcher(interceptor); 30 | this.extendedExecutor = extendedExecutor; 31 | } 32 | 33 | @RuntimeType 34 | public Object intercept(@Origin Class targetClass, 35 | @This(optional = true) Object target, 36 | @Origin Method method, 37 | @AllArguments Object[] arguments, 38 | @Morph MorphingCallable callable) throws Throwable { 39 | 40 | ExecuteContext context = ExecuteContext.builder() 41 | .targetClass(targetClass) 42 | .target(target) 43 | .method(method) 44 | .arguments(arguments) 45 | .extraExecutor(extendedExecutor) 46 | .build(); 47 | 48 | boolean continued = interceptor.beforeMethod(context); 49 | 50 | if (continued) { 51 | try { 52 | Object result = callable.call(context.getArguments()); 53 | context.setResult(result); 54 | interceptor.afterMethod(context); 55 | } catch (Throwable e) { 56 | context.setThrowable(e); 57 | interceptor.handleException(context); 58 | } finally { 59 | interceptor.onCompleted(context); 60 | } 61 | } 62 | 63 | Throwable throwable = context.getThrowable(); 64 | if (throwable == null) { 65 | return context.getResult(); 66 | } else { 67 | throw throwable; 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /gravity-plugin-api/src/main/java/io/manbang/gravity/plugin/MorphingCallable.java: -------------------------------------------------------------------------------- 1 | package io.manbang.gravity.plugin; 2 | 3 | /** 4 | * 用于修改入参的调用器 5 | * 6 | * @author duoliang.zhang 7 | * @since 2020/8/25 16:41 8 | */ 9 | public interface MorphingCallable { 10 | /** 11 | * 执行方法调用 12 | * 13 | * @param args 实参,传入前可以修改 14 | * @return 业务方法返回值 15 | */ 16 | T call(Object... args); 17 | } 18 | 19 | -------------------------------------------------------------------------------- /gravity-plugin-api/src/main/java/io/manbang/gravity/plugin/Named.java: -------------------------------------------------------------------------------- 1 | package io.manbang.gravity.plugin; 2 | 3 | /** 4 | * 具名接口 5 | * 6 | * @author duoliang.zhang 7 | * @since 2020/9/10 19:39 8 | */ 9 | public interface Named { 10 | 11 | /** 12 | * 获取插件加载器名称,默认是类的简单名 13 | * 14 | * @return 名称 15 | */ 16 | default String getName() { 17 | return getClass().getSimpleName(); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /gravity-plugin-api/src/main/java/io/manbang/gravity/plugin/Ordered.java: -------------------------------------------------------------------------------- 1 | package io.manbang.gravity.plugin; 2 | 3 | /** 4 | * 排序 5 | * 6 | * @author duoliang.zhang 7 | * @since 2020/9/10 16:46 8 | */ 9 | public interface Ordered { 10 | /** 11 | * 获取顺序,值越小,排名越靠前,默认都是 0 12 | * 13 | * @return 顺序值 14 | */ 15 | default int getOrder() { 16 | return 0; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /gravity-plugin-api/src/main/java/io/manbang/gravity/plugin/Plugin.java: -------------------------------------------------------------------------------- 1 | package io.manbang.gravity.plugin; 2 | 3 | 4 | import net.bytebuddy.description.method.MethodDescription; 5 | import net.bytebuddy.matcher.ElementMatcher; 6 | import net.bytebuddy.matcher.ElementMatchers; 7 | 8 | import java.util.LinkedList; 9 | import java.util.List; 10 | 11 | /** 12 | * 插件,用于植入到应用的代码当中 13 | * 14 | * @author 章多亮 15 | * @since 2020/8/11 9:31 16 | */ 17 | public class Plugin { 18 | /** 19 | * 插件类型 20 | */ 21 | private final PluginType type; 22 | /** 23 | * 拦截器全限定名 24 | */ 25 | private final String adviceClassName; 26 | /** 27 | * 拦截器类全限定名 28 | */ 29 | private final String interceptorClassName; 30 | /** 31 | * 待植入的拦截器实例,默认空值 32 | */ 33 | private final Object interceptor; 34 | /** 35 | * 获取插件的方法匹配器,用于Agent定位要植入代码的方法 36 | */ 37 | private final ElementMatcher methodMatcher; 38 | /** 39 | * 是否捕捉异常 40 | */ 41 | private boolean withThrowable; 42 | /** 43 | * 是否需要被拦截的方法 44 | */ 45 | private boolean withMethod; 46 | /** 47 | * 是否需要被拦截的构造器 48 | */ 49 | private boolean withConstructor; 50 | /** 51 | * {@link Advice} 控制Advice是否可以提前终止业务逻辑执行 52 | */ 53 | private boolean skipEnabled; 54 | 55 | /** 56 | * 是否是增强构造函数 57 | */ 58 | private boolean constructorAdvised; 59 | 60 | /** 61 | * 是否是定义方法 62 | */ 63 | private boolean defineMethod; 64 | /** 65 | * 定义方法修饰符 66 | */ 67 | private int modifiers; 68 | 69 | /** 70 | * 方法名 71 | */ 72 | private String methodName; 73 | 74 | /** 75 | * 拓展执行器 76 | */ 77 | private String extendedExecutor; 78 | 79 | /** 80 | * 是否需要植入扩展field 81 | */ 82 | private String[] extendFields; 83 | 84 | private Plugin(PluginType type, ElementMatcher methodMatcher, String adviceClassName, String interceptorClassName, Object interceptor) { 85 | this.type = type; 86 | this.methodMatcher = methodMatcher; 87 | this.interceptor = interceptor; 88 | this.adviceClassName = adviceClassName; 89 | this.interceptorClassName = interceptorClassName; 90 | } 91 | 92 | public static Plugin advice(ElementMatcher methodMatcher, Class adviceClass) { 93 | return new Plugin(PluginType.ADVICE, methodMatcher, adviceClass.getName(), null, null); 94 | } 95 | 96 | public static Plugin interceptor(ElementMatcher methodMatcher, Class interceptorClass) { 97 | return new Plugin(PluginType.INTERCEPTOR, methodMatcher, null, interceptorClass.getName(), null); 98 | } 99 | 100 | public static Plugin interceptor(Class interceptorClass) { 101 | return new Plugin(PluginType.INTERCEPTOR, ElementMatchers.none(), null, interceptorClass.getName(), null); 102 | } 103 | 104 | public static Plugin advice(ElementMatcher methodMatcher, String adviceClassName) { 105 | return new Plugin(PluginType.ADVICE, methodMatcher, adviceClassName, null, null); 106 | } 107 | 108 | public static Plugin interceptor(ElementMatcher methodMatcher, String interceptorClassName) { 109 | return new Plugin(PluginType.INTERCEPTOR, methodMatcher, null, interceptorClassName, null); 110 | } 111 | 112 | public static Plugin interceptor(String interceptorClassName) { 113 | return new Plugin(PluginType.INTERCEPTOR, ElementMatchers.none(), null, interceptorClassName, null); 114 | } 115 | 116 | public static Plugin interceptorInstance(ElementMatcher methodMatcher, T interceptor) { 117 | return new Plugin(PluginType.INTERCEPTOR_INSTANCE, methodMatcher, null, null, interceptor); 118 | } 119 | 120 | 121 | public static Plugin interceptorInstance(T interceptor) { 122 | return new Plugin(PluginType.INTERCEPTOR_INSTANCE, ElementMatchers.none(), null, null, interceptor); 123 | } 124 | 125 | public static Builder builder() { 126 | return new Builder(); 127 | } 128 | 129 | public String[] getExtendFields() { 130 | return extendFields; 131 | } 132 | 133 | /** 134 | * 捕捉异常 135 | */ 136 | public Plugin withThrowable() { 137 | this.withThrowable = true; 138 | return this; 139 | } 140 | 141 | /** 142 | * 获取被拦截的方法 143 | */ 144 | public Plugin withMethod() { 145 | this.withMethod = true; 146 | return this; 147 | } 148 | 149 | /** 150 | * 获取被拦截的构造器 151 | */ 152 | public Plugin withConstructor() { 153 | this.withConstructor = true; 154 | this.constructorAdvised = true; 155 | return this; 156 | } 157 | 158 | /** 159 | * 增强构造函数 160 | */ 161 | public Plugin constructorAdvised() { 162 | this.constructorAdvised = true; 163 | return this; 164 | } 165 | 166 | public Plugin defineMethod(String methodName, int modifiers) { 167 | if (this.type != PluginType.INTERCEPTOR && this.type != PluginType.INTERCEPTOR_INSTANCE) { 168 | throw new UnsupportedOperationException("only interceptors support defining methods!"); 169 | } 170 | this.modifiers = modifiers; 171 | this.methodName = methodName; 172 | this.defineMethod = true; 173 | return this; 174 | } 175 | 176 | /** 177 | * {@link Advice} 用来控制是否执行业务逻辑,如果启用, {@link Advice#enterMethod(ExecuteContext)} 的返回值有效 178 | * 179 | * @return 插件本身 180 | */ 181 | public Plugin enableSkip() { 182 | this.skipEnabled = true; 183 | return this; 184 | } 185 | 186 | /** 187 | * 增加拓展field 188 | */ 189 | public Plugin withExtendFields(String... fields) { 190 | this.extendFields = fields; 191 | return this; 192 | } 193 | 194 | public Plugin withExtendedExecutor(String extendedExecutor) { 195 | this.extendedExecutor = extendedExecutor; 196 | return this; 197 | } 198 | 199 | public PluginType getType() { 200 | return type; 201 | } 202 | 203 | public String getAdviceClassName() { 204 | return adviceClassName; 205 | } 206 | 207 | public String getInterceptorClassName() { 208 | return interceptorClassName; 209 | } 210 | 211 | public Object getInterceptor() { 212 | return interceptor; 213 | } 214 | 215 | public ElementMatcher getMethodMatcher() { 216 | return methodMatcher; 217 | } 218 | 219 | public boolean isWithThrowable() { 220 | return withThrowable; 221 | } 222 | 223 | public boolean isWithMethod() { 224 | return withMethod; 225 | } 226 | 227 | public boolean isWithConstructor() { 228 | return withConstructor; 229 | } 230 | 231 | public boolean isSkipEnabled() { 232 | return skipEnabled; 233 | } 234 | 235 | public boolean isConstructorAdvised() { 236 | return constructorAdvised; 237 | } 238 | 239 | public String getExtendedExecutor() { 240 | return extendedExecutor; 241 | } 242 | 243 | public boolean isDefineMethod() { 244 | return defineMethod; 245 | } 246 | 247 | public String methodName() { 248 | return methodName; 249 | } 250 | 251 | public int modifiers() { 252 | return modifiers; 253 | } 254 | 255 | public static class Builder { 256 | private static final Plugin[] EMPTY = new Plugin[0]; 257 | private final List plugins = new LinkedList<>(); 258 | 259 | private Builder() { 260 | } 261 | 262 | public Builder advice(ElementMatcher methodMatcher, String adviceClassName) { 263 | plugins.add(Plugin.advice(methodMatcher, adviceClassName)); 264 | return this; 265 | } 266 | 267 | public Builder interceptor(ElementMatcher methodMatcher, String interceptorClassName) { 268 | plugins.add(Plugin.interceptor(methodMatcher, interceptorClassName)); 269 | return this; 270 | } 271 | 272 | public Builder advice(ElementMatcher methodMatcher, Class adviceClass) { 273 | plugins.add(Plugin.advice(methodMatcher, adviceClass.getName())); 274 | return this; 275 | } 276 | 277 | 278 | public Builder interceptor(ElementMatcher methodMatcher, Class interceptorClass) { 279 | plugins.add(Plugin.interceptor(methodMatcher, interceptorClass.getName())); 280 | return this; 281 | } 282 | 283 | public Builder interceptorInstance(ElementMatcher methodMatcher, T interceptor) { 284 | plugins.add(Plugin.interceptorInstance(methodMatcher, interceptor)); 285 | return this; 286 | } 287 | 288 | public Plugin[] build() { 289 | return plugins.toArray(EMPTY); 290 | } 291 | } 292 | } 293 | -------------------------------------------------------------------------------- /gravity-plugin-api/src/main/java/io/manbang/gravity/plugin/PluginDefine.java: -------------------------------------------------------------------------------- 1 | package io.manbang.gravity.plugin; 2 | 3 | 4 | 5 | import net.bytebuddy.agent.builder.AgentBuilder; 6 | import net.bytebuddy.description.type.TypeDescription; 7 | import net.bytebuddy.matcher.ElementMatcher; 8 | 9 | import java.lang.instrument.Instrumentation; 10 | 11 | /** 12 | * 插件定义,描述一个插件的能力,Advice可以作用于哪些类的哪些方法 13 | * 14 | * @author 章多亮 15 | * @since 2020/08/06 16 | */ 17 | public interface PluginDefine extends Ordered, Named { 18 | /** 19 | * 很多插件需要提前将一些类或者接口初始化,可以覆盖此方法 20 | * 21 | * @param builder 代理构建器 22 | * @param instrumentation 代码植入器 23 | */ 24 | default AgentBuilder prepare(AgentBuilder builder, Instrumentation instrumentation) { 25 | return builder; 26 | } 27 | 28 | /** 29 | * 获取插件的类型匹配器,用于Agent定位要植入代码的类型 30 | * 31 | * @return 类匹配器 32 | */ 33 | ElementMatcher getTypeMatcher(); 34 | 35 | /** 36 | * 获取插件列表 37 | * 38 | * @return 插件列表 39 | */ 40 | Plugin[] getPlugins(); 41 | 42 | /** 43 | * 判断是否忽略Object对象方法 44 | * 45 | * @return 如果不需要增强 Object 方法,返回true 46 | */ 47 | default boolean ignoreObjectMethod() { 48 | return true; 49 | } 50 | 51 | /** 52 | * 返回当前插件的目击者,用于判断是否支持当前应用的代码增强,默认直接目击 53 | * 54 | * @return 目击者 55 | */ 56 | default Witness getWitness() { 57 | return Witness.always(); 58 | } 59 | 60 | /** 61 | * 是否双亲委托 62 | */ 63 | default boolean isDelegated() { 64 | return false; 65 | } 66 | 67 | /** 68 | * 忽略增强agent classloader下的class 69 | */ 70 | default boolean ignoreEnhanceAgentClass() { 71 | return true; 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /gravity-plugin-api/src/main/java/io/manbang/gravity/plugin/PluginJar.java: -------------------------------------------------------------------------------- 1 | package io.manbang.gravity.plugin; 2 | 3 | import lombok.Data; 4 | 5 | import java.util.jar.JarFile; 6 | 7 | /** 8 | * 插件Jar包 9 | * 10 | * @author duoliang.zhang 11 | * @since 2020/9/10 16:23 12 | */ 13 | @Data 14 | public class PluginJar { 15 | private String name; 16 | private String type; 17 | private String url; 18 | private JarFile file; 19 | } 20 | -------------------------------------------------------------------------------- /gravity-plugin-api/src/main/java/io/manbang/gravity/plugin/PluginLoader.java: -------------------------------------------------------------------------------- 1 | package io.manbang.gravity.plugin; 2 | 3 | import java.util.List; 4 | 5 | /** 6 | * 插件加载器 7 | * 8 | * @author duoliang.zhang 9 | * @since 2020/9/10 16:10 10 | */ 11 | public interface PluginLoader extends Ordered, Named { 12 | /** 13 | * 获取一个插件载入器,用来载入插 14 | * 15 | * @return 插件载入器 16 | */ 17 | static PluginLoader loader() { 18 | return Services.loadFirst(PluginLoader.class, LocalPluginLoader.INSTANCE); 19 | } 20 | 21 | /** 22 | * 载入插件列表,只是列表清单,没有下载到本地 23 | * 24 | * @param options Agent命令行参数 25 | * @return 插件Jar包列表 26 | */ 27 | List loadPluginJars(AgentOptions options); 28 | } 29 | -------------------------------------------------------------------------------- /gravity-plugin-api/src/main/java/io/manbang/gravity/plugin/PluginType.java: -------------------------------------------------------------------------------- 1 | package io.manbang.gravity.plugin; 2 | 3 | /** 4 | * 插件类型 5 | * 6 | * @author duoliang.zhang 7 | * @since 2020/08/11 09:31:02 8 | */ 9 | public enum PluginType { 10 | /** 11 | * 切面 12 | */ 13 | ADVICE, 14 | /** 15 | * 拦截器 16 | */ 17 | INTERCEPTOR, 18 | /** 19 | * 拦截器实例 20 | */ 21 | INTERCEPTOR_INSTANCE 22 | } 23 | -------------------------------------------------------------------------------- /gravity-plugin-api/src/main/java/io/manbang/gravity/plugin/ReflectUtil.java: -------------------------------------------------------------------------------- 1 | package io.manbang.gravity.plugin; 2 | 3 | import java.lang.reflect.Constructor; 4 | import java.lang.reflect.InvocationTargetException; 5 | import java.security.AccessController; 6 | import java.security.PrivilegedAction; 7 | 8 | /** 9 | * @author weilong.hu 10 | * @since 2022/01/22 14:14 11 | */ 12 | public class ReflectUtil { 13 | public static Class[] EMPTY_CLASS_ARRAY = new Class[0]; 14 | 15 | private ReflectUtil() { 16 | } 17 | 18 | /** 19 | * copy from org.springframework.cglib.core.ReflectUtils#newInstance(java.lang.Class) 20 | */ 21 | public static T newInstance(Class type) { 22 | return newInstance(type, EMPTY_CLASS_ARRAY, null); 23 | } 24 | 25 | public static T newInstance(Class type, Class[] parameterTypes, Object[] args) { 26 | return newInstance(getConstructor(type, parameterTypes), args); 27 | } 28 | 29 | public static Constructor getConstructor(Class type, Class[] parameterTypes) { 30 | try { 31 | Constructor constructor = type.getDeclaredConstructor(parameterTypes); 32 | if (System.getSecurityManager() != null) { 33 | AccessController.doPrivileged((PrivilegedAction) () -> { 34 | constructor.setAccessible(true); 35 | return null; 36 | }); 37 | } else { 38 | constructor.setAccessible(true); 39 | } 40 | return constructor; 41 | } catch (NoSuchMethodException e) { 42 | throw new RuntimeException(e); 43 | } 44 | } 45 | 46 | public static T newInstance(final Constructor cstruct, final Object[] args) { 47 | boolean flag = cstruct.isAccessible(); 48 | try { 49 | if (!flag) { 50 | cstruct.setAccessible(true); 51 | } 52 | Object result = cstruct.newInstance(args); 53 | return (T) result; 54 | } catch (InstantiationException e) { 55 | throw new RuntimeException(e); 56 | } catch (IllegalAccessException e) { 57 | throw new RuntimeException(e); 58 | } catch (InvocationTargetException e) { 59 | throw new RuntimeException(e.getTargetException()); 60 | } finally { 61 | if (!flag) { 62 | cstruct.setAccessible(flag); 63 | } 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /gravity-plugin-api/src/main/java/io/manbang/gravity/plugin/Services.java: -------------------------------------------------------------------------------- 1 | package io.manbang.gravity.plugin; 2 | 3 | import java.util.ArrayList; 4 | import java.util.Comparator; 5 | import java.util.List; 6 | import java.util.Optional; 7 | import java.util.ServiceLoader; 8 | 9 | /** 10 | * @author duoliang.zhang 11 | * @since 2020/9/10 17:05 12 | */ 13 | public class Services { 14 | private Services() { 15 | throw new UnsupportedOperationException(); 16 | } 17 | 18 | /** 19 | * 从当前线程类加载起载入所有的服务 20 | * 21 | * @param serviceClass SPI接口类 22 | * @param 服务的具体类型 23 | * @return 服务列表 24 | */ 25 | public static List loadAll(Class serviceClass) { 26 | return loadAll(serviceClass, Thread.currentThread().getContextClassLoader()); 27 | } 28 | 29 | /** 30 | * 从指定类加载起载入所有的服务 31 | * 32 | * @param serviceClass SPI接口类 33 | * @param classLoader 类加载器,从指定的类加载加载服务 34 | * @param 服务的具体类型 35 | * @return 服务列表 36 | */ 37 | public static List loadAll(Class serviceClass, ClassLoader classLoader) { 38 | ServiceLoader services = ServiceLoader.load(serviceClass, classLoader); 39 | 40 | List list = new ArrayList<>(); 41 | 42 | for (T service : services) { 43 | list.add(service); 44 | } 45 | 46 | if (Ordered.class.isAssignableFrom(serviceClass)) { 47 | list.sort(Comparator.comparingInt(o -> ((Ordered) o).getOrder())); 48 | } 49 | 50 | return list; 51 | } 52 | 53 | /** 54 | * 从当前线程类加载起载入第一个加载的服务,如果服务实现 {@link Ordered} 接口,则先排序,后返回,否则,直接返回第一个加载的服务,顺序取决于JDK SPI本身 55 | * 56 | * @param serviceClass SPI接口类 57 | * @param 服务的具体类型 58 | * @return 服务实例 59 | */ 60 | public static Optional loadFirst(Class serviceClass) { 61 | return Optional.ofNullable(loadFirst(serviceClass, null)); 62 | } 63 | 64 | /** 65 | * 从当前线程类加载起载入第一个加载的服务,如果服务实现 {@link Ordered} 接口,则先排序,后返回,否则,直接返回第一个加载的服务,顺序取决于JDK SPI本身 66 | * 67 | * @param serviceClass SPI接口类 68 | * @param defaultService 当什么服务都没有找到的时候,返回这个默认服务 69 | * @param 服务的具体类型 70 | * @return 服务实例 71 | */ 72 | public static T loadFirst(Class serviceClass, T defaultService) { 73 | return loadFirst(serviceClass, Thread.currentThread().getContextClassLoader(), defaultService); 74 | } 75 | 76 | /** 77 | * 从指定类加载器载入第一个加载的服务,如果服务实现 {@link Ordered} 接口,则先排序,后返回,否则,直接返回第一个加载的服务,顺序取决于JDK SPI本身 78 | * 79 | * @param serviceClass SPI接口类 80 | * @param classLoader 类加载器,从指定的类加载加载服务 81 | * @param defaultService 当什么服务都没有找到的时候,返回这个默认服务 82 | * @param 服务的具体类型 83 | * @return 服务实例 84 | */ 85 | public static T loadFirst(Class serviceClass, ClassLoader classLoader, T defaultService) { 86 | List list = loadAll(serviceClass, classLoader); 87 | 88 | if (list.isEmpty()) { 89 | return defaultService; 90 | } 91 | 92 | return list.get(0); 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /gravity-plugin-api/src/main/java/io/manbang/gravity/plugin/SkipAdviceTemplate.java: -------------------------------------------------------------------------------- 1 | package io.manbang.gravity.plugin; 2 | 3 | import net.bytebuddy.asm.Advice.AllArguments; 4 | import net.bytebuddy.asm.Advice.OnMethodEnter; 5 | import net.bytebuddy.asm.Advice.OnMethodExit; 6 | import net.bytebuddy.asm.Advice.OnNonDefaultValue; 7 | import net.bytebuddy.asm.Advice.Origin; 8 | import net.bytebuddy.asm.Advice.Return; 9 | import net.bytebuddy.asm.Advice.This; 10 | import net.bytebuddy.asm.Advice.Thrown; 11 | import net.bytebuddy.implementation.bytecode.assign.Assigner; 12 | 13 | import java.lang.reflect.Method; 14 | import java.util.Stack; 15 | import java.util.logging.Level; 16 | import java.util.logging.Logger; 17 | 18 | import static io.manbang.gravity.plugin.Constants.ADVICE_LOGGER_NAME; 19 | import static io.manbang.gravity.plugin.Constants.ENTER_METHOD_ERROR; 20 | import static io.manbang.gravity.plugin.Constants.EXIT_METHOD_ERROR; 21 | 22 | /** 23 | * @author duoliang.zhang 24 | * @since 2021/8/26 14:12 25 | */ 26 | public class SkipAdviceTemplate { 27 | public static final String WITH_METHOD_TEMPLATE = "io.manbang.gravity.plugin.SkipAdviceTemplate$WithMethodTemplate"; 28 | public static final String WITHOUT_METHOD_TEMPLATE = "io.manbang.gravity.plugin.SkipAdviceTemplate$WithoutMethodTemplate"; 29 | private static final ThreadLocal> EXECUTE_CONTEXT_HOLDER = ThreadLocal.withInitial(Stack::new); 30 | public static final Logger LOGGER = Logger.getLogger(ADVICE_LOGGER_NAME); 31 | 32 | private SkipAdviceTemplate() { 33 | throw new UnsupportedOperationException(); 34 | } 35 | 36 | public static void pushContext(ExecuteContext context) { 37 | EXECUTE_CONTEXT_HOLDER.get().push(context); 38 | } 39 | 40 | public static ExecuteContext popContext() { 41 | return EXECUTE_CONTEXT_HOLDER.get().pop(); 42 | } 43 | 44 | public static void removeContext() { 45 | EXECUTE_CONTEXT_HOLDER.remove(); 46 | } 47 | 48 | public static ExecuteContext getContext(Class targetClass, Object target, Method method, Object[] arguments, ExtendedExecutor extendedExecutor) { 49 | return ExecuteContext.builder() 50 | .targetClass(targetClass) 51 | .target(target) 52 | .method(method) 53 | .arguments(arguments) 54 | .extraExecutor(extendedExecutor) 55 | .build(); 56 | } 57 | 58 | public static class WithMethodTemplate { 59 | private static io.manbang.gravity.plugin.Advice advice; 60 | private static ExtendedExecutor extendedExecutor; 61 | 62 | private WithMethodTemplate() { 63 | throw new UnsupportedOperationException(); 64 | } 65 | 66 | public static io.manbang.gravity.plugin.Advice getAdvice() { 67 | return advice; 68 | } 69 | 70 | public static void setAdvice(io.manbang.gravity.plugin.Advice advice) { 71 | SkipAdviceTemplate.WithMethodTemplate.advice = advice; 72 | } 73 | 74 | public static ExtendedExecutor getExtendedExecutor() { 75 | return extendedExecutor; 76 | } 77 | 78 | public static void setExtendedExecutor(ExtendedExecutor extendedExecutor) { 79 | WithMethodTemplate.extendedExecutor = extendedExecutor; 80 | } 81 | 82 | @OnMethodEnter(skipOn = OnNonDefaultValue.class) 83 | public static boolean enterMethod(@Origin Class targetClass, 84 | @This(optional = true) Object target, 85 | @Origin Method method, 86 | @AllArguments(readOnly = false, typing = Assigner.Typing.DYNAMIC) Object[] arguments) { 87 | ExecuteContext context = getContext(targetClass, target, method, arguments, getExtendedExecutor()); 88 | pushContext(context); 89 | try { 90 | getAdvice().enterMethod(context); 91 | arguments = context.getArguments(); 92 | return context.isSkipped(); 93 | } catch (Throwable e) { 94 | LOGGER.log(Level.WARNING, ENTER_METHOD_ERROR, e); 95 | return true; 96 | } 97 | } 98 | 99 | @OnMethodExit(onThrowable = Throwable.class) 100 | public static void exitMethod(@This(optional = true) Object target, 101 | @Return(readOnly = false, typing = Assigner.Typing.DYNAMIC) Object result, 102 | @Thrown(readOnly = false) Throwable throwable) { 103 | ExecuteContext context = popContext(); 104 | context.setTarget(target); 105 | context.setResult(result); 106 | context.setThrowable(throwable); 107 | 108 | try { 109 | getAdvice().exitMethod(context); 110 | result = context.getResult(); 111 | throwable = context.getThrowable(); 112 | } catch (Throwable e) { 113 | LOGGER.log(Level.WARNING, EXIT_METHOD_ERROR, e); 114 | } 115 | } 116 | } 117 | 118 | public static class WithoutMethodTemplate { 119 | private static io.manbang.gravity.plugin.Advice advice; 120 | private static ExtendedExecutor extendedExecutor; 121 | 122 | private WithoutMethodTemplate() { 123 | throw new UnsupportedOperationException(); 124 | } 125 | 126 | public static io.manbang.gravity.plugin.Advice getAdvice() { 127 | return advice; 128 | } 129 | 130 | public static void setAdvice(io.manbang.gravity.plugin.Advice advice) { 131 | WithoutMethodTemplate.advice = advice; 132 | } 133 | 134 | public static ExtendedExecutor getExtendedExecutor() { 135 | return extendedExecutor; 136 | } 137 | 138 | public static void setExtendedExecutor(ExtendedExecutor extendedExecutor) { 139 | WithoutMethodTemplate.extendedExecutor = extendedExecutor; 140 | } 141 | 142 | @OnMethodEnter(skipOn = OnNonDefaultValue.class) 143 | public static boolean enterMethod(@Origin Class targetClass, 144 | @This(optional = true) Object target, 145 | @AllArguments(readOnly = false, typing = Assigner.Typing.DYNAMIC) Object[] arguments) { 146 | ExecuteContext context = getContext(targetClass, target, null, arguments, getExtendedExecutor()); 147 | pushContext(context); 148 | try { 149 | getAdvice().enterMethod(context); 150 | arguments = context.getArguments(); 151 | return context.isSkipped(); 152 | } catch (Throwable e) { 153 | LOGGER.log(Level.WARNING, ENTER_METHOD_ERROR, e); 154 | return true; 155 | } 156 | } 157 | 158 | @OnMethodExit(onThrowable = Throwable.class) 159 | public static void exitMethod(@This(optional = true) Object target, 160 | @Return(readOnly = false, typing = Assigner.Typing.DYNAMIC) Object result, 161 | @Thrown(readOnly = false) Throwable throwable) { 162 | ExecuteContext context = popContext(); 163 | context.setTarget(target); 164 | context.setResult(result); 165 | context.setThrowable(throwable); 166 | 167 | try { 168 | getAdvice().exitMethod(context); 169 | result = context.getResult(); 170 | throwable = context.getThrowable(); 171 | } catch (Throwable e) { 172 | LOGGER.log(Level.WARNING, EXIT_METHOD_ERROR, e); 173 | } 174 | } 175 | } 176 | } 177 | -------------------------------------------------------------------------------- /gravity-plugin-api/src/main/java/io/manbang/gravity/plugin/Switchable.java: -------------------------------------------------------------------------------- 1 | package io.manbang.gravity.plugin; 2 | 3 | /** 4 | * 标记插件是否支持在线开关控制,如果支持开关控制的话,会被包装成一个可开关控制的插件 5 | * 6 | * @author duoliang.zhang 7 | * @since 2020/11/26 16:56 8 | */ 9 | public interface Switchable { 10 | 11 | /** 12 | * 判断 13 | * 14 | * @return 如果当前切面支持开关控制,返回true 15 | */ 16 | default boolean isSwitchable() { 17 | return true; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /gravity-plugin-api/src/main/java/io/manbang/gravity/plugin/Switcher.java: -------------------------------------------------------------------------------- 1 | package io.manbang.gravity.plugin; 2 | 3 | import java.util.EventListener; 4 | 5 | /** 6 | * 插件使能开关 7 | * 8 | * @author duoliang.zhang 9 | * @since 2020/10/28 18:54 10 | */ 11 | public interface Switcher extends EventListener { 12 | /** 13 | * 从SPI中获取一个控制开关,用于控制左右的插件是否生效,如果没有提供开关的实现,则直接返回默认开关实现,总是工作的开关 14 | * 15 | * @return 开关 16 | */ 17 | static Switcher of(ClassLoader classLoader) { 18 | return CacheHolder.INSTANCE.loadIfAbsent(classLoader, Switcher.class, AlwaysWorkingSwitcher.INSTANCE); 19 | } 20 | 21 | /** 22 | * 监听指定开关量变化 23 | * 24 | * @param listener 开关监听器 25 | */ 26 | default void addListener(SwitcherListener listener) { 27 | // do nothing 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /gravity-plugin-api/src/main/java/io/manbang/gravity/plugin/SwitcherAdvice.java: -------------------------------------------------------------------------------- 1 | package io.manbang.gravity.plugin; 2 | 3 | import java.util.concurrent.atomic.AtomicReference; 4 | 5 | /** 6 | * 智能切面,可以监听是否启用,为了防止每次调用开关量的判断,会直接监听开关的变化,然后更换实际执行的切面,如此可以节省每次判断开关量的开销 7 | * 8 | * @author duoliang.zhang 9 | * @since 2020/11/5 10:22 10 | */ 11 | class SwitcherAdvice implements Advice, SwitcherListener { 12 | private final AtomicReference adviceRef; 13 | private final Advice advice; 14 | 15 | SwitcherAdvice(Advice advice) { 16 | this.advice = advice; 17 | this.adviceRef = new AtomicReference<>(EmptyAdvice.INSTANCE); 18 | this.subscribeSwitcherChange(advice); 19 | } 20 | 21 | private void subscribeSwitcherChange(Advice advice) { 22 | Switcher.of(advice.getClass().getClassLoader()).addListener(this); 23 | } 24 | 25 | @Override 26 | public void enterMethod(ExecuteContext context) { 27 | adviceRef.get().enterMethod(context); 28 | } 29 | 30 | @Override 31 | public void exitMethod(ExecuteContext context) { 32 | adviceRef.get().exitMethod(context); 33 | } 34 | 35 | @Override 36 | public String getName() { 37 | return advice.getName(); 38 | } 39 | 40 | @Override 41 | public void on() { 42 | adviceRef.set(advice); 43 | advice.resume(); 44 | } 45 | 46 | @Override 47 | public void off() { 48 | adviceRef.set(EmptyAdvice.INSTANCE); 49 | advice.suspend(); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /gravity-plugin-api/src/main/java/io/manbang/gravity/plugin/SwitcherInterceptor.java: -------------------------------------------------------------------------------- 1 | package io.manbang.gravity.plugin; 2 | 3 | import java.util.concurrent.atomic.AtomicReference; 4 | 5 | /** 6 | * 智能拦截器,可以监听是否启用,为了防止每次调用开关量的判断,会直接监听开关的变化,然后更换实际执行的拦截器,如此可以节省每次判断开关量的开销 7 | * 8 | * @author duoliang.zhang 9 | * @since 2020/11/5 10:49 10 | */ 11 | class SwitcherInterceptor implements Interceptor, SwitcherListener { 12 | private final AtomicReference interceptorRef; 13 | private final Interceptor interceptor; 14 | 15 | SwitcherInterceptor(Interceptor interceptor) { 16 | this.interceptor = interceptor; 17 | this.interceptorRef = new AtomicReference<>(EmptyInterceptor.INSTANCE); 18 | this.subscribeSwitcherChange(interceptor); 19 | } 20 | 21 | private void subscribeSwitcherChange(Interceptor interceptor) { 22 | Switcher.of(interceptor.getClass().getClassLoader()).addListener(this); 23 | } 24 | 25 | @Override 26 | public boolean beforeMethod(ExecuteContext context) { 27 | try { 28 | return interceptorRef.get().beforeMethod(context); 29 | } catch (Exception e) { 30 | GravityLogger.of(interceptor.getClass().getClassLoader()).error("G:Interceptor", "重力方法前置回调异常,不影响业务,如出现次数较多,可联系gravity负责人定位。", e); 31 | return true; 32 | } 33 | } 34 | 35 | @Override 36 | public void handleException(ExecuteContext context) throws Throwable { 37 | interceptorRef.get().handleException(context); 38 | } 39 | 40 | @Override 41 | public void afterMethod(ExecuteContext context) { 42 | try { 43 | interceptorRef.get().afterMethod(context); 44 | } catch (Exception e) { 45 | GravityLogger.of(interceptor.getClass().getClassLoader()).error("G:Interceptor", "重力方法后置回调异常,不影响业务,如出现次数较多,可联系gravity负责人定位。", e); 46 | } 47 | } 48 | 49 | @Override 50 | public void onCompleted(ExecuteContext context) { 51 | try { 52 | interceptorRef.get().onCompleted(context); 53 | } catch (Exception e) { 54 | GravityLogger.of(interceptor.getClass().getClassLoader()).error("G:Interceptor", "重力方法完成回调异常,不影响业务,如出现次数较多,可联系gravity负责人定位。", e); 55 | } 56 | } 57 | 58 | @Override 59 | public String getName() { 60 | return interceptor.getName(); 61 | } 62 | 63 | @Override 64 | public void on() { 65 | interceptorRef.set(interceptor); 66 | interceptor.resume(); 67 | } 68 | 69 | @Override 70 | public void off() { 71 | interceptorRef.set(EmptyInterceptor.INSTANCE); 72 | interceptor.suspend(); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /gravity-plugin-api/src/main/java/io/manbang/gravity/plugin/SwitcherListener.java: -------------------------------------------------------------------------------- 1 | package io.manbang.gravity.plugin; 2 | 3 | /** 4 | * 开关监听器 5 | * 6 | * @author duoliang.zhang 7 | * @since 2020/11/5 14:07 8 | */ 9 | public interface SwitcherListener extends Named { 10 | /** 11 | * 打开开关回调 12 | */ 13 | void on(); 14 | 15 | /** 16 | * 关闭开关回调 17 | */ 18 | void off(); 19 | } 20 | -------------------------------------------------------------------------------- /gravity-plugin-api/src/main/java/io/manbang/gravity/plugin/UnitTestContainer.java: -------------------------------------------------------------------------------- 1 | package io.manbang.gravity.plugin; 2 | 3 | import java.util.HashMap; 4 | import java.util.LinkedList; 5 | import java.util.Map; 6 | import java.util.Queue; 7 | 8 | /** 9 | * 为插件提供UT支持,思路为:植入代码负责写,Test Case负责读 10 | *

11 | * - agentRunning: 表示PluginAgent是否正常启动 12 | *

13 | * - feature: 表示UT功能是否启用 14 | *

15 | * 每个key对应的value是一个queue,用于检测植入的代码是否只执行了1次(非常重要!) 16 | * 17 | * @author Kai 18 | */ 19 | public class UnitTestContainer { 20 | /** 21 | * UT测试功能标识 22 | */ 23 | private static volatile boolean feature = false; 24 | 25 | /** 26 | * 开启UT测试 27 | */ 28 | public static void enableUnitTest() { 29 | feature = true; 30 | } 31 | 32 | /** 33 | * 判断UT测试是否开启 34 | */ 35 | public static boolean isUnitTestEnabled() { 36 | return feature; 37 | } 38 | 39 | 40 | /** 41 | * agent运行标记 42 | */ 43 | private static volatile boolean agentRunning = false; 44 | 45 | /** 46 | * 开启UT测试 47 | */ 48 | public static void markAgentRunning() { 49 | agentRunning = true; 50 | } 51 | 52 | /** 53 | * 判断UT测试是否开启 54 | */ 55 | public static boolean isAgentRunning() { 56 | return agentRunning; 57 | } 58 | 59 | /** 60 | * UT使用的KV存储 61 | */ 62 | private static ThreadLocal>> keyValueHolder = new ThreadLocal<>(); 63 | 64 | public static String peek(String key) { 65 | return getQueue(key).peek(); 66 | } 67 | 68 | public static int size(String key) { 69 | return getQueue(key).size(); 70 | } 71 | 72 | public static String poll(String key) { 73 | return getQueue(key).poll(); 74 | } 75 | 76 | public static void set(String key, String value) { 77 | add(key, value); 78 | } 79 | 80 | public static void add(String key, String value) { 81 | getQueue(key).add(value); 82 | } 83 | 84 | private static Queue getQueue(String key) { 85 | Map> map = getContainer(); 86 | 87 | return map.computeIfAbsent(key, k -> new LinkedList<>()); 88 | } 89 | 90 | private static Map> getContainer() { 91 | Map> map = keyValueHolder.get(); 92 | if (map == null) { 93 | map = new HashMap<>(); 94 | keyValueHolder.set(map); 95 | } 96 | 97 | return map; 98 | } 99 | 100 | public static void clear() { 101 | keyValueHolder.remove(); 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /gravity-plugin-api/src/main/java/io/manbang/gravity/plugin/Witness.java: -------------------------------------------------------------------------------- 1 | package io.manbang.gravity.plugin; 2 | 3 | /** 4 | * 目击证人,用于判断当前插件是否支持,目标应用的代码拦截;一般通过构建路径是否存在指定全限定名的类,或者Jar的版本号 5 | * 6 | * @author dzhang 7 | * @since 2020/08/28 8 | */ 9 | public interface Witness { 10 | /** 11 | * 总看见的目击者,说明对应的插件可以不用关心植入目标的版本的 12 | * 13 | * @return 目击者 14 | */ 15 | static Witness always() { 16 | return AlwaysWitness.INSTANCE; 17 | } 18 | 19 | /** 20 | * 通过比对Jar的META-INF来确定,是否支持拦截 21 | * 22 | * @param metaInfo Jar的META-INF信息 23 | * @return 目击者 24 | */ 25 | static Witness metaInfo(MetaInfo metaInfo) { 26 | return new MetaInfoWitness(metaInfo); 27 | } 28 | 29 | /** 30 | * 目标构建路径中必须存在指定类名列表的类型,任意一个不存,都不算目击 31 | * 32 | * @param classes 类名列表 33 | * @return 目击者 34 | */ 35 | static Witness classes(String... classes) { 36 | return new ClassesWitness(classes); 37 | } 38 | 39 | /** 40 | * Maven打包的Jar,通过指定GAV,来判断,是否匹配目标构建路径的Jar版本号;groupId和artifactId都是相等匹配,version是前缀匹配 41 | * 42 | * @param gav groupId:artifactId:version 43 | * @return 目击者 44 | */ 45 | static Witness maven(GAV gav) { 46 | return new MavenWitness(gav); 47 | } 48 | 49 | /** 50 | * 看看指定类加载中,是否有我们关注的东西 51 | * 52 | * @param classLoader 负责载入应用系统的类加载器 53 | * @return 如果看到自己关注的东西,就返回true 54 | */ 55 | boolean saw(ClassLoader classLoader); 56 | } 57 | -------------------------------------------------------------------------------- /gravity-plugin/gravity-plugin-monitor/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | gravity-plugin 7 | io.manbang 8 | 1.0.0 9 | 10 | 4.0.0 11 | 12 | gravity-plugin-monitor 13 | 14 | 15 | 8 16 | 8 17 | 18 | 19 | 20 | 21 | io.manbang 22 | gravity-plugin-api 23 | 1.0.0 24 | compile 25 | 26 | 27 | -------------------------------------------------------------------------------- /gravity-plugin/gravity-plugin-monitor/src/main/java/io/manbang/gravity/plugin/monitor/AopAdvice.java: -------------------------------------------------------------------------------- 1 | package io.manbang.gravity.plugin.monitor; 2 | 3 | import io.manbang.gravity.plugin.Advice; 4 | import io.manbang.gravity.plugin.ExecuteContext; 5 | 6 | import java.lang.reflect.Method; 7 | 8 | /** 9 | * @author weilong.hu 10 | * @since 2022/05/19 11:05 11 | */ 12 | public class AopAdvice implements Advice { 13 | private static final java.util.logging.Logger log = java.util.logging.Logger.getLogger(AopAdvice.class.getName()); 14 | 15 | @Override 16 | public void enterMethod(ExecuteContext context) { 17 | final Method method = context.getMethod(); 18 | final Object[] argument = context.getArguments(); 19 | final StringBuilder builder = new StringBuilder(); 20 | builder.append("method enter:").append(method.getName()); 21 | for (int i = 0; i < argument.length; i++) { 22 | builder.append(" arg number:").append(i).append(" arg :").append(argument[i]); 23 | } 24 | log.info(builder.toString()); 25 | } 26 | 27 | @Override 28 | public void exitMethod(ExecuteContext context) { 29 | final Method method = context.getMethod(); 30 | final Object result = context.getResult(); 31 | final StringBuilder builder = new StringBuilder(); 32 | builder.append("method exit:").append(method.getName()); 33 | builder.append("result:").append(result); 34 | log.info(builder.toString()); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /gravity-plugin/gravity-plugin-monitor/src/main/java/io/manbang/gravity/plugin/monitor/AopPluginDefine.java: -------------------------------------------------------------------------------- 1 | package io.manbang.gravity.plugin.monitor; 2 | 3 | import io.manbang.gravity.plugin.Plugin; 4 | import io.manbang.gravity.plugin.PluginDefine; 5 | import net.bytebuddy.description.type.TypeDescription; 6 | import net.bytebuddy.matcher.ElementMatcher; 7 | import net.bytebuddy.matcher.ElementMatchers; 8 | 9 | /** 10 | * @author weilong.hu 11 | * @since 2022/05/19 10:55 12 | */ 13 | public class AopPluginDefine implements PluginDefine { 14 | @Override 15 | public ElementMatcher getTypeMatcher() { 16 | return ElementMatchers.named("io.manbang.gravity.trade.Driver") 17 | .or(ElementMatchers.named("io.manbang.gravity.trade.Shippers")); 18 | } 19 | 20 | @Override 21 | public Plugin[] getPlugins() { 22 | return new Plugin[]{Plugin.advice(ElementMatchers.isMethod(), "io.manbang.gravity.plugin.monitor.AopAdvice").withMethod()}; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /gravity-plugin/gravity-plugin-monitor/src/main/resources/META-INF/services/io.manbang.gravity.plugin.PluginDefine: -------------------------------------------------------------------------------- 1 | io.manbang.gravity.plugin.monitor.AopPluginDefine -------------------------------------------------------------------------------- /gravity-plugin/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | gravity-parent 7 | io.manbang 8 | 1.0.0 9 | 10 | 4.0.0 11 | 12 | gravity-plugin 13 | pom 14 | 15 | gravity-plugin-monitor 16 | 17 | 18 | 19 | 8 20 | 8 21 | 22 | 23 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | io.manbang 8 | gravity-parent 9 | 1.0.0 10 | 11 | gravity-plugin-api 12 | gravity-agent 13 | gravity-demo 14 | gravity-plugin 15 | 16 | pom 17 | 18 | 19 | UTF-8 20 | 1.8 21 | ${java.version} 22 | ${java.version} 23 | 24 | 1.18.10 25 | 3.14.9 26 | 1.2.75 27 | 1.10.13 28 | 29 | 30 | 31 | 32 | org.projectlombok 33 | lombok 34 | 35 | 36 | 37 | 38 | 39 | 40 | net.bytebuddy 41 | byte-buddy 42 | ${byte-buddy.version} 43 | 44 | 45 | com.squareup.okhttp3 46 | okhttp 47 | ${okhttp.version} 48 | 49 | 50 | com.alibaba 51 | fastjson 52 | ${fastjson.version} 53 | 54 | 55 | org.projectlombok 56 | lombok 57 | ${lombok.version} 58 | true 59 | provided 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | org.apache.maven.plugins 69 | maven-compiler-plugin 70 | 3.8.1 71 | 72 | ${java.version} 73 | ${java.version} 74 | ${java.version} 75 | UTF-8 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | --------------------------------------------------------------------------------