概述

  Javassist是一款字节码编辑工具,可以直接编辑和生成Java生成的字节码,以达到对.class文件进行动态修改的效果。熟练使用这套工具,可以让Java编程更接近与动态语言编程。
  下面一个方法的目的是获取一个类加载器(ClassLoader),以加载指定的.jar或.class文件,在之后的代码中会使用到。
  1. private static ClassLoader getLocaleClassLoader() throws Exception {
  2. List<URL> classPathURLs = new ArrayList<>();
  3. // 加载.class文件路径
  4. classPathURLs.add(classesPath.toURI().toURL());
  5. // 获取所有的jar文件
  6. File[] jarFiles = libPath.listFiles(new FilenameFilter() {
  7. @Override
  8. public boolean accept(File dir, String name) {
  9. return name.endsWith(".jar");
  10. }
  11. });
  12. Assert.assertFalse(ObjectHelper.isArrayNullOrEmpty(jarFiles));
  13. // 将jar文件路径写入集合
  14. for (File jarFile : jarFiles) {
  15. classPathURLs.add(jarFile.toURI().toURL());
  16. }
  17. // 实例化类加载器
  18. return new URLClassLoader(classPathURLs.toArray(new URL[classPathURLs.size()]));
  19. }

获取类型信息

  1. @Test
  2. public void test() throws NotFoundException {
  3. // 获取默认类型池对象
  4. ClassPool classPool = ClassPool.getDefault();
  5. // 获取指定的类型
  6. CtClass ctClass = classPool.get("java.lang.String");
  7. System.out.println(ctClass.getName());  // 获取类名
  8. System.out.println("\tpackage " + ctClass.getPackageName());    // 获取包名
  9. System.out.print("\t" + Modifier.toString(ctClass.getModifiers()) + " class " + ctClass.getSimpleName());   // 获取限定符和简要类名
  10. System.out.print(" extends " + ctClass.getSuperclass().getName());  // 获取超类
  11. // 获取接口
  12. if (ctClass.getInterfaces() != null) {
  13. System.out.print(" implements ");
  14. boolean first = true;
  15. for (CtClass c : ctClass.getInterfaces()) {
  16. if (first) {
  17. first = false;
  18. } else {
  19. System.out.print(", ");
  20. }
  21. System.out.print(c.getName());
  22. }
  23. }
  24. System.out.println();
  25. }

修改类方法

  1. @Test
  2. public void test() throws Exception {
  3. // 获取本地类加载器
  4. ClassLoader classLoader = getLocaleClassLoader();
  5. // 获取要修改的类
  6. Class<?> clazz = classLoader.loadClass("edu.alvin.reflect.TestLib");
  7. // 实例化类型池对象
  8. ClassPool classPool = ClassPool.getDefault();
  9. // 设置类搜索路径
  10. classPool.appendClassPath(new ClassClassPath(clazz));
  11. // 从类型池中读取指定类型
  12. CtClass ctClass = classPool.get(clazz.getName());
  13. // 获取String类型参数集合
  14. CtClass[] paramTypes = {classPool.get(String.class.getName())};
  15. // 获取指定方法名称
  16. CtMethod method = ctClass.getDeclaredMethod("show", paramTypes);
  17. // 赋值方法到新方法中
  18. CtMethod newMethod = CtNewMethod.copy(method, ctClass, null);
  19. // 修改源方法名称
  20. String oldName = method.getName() + "$Impl";
  21. method.setName(oldName);
  22. // 修改原方法
  23. newMethod.setBody("{System.out.println(\"执行前\");" + oldName + "($$);System.out.println(\"执行后\");}");
  24. // 将新方法添加到类中
  25. ctClass.addMethod(newMethod);
  26. // 加载重新编译的类
  27. clazz = ctClass.toClass();      // 注意,这一行会将类冻结,无法在对字节码进行编辑
  28. // 执行方法
  29. clazz.getMethod("show", String.class).invoke(clazz.newInstance(), "hello");
  30. ctClass.defrost();  // 解冻一个类,对应freeze方法
  31. }

