使用ASM实现简单的热部署-简单记录

本文介绍了ASM字节码增强技术,它用于动态生成和修改Java类,提高反射性能。在Spring AOP中,CGlib底层就使用ASM。通过ASM,可以实现实时类替换以实现热部署,避免类加载异常。文章展示了如何使用ASM重命名类和方法,以及修改字段,以适应线上配置类的动态更新需求,同时提到了热部署的场景和自定义AOP的实现。
摘要由CSDN通过智能技术生成

字节码增强技术 

ASM

  ASM是对java字节码操纵框架。它能被用来动态生成类或者增强既有类的功能。ASM 可以直接产生二进制 class 文件,也可以在类被加载入 Java 虚拟机之前动态改变类行为。Java class 被存储在严格格式定义的 .class文件里,这些类文件拥有足够的元数据来解析类中的所有元素:类名称、方法、属性以及 Java 字节码(指令)。ASM从类文件中读入信息后,能够改变类行为,分析类信息,甚至能够根据用户要求生成新类。asm字节码增强技术主要是用来反射的时候提升性能的,如果单纯用jdk的反射调用,性能是非常低下的,而使用字节码增强技术后反射调用的时间已经基本可以与直接调用相当了。

spring 的Aop中使用的cglib的底层原理便是asm。

ASM采用的是责任链模式:

图17 ASM修改字节码

ASM Core API可以类比解析XML文件中的SAX方式,不需要把这个类的整个结构读取进来,就可以用流式的方法来处理字节码文件。好处是非常节约内存,但是编程难度较大。然而出于性能考虑,一般情况下编程都使用Core API。在Core API中有以下几个关键类:

  • ClassReader:用于读取已经编译好的.class文件。
  • ClassWriter:用于重新构建编译后的类,如修改类名、属性以及方法,也可以生成新的类的字节码文件。
  • 各种Visitor类:如上所述,CoreAPI根据字节码从上到下依次处理,对于字节码文件中不同的区域有不同的Visitor,比如用于访问方法的MethodVisitor、用于访问类变量的FieldVisitor、用于访问注解的AnnotationVisitor等。为了实现AOP,重点要使用的是MethodVisitor。

使用的场景有很多,我们这里只讲一个动态替换class文件的方法。 

我们这里的使用场景:热部署

用我们公司的项目来说:项目为多个策略配置类替换,使用自定义类加载器加载的每个策略,然后反射生成一个个单例配置策略对象放在map中。策略数量有限。

因为线上的配置类会在线更换,还需要重新动态修改方法逻辑等,自己定义AOP功能,操作且要求项目不能随意重启。所以只能热部署的方式来加载要修改的class,自己定义了一个类加载器他继承了双亲委派机制,因为还有个父类要用apploader加载,因为一个类加载器多次,报类加载异常。所以,我想到了使用热部署的方式,更换策略对象。 

因为我们线上使用的是一个单例的策略类,把当前类替换即可(ps:这里也有另一种方式:java agent)

1,更新class类名这样类加载不会报错,实现热部署。

2,实现不依托spring实现的 自定义框架AOP .

1,引用jar 包

	    <!-- https://mvnrepository.com/artifact/org.ow2.asm/asm -->
		<dependency>
			<groupId>org.ow2.asm</groupId>
			<artifactId>asm</artifactId>
			<version>9.1</version>
		</dependency>

		<!-- https://mvnrepository.com/artifact/org.ow2.asm/asm-commons -->
		<dependency>
			<groupId>org.ow2.asm</groupId>
			<artifactId>asm-commons</artifactId>
			<version>9.1</version>
		</dependency>

2,使用ASM

(1):重命名class

原class 

public class HelloAsm   {

    private int aa;
    private static String b = "123";
    private static final String c = "abc";
    private static HashMap hashMap = new HashMap();

    public void test2() {
        int aa = this.aa;
        hashMap.put("22", "11");
        System.out.println("Hello ASM :" + c);
    }


   
    public void test() {
        System.out.println(">>>>>>>>> 我是测试 ");
    }

新class 


public class GoodAsm {
    private int aa;
    private static String b = "123";
    private static final String c = "abc";
    private static HashMap hashMap = new HashMap();

    public GoodChild1() {
    }

    public void test2() {
        int var1 = this.aa;
        hashMap.put("22", "11");
        System.out.println("Hello ASM :abc");
    }

    public void test() {
        System.out.println(">>>>>>>>> 我是测试  ");
    }
}
  public static void main(String[] args) throws Exception {
        String old_name = "com/my/asm/HelloWorld";
        String old_name_path = "F:\\davinqi\\target\\classes\\com\\my\\asm\\HelloWorld.class";
        String new_name = "com/my/asm/GoodChild1";
        InputStream fileInputStream = new FileInputStream( new File(old_name_path ));

        // 读class对象
        ClassReader cr = new ClassReader(fileInputStream);

        // 构建Class写对象
        ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);

        // ClassVisitor
        Remapper remapper = new SimpleRemapper(old_name , new_name );
        ClassVisitor cv = new ClassRemapper(cw, remapper);、
        cr.accept(cv,  ClassReader.SKIP_DEBUG);

        // 生成新Class
        byte[] bytes2 = cw.toByteArray();
        FileOutputStream fos = new FileOutputStream("F:\\davinqi\\src\\main\\java\\com\\my\\asm\\GoodChild1.class");
        fos.write(bytes2);
        fos.close();
    }

执行完成后在指定路径下生成的class文件,我们就可用使用此类加载到jvm 反射生成实例对象。

F:\davinqi\src\main\java\com\my\asm\GoodAsm.class: 

(2):重命名方法名

 TODO 

(3):方法修改增强

(4):字段修改

图片以及部分描述引用:

  https://tech.meituan.com/2019/09/05/java-bytecode-enhancement.html

本文只是简单记录下,如果有错误希望大家能指正。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值