使用bybuddy编写java agent

使用bybuddy编写java agent,第1张

bytebuddy简介

可以在运行期实现字节码的生成、修改等,简单的可以理解为字节码的增强。类似于此功能的框架有比如javassist(基于ASM),Skywalking就是基于bytebuddy实现的,想知道更多的可以自行百度,本文主要讨论入门使用。

agent编写

1、新建一个maven项目(不阐述详情)

2、引入bytebuddy的包(我测试使用的是最新版本)


    net.bytebuddy
    byte-buddy
    1.12.9

 3、添加打包的插件shade插件是吧bytebuddy的包打到agent里面去,maven-jar-plugin是打包的时候自动生成MANIFEST文件的规则,如下:

   
        
            
                org.apache.maven.plugins
                maven-shade-plugin
                
                    
                        package
                        
                            shade
                        
                    
                
                
                    
                        
                            net.bytebuddy:byte-buddy:jar:
                        
                    
                
            
            
                org.apache.maven.plugins
                maven-jar-plugin
                3.1.0
                
                    
                        
                        
                            true
                        
                        
                            com.xxx.wangyu.test.TestAgent
                            com.xxx.wangyu.test.TestAgent
                            true
                            true
                        
                    
                
            
        
    

4、创建agent主类(主要是定义被拦截的类的位置,比如包路径、类名、方法名、参数、参数类型等等)

public class TestAgent {
    public static void premain(String agentArgs, Instrumentation inst) {
        System.out.println("permain进来拉");
        //其实我们也可以拦截指定的maven坐标和指定的版本号,但是需要自己在bytebuddy的基础上做扩展
        AgentBuilder.Transformer transformer = (builder, typeDescription, classLoader, javaModule) -> builder
                //指定拦截的方法和方法的入参和方法的访问修饰符,ElementMatchers里面的api很多,这里不一一举例,api设计挺好的,基本可以见明知意
                .method(ElementMatchers.named("service").and(ElementMatchers.
                                takesArguments(ServletRequest.class, ServletResponse.class)).
                        and(ElementMatchers.isPublic()))
                //指定拦截器,这个TestInteceptor也是我们开发者需要去写的拦截的业务逻辑所在之处
                .intercept(MethodDelegation.to(TestInteceptor.class));
        //创建代理
        new AgentBuilder
                .Default()
                //指定拦截的类,我的代码是拦截Servlet
                .type(ElementMatchers.named("javax.servlet.http.HttpServlet"))
                .transform(transformer)
                .installOn(inst);
    }

5、编写Inteceptor

public class TestInteceptor {
    
//@RuntimeType取消严格类型检查,有可能会报类型转换错误,解释一下这个意思,我们在编写agent的配置时,上述的TestAgent类,只指定了Inteceptor的类名,并没有指定具体的处理方法,那可以想象一个问题,我的Inteceptor是可以写多个方法的,如果我写了多个方法,方法名相同的情况下(重载),方法参数都和被代理的参数不同,他怎么知道调用哪个?大致原理是这样的,如果加了@RuntimeType注解,在找代理方法的时候会优先找同名的,如果找不到同名的,那会去找方法参数个数和类型与被代理的方法最匹配的,如果都找不到会去找最接近的,比如Inteceptor2个方法,一个入参是Object类型,一个是Integer类型,被代理的方法入参类型是String类型,那他会最终调用Object类型的这个方法。如果不加此注解,就没有这样的规则,建议加上。
@RuntimeType    
public static Object inteceptor(@Origin Method method, @SuperCall Callable callable) {
        System.out.println("进来啦!");
        //before,do your something
        System.out.println("目标方法执行之前的调用!");
        Object result;
        try {
            result = callable.call();
        } catch (Exception e) {
            //throw exception,do your something
            System.out.println("异常拉!");
            throw new RuntimeException(e);
        }
        //after,do your something
        System.out.println("目标方法执行之后的调用!");
        return result;

    }

6、在agent项目中执行mvn install,会把agent包打到本地,记住这个路径。

7、准备一个java服务,因为我要拦截的是Servlet,所以我在我本地创建了个web服务,怎么创建不在赘述

8、在你创建好的服务中配置启动参数加入以下命令:

-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=5005 -javaagent:/Users/wangyu/.m2/repository/org/example/TeseAgent/1.0-SNAPSHOT/TeseAgent-1.0-SNAPSHOT.jar

需要注意的是把我的例子中的路径换成你本地的agent包的路径

上述命令-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=5005是为了方便本地debug调试agent包的,就是能让你的被代理的服务在执行被代理的方法时可以以debug的形式进入到你本地的agent的项目中,具体做法如下:

        8-(1)在你的被代理服务上面加好上面的命令,run服务,一定要run,不要debug启动,因为这种模式下仅支持一个debug服务,run以后,会发现控制台会阻塞,如图。

        8-(2)在你的agent服务中创建好远程连接(仅第一次需要创建),如图:

 

 

最后一张图上面只需要改名字,随便起,方便自己识别,可能会有多个agent同时开发的场景,端口号就是被代理服务启动参数中的端口号,默认5005,也可以自己去改。

这种方式也可以去吧dev获取qa环境的服务debug到本地,前提是网络是通的,不建议这样,因为debug的时候会阻塞,dev环境和qa不止一个人在用,但对于特别难以复线的情况,可以在流量少的情况下这样 *** 作。

 

创建好上述点击确定,点击debug运行即可,这个时候agent debug运行了,可以看到被代理的服务阻塞取消了。

 

当被拦截的类的目标方法执行的时候,agent代码就会调用,我拦截的是Servlet,我在我本地出发请求了,就进来了。

 

Tips:

我在本地调试之初,Interceptor一直不生效,也不报错,最后定位出来的原因,我的Inteceptor里面的方法不是静态的,不是说非静态方法不能生效,而是我在定义agent拦截规则的时候,选择的方法就是静态调用,网上搜不到这个问题,也是非常巧合的在方法中的注释看到了,不然耗时会更久,所以大家最好在出现问题的时候大概读读注释 :

我的定义:

//指定拦截器
.intercept(MethodDelegation.to(TestInteceptor.class));

MethodDelegation类的to方法有很多重载的,我传入的是类对象,看注释:

如果想要非静态的调用,可以使用它的重载方法,当然,就得你自己new对象了

 

 

 

 

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/langs/725703.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-04-26
下一篇 2022-04-26

发表评论

登录后才能评论

评论列表(0条)

保存