javassist_javassist 指定泛型 - CSDN
精华内容
参与话题
  • javassist的基本功能

    千次阅读 2018-09-24 15:37:36
    javassist的基本功能 Javassist是一个动态类库,可以用来检查、”动态”修改以及创建 Java类。其功能与jdk自带的反射功能类似,但比反射功能更强大。 重要的类 ClassPool:javassist的类池,使用ClassPool 类可以...

    javassist的基本功能
    Javassist是一个动态类库,可以用来检查、”动态”修改以及创建 Java类。其功能与jdk自带的反射功能类似,但比反射功能更强大。

    重要的类
    ClassPool:javassist的类池,使用ClassPool 类可以跟踪和控制所操作的类,它的工作方式与 JVM 类装载器非常相似, 
    CtClass: CtClass提供了检查类数据(如字段和方法)以及在类中添加新字段、方法和构造函数、以及改变类、父类和接口的方法。不过,Javassist 并未提供删除类中字段、方法或者构造函数的任何方法。 
    CtField:用来访问域 
    CtMethod :用来访问方法 
    CtConstructor:用来访问构造器

    语法
    使用javassist来编写的代码与java代码不完全一致,主要的区别在于 javassist提供了一些特殊的标记符(以开头),用来表示方法,构造函数参数、方法返回值等内容。示例:System.out.println(“Argument1:”+开头),用来表示方法,构造函数参数、方法返回值等内容。示例:System.out.println(“Argument1:”+1); 其中的$1表示第1个参数.

    示例
    可以通过javassist来修改java类的方法,来修改其实现。如下所示:

          ClassPool classPool = ClassPool.getDefault();
          CtClass ctClass = classPool.get("org.light.lab.JavassistTest");
          CtMethod ctMethod = ctClass.getDeclaredMethod("test");
          ctMethod.setBody("System.out.println(\"this method is changed dynamically!\");");
          ctClass.toClass();

    上面的方法即是修改一个方法的实现,当调用ctClass.toClass()时,修改后的类将被当前的ClassLoader加载并实例化。

    完整的代码:

    public class ChangeDemo {

      public static void main(String[] args) throws NotFoundException, CannotCompileException {
           replaceMethodBody("foo.Student", "execute", "System.out.println(\"this method is changed dynamically!\");");
           Student student = new Student();
           student.execute();
       }

    public static void replaceMethodBody(String clazzName, String methodName, String newMethodBody) {
        try {
            CtClass clazz = ClassPool.getDefault().get(clazzName);
            CtMethod method = clazz.getDeclaredMethod(methodName);
            method.setBody(newMethodBody);
            clazz.toClass();
        } catch (NotFoundException | CannotCompileException e) {
           throw new RuntimeException(e);
        }
    }

    }

    限制与局限性
    需要注意的是,在调用ctClass.toClass()时,会加载此类,如果此类在之前已经被加载过,则会报一个duplicate load的错误,表示不能重复加载一个类。所以,修改方法的实现必须在修改的类加载之前进行。
    不能访问块之外的局部变量。如果在一个方法的开始和结尾都增加了代码段,那么在方法的结尾块中无法访问方法开始中的代码段中的变量(不太完美的解决方法是将原方法改名,然后再增加与原方法同名的方法)。
    与aspectj的区别
    使用aspectj也可以同样达到修改的效果,不过修改指定的类,则需要为修改这个类添加一个aspect,然后将这个aspect加入配置文件中以使其生效,比起javassist来说,修改一个类还是使用javassist相对简单一点。

    容器中的Classpath
    在tomcat之类的容器中是无法通过ClassPool.getDefault()获取到用户定义的类的,可以通过以下方法获取: 
    pool.insertClassPath(new ClassClassPath(this.getClass())); 
    或者: 
    ClassPool pool = ClassPool.getDefault(); 
    pool.insertClassPath(“/usr/local/javalib”);

    参考资料:
    http://www.ibm.com/developerworks/cn/java/j-dyn0916/ 
    http://jboss-javassist.github.io/javassist/tutorial/tutorial.html
     

     

    转载至:https://blog.csdn.net/bjo2008cn/article/details/53543467

    展开全文
  • 秒懂Java动态编程(Javassist研究)

    万次阅读 多人点赞 2018-08-04 11:53:50
    概述 什么是动态编程?动态编程解决什么问题?Java中如何使用?什么原理?如何改进?(需要我们一起探索,由于自己也是比较菜,一般深入不到这个程度)。 ...动态编程是相对于静态编程而言的,平时我们讨论比较多的...

    版权申明】非商业目的可自由转载
    博文地址:https://blog.csdn.net/ShuSheng0007/article/details/81269295
    出自:shusheng007

    概述

    什么是动态编程?动态编程解决什么问题?Java中如何使用?什么原理?如何改进?(需要我们一起探索,由于自己也是比较菜,一般深入不到这个程度)。

    什么是动态编程

    动态编程是相对于静态编程而言的,平时我们讨论比较多的就是静态编程语言,例如Java,与动态编程语言,例如JavaScript。那二者有什么明显的区别呢?简单的说就是在静态编程中,类型检查是在编译时完成的,而动态编程中类型检查是在运行时完成的。所谓动态编程就是绕过编译过程在运行时进行操作的技术,在Java中有如下几种方式:

    反射

    这个搞Java的应该比较熟悉,原理也就是通过在运行时获得类型信息然后做相应的操作。

    动态编译

    动态编译是从Java 6开始支持的,主要是通过一个JavaCompiler接口来完成的。通过这种方式我们可以直接编译一个已经存在的java文件,也可以在内存中动态生成Java代码,动态编译执行。

    调用JavaScript引擎

    Java 6加入了对Script(JSR223)的支持。这是一个脚本框架,提供了让脚本语言来访问Java内部的方法。你可以在运行的时候找到脚本引擎,然后调用这个引擎去执行脚本。这个脚本API允许你为脚本语言提供Java支持。

    动态生成字节码

    这种技术通过操作Java字节码的方式在JVM中生成新类或者对已经加载的类动态添加元素。

    动态编程解决什么问题

    在静态语言中引入动态特性,主要是为了解决一些使用场景的痛点。其实完全使用静态编程也办的到,只是付出的代价比较高,没有动态编程来的优雅。例如依赖注入框架Spring使用了反射,而Dagger2 却使用了代码生成的方式(APT)。

    例如
    1: 在那些依赖关系需要动态确认的场景:
    2: 需要在运行时动态插入代码的场景,比如动态代理的实现。
    3: 通过配置文件来实现相关功能的场景

    Java中如何使用

    此处我们主要说一下通过动态生成字节码的方式,其他方式可以自行查找资料。

    操作java字节码的工具有两个比较流行,一个是ASM,一个是Javassit

    ASM :直接操作字节码指令,执行效率高,要是使用者掌握Java类字节码文件格式及指令,对使用者的要求比较高。

    Javassit 提供了更高级的API,执行效率相对较差,但无需掌握字节码指令的知识,对使用者要求较低。

    应用层面来讲一般使用建议优先选择Javassit,如果后续发现Javassit 成为了整个应用的效率瓶颈的话可以再考虑ASM.当然如果开发的是一个基础类库,或者基础平台,还是直接使用ASM吧,相信从事这方面工作的开发者能力应该比较高。

    这里写图片描述
    上一张国外博客的图,展示处理Java字节码的工具的关系。

    接下来介绍如何使用Javassit来操作字节码

    Javassit使用方法

    Javassist是一个开源的分析、编辑和创建Java字节码的类库。是由东京工业大学的数学和计算机科学系的 Shigeru Chiba (千叶 滋)所创建的。它已加入了开放源代码JBoss 应用服务器项目,通过使用Javassist对字节码操作为JBoss实现动态AOP框架。javassist是jboss的一个子项目,其主要的优点,在于简单,而且快速。直接使用java编码的形式,而不需要了解虚拟机指令,就能动态改变类的结构,或者动态生成类。

    Javassist中最为重要的是ClassPoolCtClassCtMethod 以及 CtField这几个类。

    ClassPool:一个基于HashMap实现的CtClass对象容器,其中键是类名称,值是表示该类的CtClass对象。默认的ClassPool使用与底层JVM相同的类路径,因此在某些情况下,可能需要向ClassPool添加类路径或类字节。

    CtClass:表示一个类,这些CtClass对象可以从ClassPool获得。

    CtMethods:表示类中的方法。

    CtFields :表示类中的字段。

    动态生成一个类

    下面的代码会生成一个实现了Cloneable接口的类GenerateClass

     public void DynGenerateClass() {
         ClassPool pool = ClassPool.getDefault();
         CtClass ct = pool.makeClass("top.ss007.GenerateClass");//创建类
         ct.setInterfaces(new CtClass[]{pool.makeInterface("java.lang.Cloneable")});//让类实现Cloneable接口
         try {
             CtField f= new CtField(CtClass.intType,"id",ct);//获得一个类型为int,名称为id的字段
             f.setModifiers(AccessFlag.PUBLIC);//将字段设置为public
             ct.addField(f);//将字段设置到类上
             //添加构造函数
             CtConstructor constructor=CtNewConstructor.make("public GeneratedClass(int pId){this.id=pId;}",ct);
             ct.addConstructor(constructor);
             //添加方法
             CtMethod helloM=CtNewMethod.make("public void hello(String des){ System.out.println(des);}",ct);
             ct.addMethod(helloM);
    
             ct.writeFile();//将生成的.class文件保存到磁盘
    
             //下面的代码为验证代码
             Field[] fields = ct.toClass().getFields();
             System.out.println("属性名称:" + fields[0].getName() + "  属性类型:" + fields[0].getType());
         } catch (CannotCompileException e) {
             e.printStackTrace();
         } catch (IOException e) {
             e.printStackTrace();
         } catch (NotFoundException e) {
             e.printStackTrace();
         }
     }

    上面的代码就会动态生成一个.class文件,我们使用反编译工具,例如Bytecode Viewer,查看生成的字节码文件GenerateClass.class,如下图所示。

    这里写图片描述

    动态添加构造函数及方法

    有很多种方法添加构造函数,我们使用CtNewConstructor.make,他是一个的静态方法,其中有一个重载版本比较方便,如下所示。第一个参数是source text 类型的方法体,第二个为类对象。

     CtConstructor constructor=CtNewConstructor.make("public GeneratedClass(int pId){this.id=pId;}",ct);
     ct.addConstructor(constructor);     

    这段代码执行后会生成如下java代码,代码片段是使用反编译工具JD-GUI产生的,可以看到构造函数的参数名被修改成了paramInt

      public GeneratedClass(int paramInt)
      {
        this.id = paramInt;
      }

    同样有很多种方法添加函数,我们使用CtNewMethod.make这个比较简单的形式

    CtMethod helloM=CtNewMethod.make("public void hello(String des){ System.out.println(des);}",ct);
    ct.addMethod(helloM);

    这段代码执行后会生成如下java代码:

      public void hello(String paramString)
      {
        System.out.println(paramString);
      }

    动态修改方法体

    动态的修改一个方法的内容才是我们关注的重点,例如在AOP编程方面,我们就会用到这种技术,动态的在一个方法中插入代码。
    例如我们有下面这样一个类

    public class Point {
        private int x;
        private int y;
    
        public Point(){}
        public Point(int x, int y) {
            this.x = x;
            this.y = y;
        }
    
        public void move(int dx, int dy) {
            this.x += dx;
            this.y += dy;
        }
    }

    我们要动态的在内存中在move()方法体的前后插入一些代码

        public void modifyMethod()
        {
            ClassPool pool=ClassPool.getDefault();
            try {
                CtClass ct=pool.getCtClass("top.ss007.Point");
                CtMethod m=ct.getDeclaredMethod("move");
                m.insertBefore("{ System.out.print(\"dx:\"+$1); System.out.println(\"dy:\"+$2);}");
                m.insertAfter("{System.out.println(this.x); System.out.println(this.y);}");
    
                ct.writeFile();
                //通过反射调用方法,查看结果
                Class pc=ct.toClass();
                Method move= pc.getMethod("move",new Class[]{int.class,int.class});
                Constructor<?> con=pc.getConstructor(new Class[]{int.class,int.class});
                move.invoke(con.newInstance(1,2),1,2);
            }
            ...
        }

    使用反编译工具查看修改后的move方法结果:

      public void move(int dx, int dy) {
        System.out.print("dx:" + dx);System.out.println("dy:" + dy);
        this.x += dx;
        this.y += dy;
        Object localObject = null;//方法返回值
        System.out.println(this.x);System.out.println(this.y);
      }

    可以看到,在生成的字节码文件中确实增加了相应的代码。
    函数输出结果为:

    dx:1dy:2
    2
    4

    Javassit 还有许多功能,例如在方法中调用方法,异常捕捉,类型强制转换,注解相关操作等,而且其还提供了字节码层面的API(Bytecode level API)。

    什么原理

    反射:由于Java执行过程中是将类型载入虚拟机中的,在运行时我们就可以动态获取到所有类型的信息。只能获取却不能修类型信息。
    动态编译与动态生成字节码:这两种方法比较相似,原理也都是利用了Java的设计原理,存在一个虚拟机执行字节码,这就使我们在此处有了改变字节码的操作空间。

    总结

    有关动态编程的知识在平时的应用层使用不是特别多,多是用在构建框架。例如Spring框架使用反射来构建,而用于AOP编程的动态代理则多是采用生成字节码的方式,例如JBossSpring中的AOP部分。了解这部分知识可以在日后遇到相关问题时比别人多一条思考的思路也是好的,做一个思路开阔的Developer

    展开全文
  • JAVA_javassist学习

    2020-07-07 10:02:50
    javassist 用于对java字节码文件进行操作,可以动态生成新的类、方法、添加属性等,亦可对已有类、方法、属性进行修改 1.使用javassist生成一个新的类 package com.bjsxt.test; import javassist....

    javassist
    用于对java字节码文件进行操作,可以动态生成新的类、方法、添加属性等,亦可对已有类、方法、属性进行修改

    需要导入的jar
    javassist.jar

    1.使用javassist生成一个新的类
    package com.bjsxt.test;
    
    import javassist.CannotCompileException;
    import javassist.ClassPool;
    import javassist.CtClass;
    import javassist.CtConstructor;
    import javassist.CtField;
    import javassist.CtMember;
    import javassist.CtMethod;
    
    /**
     * 测试使用javassist生成一个新的类
     * @author WL20180732
     *
     */
    public class Demo01 {
    	public static void main(String[] args) throws Exception {
    		ClassPool pool = ClassPool.getDefault();
    		CtClass cc = pool.makeClass("com.bjsxt.bean.Emp");
    		
    		// 创建属性
    		CtField f1 = CtField.make("private int empno;", cc);
    		CtField f2 = CtField.make("private String ename;", cc);
    		cc.addField(f1);
    		cc.addField(f2);
    		
    		// 创建方法
    		CtMethod m1 = CtMethod.make("public int getEmpno() {return this.empno;}", cc);
    		CtMethod m2 = CtMethod.make("public void setEmpno(int empno) {this.empno = empno;}", cc);
    		cc.addMethod(m1);
    		cc.addMethod(m2);
    		
    		// 添加构造器
    		CtConstructor constructor = new CtConstructor(
    				new CtClass[]{CtClass.intType, pool.get("java.lang.String")}, cc);
    		
    		constructor.setBody("{this.empno = empno; this.ename = ename;}");
    		cc.addConstructor(constructor);
    		cc.writeFile("E:/mycode"); // 将上面构造好的类写入指定目录下
    		System.out.println("类生成成功");
    	}
    }
    
    


    在这里插入图片描述

    2.测试javassist的API
    package com.bjsxt.test;
    
    import java.io.IOException;
    import java.lang.reflect.Method;
    import java.util.Arrays;
    
    import javax.security.sasl.AuthorizeCallback;
    
    import javassist.CannotCompileException;
    import javassist.ClassPool;
    import javassist.CtClass;
    import javassist.CtConstructor;
    import javassist.CtField;
    import javassist.CtMethod;
    import javassist.CtNewMethod;
    import javassist.Modifier;
    import javassist.NotFoundException;
    
    /**
     * 测试javassist的API
     * @author WL20180732
     *
     */
    public class Demo02 {
    	
    	/**
    	 * 处理类的基本用法
    	 * @throws NotFoundException 
    	 * @throws CannotCompileException 
    	 * @throws IOException 
    	 */
    	public static void test01() throws Exception {
    		ClassPool classPool = ClassPool.getDefault();
    		CtClass cc = classPool.get("com.bjsxt.test.Emp");  // 获得已有类
    		
    		byte[] bytes = cc.toBytecode();
    		System.out.println(Arrays.toString(bytes));
    		
    		System.out.println(cc.getName()); // 获取全类名
    		System.out.println(cc.getSimpleName()); // 获取类名
    		System.out.println(cc.getSuperclass()); // 获得父类
    		System.out.println(cc.getInterfaces()); // 获取接口
    	}
    	
    	/**
    	 * 测试产生新的方法
    	 * @throws Exception 
    	 */
    	public static void test02() throws Exception {
    		ClassPool classPool = ClassPool.getDefault();
    		CtClass cc = classPool.get("com.bjsxt.test.Emp");  // 获得已有类
    		
    //		CtMethod m = CtNewMethod.make("public int add(int a, int b) {return a+b;}", cc);
    		CtMethod m = new CtMethod(
    				CtClass.intType,   // 返回值类型
    				"add", 
    				new CtClass[]{CtClass.intType, CtClass.intType}, cc);
    		
    		m.setModifiers(Modifier.PUBLIC);  //设置方法修饰符
    		m.setBody("{System.out.println($1 + $2); return $1 + $2;}");  // $0:this关键字,$$: 方法的所有参数,$1:方法的第一个参数,$2:方法的第二个参数...
    		
    		cc.addMethod(m);
    		
    		//通过反射调用生成的方法
    		Class clazz = cc.toClass();
    		Object obj = clazz.newInstance();  // 通过调用emp的无参构造,创建新的Emp对象
    		Method method = clazz.getDeclaredMethod("add", int.class, int.class);
    		Object result = method.invoke(obj, 200, 300);
    		System.out.println(result);
    	}
    	
    	/**
    	 * 修改已有的方法信息
    	 * @throws Exception
    	 */
    	public static void test03() throws Exception{
    		ClassPool classPool = ClassPool.getDefault();
    		CtClass cc = classPool.get("com.bjsxt.test.Emp");  // 获得已有类
    		
    		CtMethod cm = cc.getDeclaredMethod("sayHello", new CtClass[]{CtClass.intType});
    		cm.insertBefore("System.out.println($1);System.out.println(\"start!!\");");
    		cm.insertAfter("System.out.println(\"end!!\");");
    		cm.insertAt(27, "System.out.println(\"lineNum=\" + 27);"); // 在某一行处加代码
    		//通过反射调用生成的方法
    		Class clazz = cc.toClass();
    		Object obj = clazz.newInstance();  // 通过调用emp的无参构造,创建新的Emp对象
    		Method method = clazz.getDeclaredMethod("sayHello", int.class);
    		Object result = method.invoke(obj, 200);
    		System.out.println(result);
    	}
    	
    	/**
    	 * 属性的操作
    	 * @throws Exception
    	 */
    	public static void test04() throws Exception{
    		ClassPool classPool = ClassPool.getDefault();
    		CtClass cc = classPool.get("com.bjsxt.test.Emp");  // 获得已有类
    		
    //		CtField f1 = CtField.make("private int empno;", cc);
    		CtField f1 = new CtField(CtClass.intType, "salary", cc);
    		f1.setModifiers(Modifier.PRIVATE);
    		cc.addField(f1);
    		cc.addMethod(CtNewMethod.getter("getSalary", f1)); // 给f1属性添加getter方法
    		cc.addMethod(CtNewMethod.getter("setSalary", f1)); // 给f1属性添加setter方法
    //		cc.getDeclaredField("ename");  // 获取指定属性
    		
    		
    	}
    	
    	/**
    	 * 构造方法的操作
    	 * @throws Exception
    	 */
    	public static void test05() throws Exception{
    		ClassPool classPool = ClassPool.getDefault();
    		CtClass cc = classPool.get("com.bjsxt.test.Emp");  // 获得已有类
    		
    		CtConstructor[] constructors = cc.getConstructors();
    		for (CtConstructor ctConstructor : constructors) {
    			System.out.println(ctConstructor.getLongName());
    //			ctConstructor.insertBefore(src);
    		}
    	}
    	
    	public static void test06() throws Exception{
    		ClassPool classPool = ClassPool.getDefault();
    		CtClass cc = classPool.get("com.bjsxt.test.Emp");  // 获得已有类
    		Object[] all = cc.getAnnotations();
    		Author a = (Author) all[0];
    		String name = a.name();
    		int year = a.year();
    		System.out.println("name="+name + ",year=" + year);
    	}
    	
    	public static void main(String[] args) throws Exception {
    //		test01();
    //		test02();
    //		test03();
    //		test04();
    //		test05();
    		test06();
    	}
    }
    
    
    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    
    @Target(value={ElementType.TYPE})
    @Retention(RetentionPolicy.RUNTIME)
    public @interface Author {
    	String name();
    	int year();
    }
    
    
    package com.bjsxt.test;
    
    @Author(name="azor", year=2020)
    public class Emp {
    	
    	private int empno;
    	private String ename;
    	public int getEmpno() {
    		return empno;
    	}
    	public void setEmpno(int empno) {
    		this.empno = empno;
    	}
    	public String getEname() {
    		return ename;
    	}
    	public void setEname(String ename) {
    		this.ename = ename;
    	}
    	public Emp() {
    	}
    	public Emp(int empno, String ename) {
    		this.empno = empno;
    		this.ename = ename;
    	}
    	
    	public void sayHello(int a) {
    		System.out.println("say Hello," + a);
    	}
    	
    }
    
    
    展开全文
  • javassist 介绍和使用

    千次阅读 2013-01-15 12:59:34
    Javassist是一个执行字节码操作的强而有力的驱动代码库。它允许开发者自由的在一个已经编译好的类中添加新的方法,或者是修改已有的方法。但是, 和其他的类似库不同的是,Javassist并不要求开发者对字节码方面具有...
    Javassist是一个执行字节码操作的强而有力的驱动代码库。它允许开发者自由的在一个已经编译好的类中添加新的方法,或者是修改已有的方法。但是, 和其他的类似库不同的是,Javassist并不要求开发者对字节码方面具有多么深入的了解,同样的,它也允许开发者忽略被修改的类本身的细节和结构。
    字 节码驱动通常被用来执行对于已经编译好的类的修改,或者由程序自动创建执行类等等等等相关方面的操作。这就要求字节码引擎具备无论是在运行时或是编译时都 能修改程序的能力。当下有些技术便是使用字节码来强化已经存在的Java类的,也有的则是使用它来使用或者产生一些由系统在运行时动态创建的类。举例而 言,JDO1.0规范就使用了字节码技术对数据库中的表进行处理和预编译,并进而包装成Java类。特别是在面向对象驱动的系统开发中,相当多的框架体系 使用字节码以使我们更好的获得程序的范型性和动态性。而某些EJB容器,比如JBOSS项目,则通过在运行中动态的创建和加载EJB,从而戏剧性的缩短了 部署EJB的周期。这项技术是如此的引人入胜,以至于在JDK中也有了标准的java.lang.reflect.Proxy类来执行相关的操作。 

    但是,尽管如此,编写字节码对于框架程序开发者们而言,却是一个相当不受欢迎的繁重任务。学习和使用字节码在某种程度上就如同使用汇编语言。这使得于大多数 开发者而言,尽管在程序上可以获得相当多的好处,可攀登它所需要的难度则足以冷却这份热情。不仅如此,在程序中使用字节码操作也大大的降低了程序的可读性 和可维护性。

    这是一块很好的奶油面包,但是我们却只能隔着橱窗流口水 难道我们只能如此了吗? 

    所幸的是,我们还有Javassist。Javassist是一个可以执行字节码操作的函数库,可是尽管如此,它却是简单而便与理解的。他允许开发者对自己的程序自由的执行字节码层的操作,当然了,你并不需要对字节码有多深的了解,或者,你根本就不需要了解。

    API Parallel to the Reflection API 

    Javassist 的最外层的API和JAVA的反射包中的API颇为类似。它使你可以在装入ClassLoder之前,方便的查看类的结构。它主要由 CtClass,,CtMethod,,以及CtField几个类组成。用以执行和JDK反射API中 java.lang.Class,,java.lang.reflect.Method,, java.lang.reflect.Method .Field相同的操作。这些类可以使你在目标类被加载前,轻松的获得它的结构,函数,以及属性。此外,不仅仅是在功能上,甚至在结构上,这些类的执行函 数也和反射的API大体相同。比如getName,getSuperclass,getMethods,,getSignature,等等。如果你对 JAVA的反射机制有所了解的话,使用Javassist的这一层将会是轻松而快乐的。


    接下来我们将给出一个使用Javassist来读取org.geometry.Point.class的相关信息的例子(当然了,千万不要忘记引入javassist.*包):


    1. ClassPool pool = ClassPool.getDefault(); 


    2. CtClass pt = pool.get("org.geometry.Point"); 


    3. System.out.println(pt.getSuperclass().getName()); 


    其中,ClassPool是CtClas 的创建工厂。它在classpath中查找CtClass的位置,并为每一个分析请求创建一个CtClass实例。而“getSuperclass().getName()”则展示 出org.geometry.Point.class所继承的父类的名字。

    但是,和反射的API不尽相同的 是,Javassist并不提供构造的能力,换句话说,我们并不能就此得到一个org.geometry.Point.class类的实例。另一方面,在该类没有实例化前,Javassist也不提供对目标类的函数的调用接口和获取属性的值的方法。在分析阶段,它仅仅提供对目标类的类定义修改,而这点,却是反射API所无法做到的。


    举例如下: 


    4. pt.setSuperclass(pool.get("Figure")); 


    这样做将修改目标类和其父类之间的关系。我们将使org.geometry.Point.clas改继承自Figure类。当然了,就一致性而言,必须确保Figure类和原始的父类之间的兼容性。


    而往目标类中新增一个新的方法则更加的简单了。首先我们来看字节码是如何形成的: 


    5. CtMethod m = CtNewMethod.make("public int xmove(int dx) { x += dx; }", pt); 


    6. pt.addMethod(m); 


    CtMethod类的让我们要新增一个方法只需要写一段小小的函数。这可是一个天大的好消息,开发者们再也不用为了实现这么一个小小的操作而写一大段的虚拟机指令序列了。Javassist将使用一个它自带的编译器来帮我们完成这一切。


    最后,千万别忘了指示Javassist把已经写好的字节码存入到你的目标类里: 


    7. pt.writeFile(); 


    writeFile方法可以帮我们把修改好了的定义写到目标类的.class文件里。当然了,我们甚至可以在该目标类加载的时候完成这一切,Javassist可以很好的和ClassLoader协同工作,我们不久就将看到这一点。


    Javassist 并不是第一套用以完成从代码到字节码的翻译的函数库。Jakarta的BCEL也是一个比较知名的字节码引擎工具。但是,你却无法使用BCEL来完成代码 级别的字符码操作。如果你需要在一个已经编译好的类中添加一个新的方法,假如你用的是BCEL的话,你只能定义一段由那么一大串字符码所构成的指令序列。 正如上文所说,这并不是我们所希望看到的。因此,就此方面而言,Javassis使用代码的形式来插入新的方法实在是一大福音。


    Instrumenting a Method Body 


    和 方法的新增一样,对于一个类的方法的其他操作也是定义在代码层上的。换而言之,尽管这些步骤是必须的,开发者们也同样无须直接对虚拟机的指令序列进行操作 和修改,Javassis将自动的完成这些操作。当然了,如果开发者认为自己有必要对这些步骤进行管理和监控,或者希望由自己来管理这些操作的 话,Javassist同样提供了更加底层的API来实现,不过我们在这篇文章中将不会就此话题再做深入探讨。恩,尽管从结构而言,它和BCEL的字节码 层API差不多。


    设计Javassist对目标类的子函数体的操作API的设想立足与ASPect-Oriented Programming(AOP)思想。Javassist允许把具有耦合关系的语句作为一个整体,它允许在一个插入语句中调用或获取其他函数或者及属性 值。它将自动的对这些语句进行优先级分解并执行嵌套操作。


    如下例所示,清单1首先包含了一个CtMethod,它主要针对 Screen类的draw方法。然后,我们定义一个Point类,该类有一个move操作,用来实现该Point的移动。当然了,在移动前,我们希望可以 通过draw方法得到该point目前的位置,那么,我们需要对该move方法加增如下的定义:


    { System.out.println("move"); $_ = $proceed($$); } 


    这样,在执行move之前,我们就可以打印出它的位置了。请注意这里的调用语句,它是如下格式的: 


    $_ = $proceed($$); 


    这样我们就将使用原CtMethod类中的process()对该point的位置进行追踪了。 


    基 与如上情况,CtMethod的关于methord的操作其实被划分成了如下步骤,首先,CtMethod的methord将扫描插入语句(代码)本身。 一旦发现了子函数,则创建一个ExprEditor实例来分析并执行这个子函数的操作。这个操作将在整个插入语句执行之前完成。而假如这个实例存在某个 static的属性,那么methord将率先检测对插入语句进行检测。然后,在执行插入到目标类---如上例的point类---之前,该static 属性将自动的替换插入语句(代码)中所有的相关的部分。不过,值得注意的是,以上的替换操作,将在Javassist把插入语句(代码)转变为字节码之后 完成。 


    Special Variables 


    在替换的语句(代码)中,我们也有可能需要用到一些特殊变量 来完成对某个子函数的调用,而这个时候我们就需要使用关键字“$”了。在Javassist中,“$”用来申明此后的某个词为特殊参数,而“$_”则用来 申明此后的某个词为函数的回传值。每一个特殊参数在被调用时应该是这个样子的“$1,$2,$3 ”但是,特别的,目标类本身在被调用时,则被表示为 “$0”。这种使用格式让开发者在填写使用子函数的参数时轻松了许多。比如如下的例子:


    { System.out.println("move"); $_ = $proceed($1, 0); } 


    请注意,该子函数的第2个参数为0。 


    另 外一个特殊类型则是$arg,它实际上是一个容纳了函数所有调用参数的Object队列。当Javassist在扫描该$arg时,如果发现某一个参数为 JAVA的基本类型,则它将自动的对该参数进行包装,并放入队列。比如,当它发现某一个参数为int类型时,它将使用java.lang.integer 类来包装这个int参数,并存入参数队列。和Java的反射包:java.lang.reflect.Methord类中的invoke方法相 比,$args明显要省事的多。


    Javassist也同样允许开发者在某个函数的头,或者某个函数的尾上插入某段语句(代码)。比如,它有一个insertBefore方法用以在某函数的调用前执行某个操作,它的使用大致是这个样子的:


    1. ClassPool pool = ClassPool.getDefault(); 
    2. CtClass cc = pool.get("Screen"); 
    3. CtMethod cm = cc.getDeclaredMethod("draw", new CtClass[0]); 
    4. cm.insertBefore("{ System.out.println($1); System.out.println($2); }"); 
    5. cc.writeFile(); 


    以上例子允许我们在draw函数调用之前执行打印操作---把传递给draw的两个参数打印出来。 


    同样的,我们也可以使用关键字$对某一个函数进行修改或者是包装,下面就 


    1. CtClass cc = sloader.get("Point"); 
    2. CtMethod m1 = cc.getDeclaredMethod("move"); 
    3. CtMethod m2 = CtNewMethod.copy(m1, cc, null); 
    4. m1.setName(m1.getName() + "_orig"); 
    5. m2.setBody("{ System.out.println("call"); return $proceed($$); 
    }", "this", m1.getName()); 
    6. cc.addMethod(m2); 
    7. cc.writeFile(); 


    以上代码的前四行不难理解,Javassist首先对Point中的move方法做了个拷贝,并创建了一个新的函数。然后,它把存在与Point类中的原 move方法更名为“_orig”。接下来,让我们关注一下程序第五行中的几个参数:第一个参数指示该函数的在执行的最初部分需要先打印一段信息,然后执 行子函数proceed()并返回结果,这个和move方法差不多,很好理解。第二个参数则只是申明该子函数所在的类的位置。这里为this即为 Point类本身。第三个参数,也就是“m1.getName()”则定义了这个新函数的名字。


    Javassist也同样具有其他的操作和类来帮助你实现诸如修改某一个属性的值,改变函数的回值,并在某个函数的执行后补上其他操作的功能。您可以浏览www.javassist.org以获得相关的信息。

    官方主页:http://www.csg.is.titech.ac.jp/~chiba/javassist/

    JavassistCglib等是一些代码增强工具,在运行时刻进行Java字节码增强,虽然速度上稍微慢一点点,但是带来的是代码的简洁,今天用Javassist进行代码增强。

    代码:

    TestBean.java

    1. package javassist.sample;
    2. public abstract class TestBean {
    3.     public String field;
    4.     public abstract String getM();
    5.     public abstract void setF(String f);
    6.     public String getF() {
    7.         return this.field;
    8.     }
    9. }

    TestByteCode.java

    1. package javassist.sample;
    2. import javassist.ClassPool;
    3. import javassist.CtClass;
    4. import javassist.CtConstructor;
    5. import javassist.CtMethod;
    6. public class TestByteCode {
    7.     public static void main(String[] args) throws Exception {
    8.         
    9.         ClassPool pool = ClassPool.getDefault();
    10.         CtClass pt = pool.makeClass("asdf", pool.get("javassist.sample.TestBean"));
    11.         CtMethod method1 = new CtMethod(pool.get("java.lang.String"), "getM"null, pt);
    12.         method1.setBody("{return /"你好/";}");
    13.         pt.addMethod(method1);
    14.         CtConstructor cc = new CtConstructor(null, pt);
    15.         cc.setBody("this.field=/"why?/";");
    16.         pt.addConstructor(cc);
    17.         CtMethod method2 = new CtMethod(CtClass.voidType, "setF",
    18.                 new CtClass[] { pool.get("java.lang.String") }, pt);
    19.         method2.setBody("{this.field=$1;}");
    20.         pt.addMethod(method2);
    21.         Class<?> c = pt.toClass();
    22.         TestBean bean = (TestBean) c.newInstance();
    23.         System.out.println(bean.getM());
    24.         System.out.println(bean.getF());
    25.         bean.setF("setf");
    26.         System.out.println(bean.getF());
    27.     }
    28. }

    输出为:

    你好

    why?

    setf

    可以看到实现了动态的构造,动态实现抽象函数。在进行代码自动化项目中,能够使用字节码增强将大大提高代码的质量,和减少代码的数量。

     

    再看一个例子:

    A.java

    1. package javassist.demo;
    2. public class A {
    3.     public void method() {
    4.         for (int i = 0; i < 1000000; i++) {
    5.         }
    6.         System.out.println("method1");
    7.     }
    8. }

    JavassistTest.java

    1. package javassist.demo;
    2. import javassist.ClassPool;
    3. import javassist.CtClass;
    4. import javassist.CtMethod;
    5. import javassist.CtNewMethod;
    6. public class JavassistTest {
    7.     public static void main(String[] args) throws Exception {
    8.         // 用于取得字节码类,必须在当前的classpath中,使用全称
    9.         CtClass ctClass = ClassPool.getDefault().get("javassist.demo.A");
    10.         // 需要修改的方法名称
    11.         String mname = "method";
    12.         CtMethod mold = ctClass.getDeclaredMethod(mname);
    13.         // 修改原有的方法名称
    14.         String nname = mname + "$impl";
    15.         mold.setName(nname);
    16.         // 创建新的方法,复制原来的方法
    17.         CtMethod mnew = CtNewMethod.copy(mold, mname, ctClass, null);
    18.         // 主要的注入代码
    19.         StringBuffer body = new StringBuffer();
    20.         body.append("{/nlong start = System.currentTimeMillis();/n");
    21.         // 调用原有代码,类似于method();($$)表示所有的参数
    22.         body.append(nname + "($$);/n");
    23.         body.append("System.out.println(/"Call to method " + mname
    24.                 + " took /" +/n (System.currentTimeMillis()-start) + "
    25.                 + "/" ms./");/n");
    26.         body.append("}");
    27.         // 替换新方法
    28.         mnew.setBody(body.toString());
    29.         // 增加新方法
    30.         ctClass.addMethod(mnew);
    31.         // 类已经更改,注意不能使用A a=new A();,因为在同一个classloader中,不允许装载同一个类两次
    32.         A a = (A) ctClass.toClass().newInstance();
    33.         a.method();
    34.     }
    35. }

    资料:

    http://www.blogjava.net/zyl/archive/2007/02/10/99171.html

    http://www.ibm.com/developerworks/cn/java/j-dyn0916/ 


    展开全文
  • Javassist 教程(一)

    千次阅读 2016-01-07 17:01:41
     Javassist是一个处理Java字节码的类库。Java字节码存储在名叫class file的二进制文件里。每个class文件包含一个Java类或者接口。  Javassit.CtClass是一个class文件的抽象表示。一个CtClass(compile-time ...
  • Javassist操作方法总结

    万次阅读 2016-07-15 15:03:44
    参考手册: 1、读取和输出字节码 ClassPool pool = ClassPool.getDefault(); //会从classpath中查询该类 CtClass cc = pool.get("test.Rectangle"); //设置.Rectangle的父类 cc.setSuperclass(pool.get("test....
  • javassist 最新版

    2020-07-30 23:30:21
    在 struts 2 的开发中,经常启动tomcat会报一种错误,就是缺少这个jar包导致的!
  • Java动态代理机制详解(JDK 和CGLIB,Javassist,ASM)

    万次阅读 多人点赞 2014-06-16 18:04:40
    本文阐述:class 文件 和代码中的class 对象之间的关系;动态代理中Invocation Handler角色的由来;Javassist 和ASM框架生成字节码;类加载器
  • Javassist 中,类CtClass表示class文件。 我们可以用javassist类库实现动态创建类、添加类的属性和方法、设置类的父类,以及修改类的方法等操作。Javassist不允许删除方法或字段,但它允许更改名称。所以...
  • javassist是一个动态生成字节码的框架,生成的字节码可以输出或实时加载到jvm。文档内容全面。
  • 常见问题2:dubbo 依赖包 javassist 版本异常Error:dubbo启动报java.lang.ClassNotFoundException: javassist.ClassPath Reason: 在dubbo启动的过程中报错误: java.lang.ClassNotFoundException: javassist....
  • 1、异常描述使用Dubbo时,引入了javassist导致出现了ClassNotFoundException: javassist.ClassPath异常,因为此异常网上资料较少,特分享出来供大家脱坑: 服务启动后报错:message: Context initialization failed...
  • 一、背景 技术架构:jiyu
  • 构建的SSH项目,启动时报错java.lang.ClassNotFoundException: javassist.ClassPath 打开 maven仓库 ,搜索 javassist jar包, 在pom.xml里加如下代码: org.javassist javassist 3.21.0-GA 加版本...
  • javassist-3.12.0.GA.jar的作用 java

    千次阅读 2017-08-16 00:16:51
    Javassist是一款字节码编辑工具,可以直接编辑和生成Java生成的字节码,以达到对.class文件进行动态修改的效果。熟练使用这套工具,可以让Java编程更接近与动态语言编程。
  • Tomcat启动 Unable to process Jar entry [javassist/XXXXXX.class]—— 看来这是tomcat 8.0.9的专有bug啊...例如:03-Mar-2017 17:01:45.864 SEVERE [RMI TCP Connection(3)-127.0.0.1] org.apache.catalina.startup...
  • tomcat版本低导致启动报"Unable to process Jar entry" for all javassist classes 本地原来tomcat版本是7.0.37,启动web应用时,报: Unable to process Jar entry [javassist/ByteArrayClassPath.class] from ...
  • tomcat启动时报错误:SEVERE: Unable to process Jar entry [javassist/util/proxy/SerializedProxy.class] from Jar 但是不影响程序的运行,导致这个错误的发生的原因是tomcat版本低~~~~,将tomcat换成7.0.68或者更...
  • java 使用javassist 动态修改注解的值

    千次阅读 2017-02-23 19:32:46
    如下实例: @Test public void aetTestCaseAtrributes() throws NotFoundException { ClassPool pool = ClassPool.getDefault(); // 获取需要修改的类 CtClass ct = pool.get(...
  • 在起动服务器得时候会出现这个页面![图片说明]... 点击ok之后会报Usage: java javassist.tools.web.Webserver \ 我这个项目使用springboot整合SSM得 这个问题该如何解决?
1 2 3 4 5 ... 20
收藏数 19,573
精华内容 7,829
关键字:

javassist