Javassit提供了运行时操作Java字节码的方法,其效率低于asm。javassist主要是提供了代码级别的修改(也有bytecode级别),相比与asm的字节码级别的修改,学习成本低,开发效率高。因此,在实际应用中javassist是一个非常不错的选择。以下是在使用javassist的过程中碰到的问题及处理方法:
1、ClassLoader问题
我们知道java中有ExtClassLoader、AppClassLoader等来加载运行时需要的字节码,同时系统也允许我们自定义ClassLoader来实现不同的加载方式(如tomcat实现的加载机制)。在实际应用中会有这样的问题,如AClassLoader加载/home/admin/a/目录下的类A,BClassLoader加载/home/admin/b目录下的类B,类A想要引用B是无法引用成功的,因为类A的ClassLoader无法找到类B的定义。解决的方法就是加载B时指定BClassLoader去加载。对于Javassit来说,要想修改某个类,必须要先加载类信息,因此也存在类加载问题。知道了问题,处理起来就比较简单了,javassist中有一个ClassPath接口,该接口提供了查找类、加载类的字节码的方法。在遇到ClassLoader问题时,我们可以使用LoaderClassPath来处理,代码如下:
ClassPool pool = new ClassPool(true); pool.appendClassPath(new LoaderClassPath(classLoader));
ClassPath还有其他的实现来应对不同的情况:ByteArrayClassPath、ClassClassPath、DirClassPath、JarClassPath、JarDirClassPath、UrlClassPath。
如果一个应用中有存在多个不同的ClassLoader,建议对不同的ClassLoader创建不同的ClassPool,示例代码:
private static ConcurrentHashMap<ClassLoader, ClassPool> CLASS_POOL_MAP = new ConcurrentHashMap<ClassLoader, ClassPool>(); /** * 不同的ClassLoader返回不同的ClassPool * @param loader * @return */ public static ClassPool getClassPool(ClassLoader loader) { if (null == loader) { return ClassPool.getDefault(); } ClassPool pool = CLASS_POOL_MAP.get(loader); if (null == pool) { pool = new ClassPool(true); pool.appendClassPath(new LoaderClassPath(loader)); CLASS_POOL_MAP.put(loader, pool); } return pool; }
2、内存占用问题
javassist在加载类时会用Hashtable将类信息缓存到内存中,这样随着类的加载,内存会越来越大,甚至导致内存溢出。如果你的应用中要加载的类比较多,建议在使用完CtClass之后删除缓存:CtClass.detach()。
3、class的NotFoundException问题
NotFoundException包括找不到类定义、找不到方法定义等等,我们这里主要讨论找不到类定义的情况。你可能会觉得奇怪,前面不是有这么多ClassPath实现,难道还有这些ClassPath没有覆盖的情况? 是的,确实存在这种状态。比如我们使用javassist生成了一个自定义的类C, 由于该类完全是在内存中生成的,你无法通过一个具体的路径找到它,因此如果你后续希望再引用C,你可能会找不到它。为什么是可能? javassist在加载类时会将其信息缓存起来,然而有的应用因为内存方面的考虑,会通过detach移除缓存信息。对于普通的类来说,缓存移除后通过添加LoaderClassPath或者其他ClassPath的方式可以重新加载,但是对于javassist动态生成的类来说,由于其只在内存中存在,因此无法再次找到其信息。 知道了问题以后,我们可以怎么处理呢?
a) 在CtClass.detach()之前,将生成的字节码保存到指定目录下:CtClass.writeFile(dir), 然后通过指定DirClassPath来重新加载信息。
b) 如果CtClass操作已经被封装,无法加入writeFile方法的话,可以在系统启动时指定静态变量CtClass.debugDump="/home/admin/code_cache/dump"; 然后在需要对动态类进行二次代理时调用
pool.appendClassPath(new DirClassPath("/home/admin/code_cache/dump"));
4、特殊变量
javassist提供了一些特殊的变量来方便你操作(http://jboss-javassist.github.io/javassist/tutorial/tutorial2.html#before):
$0 ,$1 ,$2 , ... |
$0表示this,其他的表示实际的参数 |
$args |
参数数组. 相当于newObject[]{$1,$2,....},其中的基本类型会被转为包装类型
|
$$ |
所有的参数,如m($$ )相当于m($1,$2...),如果m无参数则m($$ )相当于m()
|
$cflow( ...)
|
表示一个指定的递归调用的深度 |
$r |
用于类型装换,表示返回值的类型. |
$w |
将基础类型转换为一个包装类型.如Integer a=($w)5;表示将5转换为Integer。如果不是基本类型则什么都不做。 |
$_ |
返回值,如果方法为void,则返回值为null; 值在方法返回前获得, 如果希望发生异常是有返回值(默认值,如nul),需要将insertAfter方法的第二个参数asFinally设置为true |
$sig |
方法参数的类型数组,数组的顺序为参数的顺序 |
$type |
返回类型的class, 如返回Integer则$type相当于java.lang.Integer.class, 注意其与$r的区别 |
$class |
方法所在的类的class |
其中cflow的用法如下:
// 被修改的方法 int fact(int n) { if (n <= 1) return n; else return n * fact(n - 1); } // 修改前的调用 CtMethod cm = fact方法; cm.useCflow("fact"); //此时$cflow(fact)表示fact方法的递归深度,第一次调用是为0 cm.insertBefore("if ($cflow(fact) == 0) {System.out.println(\"fact \" + $1);}");
cflow使用场景举例:
应用需要监控方法的执行时间,并找出执行时间长的方法,如果遇到递归调用期望忽略内部递归的记录,只记录最外层的时间,此时可以使用cflow。
最后,顺便提醒javassist也提供了动态代理的接口(javassist.util.proxy.ProxyFactory),但效率非常低,可测试时使用,不建议在生产环境下使用。
相关推荐
4. **面向切面编程(AOP)**:在AOP中,Javassist可以用来动态插入切面代码,实现如日志记录、事务管理等横切关注点。 5. **教学与研究**:由于它可以轻松地反编译和修改字节码,Javassist在教学和研究Java字节码和...
2. **修改main方法**:在得到源代码后,可以使用Javassist定位并修改main方法,例如增加日志记录、改变控制流等。 3. **字节码重新生成**:使用Javassist将修改后的源代码重新编译为字节码,并替换原有的.class文件...
- **社区支持**:Javaassist有一个活跃的社区,遇到问题时可以寻求帮助。 总结来说,"javassist-3.15.0-GA"是Java开发中的一个重要工具,它使得在运行时动态修改和增强Java代码成为可能,广泛应用于AOP、动态代理...
在Java中,我们可以使用JDK自带的动态代理或者第三方库如CGLIB、Javassist、ASM来实现。 **JDK动态代理**: JDK的动态代理主要依赖于`java.lang.reflect.Proxy`和`java.lang.reflect.InvocationHandler`两个类。...
在实际应用中,`javassist-3.7.ga.jar`的使用可能包括以下步骤: 1. 引入依赖:将jar包添加到项目的类路径中,如果是Maven项目,则在pom.xml中添加对应的依赖配置。 2. 创建`ClassPool`:这是使用Javaassist的第一步...
Javaassist是一个开源库,主要用在Java编程中,它提供了对字节码操作的强大功能,使得程序员可以在运行时动态修改类或创建新的类。这个"javassist-3.9.0.GA.jar.zip"文件包含了Javaassist库的版本3.9.0 GA,这是一个...
在实际应用中,这样的技术可以用于实现如AOP拦截器、性能监控、日志记录等功能,而无需改变原有代码。通过这种方式,我们可以在不侵入原始代码的情况下,灵活地扩展或修改类的行为。同时,由于注解的存在,这种修改...
3. 类加载器:在运行时,Javaassist使用自定义的类加载器 (`ClassPool` 中的 `makeClass` 方法) 将修改后的字节码转换为实际的Java类并加载到JVM中。 4. 代码生成:Javaassist提供了类似于Java语法的API,可以方便...
1. 类转换:Javassist可以读取任何已存在的类,并将其转换为字节码表示。这使得在运行时修改已有类的结构成为可能,比如添加新的方法、字段或者修改已有方法的行为。 2. 动态代理:在Java中,Javassist可以用于创建...
代码中使用了Struts2 API的地方需要检查和适配。 4. **兼容性测试**:在升级后,进行全面的单元测试和集成测试,确保所有功能正常工作,特别是那些依赖于Struts2核心功能的部分。 5. **安全修复**:Struts2 2.5.30...
描述中提到的问题,当只引入了Struts2的核心包并尝试启动Tomcat服务器时,可能会遇到报错。这通常是由于缺少必要的依赖导致的。在这种情况下,"javassist-3.9.0.GA.jar"和"commons-io-2.0.1.jar"是两个关键的辅助JAR...
在Hibernate项目中,日志记录是非常关键的,它可以帮助开发者在调试和问题排查时获取必要的信息。因此,缺少这个jar包可能会导致日志功能无法正常使用。 一个完整的Hibernate环境通常需要以下主要的jar包: 1. ...
HikariCP还可能依赖其他的jar包,如slf4j-api.jar(日志框架API)、logback-classic.jar(具体的日志实现)、guava.jar(Google的工具集,提供各种实用工具类)、spring-jdbc.jar(如果在Spring框架中使用HikariCP)...
在项目中使用Spring JPA之前,需要添加以下JAR包到项目依赖中: 1. **commons-collections.jar**:提供了一系列强大的集合操作工具。 2. **hibernate-entitymanager.jar**:Hibernate提供的JPA实现的核心类库。 3. ...
- **文档更新**:更新项目文档,记录此次升级的具体过程及遇到的问题解决方案。 通过以上步骤,可以有效地将项目中的Struts2版本从较低版本升级到2.3.16.3,并解决升级过程中可能出现的安全漏洞问题。这不仅有助于...
5. **导入与配置**:在项目中使用这些jar包,通常需要将它们添加到项目的类路径中,如果是Maven项目,则需要在pom.xml中配置对应的依赖。同时,还需要对struts2、spring和hibernate进行相应的配置,例如在struts2的...
当应用需要访问数据库时,可以从连接池中获取一个已存在的连接,用完后再归还,而不是直接关闭,从而提高了系统性能。 HikariCP之所以受到青睐,有以下几个主要原因: 1. **快速响应**:HikariCP的设计注重性能,...
在整合这些jar包时,需要注意版本兼容性问题,因为不同的库之间可能存在版本冲突。此外,为了避免重复引入,可以使用依赖管理工具的排除机制来解决冲突。总的来说,理解Hibernate所需的jar包以及它们在应用程序中的...
在Spring中,还需要创建一个SessionFactory的bean,以便在应用中注入并使用。 理解并正确配置这些jar包对于SSH开发者来说至关重要,因为它们构成了Hibernate与数据库交互的基础。没有这些依赖,Hibernate无法正确...
7. `gson-2.2.2.jar`:Google的Gson库,用于JSON对象和Java对象之间的转换,可能在数据序列化或网络通信中使用。 8. `jakarta-regexp-1.4.jar`:这是一个正则表达式库,可能在文本处理和查询解析中使用。 综合这些...