动态创建类

  1. @Test
  2. public void test() throws Exception {
  3. ClassPool classPool = ClassPool.getDefault();
  4. // 创建一个类
  5. CtClass ctClass = classPool.makeClass("edu.alvin.reflect.DynamiClass");
  6. // 为类型设置接口
  7. //ctClass.setInterfaces(new CtClass[] {classPool.get(Runnable.class.getName())});
  8. // 为类型设置字段
  9. CtField field = new CtField(classPool.get(String.class.getName()), "value", ctClass);
  10. field.setModifiers(Modifier.PRIVATE);
  11. // 添加getter和setter方法
  12. ctClass.addMethod(CtNewMethod.setter("setValue", field));
  13. ctClass.addMethod(CtNewMethod.getter("getValue", field));
  14. ctClass.addField(field);
  15. // 为类设置构造器
  16. // 无参构造器
  17. CtConstructor constructor = new CtConstructor(null, ctClass);
  18. constructor.setModifiers(Modifier.PUBLIC);
  19. constructor.setBody("{}");
  20. ctClass.addConstructor(constructor);
  21. // 参数构造器
  22. constructor = new CtConstructor(new CtClass[] {classPool.get(String.class.getName())}, ctClass);
  23. constructor.setModifiers(Modifier.PUBLIC);
  24. constructor.setBody("{this.value=$1;}");
  25. ctClass.addConstructor(constructor);
  26. // 为类设置方法
  27. CtMethod method = new CtMethod(CtClass.voidType, "run", null, ctClass);
  28. method.setModifiers(Modifier.PUBLIC);
  29. method.setBody("{System.out.println(\"执行结果\" + this.value);}");
  30. ctClass.addMethod(method);
  31. // 加载和执行生成的类
  32. Class<?> clazz = ctClass.toClass();
  33. Object obj = clazz.newInstance();
  34. clazz.getMethod("setValue", String.class).invoke(obj, "hello");
  35. clazz.getMethod("run").invoke(obj);
  36. obj = clazz.getConstructor(String.class).newInstance("OK");
  37. clazz.getMethod("run").invoke(obj);
  38. }

创建代理类

  1. @Test
  2. public void test() throws Exception {
  3. // 实例化代理类工厂
  4. ProxyFactory factory = new ProxyFactory();
  5. //设置父类,ProxyFactory将会动态生成一个类,继承该父类
  6. factory.setSuperclass(TestProxy.class);
  7. //设置过滤器,判断哪些方法调用需要被拦截
  8. factory.setFilter(new MethodFilter() {
  9. @Override
  10. public boolean isHandled(Method m) {
  11. return m.getName().startsWith("get");
  12. }
  13. });
  14. Class<?> clazz = factory.createClass();
  15. TestProxy proxy = (TestProxy) clazz.newInstance();
  16. ((ProxyObject)proxy).setHandler(new MethodHandler() {
  17. @Override
  18. public Object invoke(Object self, Method thisMethod, Method proceed, Object[] args) throws Throwable {
  19. //拦截后前置处理,改写name属性的内容
  20. //实际情况可根据需求修改
  21. System.out.println(thisMethod.getName() + "被调用");
  22. try {
  23. Object ret = proceed.invoke(self, args);
  24. System.out.println("返回值: " + ret);
  25. return ret;
  26. } finally {
  27. System.out.println(thisMethod.getName() + "调用完毕");
  28. }
  29. }
  30. });
  31. proxy.setName("Alvin");
  32. proxy.setValue("1000");
  33. proxy.getName();
  34. proxy.getValue();
  35. }

  其中,TestProxy类内容如下:

  1. public class TestProxy {
  2. private String name;
  3. private String value;
  4. public String getName() {
  5. return name;
  6. }
  7. public void setName(String name) {
  8. this.name = name;
  9. }
  10. public String getValue() {
  11. return value;
  12. }
  13. public void setValue(String value) {
  14. this.value = value;
  15. }
  16. }

获取方法名称

  1. @Test
  2. public void test() throws Exception {
  3. // 获取本地类加载器
  4. ClassLoader classLoader = getLocaleClassLoader();
  5. // 获取要修改的类
  6. Class<?> clazz = classLoader.loadClass("edu.alvin.reflect.TestLib");
  7. // 实例化类型池
  8. ClassPool classPool = ClassPool.getDefault();
  9. classPool.appendClassPath(new ClassClassPath(clazz));
  10. CtClass ctClass = classPool.get(clazz.getName());
  11. // 获取方法
  12. CtMethod method = ctClass.getDeclaredMethod("show", ObjectHelper.argumentsToArray(CtClass.class, classPool.get("java.lang.String")));
  13. // 判断是否为静态方法
  14. int staticIndex = Modifier.isStatic(method.getModifiers()) ? 0 : 1;
  15. // 获取方法的参数
  16. MethodInfo methodInfo = method.getMethodInfo();
  17. CodeAttribute codeAttribute = methodInfo.getCodeAttribute();
  18. LocalVariableAttribute localVariableAttribute = (LocalVariableAttribute)codeAttribute.getAttribute(LocalVariableAttribute.tag);
  19. for (int i = 0; i < method.getParameterTypes().length; i++) {
  20. System.out.println("第" + (i + 1) + "个参数名称为: " + localVariableAttribute.variableName(staticIndex + i));
  21. }
  22. }

  关于“获取方法名称”,其主要作用是:当Java虚拟机加载.class文件后,会将类方法“去名称化”,即丢弃掉方法形参的参数名,而是用形参的序列号来传递参数。如果要通过Java反射获取参数的参数名,则必须在编辑是指定“保留参数名称”。Javassist则不存在这个问题,对于任意方法,都能正确的获取其参数的参数名。

  Spring MVC就是通过方法参数将请求参数进行注入的,这一点比struts2 MVC要方便很多,Spring也是借助了Javassist来实现这一点的。

