修改第三方jar包功能(debug或者字节码增强)

/ 后端 / 没有评论 / 393浏览

问题

有时候我们调用第三方依赖jar包,发现代码有bug存在,如果找到了代码错误所在,可以Github反馈给作者进行修复,但是需要时间等待作者修复后使用,所以需要当下解决这个问题:

解决办法

1.自己打包

将源码clone下来,修复错误,然后自己打包使用;

2.功能提取

将有问题的功能(类及方法)提取出来,单独修改使用;这样做比较复杂,因为可能用到的某个方法层层依赖于其他类,需要把依赖的全部提取出来;

3.字节码增强

使用ASM,Javassist,Byte Buddy等字节码增强技术,在运行中修改字节码,达到改变功能及增强目的;

1.修改普通类的方法

原方法

public class RandomIntTest {
    public int randomInt() {
        return new Random().nextInt(10);
    }
}

修改字节码运行

  public static void main(String[] args) throws NotFoundException, CannotCompileException {
        ClassPool classPool = ClassPool.getDefault();
        classPool.appendClassPath(new LoaderClassPath(Thread.currentThread().getContextClassLoader()));
        CtClass ctClass = classPool.get("demo.RandomIntTest");
        CtMethod ctMethod = ctClass.getDeclaredMethod("randomInt");
        ctMethod.setBody("return  -999;");
        ctClass.toClass();
        ctClass.detach();

        int i = new RandomIntTest().randomInt();
        System.out.println(i);
    }

2.修改springboot中的bean方法

原bean

@Service
public class RandomIntTest {

    public int randomInt() {
        return new Random().nextInt(10);
    }
}

启动方法里面进行修改:

@SpringBootApplication
public class TomcatTestApplication {

    public static void main(String[] args) {
        init();

        SpringApplication.run(TomcatTestApplication.class, args);
    }

    public static void init() {
        try {
            ClassPool classPool = ClassPool.getDefault();
            classPool.appendClassPath(new LoaderClassPath(Thread.currentThread().getContextClassLoader()));
            CtClass ctClass = classPool.get("com.example.tomcattest.service.RandomIntTest");
            CtMethod ctMethod = ctClass.getDeclaredMethod("randomInt");
            ctMethod.setBody("return  -999;");
            ctClass.toClass();
            ctClass.detach();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

直接调用

@RestController
public class TestController {

    @Autowired
    RandomIntTest test;

    @GetMapping("/Javassist")
    public Integer Javassist() throws Exception {
        return test.randomInt();
    }
}

还可以使用:

这是整个spring容器在刷新之前初始化ConfigurableApplicationContext的回调接口,简单来说,就是在容器刷新之前调用此类的initialize方法。这个点允许被用户自己扩展。用户可以在整个spring容器还没被初始化之前做一些事情。在最开始激活一些配置,或者利用这时候class还没被类加载器加载的时机,进行动态字节码注入等操作。

public class TestApplicationContextInitializer implements ApplicationContextInitializer {    
    @Override    
    public void initialize(ConfigurableApplicationContext applicationContext) {    
        System.out.println("[ApplicationContextInitializer]");    
    }    
}    

3.使用热插拔更新HotSwapper(启动命令开启远程调试,添加-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=8080)