javassist基本用法(代码片段)

专注着 专注着     2022-12-04     407

关键词:

Javassist是一个能够操作字节码框架,在学习的过程中存在了一些问题,用博客的方式记录下来,希望对大家有所帮助。


一、实例功能

    学习的实例来自于 IBM developer   主要功能实现计算一个方式具体的执行时间. 

二、代码实例

  

package org.java.javassist.one;

/**
 * 该类并不是对StringBuilder进行解释,而是提供了中方式,方便我们来使用javassist的一些细节
 * @author xianglj
 * @date 2016/7/13
 * @time 9:59
 */
public class StringBuilderTest 
    /**
     * 假如我们现在需要计算该程序的计算时间:
     * 则可以标记开始时间(start)和结束时间(end)
     * 最终的执行时间为(end - start)的值
     * @param length
     * @return
     */
    public String buildString(int length) 
        String result = "";
        for(int i = 0; i<length; i++ ) 
            result += (i %26 + 'a');
        
        return result;
    

    public static void main(String[] args) 
        /**
         * class.getName()返回的字符串中,不仅包括了类的名称,同时也包含了该类所在的包名称
         * <pre>
         *     格式:
         *     packagename.classname
         * </pre>
         */
//        System.out.println(StringBuilderTest.class.getName());
        StringBuilderTest test = new StringBuilderTest();
        if(null != args) 
            for(int i = 0, len = args.length; i<len; i++) 
                String result = test.buildString(Integer.parseInt(args[i]));
                System.out.println("result:" + result);
            
        
    

这是一个基本实例,通过一个参数传入的length,来生成length长度的字符串。(但是该方式存在一个很验证的性能问题,就是当length的长度组件增大时,该方法的效率就会越低),但在此处不必关系方法的效率问题

三、解决方案

    1) 第一个中解决方案,也是最直观的方式,就是在进入方法时,记录一个当前时间 start , 当代码执行完成之后,获取当前时间 end , 然后采用 (end - start)的方式,来获取代码执行时间

    2) 第二中方式,我们采用Javassit框架来实现:

        (1)  使用通过ClassPool 来获取 CtClass对象

        (2)  从CtClass对象中,获取buildString()的方法

        (3)  为buildString()方法添加代码块

特别说明:

         为方法添加代码块,有三种方式可以实现

       

 * <pre>
 *     1. 添加的代码不能引用在方法中其他地方定义的局部变量。这种限制使我不能在 Javassist 中使用在源代码中使用的同样方法实现计时代码,
 *        在这种情况下,我在开始时添加的代码中定义了一个新的局部变量,并在结束处添加的代码中引用这个变量。
 *
 *     2. 我 可以在类中添加一个新的成员字段,并使用这个字段而不是局部变量。不过,这是一种糟糕的解决方案,
 *        在一般性的使用中有一些限制。例如,考虑在一个递归方法中会发生的事情。每次方法调用自身时,上次保存的开始时间值就会被覆盖并且丢失。
 *
 *     3. 我可以保持原来方法的代码不变,只改变方法名,然后用原来的方法名增加一个新方法。
 * </pre>
前两种方式,在实现上都有一定的问题,所以我们采用第三种方式实现,会比较的容易实现

代码如下:

package org.java.javassist.one;

import javassist.*;

import java.io.IOException;

/**
 * 通过Javassist来为需要实现计算的方法前后各加上一个拦截器,
 * 依次来实现方法计算的时间
 * <pre>
 *     1. 添加的代码不能引用在方法中其他地方定义的局部变量。这种限制使我不能在 Javassist 中使用在源代码中使用的同样方法实现计时代码,
 *        在这种情况下,我在开始时添加的代码中定义了一个新的局部变量,并在结束处添加的代码中引用这个变量。
 *
 *     2. 我 可以在类中添加一个新的成员字段,并使用这个字段而不是局部变量。不过,这是一种糟糕的解决方案,
 *        在一般性的使用中有一些限制。例如,考虑在一个递归方法中会发生的事情。每次方法调用自身时,上次保存的开始时间值就会被覆盖并且丢失。
 *
 *     3. 我可以保持原来方法的代码不变,只改变方法名,然后用原来的方法名增加一个新方法。
 * </pre>
 * @author xianglj
 * @date 2016/7/13
 * @time 10:07
 */
