Javassist 通用工具之 CodeInjector

时间:2022-05-26 00:05:29

Javassist 通用工具之CodeInjector

最近在做一个APM项目,要在运行时代码修改。目前常用修改的几种工具有:ASM、BCEL、Javassist。经过对比,项目中采用了Javassist。

看这篇文章,需要对Javassist有一定的了解,可以参考:Javassist: Quick Start

在使用Javassist过程中,最常用的方法有CtMethod(或者CtConstructor)的insertBefore,insertAfter,addCatch,另外还有一种是injectAround(这种是需要自己来完成的)。可以参考:Spring:Aop before after afterReturn afterThrowing around 的原理

在代码里引入了before,beforeAround,beforeAfter,beforeThrowing,beforeReturning的概念,是取材于Spring AOP配置中的叫法。

package org.fjn.frameworkex.javassist;

import javassist.CtClass;
import javassist.CtConstructor;
import javassist.CtMethod;
import javassist.CtNewMethod;

/**
 * Code Inject Tool
 *
 * @author <a href="mailto:fs1194361820@163.com">fs1194361820@163.com</a>
 *
 */
public class CodeInjector {
    public static final String METHOD_RUTURN_VALUE_VAR = "__FJN__result";
    public static final String METHOD_EXEC_EXCEPTION_VAR = "ex";
    public static final String CONSTRUCTOR_DELEGATE_PREFIX = "__FJN__DOC__";
    public static final String METHOD_DELEGATE_PREFIX = "__FJN__DOM__";
    public static final String PROCEED = "$proceed";
    public static final String CRLF = "\n";
    public static final String CRLF_TAB = "\n\t";
    public static final String CRLF_2TAB = "\n\t\t";
    public static final String CRLF_3TAB = "\n\t\t\t";
    public static final String CRLF_4TAB = "\n\t\t\t\t";

    public static final String getDelegateMethodNameOfConstructor(String constructorName) {
        return CONSTRUCTOR_DELEGATE_PREFIX + constructorName;
    }

    public static final String getDelegateMethodNameOfMethod(String methodName) {
        return METHOD_DELEGATE_PREFIX + methodName;
    }

    /**
     * Inject around code to the specified method
     *
     * @see #injectInterceptor(CtClass, CtMethod, String, String, String,
     *      String, String)
     */
    public void injectAround(CtMethod method, String beforeAround, String afterAround, String afterThrowing,
            String afterReturning) throws Exception {
        CtClass clazz = method.getDeclaringClass();
        injectAround(clazz, method, beforeAround, afterAround, afterThrowing, afterReturning);
    }

    /**
     * Inject around code to the specified method
     *
     * @see #injectInterceptor(CtClass, CtMethod, String, String, String,
     *      String, String)
     */
    public void injectAround(CtClass clazz, CtMethod method, String beforeAround, String afterAround,
            String afterThrowing, String afterReturning) throws Exception {
        injectInterceptor(clazz, method, null, beforeAround, afterAround, afterThrowing, afterReturning);
    }

    /**
     * Inject around code to the specified method
     *
     * <pre>
     * <code>
     * <span style="font-size:12px; color:green;">before block ... </span>
     * try{
     *     <span style="font-size:12px; color:green;">beforeAround block ... </span>
     *     $procced($$);
     *     <span style="font-size:12px; color:green;">afterAround block ... </span>
     * }catch (Throwable ex){
     *     <span style="font-size:12px; color:green;">afterThrowing block ... </span>
     * }finally{
     *     <span style="font-size:12px; color:green;">afterReturning block ... </span>
     * }
     * </code>
     * </pre>
     *
     */
    public void injectInterceptor(CtClass clazz, CtMethod method, String before, String beforeAround,
            String afterAround, String afterThrowing, String afterReturning) throws Exception {
        clazz.defrost();
        int modifiers = method.getModifiers();
        CtClass returnType = method.getReturnType();
        CtClass[] parameters = method.getParameterTypes();
        CtClass[] exceptions = method.getExceptionTypes();
        String methodName = method.getName();
        String delegateMethod = getDelegateMethodNameOfMethod(methodName);
        method.setName(delegateMethod);
        StringBuilder buffer = new StringBuilder(256);

        boolean hasReturnValue = (CtClass.voidType == returnType);
        buffer.append("{" + CRLF_TAB);
        {
            if (hasReturnValue) {
                String returnClass = returnType.getName();
                buffer.append(returnClass + " " + METHOD_RUTURN_VALUE_VAR + ";");
            }
            if (before != null) {
                buffer.append(before);
            }
            buffer.append(CRLF_TAB);
            buffer.append("try {" + CRLF_2TAB);
            {
                if (beforeAround != null) {
                    buffer.append(beforeAround);
                }
                buffer.append(CRLF_2TAB);
                if (hasReturnValue) {
                    buffer.append(METHOD_RUTURN_VALUE_VAR + " = ($r)" + delegateMethod + "($$);");
                } else {
                    buffer.append(delegateMethod + "($$);");
                }
                if (afterAround != null) {
                    buffer.append(CRLF_2TAB);
                    buffer.append(afterAround);
                }
                if (hasReturnValue) {
                    buffer.append(CRLF_2TAB);
                    buffer.append("return " + METHOD_RUTURN_VALUE_VAR);
                }
            }
            buffer.append(CRLF_TAB);
            buffer.append("} catch (Throwable ex) {");
            {
                buffer.append(CRLF_2TAB);
                if (afterThrowing != null) {
                    buffer.append(afterThrowing);
                }
                buffer.append(CRLF_2TAB);
                buffer.append("throw ex;");
            }
            buffer.append(CRLF_TAB);
            buffer.append("}");

            if (afterReturning != null) {
                buffer.append(CRLF_TAB);
                buffer.append("finally {");
                {
                    buffer.append(CRLF_2TAB);
                    buffer.append(afterReturning);
                }
                buffer.append(CRLF_TAB);
                buffer.append("}");
            }
        }
        buffer.append(CRLF);
        buffer.append("}");
        System.out.println(methodName + " will be modified as :\n" + buffer.toString());
        CtMethod newMethod = CtNewMethod.make(modifiers, returnType, methodName, parameters, exceptions,
                buffer.toString(), clazz);
        clazz.addMethod(newMethod);
    }

    /**
     * Inject around code to the specified constructor
     *
     * @see #injectAround(CtClass, CtConstructor, String, String, String,
     *      String)
     */
    public void injectAround(CtConstructor constructor, String beforeAround, String afterAround, String afterThrowing,
            String afterReturning) throws Exception {
        CtClass clazz = constructor.getDeclaringClass();
        injectAround(clazz, constructor, beforeAround, afterAround, afterThrowing, afterReturning);
    }

    /**
     * Inject around code to the specified constructor
     *
     * <pre>
     * <code>
     * try{
     *     <span style="font-size:12px; color:green;">beforeAround block ... </span>
     *     $procced($$);
     *     <span style="font-size:12px; color:green;">afterAround block ... </span>
     * }catch (Throwable ex){
     *     <span style="font-size:12px; color:green;">afterThrowing block ... </span>
     * }finally{
     *     <span style="font-size:12px; color:green;">afterReturning block ... </span>
     * }
     * </code>
     * </pre>
     *
     */
    public void injectAround(CtClass clazz, CtConstructor constructor, String beforeAround, String afterAround,
            String afterThrowing, String afterReturning) throws Exception {
        clazz.defrost();
        String delegateMethodName = getDelegateMethodNameOfConstructor(constructor.getName());
        CtMethod delegateMethod = constructor.toMethod(delegateMethodName, clazz);
        clazz.addMethod(delegateMethod);
        injectAround(clazz, delegateMethod, beforeAround, afterAround, afterThrowing, afterReturning);
        constructor.setBody("{" + PROCEED + "($$);", "this", delegateMethodName);
    }

    /**
     * Copy form the src method's body to a overrid method's body in target
     * class.
     *
     * @param targetClass
     *            overrid the method in the target class
     * @param srcMethod
     *            the overrid from will copy from the src method. If the target
     *            class has not owner overrid method , you should specified the
     *            srcMethod in the super class.
     * @param body
     *            the body of the override method
     * @param delegateObject
     *            the delegate object default value is "this".
     * @param delegateMethod
     * @throws Exception
     */
    public void overrideMethod(CtClass targetClass, CtMethod srcMethod, String body, String delegateObject,
            String delegateMethod) throws Exception {
        targetClass.defrost();
        System.out.println(body);
        if (delegateObject == null) {
            delegateObject = "this";
        }
        // override method in a super class of the target class
        if (srcMethod.getDeclaringClass() != targetClass) {
            CtMethod destMethod = CtNewMethod.copy(srcMethod, targetClass, null);
            if (body != null && !body.isEmpty()) {
                if (delegateMethod != null && !delegateMethod.isEmpty()) {
                    destMethod.setBody(body, delegateObject, delegateMethod);
                } else {
                    destMethod.setBody(body);
                }
            }
            targetClass.addMethod(destMethod);
        }
        // override method in the target class
        else {
            if (delegateMethod != null && !delegateMethod.isEmpty()) {
                srcMethod.setBody(body, delegateObject, delegateMethod);
            } else {
                srcMethod.setBody(body);
            }
        }
    }
}

injectInterceptor()的 实现原理:将原来的方法改名为一个delegateMethod,重新创建一个target method,方法体是织入代码,并调用delegateMethod。

injectAround(CtConstrouctor)的实现原理:先将构造体的内容提取到一个delegateMethod中,再对delegateMethod做织入,最后设置新的构建体。在新的构造体中调用delegateMethod。

