class文件扫描分析

一.简介

最近项目中需要分析java文件中依赖的类、Jar包中class文件扫描,通过提取字节码文件中的Constant_Class_info 常量池中的信息实现class 文件依赖分析。使用了ASM 轻量级jar包读取字节码文件,根据JVM虚拟机规范解析常量池信息。

字节码文件类型描述:
在这里插入图片描述

<dependency>
   <groupId>org.ow2.asm</groupId>
   <artifactId>asm</artifactId>
   <version>8.0</version>
</dependency>

二.实现



import org.objectweb.asm.ClassReader;
import java.io.*;
import java.util.*;
import java.util.stream.Collectors;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;

public class ClassUtil {

    /**
     * 分析class 文件中Constant_Class_info 信息
     * @param classStream
     * @return
     * @throws IOException
     */
    public static List<String> constClassInfo(InputStream classStream) throws IOException {
        ClassReader reader = new ClassReader(classStream);
        ByteArrayOutputStream bos = new ByteArrayOutputStream(1024);
        List<String> infos = new ArrayList<>();
        for(int i=1;i<reader.getItemCount();i++) {
            int off = reader.getItem(i);
            if(off > 0 && reader.readByte(off-1) == 7){
                int index = reader.readUnsignedShort(off);
                off = reader.getItem(index);
                int len = reader.readUnsignedShort(off);
                for(int j=0;j<len;j++){
                    int b = reader.readByte(off+2+j);
                    bos.write(b);
                }
                infos.add(bos.toString("utf-8"));
                bos.reset();
            }
        }
        return infos;
    }


    /**
     * 分析class 文件中Constant_Utf8_info 常量池信息项
     * @param classStream   class 字节码流
     * @throws IOException
     */
    public static List<String> constUtf8Info(InputStream classStream) throws IOException {
        ClassReader reader = new ClassReader(classStream);
        List<String> infos = new ArrayList<>(128);
        ByteArrayOutputStream bos = new ByteArrayOutputStream(1024);
        for(int i=1;i<reader.getItemCount();i++) {
            int off = reader.getItem(i);
            if(off > 0 && reader.readByte(off-1) == 1){
                int len = reader.readUnsignedShort(off);
                for(int j=0;j<len;j++){
                    int b = reader.readByte(off+2+j);
                    bos.write(b);
                }
                infos.add(bos.toString("utf-8"));
                bos.reset();
            }
        }
        return infos;
    }


    /**
     * 扫描zip格式文件
     * @param zip       zip格式文件流
     * @param buf       内部写缓冲区(会调用buf的reset方法重置写入位置,使用时需要特别留意)
     * @param exts       扫描文件的后缀名(为空则扫描全部文件)
     * @param visitor   zip文件项回调
     * @throws IOException
     */
    public static void zipScan(InputStream zip,ByteArrayOutputStream buf,List<String> exts,ZipEntryVisitor visitor) throws IOException{
        ZipInputStream zipIs = zip instanceof ZipInputStream ? (ZipInputStream)zip : new ZipInputStream(zip);
        byte[] b = new byte[4096];
        for(ZipEntry entry;(entry=zipIs.getNextEntry()) != null;){
            if(entry.isDirectory()){
                visitor.visit(entry.getName(),null);
            }else if(entry.getSize() > 0){
                if(exts != null && exts.size() > 0){
                    boolean flag = false;
                    for(String ext : exts){
                        if(entry.getName().endsWith(ext)){
                            flag = true;
                            break;
                        }
                    }
                    if(!flag){
                        continue;
                    }
                }
                buf.reset();
                for(int len;(len=zipIs.read(b)) > 0;){
                    buf.write(b,0,len);
                }
                visitor.visit(entry.getName(),new ByteArrayInputStream(buf.toByteArray()));
                zipIs.closeEntry();
            }
        }
    }


    /**
     * 扫描zip格式文件中的所有class文件
     * @param zip   zip格式文件流
     * @param ignoreZips       忽略不扫描的zip格式文件(.zip .jar .war .ear)
     * @param buf   缓冲区
     * @param visitor
     * @throws IOException
     */
    public static void zipClassScan(InputStream zip,List<String> ignoreZips,ByteArrayOutputStream buf,ZipEntryVisitor visitor) throws IOException {
        zipScan(zip, buf, Arrays.asList(".zip",".jar",".war",".ear",".class"), new ZipEntryVisitor() {
            @Override
            public void visit(String name, InputStream is) throws IOException {
                if(name.endsWith(".zip") || name.endsWith(".jar") || name.endsWith(".war") || name.endsWith(".ear")){
                    if(ignoreZips != null && ignoreZips.indexOf(name) >= 0){
                        return;
                    }
                    zipClassScan(is,ignoreZips,buf,visitor);
                }else if(name.endsWith(".class")){
                    visitor.visit(name,is);
                }
            }
        });
    }