Javassist字节码增强示例的更多相关文章

  1. JVM插庄之一:JVM字节码增强技术介绍及入门示例

    字节码增强技术:AOP技术其实就是字节码增强技术,JVM提供的动态代理追根究底也是字节码增强技术. 目的:在Java字节码生成之后,对其进行修改,增强其功能,这种方式相当于对应用程序的二进制文件进行修 ...

  2. 字节码增强技术-Byte Buddy

    本文转载自字节码增强技术-Byte Buddy 为什么需要在运行时生成代码? Java 是一个强类型语言系统,要求变量和对象都有一个确定的类型,不兼容类型赋值都会造成转换异常,通常情况下这种错误都会被 ...

  3. 深入浅出Java探针技术1--基于java agent的字节码增强案例

    Java agent又叫做Java 探针,本文将从以下四个问题出发来深入浅出了解下Java agent 一.什么是java agent? Java agent是在JDK1.5引入的,是一种可以动态修改 ...

  4. 从底层入手,解析字节码增强和Btrace应用

    这篇文章聊下字节码和相关的应用. 1.机器码和字节码 机器码(machine code),学名机器语言指令,有时也被称为原生码(Native Code),是电脑的CPU可直接解读的数据. 通常意义上来 ...

  5. Java字节码增强技术

    简单介绍下几种java字节码增强技术. ASM ASM是一个Java字节码操控框架,它能被用来动态生成类或者增强既有类的功能.ASM可以直接产生class文件,也可以在类被加载入Java虚拟机之前动态 ...

  6. 字节码增强-learnning

    jvm加载java的过程主要是: 编写java文件->进行java文件的编译->生成.class字节码文件->jvm通过类加载器去加载生成的二进制文件 java编译器将源码文件编译称 ...

  7. Javassist 字节码 语法 MD

    Markdown版本笔记 我的GitHub首页 我的博客 我的微信 我的邮箱 MyAndroidBlogs baiqiantao baiqiantao bqt20094 baiqiantao@sina ...

  8. JVM——字节码增强技术简介

    Java字节码增强指的是在Java字节码生成之后,对其进行修改,增强其功能,这种方式相当于对应用程序的二进制文件进行修改.Java字节码增强主要是为了减少冗余代码,提高性能等. 实现字节码增强的主要步 ...

  9. SpringAOP之CGLIB字节码增强

    SpringAOP的基础原理就是动态代理 有两种实现方式:1)jdk动态代理 2)cglib动态代理 jdk动态代理和cglib动态代理的区别在于: cglib没有接口(通过继承父类) 只有实现类.  ...

随机推荐

  1. Java进阶(五十一)必须记住的Myeclipse快捷键

    Java进阶(五十一)必须记住的Myeclipse快捷键 在调试程序的时候,我们经常需要注释一些代码,在用Myeclipse编程时,就可以用 Ctrl+/ 为选中的一段代码加上以 // 打头的注释:当 ...

  2. 《java入门第一季》之面向对象(构造方法)

    /* 构造方法: 给对象的数据进行初始化 格式: A:方法名与类名相同 B:没有返回值类型,连void都没有 C:没有具体的返回值 */ class Student { private String ...

  3. mysql大小写敏感(默认为1,不敏感)

    在 MySQL 中,数据库和表其实就是数据目录下的目录和文件,因而,操作系统的敏感性决定数据库和表命名的大小写敏感.这就意味着数据库和表名在 Windows 中是大小写不敏感的,而在大多数类型的 Un ...

  4. 如何调整DOS窗口的宽高

    运行->cmd->dos窗口上沿点右键->默认值所选字体字体: 新宋体大小: 13窗口大小宽度: 100高度: 40屏幕缓冲区大小宽度: 100高度: 300 在这里还可以设置依他常 ...

  5. android Material Design详解

    原文地址:http://blog.csdn.net/jdsjlzx/article/details/41441083/ 前言 我们都知道Marterial Design是Google推出的全新UI设计 ...

  6. PHP单元测试使用

    单元测试(unit testing),是指对软件中的最小可测试单元进行检查和验证.对于单元测试中单元的含义,一般来说,要根据实际情况去判定其具体含义,如C语言中单元指一个函数,Java里单元指一个类, ...

  7. sql中的case when then else end

    hive中的case when的用法举例 select * from (select id, count(distinct ] in ("Virus","Worm&quo ...

  8. angularjs作用域之transclude

    transclude是一个可选的参数.如果设置了,其值必须为true,它的默认值是false.嵌入有时被认为是一个高级主题,但某些情况下它与我们刚刚学习过的作用域之间会有非常好的配合.使用嵌入也会很好 ...

  9. oracle 随机数(转载)

    http://blog.sina.com.cn/s/blog_6a01140c0100wimi.html 1.从表中随机取记录 select * from (select * from staff o ...

  10. 关于MVC的特性(AuthorizeAttribute)的一些理解

    许多 Web 应用程序要求在用户登录之后才授予其对受限制内容的访问权限. 在某些应用程序中,即使是登录的用户,也会限制他们可以查看的内容或可以编辑的字段. 要限制对 ASP.NET MVC 视图的访问 ...