All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.ideaaedi.commonds.codebytes.JavassistUtil Maven / Gradle / Ivy

The newest version!
package com.ideaaedi.commonds.codebytes;

import com.ideaaedi.commonds.constants.StrConstant;
import com.ideaaedi.commonds.io.IOUtil;
import javassist.CannotCompileException;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;
import javassist.NotFoundException;
import javassist.bytecode.BadBytecode;
import javassist.bytecode.Bytecode;
import javassist.bytecode.CodeAttribute;
import javassist.bytecode.CodeIterator;
import javassist.bytecode.ExceptionTable;
import javassist.compiler.CompileError;
import javassist.compiler.Javac;
import lombok.extern.slf4j.Slf4j;

import java.io.File;
import java.io.IOException;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * 字节码操作工具类
 *
 * @author JustryDeng 
 * @since 1.0.0
 */
@Slf4j
public final class JavassistUtil {
    
    /** 临时目录名 */
    public static String TMP_DIR_SUFFIX = "__temp__";
    
    /**
     * 清空类中的方法体,并简单处理一下main,使提示密码无效
     *
     * @param classPool
     *            javassist的classPool
     * @param className
     *            要修改的class类的全类名
     * @param keepOriginArgsName
     *            是否保留原方法的参数名
     * @param tips
     *            被清空了方法体的方法内部的提示信息
     *
     * @return  处理后的类的字节
     * @throws NotFoundException 当清空的类中涉及到了某些其他的类,但是根本就没有引入(这些其他的类)相应的依赖时,就会抛出此异常(即:这个类本身就报错来着)
     *                           注:在编写项目A时,如果引入的依赖B的scope范围是provided时,那么当另一个项目X,引入A的依赖时,B是不会被依赖传递到X的,
     *                               就会出现上面的情况。
     *                           注:在某些其它情况下,也会抛出此异常。
     * @throws  CannotCompileException 统一封装异常
     */
    public static byte[] clearMethodBody(ClassPool classPool, String className, boolean keepOriginArgsName, String tips)
            throws CannotCompileException, NotFoundException {
        CtClass ctClass;
        try {
            ctClass = classPool.getCtClass(className);
            if (ctClass.isFrozen()) {
                log.debug("defrost class " + className);
                ctClass.defrost();
            }
            CtMethod[] methods = ctClass.getDeclaredMethods();
            for (CtMethod ctMethod : methods) {
                // 当前类中的方法 && 非构造方法
                if (ctMethod.getLongName().startsWith(className) && !ctMethod.getName().contains("<")) {
                    if (keepOriginArgsName) {
                        CodeAttribute codeAttribute = ctMethod.getMethodInfo().getCodeAttribute();
                        // 如果是接口,那么codeAttribute就是null; 方法体本来就是空的,codeAttribute.getCode()[0]就是-79
                        if (codeAttribute != null && codeAttribute.getCodeLength() != 1 && codeAttribute.getCode()[0] != 79) {
                            clearMethodBodyKeepOriginArgName(ctMethod);
                        }
                    } else {
                        ctMethod.setBody(null);
                    }
                    /// 顺带处理一下main方法:提示jar包已经被保护,请使用javaagent启动项目
                    /// if (isMain(ctMethod)) {
                    ///     ctMethod.insertBefore(
                    ///             "System.out.println(\"\\n " + tips +" \\n\");"
                    ///                     + "\nSystem.exit(-1);"
                    ///     );
                    /// }
                    try {
                        ctMethod.insertBefore(
                                "System.out.println(\"\\n " + tips +" \\n\");"
                                + "\nSystem.exit(-1);"
                        );
                    } catch (CannotCompileException e) {
                        if ("no method body".equals(e.getMessage())) {
                            log.debug("[" + ctMethod.getLongName() + "] no method body. ignore to add tips info.");
                            // ignore
                        } else {
                            throw e;
                        }
                    }
                }
            }
            return ctClass.toBytecode();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
    
    /**
     * 加载paths指定的.jar文件(或加载对应子孙目录下的所有jar包)
     *
     * @param classPool
     *            javassist的classPool
     * @param paths
     *            要加载的文件夹或jar包
     */
    public static void loadJar(ClassPool classPool, String... paths) throws NotFoundException {
        if (paths == null) {
            return;
        }
        for (String path : paths) {
            loadJar(classPool, new File(path));
        }
    }
    
    /**
     * 加载指定的.class文件(或加载对应子孙目录下的所有.class文件)
     *
     * @param classPool
     *            javassist的classPool
     * @param paths
     *            要加载的文件夹或.class文件
     */
    public static void loadClass(ClassPool classPool, String... paths) throws NotFoundException {
        if (paths == null) {
            return;
        }
        for (String path : paths) {
            loadClasses(classPool, new File(path));
        }
    }
    
    /**
     * 判断ctMethod是否代表main方法
     */
    private static boolean isMain(CtMethod ctMethod) throws NotFoundException {
        // 返回值校验
        boolean returnValueValid = "void".equalsIgnoreCase(ctMethod.getReturnType().getName());
        // 方法名、参数类型校验
        boolean nameArgTypeValid = ctMethod.getLongName().endsWith(".main(java.lang.String[])");
        // 访问修饰符校验
        boolean accessFlag = ctMethod.getMethodInfo().getAccessFlags() == 9;
        return returnValueValid && nameArgTypeValid && accessFlag;
    }
    
    
    /**
     * 清空方法体,并且保留原参数名
     *
     * @param ctMethod
     *            javassist的方法对象
     * @throws CannotCompileException 编译异常
     * @throws NotFoundException 确实类异常
     */
    private static void clearMethodBodyKeepOriginArgName(CtMethod ctMethod) throws CannotCompileException, NotFoundException {
        CtClass ctClass = ctMethod.getDeclaringClass();
        if (ctClass.isFrozen()) {
            throw new RuntimeException(ctClass.getName() + " class is frozen.");
        }
        CodeAttribute codeAttribute = ctMethod.getMethodInfo().getCodeAttribute();
        if (codeAttribute == null) {
            throw new RuntimeException("no method body.");
        } else {
            CodeIterator iterator = codeAttribute.iterator();
            Javac jv = new Javac(ctClass);
            try {
                Bytecode bytecode = jv.compileBody(ctMethod, null);
                int maxStack = bytecode.getMaxStack();
                if (maxStack > codeAttribute.getMaxStack()) {
                    codeAttribute.setMaxStack(maxStack);
                }
                int maxLocals = bytecode.getMaxLocals();
                if (maxLocals > codeAttribute.getMaxLocals()) {
                    codeAttribute.setMaxLocals(maxLocals);
                }
                iterator.insertEx(bytecode.get());
                /*
                 * 移除ExceptionTable中的所有项.
                 *
                 * 注: 因为都把方法体置空了,里面的所有异常(try-catch)处理都没了,那么方法体当然需要要置空。
                 * 注: 一个方法的exception table中的entry数量等于这个方法内部catch了多少次异常,
                 *      如: ...catch (AbcException e)... ,那么entry个数为1
                 *      如: ...catch (AbcException|XyzException e)... ,那么entry个数为2
                 *      如: ...catch (AbcException e)... ,   ...catch (QwerException e)..那么entry个数为2
                 * 

* exception table 表示异常表,异常表是用于存储代码中涉及到的所有异常,每个类编译后,都会跟随一个异常表,如果发生异常,首先在异常表中查找对应的行(即代码中相应的 try{}catch(){}代码块),如果找到,则跳转到异常处理代码执行,如果没有找到,则返回(执行 finally 之后),并 copy 异常的应用给父调用者,接着查询父调用的异常表,以此类推。 */ ExceptionTable exceptionTable = codeAttribute.getExceptionTable(); if (exceptionTable != null) { int size = exceptionTable.size(); for (int i = size - 1; i >= 0; i--) { exceptionTable.remove(i); } } ctMethod.getMethodInfo().rebuildStackMapIf6(ctClass.getClassPool(), ctClass.getClassFile2()); } catch (CompileError compileError) { throw new CannotCompileException(compileError); } catch (BadBytecode badBytecode) { throw new CannotCompileException(badBytecode); } } } /** * 加载指定的jar包(或加载对应子孙目录下的所有jar包) * * @param pool * javassist的ClassPool * @param dirOrFile * 要加载的文件夹或.jar文件 */ private static void loadJar(ClassPool pool, File dirOrFile) throws NotFoundException { if (dirOrFile == null || !dirOrFile.exists()) { return; } List jars = IOUtil.listFileOnly(dirOrFile, StrConstant.JAR_SUFFIX); for (File jar : jars) { pool.insertClassPath(jar.getAbsolutePath()); } } /** * 加载指定的.class文件(或加载对应子孙目录下的所有.class文件) * * @param pool * javassist的ClassPool * @param dirOrFile * 要加载的文件夹或.class文件 */ private static void loadClasses(ClassPool pool, File dirOrFile) throws NotFoundException { if (dirOrFile == null || !dirOrFile.exists()) { return; } /* * 获取.class全类名对应的目录的根目录 * 假设class文件全路径为/tmp/classes/com/niantou/iwork/core/Abc.class * 那么这里获取到的就是/tmp/classes */ Set classesRootDirSet = IOUtil.listFileOnly(dirOrFile, StrConstant.CLASS_SUFFIX) .stream().map(x -> resolveClassName(x.getAbsolutePath(), false)) .collect(Collectors.toSet()); for (String rootDir : classesRootDirSet) { /* * pathname – the path name of the directory or jar file. It must not end with a path separator ("/"). * If the path name ends with "/*", then all the jar files matching the path name are inserted */ pool.insertClassPath(rootDir); } } /** * 根据class的绝对路径解析出class全类名(或class全类名文件所在的目录路径) *

* 假设文件的全路径名是这样的/tmp/class-winter-core/src/main/java/com/niantou/iwork/core/Abc.class, * 那么, 解析出来的class全类名即为com.niantou.iwork.core.Abc * 解析出来的class全类名文件所在的目录路径即为/tmp/class-winter-core/src/main/java * *

* * @param fileName * class文件的绝对路径 * @param classOrPath * true-解析全类名;false-解析全类名文件所在的目录路径 * @return class所代表类的全类名(如com.aaa.bbb.Abc) 或者路径(如: /tmp/class-winter-core/src/main/java) */ public static String resolveClassName(String fileName, boolean classOrPath) { // 去除后缀名 String nonSuffixFileName = fileName.substring(0, fileName.length() - StrConstant.CLASS_SUFFIX.length()); String classesFlag = File.separator + "classes" + File.separator; String libFlag = File.separator + "lib" + File.separator; String classPath; String className; int libFlagIndex = nonSuffixFileName.indexOf(libFlag, nonSuffixFileName.indexOf(TMP_DIR_SUFFIX)); int classesFlagIndex = nonSuffixFileName.indexOf(classesFlag, nonSuffixFileName.indexOf(TMP_DIR_SUFFIX)); // lib内的jar包 if (libFlagIndex >= 0) { /* * 如果是对jar包(如my-project-1.0.0.jar)中的某些lib(如cglib-3.1.jar)还需要进行加密的话,那么根据本项目class-winter的解压逻辑, * 解压后的目录就会存在形如下面这样的解压临时路径 * /tmp/abc/my-project-1.0.0__temp__/BOOT-INF/lib/cglib-3.1__temp__/com/aaa/bbb/Abc.class; * 所以要从lib开始找__temp__, * 然后再跳过__temp__本身的长度 */ className = nonSuffixFileName.substring(nonSuffixFileName.indexOf(TMP_DIR_SUFFIX, libFlagIndex) + TMP_DIR_SUFFIX.length() + 1); } // jar/war包xxx-INF/classes下的class文件 else if (classesFlagIndex >= 0) { /* * nonSuffixFileName为/tmp/abc/my-project-1.0.0__temp__/BOOT-INF/classes/com/aspire/ssm/Aaaaaaaaa * 获取到的className为 */ className = nonSuffixFileName.substring(classesFlagIndex + classesFlag.length()); } // jar包下的class文件 else { className = nonSuffixFileName.substring(nonSuffixFileName.indexOf(TMP_DIR_SUFFIX) + TMP_DIR_SUFFIX.length() + 1); } classPath = nonSuffixFileName.substring(0, nonSuffixFileName.length() - className.length() - 1); return classOrPath ? className.replace(File.separator, ".") : classPath; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy