`
ajuanlong
  • 浏览: 107796 次
社区版块
存档分类
最新评论

Javassist字节码增强

 
阅读更多

通常对一个方法增加日志记录,安全检查都会说采用AOP或CGLIB动态代理,但无论哪种方式都必需改变原有的调用方式;
同时,大量的反射调用也必增加系统的开销。下面介绍一种不需要改变客户端调用方式而又能实现对指定方法增加缓存或日志的方式,那就是——字节码增强!

在实际项目中通常需要对一些频繁访问数据库的方法采用对象缓存,从而提高系统性能减少不必要的网络开销。
这时候一般我们会去修改方法的源码,增加Cache的put,get调用,要么采用AspectJ或cglib进行方法执行前或执行后的拦截

但采用无论采用哪种方式都必需改变客户端原有的调用方式,可能涉及变动的模块又零散(如对Account,Bank,Customer对象增加Log或Cache),况且如果对于遗留系统而又没有源码呢?

因此采用字节码增强成立一种可选的手段,允许在不改变原调用方式的情况下进行字节码增强

最直接的改造 Java 类的方法莫过于直接改写 class 文件。Java 规范详细说明了class 文件的格式,直接编辑字节码确实可以改变 Java 类的行为。操作字节码开源实现有ObjectWeb ASM,JBOSS Javassist等,最终选择Javassist是由于它操作简单,直观。

以下是字节码增强的生成器ClassEnhancedGenerator,实现对一个类的多个方法进行字节码增强



  1. public class ClassEnhancedGenerator {
  2. private ClassEnhancedGenerator(){
  3. }
  4. /**
  5. * 类方法增强<BR>
  6. *
  7. * 对指定类的方法进行代码增强(将指定的原方法名改为$enhanced,同时复制原方法名进行代码注入)
  8. * @param className 待增强的类名
  9. * @param methodName 待增强的方法名
  10. * @param injectType {@link InjectType}注入类型
  11. * @param provider {@link ClassInjectProvider}实现类
  12. * @throws Exception
  13. */
  14. public static void enhancedMethod(String className,String[] methods,
  15. InjectType injectType,
  16. ClassInjectProvider provider)throws Exception{
  17. CtClass ctClass = ClassPool.getDefault().get(className);
  18. for(int i=0;i<methods.length;i++){
  19. injectCodeForMethod(ctClass,methods[i],injectType,provider);
  20. }
  21. String resource = className.replace(".", "/") + ".class";
  22. URI uri = ClassLoader.getSystemClassLoader().getResource(resource).toURI();
  23. String classFilePath = uri.getRawPath().substring(0,uri.getRawPath().length() - resource.length());
  24. ctClass.writeFile(classFilePath);
  25. }
  26. private static void injectCodeForMethod(CtClass ctClass,String methodName,InjectType injectType,ClassInjectProvider provider)throws Exception{
  27. CtMethod oldMethod = ctClass.getDeclaredMethod(methodName);
  28. //修改原有的方法名称为"方法名$enhanced",如果已存在该方法则返回
  29. String originalMethod = methodName + "$enhanced";
  30. CtMethod[] methods=ctClass.getMethods();
  31. for(int i=0;i<methods.length;i++){
  32. CtMethod method=methods[i];
  33. if(method.getName().equals(originalMethod)){
  34. return ;
  35. }
  36. }
  37. oldMethod.setName(originalMethod);
  38. //增加代码,复制原来的方法名作为增强的新方法,同时调用原有方法即"方法名$enhanced"
  39. CtMethod enhancedMethod = CtNewMethod.copy(oldMethod, methodName, ctClass, null);
  40. //对复制的方法注入代码
  41. StringBuffer methodBody = new StringBuffer();
  42. methodBody.append("{");
  43. switch(injectType){
  44. case AFTER:
  45. methodBody.append(provider.injectCode(enhancedMethod));
  46. methodBody.append(originalMethod + "($$); ");
  47. break;
  48. case BEFORE:
  49. methodBody.append(originalMethod + "($$); ");
  50. methodBody.append(provider.injectCode(enhancedMethod));
  51. break;
  52. default:
  53. String injectCode=provider.injectCode(enhancedMethod);
  54. methodBody.append(injectCode);
  55. }
  56. methodBody.append("}");
  57. enhancedMethod.setBody(methodBody.toString());
  58. ctClass.addMethod(enhancedMethod);
  59. }
  60. }


代码注入Provider接口


  1. public interface ClassInjectProvider {
  2. /**
  3. * 对指定的方法注入代码
  4. *
  5. * @param ctMethod CtMethod
  6. * @return
  7. */
  8. public String injectCode(final CtMethod ctMethod)throws Exception;
  9. }


缓存注入的实现类:


  1. public class CacheInjectProvider implements ClassInjectProvider{
  2. private static MyLogger logger=new MyLogger(CacheInjectProvider.class);
  3. public CacheInjectProvider(){
  4. }
  5. /**
  6. * 注入缓存,缓存键值以类名+#+方法名(参数值1...参数值n)为key
  7. *
  8. * @param method CtMethod
  9. */
  10. public String injectCode(final CtMethod method) throws Exception{
  11. StringBuffer cacheCode=new StringBuffer();
  12. try{
  13. if(method.getReturnType()==CtClass.voidType){
  14. cacheCode.append(method.getName()+"$enhanced($$); ");
  15. }
  16. else{
  17. cacheCode.append("StringBuilder cacheKeyBuilder=new StringBuilder(); ");
  18. cacheCode.append("MethodCacheKey cacheKey=new MethodCacheKey(); ");
  19. cacheCode.append("cacheKey.setClassName("").append(method.getDeclaringClass().getName()).append(""); ");
  20. cacheCode.append("cacheKey.setMethodName("").append(method.getName()).append(""); ");
  21. CtClass[] ctClass=method.getParameterTypes();
  22. cacheCode.append("Object[] parameters=new Object[").append(ctClass.length).append("]; ");
  23. for(int i=0;i<ctClass.length;i++){
  24. cacheCode.append("parameters["+i+"]=").append("($w)$"+(i+1)).append("; ");
  25. }
  26. cacheCode.append("cacheKey.setParameters(parameters); ");
  27. cacheCode.append("Cache cache=CacheFactory.getCache();").append(" ");
  28. cacheCode.append("if(cache.get(cacheKey)==null)").append(" ");
  29. cacheCode.append("{").append(" ");
  30. if(method.getReturnType().isPrimitive()){
  31. cacheCode.append("Object result=($w)").append(method.getName()+"$enhanced($$);").append(" ");
  32. }
  33. else{
  34. cacheCode.append("Object result=").append(method.getName()+"$enhanced($$);").append(" ");
  35. }
  36. cacheCode.append("cache.put(cacheKey,result);").append(" ");
  37. cacheCode.append("}").append(" ");
  38. cacheCode.append("return ").append("($r)cache.get(cacheKey);");
  39. }
  40. }catch(Exception e){
  41. }
  42. logger.log("inject sourcecode>>>: "+cacheCode.toString());
  43. return cacheCode.toString();
  44. }
  45. }


说明:
$1,$2 表示方法的第一个参数值,第二个参数值
$w 表示将指定的基本类型变量转换为对象,如($w)$1 即为Integer i=new Integer(1);
$$ 表示方法的所有参数
$r 方法的返回类型

缺点:
1)类重新编译后,字节码增强生成器要重新执行

这一点可以通过StartupRuntime 接口的方式在系统启动的时候运行指定方法,如

  1. public interface StartupRuntime {
  2. /**
  3. * 系统启动加载执行方法
  4. * @throws Throwable
  5. */
  6. public void execute()throws SystemRuntimeException;
  7. }


转自:http://gocom.primeton.com/blog3936_16703.htm


分享到:
评论

相关推荐

    javassist试图简化Java字节码的编辑

    Java字节码编辑是Java开发中的一个高级主题,它允许开发者在运行时修改或增强类的行为。`javassist`库正是这样一个工具,它为Java字节码操作提供了一个简洁的API,试图降低这个过程的复杂性。在Java世界中,字节码...

    字节码编程和操作系统等知识文档

    字节码编程允许开发者在类加载到JVM之前或之后,对其进行动态地修改或增强,这在AOP(面向切面编程)和性能监控等领域有着广泛的应用。 在描述中提到的非入侵式全链路监控,是一种在不需要修改业务代码的情况下,对...

    JavaAgent:Javassist 与 Asm JavaAgent 字节码动态编程项目

    在这个“JavaAgent: Javassist与Asm JavaAgent字节码动态编程项目”中,我们将深入探讨如何利用Javassist和ASM这两个库来实现JavaAgent。 首先,Javassist是一个开源的Java字节码操作框架,它使得开发者可以在运行...

    字节码增强在全链路压测场景的使用.docx

    在Java技术栈中,全链路压测的适配通常涉及字节码增强技术。有两类实现方式:一是直接修改开源SDK的源码,二是利用Java Agent在运行时修改字节码。考虑到业务侵入性、代码可读性、维护难度和升级便利性,字节码增强...

    jboss-javassist和JByteMode整合包

    Javassist和JByteMode是两个强大的工具,分别在不同的场景下提供了便捷的Java字节码操作能力。本文将深入探讨这两个库以及它们在整合中的应用。 **Javassist简介** Javassist,全称为Java Assistant,是一个开源的...

    javassist+jd-gui

    3. **字节码修改**:可以插入、删除或替换已有的字节码指令,实现代码的动态增强。 4. **类转换**:`javassist` 提供了转换器,可以将类从一种形式转换为另一种形式,例如从 Java 源码转换为字节码。 另一方面,`jd...

    javaagent+javassist

    Javaagent和javassist是Java开发中的两个重要工具,它们在软件开发中有着广泛的应用,特别是在动态代理、代码增强和字节码操作等领域。本篇将详细介绍这两个技术,并结合实际示例进行解析。 首先,让我们来了解`...

    java 反编译工具 jboss-javassist

    Java反编译工具JBoss Javassist是一款强大的Java字节码操作库,它允许程序员在运行时修改类或者创建新的类。Javassist是Java字节码操纵和分析框架,常用于动态代理、AOP(面向切面编程)以及逆向工程等场景。通过这...

    “字节码插桩“ Demo.rar

    这通常使用ASM、BCEL、Javassist等字节码库完成,这些库提供了方便的方法解析和操作字节码。 3. **插入字节码**:在分析了字节码后,根据需求,我们可以选择在特定的位置插入新的字节码指令。例如,如果我们想要在...

    Javassist18版20版22版的jar包

    - 开始使用Javassist,你需要理解Java字节码的概念,了解如何使用CtClass、CtMethod和CtField等核心类。 - Javassist的官方文档是学习的最佳资源,它详细介绍了API的使用方法和示例。 - 在实践中,你可以从简单的...

    javassist-3.18.1-GA.jar

    总的来说,`javassist-3.18.1-GA.jar`是Javaassist库的一个稳定版本,提供了丰富的API和工具,用于在运行时操纵Java类的字节码。这对于理解和实现诸如Hibernate这样的高级框架的内部工作原理非常有帮助。通过阅读...

    javassist-3.20.0-GA.zip

    Javaassist是一个强大的开源库,主要用于在运行时动态地修改Java字节码。它是一个非常实用的工具,尤其在需要对已编译的类进行修改或增强功能时。这个"javassist-3.20.0-GA.zip"压缩包包含了Javaassist的3.20.0-GA...

    行业分类-设备装置-基于Java字节码的大型应用回归测试信息处理方法.zip

    2. **字节码分析与重构**:使用工具如ASM、ByteBuddy或Javassist等,分析字节码结构,找出可能的bug或性能瓶颈,然后进行优化。这种方法可以避免直接修改源代码,减少了回归测试的复杂性。 3. **覆盖跟踪与报告**:...

    javassist-3.18.0-ga

    总的来说,`javassist-3.18.0-ga.jar`是Java开发中一个强大的工具,对于那些需要在运行时操作字节码的项目来说,它是不可或缺的。通过理解和熟练使用Javaassist,开发者可以实现更多创新和灵活的编程策略。

    javassist-3.15.0-GA

    - **字节码操作**:Javaassist通过字节码操作实现对类的修改,它可以解析.class文件,并允许用户以Java语法修改字节码。 2. **使用场景**: - **AOP(面向切面编程)**:在不修改源代码的情况下,插入切面代码,...

    基于 javaagent 对 java 原生类的 方法进行字节码动态修改, 以此引发的一些关于 绕过 Java 软件.zip

    在实际操作中,我们可能会使用如 ASM、ByteBuddy 或 Javassist 这样的字节码库来帮助创建和应用 ClassFileTransformer。这些库提供了友好的 API,让我们能够方便地读取、修改和生成字节码。 然而,这种方法并不总是...

    javassist18,20,22三个版本.zip

    Javaassist是一个强大的字节码操作库,它允许程序员在运行时动态修改或创建Java类。这个库被广泛用于实现AOP(面向切面编程)、元编程以及动态代理等高级编程技术。在这里,我们讨论的是javassist的三个不同版本:...

    javassist-3.9.0.GA.jar.zip

    总的来说,"javassist-3.9.0.GA.jar.zip"提供的Javaassist库是一个强大的字节码操作工具,对于理解Java字节码以及在运行时修改代码有着重要的作用。开发者可以利用这个库轻松地实现复杂的编程任务,提高代码的可扩展...

    javassist-3.7.ga.jar

    例如,可以通过 `CtMethod.insertAt()` 方法在特定位置插入字节码,实现方法的动态增强。 3. **代码生成**:Javaassist 提供了类似于 Java 语法的 API 来生成字节码。`CtNewMethod` 和 `CtNewConstructor` 可以帮助...

Global site tag (gtag.js) - Google Analytics