Javassist 通用工具之 CodeInjector

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:[email protected]">[email protected]</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。

时间: 2024-10-31 20:02:42

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

JAVAssist字节码操作

Java动态性的两种常见实现方式 字节码操作 反射 运行时操作字节码可以让我们实现如下功能: 动态生成新的类 动态改变某个类的结构(添加/删除/修改  新的属性/方法) 优势: 比反射开销小,性能高 JAVAasist性能高于反射,低于ASM 常见的字节码操作类库 BCEL 这是Apache Software Fundation的jakarta项目的一部分.BCEL是javaclassworking广泛使用的一种跨级啊,它可以让你深入JVM汇编语言进行类的操作的细节.BCEL与javassist

Javassist字节码增强示例

概述 Javassist是一款字节码编辑工具,可以直接编辑和生成Java生成的字节码,以达到对.class文件进行动态修改的效果.熟练使用这套工具,可以让Java编程更接近与动态语言编程. 下面一个方法的目的是获取一个类加载器(ClassLoader),以加载指定的.jar或.class文件,在之后的代码中会使用到. [java] view plaincopy private static ClassLoader getLocaleClassLoader() throws Exception {

struts启动报错Javassist library is missing

很久不用struts2,最近在配置的时候,启动服务器报错 Caused by: java.lang.ExceptionInInitializerError at com.opensymphony.xwork2.ognl.OgnlValueStackFactory.setContainer(OgnlValueStackFactory.java:84) ... 64 more Caused by: java.lang.IllegalArgumentException: Javassist libra

Java 编程的动态性,第 6 部分: 利用 Javassist 进行面向方面的更改--转载

本系列的 第 4 部分和 第 5 部分讨论了如何用 Javassist 对二进制类进行局部更改.这次您将学习以一种更强大的方式使用该框架,从而充分利用 Javassist 对在字节码中查找所有特定方法或者字段的支持.对于 Javassist 功能而言,这个功能至少与它以类似源代码的方式指定字节码的能力同样重要.对选择替换操作的支持也有助于使 Javasssist 成为一个在标准 Java 代码中增加面向方面的编程功能的绝好工具. 第 5 部分介绍了 Javassist 是如何让您拦截类加载过程的

Java 编程的动态性, 第4部分: 用 Javassist 进行类转换--转载

讲过了 Java 类格式和利用反射进行的运行时访问后,本系列到了进入更高级主题的时候了.本月我将开始本系列的第二部分,在这里 Java 类信息只不过是由应用程序操纵的另一种形式的数据结构而已.我将这个主题的整个内容称为 classworking. 我将以 Javassist 字节码操作库作为对 classworking 的讨论的开始.Javassist 不仅是一个处理字节码的库,而且更因为它的另一项功能使得它成为试验 classworking 的很好的起点.这一项功能就是:可以用 Javassi

Unity3d通用工具类之定时触发器

时隔多日,好不容易挤出点时间来写写博文.不容易,请送我几朵红花,点个赞也行. 今天呢,我们主要来扩展下通用工具类==>定时触发器. 顾名思义,所谓的定时触发器,就是告诉程序在过多长时间后,我要执行某个特定的任务. 比如举个小栗子: 电饭煲,相信大家都用过,当我们出去工作或者上学的时候,我们只要设置下煮饭时间,就可以安心的离开. 电饭煲会自动的开始计时工作,等到了你设置的时间后,他就会自动的开始煮饭啊什么的.而你却可以在远在千里的上班. 智能化,对就是这样的效果.我们今天就来写写这个智能的小东西.

Caused by: java.lang.ClassNotFoundException: javassist.util.proxy.MethodFilter

Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'hibernateBaseDao': Injection of autowired dependencies failed; nested exception is org.springframework.beans.factory.BeanCreationException: Could not a

javassist学习

最近学习了javassist的使用方法. javassist是一个字节码类库,可以用他来动态生成类,动态修改类等等. 下面是如果用javassist来动态创建一个类的demol 我们需要创建的目标类,如下: Java代码   public class JavassistClass{ private String name="default"; public JavassistClass(){ name="me"; } public String getName() 

Caused by: java.lang.ClassNotFoundException: javassist.ClassPool

1.错误原因 usage: java org.apache.catalina.startup.Catalina [ -config {pathname} ] [ -nonaming ] { -help | start | stop } 2014-7-10 23:07:25 org.apache.catalina.core.AprLifecycleListener init 信息: Loaded APR based Apache Tomcat Native library 1.1.29 using