zoukankan      html  css  js  c++  java
  • 初探Java agent技术

    前言

    不知道各位小伙伴在此之前,是否有听过或者了解过agent相关技术,没有听说过也没有关系,我们今天的目的就是介绍agent的相关技术,探讨agent的应用场景,分享一些实际开发中的应用案例。

    印象中,我第一次了解agent技术,是在分享skywalking这款工具的时候,skywalking与我们项目的绑定就是通过agent来实现的。好了,先说这么多,接下来我们就来详细介绍下agent的一些技术点。

    Agent

    Agent是什么

    Agent中文含义代理,但是在java中我跟喜欢称它为探针而非代理,尽管他也属于代理技术,但是代理本身并不能体现agent的作用。

    agent技术是在JDK1.5引入的,通过agent技术,我们可以构建一个独立于应用程序的代理程序,用来协助监测、运行甚至替换其他JVM上的程序。使用它可以实现虚拟机级别的AOP功能。

    Agent分为两种,一种是在主程序之前运行的Agent,一种是在主程序之后运行的Agent(前者的升级版,1.6以后提供),稍后我们会有具体实例展示。

    Agent能干什么

    首先它最大的作用就是解耦,比如skywalking中的应用,我们不需要对我们的程序做任何修改,只需要通过agent技术引入skywalking的代理包即可;其次最常应用的场景就是jvm级的AOP,比如jvm的监测;另一种就是类似热部署这样的字节码动态操作。

    Agent技术演示

    说了这么多好多小伙伴肯定看的云里雾里的,接下来我们通过两个简单示例,来演示下Agent技术的神奇之处。

    先看第一种,也就是在主程序之前运行的Agent

    在主程序之前运行的Agent

    首先我们创建一个maven项目,编写这样一个Agent类:

    import java.lang.instrument.Instrumentation;
    /**
     * 在主程序之前运行的Agent
     */
    public class PremainAgent {
        public static void premain(String preArgs, Instrumentation instrumentation) {
            System.out.println("premainAgent.premain start");
            System.out.println("preArgs: " + preArgs);
            Class[] allLoadedClasses = instrumentation.getAllLoadedClasses();
            for (Class allLoadedClass : allLoadedClasses) {
                System.out.println("premainAgent LoadedClass: " + allLoadedClass.getName());
            }
        }
    }
    

    这里的方法名和参数列表是固定的,根据方法名我们能看出这个方法应该是运行在main方法之前的,等下测试下就知道了。

    接着,我们在pom.xml文件中增加如下内容:

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-jar-plugin</artifactId>
                <version>2.4</version>
                <configuration>
                    <archive>
                        <manifest>
                            <addClasspath>true</addClasspath>
                        </manifest>
                        <manifestEntries>
                            <Premain-Class>io.github.syske.agent.PremainAgent</Premain-Class>
                        </manifestEntries>
                    </archive>
                </configuration>
            </plugin>
        </plugins>
    </build>
    

    上面这些内容是配置我们构建时生成的MANIFEST文件,通常我们打的jar包都有这个文件。最核心的配置就是Premain-Class,这里配置的是我们探针的类名,如果没有这个配置,我们的premain方法是不会被识别的。

    然后我们通过maven把我们当前项目打成一个jar包,打完包之后的jar文件如上图,打开MANIFEST.MF文件,你会发现我们指定的Premain-Class也被写入了,这时候我们的包就是打好了,下面就是运行测试了。

    运行也很简单,只需要找到一个可运行的jar包,比如一个springboot项目的包,然后在jar文件的启动命令中,增加如下配置即可:

    --javaagent:你的agent文件完整路径/agent文件名.jar
    # 例如我的:D:workspacelearningexample-everydayexample-2021.07.02	argetexample-2021.07.02-1.0-SNAPSHOT.jar
    

    这里我用之前的一个springboot项目演示:

    java -javaagent:D:workspacelearningexample-everydayexample-2021.07.02	argetexample-2021.07.02-1.0-SNAPSHOT.jar -jar 
    

    大家注意,在javaagentagent文件之间不能有空格,否则会报如下错误

    如果你的配置和启动命令都没有问题,在启动控制台应该会显示如下信息:

    我们可以看到premain方法在我们springboot项目启动前被执行了,但是preArgsnull,这是由于我们没有注入参数,所以显示为空,我们可以通过这样的方式为preArgs注入参数:

    java -javaagent:D:workspacelearningexample-everydayexample-2021.07.02	argetexample-2021.07.02-1.0-SNAPSHOT.jar=syske -jar  .springboot-learning-0.0.1-SNAPSHOT.jar
    

    也就是在我们的agent包后面直接=需要注入的参数值就可以了,然后再次执行你会发现参数已经被注入了:

    关于Instrumentation这个参数,今天先不讲了,我们说的字节码操都是基于这个参数进行操作的。下面我们看下第二种Agent

    在主程序之后运行的Agent

    相比第一种agent,第二种是在main方法启动后运行agent方法,而且这种方式应用最广泛,比如我们前面说的热部署,就是这种方式实现的,下来我们看下具体如何实现。

    第一步,也是写Agent实现类:

    public class AgentMain {
        public static void agentmain(String args, Instrumentation instrumentation) {
            System.out.println("AgentMainTest.agentmain start");
            System.out.println("args: " + args);
            Class[] allLoadedClasses = instrumentation.getAllLoadedClasses();
    //        for (Class allLoadedClass : allLoadedClasses) {
            System.out.println("AgentMainTest LoadedClass: " + allLoadedClasses[0].getName());
    //        }
        }
    }
    

    和第一种agent不一样的只有方法名,连参数都一幕一样,这里为了方便查看,我只打印了一行数据。然后我们还需要修改下maven的打包配置,需要把之前的Premain-Class标签改成Agent-Class,其他都一样:

    然后再打包,但是这一次运行方式和第一次不一样,这里的agent要通过代码来启动。

    我们创建一个测试类,写一个main方法,因为要用到tools包下的类,所以要先引入tools包:

    测试类如下:

    import com.sun.tools.attach.AgentInitializationException;
    import com.sun.tools.attach.AgentLoadException;
    import com.sun.tools.attach.AttachNotSupportedException;
    import com.sun.tools.attach.VirtualMachine;
    import com.sun.tools.attach.VirtualMachineDescriptor;
    
    public class MainTest {
        public static void main(String[] args) throws IOException, AttachNotSupportedException, AgentLoadException, AgentInitializationException {
            List<VirtualMachineDescriptor> machineDescriptorList = VirtualMachine.list();
            for (VirtualMachineDescriptor virtualMachineDescriptor : machineDescriptorList) {
                if ("io.github.syske.agent.MainTest".equals(virtualMachineDescriptor.displayName())) {
                    String id = virtualMachineDescriptor.id();
                    VirtualMachine virtualMachine = VirtualMachine.attach(id);
                    virtualMachine.loadAgent("D:\workspace\learning\example-everyday\example-2021.07.02\target\example-2021.07.02-1.0-SNAPSHOT.jar",
                            "syske agentmain");
                    virtualMachine.detach();
                }
            }
            System.out.println("MainTest start");
        }
    }
    

    这里解释下,VirtualMachine.list()是获取当前运行的所有jvm虚拟机,运行结果如下:

    其中的VirtualMachineDescriptor包含如下信息:

    我们需要从中拿出displayNameio.github.syske.agent.MainTest,也就是当前类的虚拟机描述信息,然后根据虚拟机id拿到对应虚拟机,然后为该虚拟机加载Agent包,同时我们还在加在Agent包的同时,传入了syske agentmain参数,这里的参数和我们第一种方式=的方式类似,就相当于给args赋值,然后断开虚拟机连接。

    运行代码,结果如下:

    根据运行结果,我们发现这种Agent并发是在main方法之后执行,而是可以在你任意需要的地方调用。相比于第一种,确实要灵活一些。

    总结

    Agent其实在日常开发中经常用到,但是由于我们大部分情况下都用的是继承开发环境,所以感知不强,像日志采集、热部署等基本上都是基于Agent来实现的。

    当然,agent最大的好处在于,它可以有效解耦,实现jvm层面的AOP,而且它又支持字节码操作,如果你玩的够溜,你就可以实现更多强大功能,而且可玩性还高,简直可以为所欲为。

    今天我们暂时就讲这么多,后面抽时间用agent实现一些具体的功能,比如字节码操作,让大家真正见识Agent的强大之处。

  • 相关阅读:
    LeetCode0680.验证回文字符串 Ⅱ
    20145208 蔡野 《网络对抗》Exp6 信息搜集与漏洞扫描
    20145208 蔡野 《网络对抗》Exp5 MSF基础应用
    辅助模块:udp_sweep
    对客户端攻击:adobe_toolbutton
    对浏览器攻击:MS10-002
    主动攻击:利用ms08_067_netapi进行攻击
    20145208 蔡野 《网络对抗》Exp4 恶意代码分析
    20145208 蔡野《网络对抗》Exp3 Advanced 恶意代码伪装技术实践
    20145208 蔡野《网络对抗》shellcode注入&Return-to-libc攻击深入
  • 原文地址:https://www.cnblogs.com/caoleiCoding/p/15015088.html
Copyright © 2011-2022 走看看