public class JavassistTiming 
    public static void main(String[] args) 
        //开始获取class的文件
        javassist();
    

    public static void javassist() 
        //开始获取class的文件
        try 
//            String classFileName = StringBuilderTest.class.getName();
            String classFileName = "org.java.javassist.one.StringBuilderTest";
            CtClass ctClass = ClassPool.getDefault().getCtClass(classFileName);
            if(ctClass == null) 
                System.out.println("Class File (" + classFileName + ") Not Found.....");
             else 
                addTiming(ctClass, "buildString");
                //为class添加计算时间的过滤器
                ctClass.writeFile();
            
            Class<?> clazz = ctClass.toClass();
            StringBuilderTest test = (StringBuilderTest) clazz.newInstance();
            test.buildString(2000);

         catch (NotFoundException e)  //类文件没有找到
            e.printStackTrace();
         catch (CannotCompileException e) 
            e.printStackTrace();
         catch (IOException e) 
            e.printStackTrace();
         catch (InstantiationException e) 
            e.printStackTrace();
         catch (IllegalAccessException e) 
            e.printStackTrace();
        
    

    /**
     * 为方法添加拦截器:
     * <pre>
     *     构造拦截器方法的正文时使用一个 java.lang.StringBuffer 来累积正文文本(这显示了处理 String 的构造的正确方法,
     *     与在 StringBuilder 的构造中使用的方法是相对的)。这种变化取决于原来的方法是否有返回值。
     *     如果它 有返回值,那么构造的代码就将这个值保存在局部变量中,这样在拦截器方法结束时就可以返回它。
     *     如果原来的方法类型为 void ,那么就什么也不需要保存,也不用在拦截器方法中返回任何内容。
     * </pre>
     * @param clazz 方法所属的类
     * @param method 方法名称
     */
    private static void addTiming(CtClass clazz, String method) throws NotFoundException, CannotCompileException 

        //获取方法信息,如果方法不存在,则抛出异常
        CtMethod ctMethod = clazz.getDeclaredMethod(method);
        //将旧的方法名称进行重新命名,并生成一个方法的副本,该副本方法采用了过滤器的方式
        String nname = method + "$impl";
        ctMethod.setName(nname);
        CtMethod newCtMethod = CtNewMethod.copy(ctMethod, method, clazz, null);

        /*
         * 为该方法添加时间过滤器,用来计算时间。
         * 这就需要我们去判断获取时间的方法是否具有返回值
         */
        String type = ctMethod.getReturnType().getName();
        StringBuffer body = new StringBuffer();
        body.append("\\n long start = System.currentTimeMillis();\\n");

        if(!"void".equals(type)) 
            body.append(type + " result = ");
        

        //可以通过$$将传递给拦截器的参数,传递给原来的方法
        body.append(nname + "($$);\\n");

        //  finish body text generation with call to print the timing
        //  information, and return saved value (if not void)
        body.append("System.out.println(\\"Call to method " + nname + " took \\" + \\n (System.currentTimeMillis()-start) + " +  "\\" ms.\\");\\n");
        if(!"void".equals(type)) 
            body.append("return result;\\n");
        

        body.append("");

        //替换拦截器方法的主体内容,并将该方法添加到class之中
        newCtMethod.setBody(body.toString());
        clazz.addMethod(newCtMethod);

        //输出拦截器的代码块
        System.out.println("拦截器方法的主体:");
        System.out.println(body.toString());
    


可能会出现的问题:

1. LinkageError  我在实践的过程中,由于想方便,采用了StringBuilderTest.class.getName() 的方法来代替手写的字符串,这个时候,我在使用CtClass.toClass()时出现了异常,异常原因大致为: 一个Class只能被加载一次,因为我们在调用toClass()方法时,会去再加载Class,所以会出现重复加载。

官方文档如下 Javassist Tutorial


        初步学习就到这里,后面会继续更新关于该框架的学习

字节码使用javassist在运行时重新加载类「替换原方法输出不一样的结果」实时加载类(代码片段)

