APM - Javassist 入门 生成一个简单类

本文涉及的产品
应用实时监控服务ARMS - 应用监控,每月50GB免费额度
简介: APM - Javassist 入门 生成一个简单类

20200717135742206.png

官网

http://www.javassist.org/

http://www.javassist.org/tutorial/tutorial.html


概述


Javassist是一个开源的分析、编辑和创建Java字节码的类库,可以直接编辑和生成Java生成的字节码。

相对于bcel, asm等这些工具,开发者不需要了解虚拟机指令,就能动态改变类的结构,或者动态生成类。

Javassist简单易用, 快速。


Javassist作用


  • 运行时监控插桩埋点
  • AOP动态代理实现(性能上比Cglib生成的要慢)
  • 获取访问类结构信息:如获取参数名称信息

常用API


image.png


Javassist 语法


image.png

20200926145013326.png



Javassist使用流程



2020071714342555.png


Demo

依赖

   <dependency>
            <groupId>org.javassist</groupId>
            <artifactId>javassist</artifactId>
            <version>3.18.1-GA</version>
        </dependency>
import javassist.*;
/**
 * 使用Javassist 构建 一个新的类 并执行
 */
public class FirstJavasisit {
    public static void main(String[] args) throws CannotCompileException,
            NotFoundException, InstantiationException, IllegalAccessException {
        ClassPool pool = new ClassPool(true);
        // 插入类路径,通过类路径去搜索我们要的类
        pool.insertClassPath(new LoaderClassPath(FirstJavasisit.class.getClassLoader()));
        // 构建一个新的CtClass对象
        CtClass targetClass = pool.makeClass("com.artisan.Hello");
        // 实现一个接口
        targetClass.addInterface(pool.get(IHello.class.getName()));
        // 获取返回类型
        CtClass returnType = pool.get(void.class.getName());
        // 方法名称
        String mname = "sayHello";
        // 方法参数
        CtClass[] parameters = new CtClass[]{pool.get(String.class.getName())};
        // 实例化方法
        CtMethod method = new CtMethod(returnType, mname, parameters, targetClass);
        // 方法中的源码
        String src = "{"
                + "System.out.println($1);"
                + "}";
        // 设置src到方法中
        method.setBody(src);
        // 添加方法
        targetClass.addMethod(method);
        // 装在到当前的ClassLoader中
        Class cla = targetClass.toClass();
        // 实例化
        IHello hello = (IHello) cla.newInstance();
        // 方法调用
        hello.sayHello("artisan");
    }
    /**
     * 接口不是必须的,只是为了方便演示,少写点反射代码
     */
    public interface IHello {
        void sayHello(String name);
    }
}

20200717143659614.png

Demo2

让我们对UserService类 插装一下

package com.artisan.agent;
public class UserService {
    /**
     * 无参方法
     * @throws InterruptedException
     */
    public void sayHello() throws InterruptedException {
        Thread.sleep(100);
        System.out.println("hello 小工匠");
    }
    /**
     * 无返回值的
     * @param name
     * @param age
     * @param other
     * @throws InterruptedException
     */
    public void say2Void(String name,int age,Object other) throws InterruptedException {
        Thread.sleep(100);
        System.out.println("hello 小工匠2");
    }
    /**
     * 带有返回值
     * @param name
     * @param age
     * @param other
     * @return
     * @throws InterruptedException
     */
    public String say2(String name,int age,Object other) throws InterruptedException {
        Thread.sleep(100);
        System.out.println("hello 小工匠2 with return ");
        return "ttttt";
    }
}
  @Test
    public void test3() throws NotFoundException, CannotCompileException, InterruptedException {
        // 类加载器
        ClassPool classPool = new ClassPool();
        // 追加系统ClassLoader
        classPool.appendSystemPath();
        // 获取要操作的类
        CtClass ctClass = classPool.get("com.artisan.agent.UserService");
        // 获取方法
        CtMethod originMethod = ctClass.getDeclaredMethod("say2Void");
        // copy 一个新的方法
        CtMethod newMethod = CtNewMethod.copy(originMethod,ctClass,null);
        // 设置新名字
        originMethod.setName(originMethod.getName()+ "$agent");
        // 对原方法进行包装,比如加计算方法耗时
        newMethod.setBody("{ long begin = System.currentTimeMillis();\n" +
                "        say2Void$agent($$);\n" +
                "        long end = System.currentTimeMillis();\n" +
                "        System.out.println(end - begin);" +
                "}");
        // 将新方法添加到单签类中
        ctClass.addMethod(newMethod);
        //把修改后的class装载到JVM
        ctClass.toClass();
        new com.artisan.agent.UserService().say2Void("art2",18,"xxxx");
    }
    @Test
    public void test4() throws NotFoundException, CannotCompileException, InterruptedException {
        // 类加载器
        ClassPool classPool = new ClassPool();
        // 追加系统ClassLoader
        classPool.appendSystemPath();
        // 获取要操作的类
        CtClass ctClass = classPool.get("com.artisan.agent.UserService");
        // 获取方法
        CtMethod originMethod = ctClass.getDeclaredMethod("say2");
        // copy 一个新的方法
        CtMethod newMethod = CtNewMethod.copy(originMethod,ctClass,null);
        // 设置新名字
        originMethod.setName(originMethod.getName()+ "$agent");
        // 对原方法进行包装,比如加计算方法耗时   带有返回值的的 $r
        newMethod.setBody("{ long begin = System.currentTimeMillis();\n" +
                "        say2$agent($$);\n" +
                "        long end = System.currentTimeMillis();\n" +
                "        System.out.println(end - begin);" +
                " Object s = \"test\" ;" +
                " return ($r)s ;" +
                "}");
        // 将新方法添加到单签类中
        ctClass.addMethod(newMethod);
        //把修改后的class装载到JVM
        ctClass.toClass();
        System.out.println((new com.artisan.agent.UserService().say2("art2", 18, "xxxx")));
    }