    /**
     * 扫描path路径下的class 文件,
     * @param path      扫描的文件(可以是路径、.class、.jar、.war、.ear、.zip)
     * @param buf       缓冲区
     * @param ignoreFiles       忽略不扫描的路径
     * @param ignoreZips       忽略不扫描的zip格式文件(.zip .jar .war .ear)
     * @param visitor   扫描到.class 文件的回调
     * @throws IOException
     */
    public static void classScan(File path,List<File> ignoreFiles,List<String> ignoreZips,ByteArrayOutputStream buf,ZipEntryVisitor visitor) throws IOException {
        if(path.isFile()){
            String name = path.getName();
            if(name.endsWith(".class")){
                try(InputStream is=new BufferedInputStream(new FileInputStream(path))){
                    buf.reset();
                    for(int b;(b=is.read()) != -1;){
                        buf.write(b);
                    }
                }
                visitor.visit(name,new ByteArrayInputStream(buf.toByteArray()));
            }else if(name.endsWith(".zip") || name.endsWith(".jar") || name.endsWith(".war") || name.endsWith(".ear")){
                if(ignoreZips != null && ignoreZips.indexOf(name) >= 0){
                    return;
                }
                try(InputStream is=new BufferedInputStream(new FileInputStream(path))){
                    zipClassScan(is,ignoreZips,buf,visitor);
                }
            }
        }else{
            for(File file : path.listFiles()){
                boolean flag = false;
                if(ignoreFiles != null && ignoreFiles.size() > 0){
                    for(File f : ignoreFiles){
                        if(file.equals(f)){
                            flag = true;
                            break;
                        }
                    }
                }
                if(!flag) {
                    classScan(file, ignoreFiles,ignoreZips, buf, visitor);
                }
            }
        }
    }


   /**
     * 向zip格式文件中添加entryName项
     * @param src       需要添加的文件
     * @param buf       缓冲区
     * @param names     添加项的名字数组
     * @param datas     添加项的数据
     */
    public static void addEntry(File src,byte[] buf,String[] names,byte[][] datas) throws IOException{
        File tmpOut = new File("."+src.getName());
        try(ZipInputStream zis=new ZipInputStream(new FileInputStream(src)); ZipOutputStream zos=new ZipOutputStream(new FileOutputStream(tmpOut))){
            for(ZipEntry inEnt;(inEnt=zis.getNextEntry()) != null;zis.closeEntry()){
                if(inEnt.isDirectory()){
                    continue;
                }
                zos.putNextEntry(new ZipEntry(inEnt.getName()));
                for(int len;(len=zis.read(buf)) > 0;){
                    zos.write(buf,0,len);
                }
                zos.closeEntry();
            }
            for(int i=0;i<names.length;i++){
                zos.putNextEntry(new ZipEntry(names[i]));
                zos.write(datas[i]);
            }
        }catch (IOException e){
            tmpOut.delete();
            throw e;
        }
        Files.move(tmpOut.toPath(),src.toPath(), StandardCopyOption.REPLACE_EXISTING);
    }


    /**
     * class 文件常量池的Constant_Class_info 项的访问信息
     */
    public interface ClassInfoVisitor{
        /**
         *
         * @param name      class文件名
         * @param classes   该class文件的Constant_Class_info项的类型信息
         */
        void visit(String name,Set<String> classes);
    }


    /**
     * class 文件常量池的Utf8_info 项的访问信息
     */
    public interface Utf8InfoVisitor{
        /**
         *
         * @param info  Utf8_info常量池项的名称
         */
        void visit(String info);
    }


    /**
     * jar/ear/war 格式文件是一种zip 格式的文件
     * zip格式压缩文件项的访问
     */
    public interface ZipEntryVisitor{
        /**
         *
         * @param name      zip格式文件项的名字
         * @param is        name名字的输入流信息,如果输入项是文件夹,is为空
         */
        void visit(String name,InputStream is) throws IOException;
    }
}


  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值