1.概述上一篇文章:【字节码】javassist定义属性以及创建方法时多种入参和出参类型的使用转载来源于:小傅哥的字节码编程-(公众号:bugstack虫洞栈)仅供学习。并且做了稍微修改。通过前面两篇javassist的基本内容,大体介绍了... 查看详情

字节码增强之javassist(代码片段)

字节码增强之JavassistJavassist(JavaProgrammingAssist)是编辑字节码的Java类库,它使Java字节码操作变得简单。通过使用Javassist可以使Java程序在运行时定义一个新的类,并且在JVM加载类文件时修改它。Javassist提供两个级别的API࿱... 查看详情

字节码增强之javassist(代码片段)

字节码增强之JavassistJavassist(JavaProgrammingAssist)是编辑字节码的Java类库,它使Java字节码操作变得简单。通过使用Javassist可以使Java程序在运行时定义一个新的类,并且在JVM加载类文件时修改它。Javassist提供两个级别的API࿱... 查看详情

字节码增强之javassist(代码片段)

字节码增强之JavassistJavassist(JavaProgrammingAssist)是编辑字节码的Java类库,它使Java字节码操作变得简单。通过使用Javassist可以使Java程序在运行时定义一个新的类,并且在JVM加载类文件时修改它。Javassist提供两个级别的API࿱... 查看详情

javahibernate基本用法(代码片段)

查看详情

html基本用法(代码片段)

查看详情

字节码javassist定义属性以及创建方法时多种入参和出参类型的使用(代码片段)

1.概述上一篇文章:【字节码】基于javassist的第一个案例helloworld转载来源于:小傅哥的字节码编程-(公众号:bugstack虫洞栈)仅供学习。在上一篇Helloworld中,我们初步尝试使用了Javassist字节编程的方式,来创建我们的方法体并通过... 查看详情

java与maven的javassist(代码片段)

查看详情

javassist代码转换(代码片段)

...,运行某一个方法,来记录日志信息!packageorg.java.javassist.six;importjavassist.*;/***采用javassist实现代码转换:*<pre>*Javassist提供了两种方式用于对系统字节码修改的处理方法:**1.代码转转:*Java字节码修改的第一种Javassit技术使... 查看详情

androidaop编程——javassist基础(代码片段)

什么是Javassist这是Javassist官方网站上的说明:Javassist(Java编程助手)使Java字节码操作变得简单。它是Java中用于编辑字节码的类库;它使Java程序能够在运行时定义一个新类,并在JVM加载类文件时修改它。与其... 查看详情

androidaop编程——javassist基础(代码片段)

什么是Javassist这是Javassist官方网站上的说明:Javassist(Java编程助手)使Java字节码操作变得简单。它是Java中用于编辑字节码的类库;它使Java程序能够在运行时定义一个新类,并在JVM加载类文件时修改它。与其... 查看详情

markdownansible模板的基本用法(代码片段)

查看详情

javascript`createnamespacereducer`的基本用法(代码片段)

查看详情

stringbuilder基本用法(代码片段)

//StringBuilder用法publicclassStringBuilderTestpublicstaticvoidmain(String[]args)StringBuildersb=newStringBuilder();//追加字符串sb.append("Java");//sb="Java";System.out.println(sb);//插入sb.insert(0,"Hello") 查看详情

volley基本用法(代码片段)

下载Volleygitclonehttps://android.googlesource.com/platform/frameworks/volleyAndroidManifest.xml中添加如下权限:<uses-permissionandroid:name="android.permission.INTERNET"/>StringRequest的用法Reques 查看详情

history基本用法(代码片段)

设置记录保存的数量:/etc/profile查看修改记录保存的文件:~/.bash_history如果是root用户就是在/root/.bash_history  查看详情

reactnavigation基本用法(代码片段)

/***Createdbyappleon2018/9/23.*/importReact,Componentfrom‘react‘;importAppRegistry,View,Text,Buttonfrom‘react-native‘;importStackNavigatorfrom‘react-navigation‘;constHomeScreen=(navigation)=&g 查看详情

csharptopshelf与windsor的基本用法(代码片段)

查看详情