20200926112845521.png

20200926112902215.png


注意事项


所引用的类型,必须通过ClassPool获取后才可以使用

代码块中所用到的引用类型,使用时必须写全量类名

代码块内容写错了,只有在运行时才报错

javassist只接受单个语句或用大括号括起来的语句块

动态修改的类,必须在修改之前,jvm中不存在这个类的实例对象。修改方法的实现必须在修改的类加载之前进行


参考


https://baijiahao.baidu.com/s?id=1660843613132087355&wfr=spider&for=pc


https://www.cnblogs.com/scy251147/p/11100961.html


https://blog.csdn.net/21aspnet/article/details/81671777


https://www.cnblogs.com/rickiyang/p/11336268.html


相关实践学习
通过云拨测对指定服务器进行Ping/DNS监测
本实验将通过云拨测对指定服务器进行Ping/DNS监测,评估网站服务质量和用户体验。
相关文章
|
10月前
|
Arthas Dubbo Java
Alibaba Java诊断工具Arthas查看Dubbo动态代理类
Alibaba Java诊断工具Arthas查看Dubbo动态代理类
86 0
|
3月前
|
消息中间件 监控 Java
Java一分钟之-Spring Integration:企业级集成
【6月更文挑战第11天】Spring Integration是Spring框架的一部分,用于简化企业应用的集成,基于EIP设计,采用消息传递连接不同服务。核心概念包括通道(Channel)、端点(Endpoint)和适配器(Adapter)。常见问题涉及过度设计、消息丢失与重复处理、性能瓶颈。解决策略包括遵循YAGNI原则、使用幂等性和事务管理、优化线程配置。通过添加依赖并创建简单消息处理链,可以开始使用Spring Integration。注意实践中要关注消息可靠性、系统性能,逐步探索高级特性以提升集成解决方案的质量和可维护性。
58 3
Java一分钟之-Spring Integration:企业级集成
|
3月前
|
Prometheus 监控 Cloud Native
Java一分钟之-Micrometer:应用指标库
【6月更文挑战第11天】Micrometer是Java应用的度量库,提供统一API与多监控系统集成,如Prometheus、InfluxDB。它有标准化接口、广泛后端支持、自动配置和多种度量类型。常见问题包括度量命名不规范、数据过载和忽略维度。解决办法包括遵循命名规范、选择重要指标和使用标签。了解API、设计度量策略和选好监控系统是关键。通过正确使用Micrometer,可建立高效监控体系,保障应用稳定性和性能。
62 1
|
4月前
|
Java 关系型数据库 MySQL
【Java Spring开源项目】新蜂(NeeBee)商城项目运行、分析、总结
【Java Spring开源项目】新蜂(NeeBee)商城项目运行、分析、总结
287 4
|
SQL 存储 Java
【java_wxid项目】【第十五章】【Spring Cloud Skywalking集成】
点击:【使用Spring Boot快速构建应用】 点击:【使用Spring Cloud Open Feign基于动态代理动态构造请求实现与其他系统进行交互】 点击:【使用Spring Cloud Hystrix实现服务容错、熔断、降级、监控】 点击:【使用Spring Cloud Ribbon以库的方式集成到服务的消费方实现客户端负载均衡】
345 0
|
SQL Java API
【java_wxid项目】【第五章】【Spring Cloud Hystrix集成】
主项目链接:https://gitee.com/java_wxid/java_wxid 项目架构及博文总结:
212 0
|
负载均衡 Java API
【java_wxid项目】【第四章】【Spring Cloud Ribbon集成】
【java_wxid项目】【第四章】【Spring Cloud Ribbon集成】
105 0
|
SQL 存储 Java
【java_wxid项目】【第十四章】【Spring Cloud Stream集成】
【java_wxid项目】【第十四章】【Spring Cloud Stream集成】
138 0
|
Java Nacos 微服务
【java_wxid项目】【第三章】【Spring Cloud Open Feign集成】
【java_wxid项目】【第三章】【Spring Cloud Open Feign集成】
|
SQL Java API
【java_wxid项目】【第六章】【Spring Cloud Gateway集成】
主项目链接:https://gitee.com/java_wxid/java_wxid 项目架构及博文总结:
下一篇
DDNS