Javassist 通用工具之 CodeInjector的更多相关文章

  1. 用 javassist 来修改 class 文件

    import javassist.ClassPool; import javassist.CtClass; import javassist.CtMethod; public class Test { ...

  2. SSH整合时执行hibernate查询报错&colon;java&period;lang&period;ClassCastException&colon; com&period;ch&period;hibernate&period;Department&lowbar;&dollar;&dollar;&lowbar;javassist&lowbar;0 cannot be cast to javassist&period;util&period;proxy

    今天在整合ssh三个框架时,有一个功能,是查询所有员工信息,且员工表和部门表是多对一的映射关系,代码能正常运行到查询得到一个List集合,但在页面展示的时候,就报异常了, java.lang.Clas ...

  3. Java动态编程初探——Javassist

    最近需要通过配置生成代码,减少重复编码和维护成本.用到了一些动态的特性,和大家分享下心得. 我们常用到的动态特性主要是反射,在运行时查找对象属性.方法,修改作用域,通过方法名称调用方法等.在线的应用不 ...

  4. Java javassist动态代理

    package org.windwant.spring.core.proxy; import javassist.ClassPool; import javassist.CtClass; import ...

  5. Javassist 字节码操作

    1.读写字节码 Javassist是用来处理java字节码的类库.字节码保存在二进制文件中称为类文件.每个类文件夹包括一个java类或接口. Javasssist.CtClass这个类是一个类文件的抽 ...

  6. 【hibernate 报错】No serializer found for class org&period;hibernate&period;proxy&period;pojo&period;javassist&period;JavassistLazyInitializer 【get和load的区别】

    报错: HTTP Status 500 - Could not write content: No serializer found for class org.hibernate.proxy.poj ...

  7. Android RecyclerView单击、长按事件&colon;基于OnItemTouchListener &plus;GestureDetector标准实现(二),封装抽取成通用工具类

     Android RecyclerView单击.长按事件:基于OnItemTouchListener +GestureDetector标准实现(二),封装抽取成通用工具类 我写的附录文章2,介绍了 ...

  8. javassist AOP

    对于AOP,这个概念,不用解释,主要用途很多,我这里主要是为了后续研究如何实现APM做准备.前面研究了动态代理实现AOP,考虑到性能的问题,改用javassist直接修改直接码实现! javassis ...

  9. org&period;codehaus&period;jackson&period;map&period;JsonMappingException&colon; No serializer found for class org&period;hibernate&period;proxy&period;pojo&period;javassist&period;

    2011-08-16 13:26:58,484 [http-8080-1] ERROR [core.web.ExceptionInterceptor] - org.codehaus.jackson.m ...

随机推荐

  1. 三款不错的图片压缩上传插件&lpar;webuploader&plus;localResizeIMG4&plus;LUploader&rpar;

    涉及到网页图片的交互,少不了图片的压缩上传,相关的插件有很多,相信大家都有用过,这里我就推荐三款,至于好处就仁者见仁喽: 1.名气最高的WebUploader,由Baidu FEX 团队开发,以H5为 ...

  2. 【转载】java版打字练习软件

    网上找到一个java版的打字的游戏 import java.applet.Applet; import java.applet.AudioClip; import java.awt.Dimension ...

  3. 单片机TM4C123学习(九):PWM

    1.头文件与变量定义 #include "tiva_pwm.h" // PWM 2.初始化 // PWM 初始化,频率为1000,占空比为0 M1PWM7_init(, ); // ...

  4. Java的大数操作分为BigInteger和BigDecimal

    Java的大数操作分为BigInteger和BigDecimal,但这两给类是分开使用的,有时候在编程的时候显得略微繁琐,现在编写了一个将二者合二为一的大数操作类. 大数操作类代码如下: 1 pack ...

  5. HDU 4293 Groups

    模型挺好的dp题,其实这道题就是建一个模型然后就很容易想到递推过程了,我们可以把每个人的描述,存到数组a中,a[l][r]表示左边有l个,到第r个这个人所在一层停止...然后就可以写出转移状态方程了. ...

  6. VSTO之旅系列&lpar;五&rpar;:创建Outlook解决方案

    原文:VSTO之旅系列(五):创建Outlook解决方案 本专题概要 引言 Outlook对象模型 自定义Outlook窗体 小结 一.引言 在上一个专题中,为大家简单介绍了下如何创建Word解决方案 ...

  7. incomplete type is not allowed ofstream

    错误: incomplete type is not allowed 解决方案: #include<fstream>

  8. &lbrack;BZOJ 2257&rsqb;&lbrack;JSOI2009&rsqb;瓶子和燃料 题解(GCD)

    [BZOJ 2257][JSOI2009]瓶子和燃料 Description jyy就一直想着尽快回地球,可惜他飞船的燃料不够了. 有一天他又去向火星人要燃料,这次火星人答应了,要jyy用飞船上的瓶子 ...

  9. 【转载】pycharm常用快捷键

    来源: (https://blog.csdn.net/weixin_41059146/article/details/78826163) 1.编辑(Editing) Ctrl + Space    基 ...

  10. 高性能&period;NET MVC之QMVC&excl;

    ASP.NET!这个词代表者一个单词Fat!因为他总是捆绑着太多的太多的类,太多太多的各种功能!你也许会用到,如果你反编译或阅读他们开源的源码,你会不会犹如在大海中找不到方向?不管是Web form ...