JDK中,String.class文件被替换问题

JDK包下的String.class替换为我自己写的,被加载时会校验失败,请问是什么地方校验的?

请看这个回答:https://stackoverflow.com/a/23473601

 

> Hotspot had hardcoded assumptions about the field offsets of the various fields in java.lang.String. This means if you add fields to the String class which cause the class layouting logic to move the existing fields around you will break the JVM. 

 

具体bug说明:https://bugs.openjdk.java.net/browse/JDK-6924259

具体的校验代码见:http://hg.openjdk.java.net/lambda/lambda/hotspot/rev/8f972594effc

 

具体的代码片段:

http://hg.openjdk.java.net/lambda/lambda/hotspot/file/8f972594effc/src/share/vm/classfile/javaClasses.cpp

从第3109行的注释开始看:

// These functions exist to assert the validity of hard-coded field offsets to guard
// against changes in the class files

校验方法见3112~3135行:即:

 

check_offset(const char *klass_name, int hardcoded_offset, const char *field_name, const char* field_sig)

这个方法

 

String的校验见3210~3219行:

 // java.lang.String

  CHECK_OFFSET("java/lang/String", java_lang_String, value, "[C");
  if (java_lang_String::has_offset_field()) {
    CHECK_OFFSET("java/lang/String", java_lang_String, offset, "I");
    CHECK_OFFSET("java/lang/String", java_lang_String, count, "I");
  }
  if (java_lang_String::has_hash_field()) {
    CHECK_OFFSET("java/lang/String", java_lang_String, hash, "I");
  }

 

你是怎么替换的?要替换你自己的要加上包名前缀。

我的操作步骤如下:

1、就是把原来的class文件反编译,增加了属性和方法,包名还是:package java.lang;

2、再讲自己写的反编译,

3、用解压缩软件打开rt.jar(不是解压),将自己的反编译文件拖进去。

您好,我是有问必答小助手,您的问题已经有小伙伴解答了,您看下是否解决,可以追评进行沟通哦~

如果有您比较满意的答案 / 帮您提供解决思路的答案,可以点击【采纳】按钮,给回答的小伙伴一些鼓励哦~~

ps:问答VIP仅需29元,即可享受5次/月 有问必答服务,了解详情>>>https://vip.csdn.net/askvip?utm_source=1146287632

jdk自带的类加载器会自动去识别自身java.开头的类。如果你自定义类加载器去加载你自己定义的类,也是会抛异常的,jvm机制就是这样的

首先,你这思路就不合适,你要改String类里面的东西,就不能反编译代码再修改,你为什么不用扩展函数,可以在你的应用里面给String自定义方法

 

如果仅仅替换某个字符,使用replaceall("xx","")即可,其中.的替换要使用\\.否则会替换除换行符外的所有字符。

如果要替换所有特殊字符(即非字母数字),使用正则表达式replaceAll("[^a-zA-Z0-9]+","");即可

你可以把java源码下下来,修改在编译打包。不然你的研究JVM代码

class文件是被ClassLoader(类加载器)校验的  

preview

这个是JVM的双亲委派机制吧,JDK里面的一些核心的类,比如String 在设计的时候就是不可被更改的,是从程序的安全性考虑的,他会通过特定的类加载器进行加载,进行校验,你可以百度下JVM的双亲委派机制。

本文的目的:

使用者在程序运行期间,可以动态的写Java Class,不需要生成任何.Class文件就可以完全在内存中编译,加载,实例化。

 

1、需要用到的组件介绍

1)JavaCompiler:用于编译Java Code。

2)CharSequenceJavaFileObject:用于保存Java Code,提供方法给JavaCompiler获取String形式的Java Code。

3)ClassFileManager:用于JavaCompiler将编译好后的Class文件保存在指定对象中。

4)JavaClassObject:ClassFileManager告诉JavaCompiler需要将Class文件保存在JavaClassObject中,但是由JavaClassObject来决定最终以byte流来保存数据。

5)DynamicClassLoader:自定义类加载器,用于加载最后的二进制Class

 

2、源码展现:

CharSequenceJavaFileObject.java


package com.ths.platform.framework.dynamic;

import javax.tools.SimpleJavaFileObject;
import java.net.URI;

/**
 * 用于将java源码保存在content属性中
 */
public class CharSequenceJavaFileObject extends SimpleJavaFileObject {

    /**
     * 保存java code
     */
    private String content;


    /**
     * 调用父类构造器,并设置content
     * @param className
     * @param content
     */
    public CharSequenceJavaFileObject(String className, String content){
        super(URI.create("string:///" + className.replace('.', '/')
                + Kind.SOURCE.extension), Kind.SOURCE);
        this.content = content;
    }

    /**
     * 实现getCharContent,使得JavaCompiler可以从content获取java源码
     * @param ignoreEncodingErrors
     * @return
     */
    @Override
    public String getCharContent(boolean ignoreEncodingErrors) {
        return content;
    }
}

 

ClassFileManager.java

 

 
package com.ths.platform.framework.dynamic;

import java.io.IOException;

import javax.tools.*;

/**
 * 类文件管理器
 * 用于JavaCompiler将编译好后的class,保存到jclassObject中
 */
public class ClassFileManager extends ForwardingJavaFileManager {

    /**
     * 保存编译后Class文件的对象
     */
    private JavaClassObject jclassObject;

    /**
     * 调用父类构造器
     * @param standardManager
     */
    public ClassFileManager(StandardJavaFileManager standardManager) {
        super(standardManager);
    }

    /**
     * 将JavaFileObject对象的引用交给JavaCompiler,让它将编译好后的Class文件装载进来
     * @param location
     * @param className
     * @param kind
     * @param sibling
     * @return
     * @throws IOException
     */
    @Override
    public JavaFileObject getJavaFileForOutput(Location location, String className, JavaFileObject.Kind kind, FileObject sibling)
            throws IOException {
        if (jclassObject == null)
            jclassObject = new JavaClassObject(className, kind);
        return jclassObject;
    }

    public JavaClassObject getJavaClassObject() {
        return jclassObject;
    }
}

JavaClassObject.java

 

package com.ths.platform.framework.dynamic;

import javax.tools.SimpleJavaFileObject;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.URI;
/**
 * 将输出流交给JavaCompiler,最后JavaCompiler将编译后的class文件写入输出流中
 */
public class JavaClassObject extends SimpleJavaFileObject {

    /**
     * 定义一个输出流,用于装载JavaCompiler编译后的Class文件
     */
    protected final ByteArrayOutputStream bos = new ByteArrayOutputStream();

    /**
     * 调用父类构造器
     * @param name
     * @param kind
     */
    public JavaClassObject(String name, Kind kind) {
        super(URI.create("string:///" + name.replace('.', '/') + kind.extension), kind);
    }

    /**
     * 获取输出流为byte[]数组
     * @return
     */
    public byte[] getBytes() {
        return bos.toByteArray();
    }

    /**
     * 重写openOutputStream,将我们的输出流交给JavaCompiler,让它将编译好的Class装载进来
     * @return
     * @throws IOException
     */
    @Override
    public OutputStream openOutputStream() throws IOException {
        return bos;
    }

    /**
     * 重写finalize方法,在对象被回收时关闭输出流
     * @throws Throwable
     */
    @Override
    protected void finalize() throws Throwable {
        super.finalize();
        bos.close();
    }
}

 

 

DynamicEngine.java(职责:使用JavaCompiler编译Class,并且使用DynamicClassLoader加载Class)

 

package com.ths.platform.framework.dynamic;
 
import java.io.File;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.List;

import javax.tools.Diagnostic;
import javax.tools.DiagnosticCollector;
import javax.tools.JavaCompiler;
import javax.tools.JavaFileObject;
import javax.tools.ToolProvider;
 

/**
 * 在Java SE6中最好的方法是使用StandardJavaFileManager类。
 * 这个类可以很好地控制输入、输出,并且可以通过DiagnosticListener得到诊断信息,
 * 而DiagnosticCollector类就是listener的实现。
 * 使用StandardJavaFileManager需要两步。
 * 首先建立一个DiagnosticCollector实例以及通过JavaCompiler的getStandardFileManager()方法得到一个StandardFileManager对象。
 * 最后通过CompilationTask中的call方法编译源程序。
 */
public class DynamicEngine {
    //单例
    private static DynamicEngine ourInstance = new DynamicEngine();
 
    public static DynamicEngine getInstance() {
        return ourInstance;
    }
    private URLClassLoader parentClassLoader;
    private String classpath;
    private DynamicEngine() {
        //获取类加载器
        this.parentClassLoader = (URLClassLoader) this.getClass().getClassLoader();
        
        //创建classpath
        this.buildClassPath();
    }
    
   
    /**
     * @MethodName    : 创建classpath
     */
    private void buildClassPath() {
        this.classpath = null;
        StringBuilder sb = new StringBuilder();
        for (URL url : this.parentClassLoader.getURLs()) {
            String p = url.getFile();
            sb.append(p).append(File.pathSeparator);
        }
        this.classpath = sb.toString();
    }
    
    /**
     * @MethodName    : 编译java代码到Object
     * @Description    : TODO
     * @param fullClassName   类名
     * @param javaCode  类代码
     * @return Object
     * @throws Exception
     */
    public Object javaCodeToObject(String fullClassName, String javaCode) throws Exception {
        long start = System.currentTimeMillis(); //记录开始编译时间
        Object instance = null;
        //获取系统编译器
        JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); 
        // 建立DiagnosticCollector对象
        DiagnosticCollector<JavaFileObject> diagnostics = new DiagnosticCollector<JavaFileObject>();
        
         // 建立用于保存被编译文件名的对象
         // 每个文件被保存在一个从JavaFileObject继承的类中
        ClassFileManager fileManager = new ClassFileManager(compiler.getStandardFileManager(diagnostics, null, null));
 
        List<JavaFileObject> jfiles = new ArrayList<JavaFileObject>();
        jfiles.add(new CharSequenceJavaFileObject(fullClassName, javaCode));
 
        //使用编译选项可以改变默认编译行为。编译选项是一个元素为String类型的Iterable集合
        List<String> options = new ArrayList<String>();
        options.add("-encoding");
        options.add("UTF-8");
        options.add("-classpath");
        options.add(this.classpath);
 
        JavaCompiler.CompilationTask task = compiler.getTask(null, fileManager, diagnostics, options, null, jfiles);
        // 编译源程序
        boolean success = task.call();
 
        if (success) {
            //如果编译成功,用类加载器加载该类
            JavaClassObject jco = fileManager.getJavaClassObject();
            DynamicClassLoader dynamicClassLoader = new DynamicClassLoader(this.parentClassLoader);
            Class clazz = dynamicClassLoader.loadClass(fullClassName,jco);
            instance = clazz.newInstance();
        } else {
            //如果想得到具体的编译错误,可以对Diagnostics进行扫描
            String error = "";
            for (Diagnostic diagnostic : diagnostics.getDiagnostics()) {
                error += compilePrint(diagnostic);
            }
        }
        long end = System.currentTimeMillis();
        System.out.println("javaCodeToObject use:"+(end-start)+"ms");
        return instance;
    }
 
    /**
     * @MethodName    : compilePrint
     * @Description    : 输出编译错误信息
     * @param diagnostic
     * @return
     */
    private String compilePrint(Diagnostic diagnostic) {
        System.out.println("Code:" + diagnostic.getCode());
        System.out.println("Kind:" + diagnostic.getKind());
        System.out.println("Position:" + diagnostic.getPosition());
        System.out.println("Start Position:" + diagnostic.getStartPosition());
        System.out.println("End Position:" + diagnostic.getEndPosition());
        System.out.println("Source:" + diagnostic.getSource());
        System.out.println("Message:" + diagnostic.getMessage(null));
        System.out.println("LineNumber:" + diagnostic.getLineNumber());
        System.out.println("ColumnNumber:" + diagnostic.getColumnNumber());
        StringBuffer res = new StringBuffer();
        res.append("Code:[" + diagnostic.getCode() + "]\n");
        res.append("Kind:[" + diagnostic.getKind() + "]\n");
        res.append("Position:[" + diagnostic.getPosition() + "]\n");
        res.append("Start Position:[" + diagnostic.getStartPosition() + "]\n");
        res.append("End Position:[" + diagnostic.getEndPosition() + "]\n");
        res.append("Source:[" + diagnostic.getSource() + "]\n");
        res.append("Message:[" + diagnostic.getMessage(null) + "]\n");
        res.append("LineNumber:[" + diagnostic.getLineNumber() + "]\n");
        res.append("ColumnNumber:[" + diagnostic.getColumnNumber() + "]\n");
        return res.toString();
    }
}

 

 

DynamicClassLoader.java

 

package com.ths.platform.framework.dynamic;

import java.net.URLClassLoader;
import java.net.URL;
 
/**
 * 自定义类加载器
 */
public class DynamicClassLoader extends URLClassLoader {
    public DynamicClassLoader(ClassLoader parent) {
        super(new URL[0], parent);
    }
 
    public Class findClassByClassName(String className) throws ClassNotFoundException {
        return this.findClass(className);
    }
 
    public Class loadClass(String fullName, JavaClassObject jco) {
        byte[] classData = jco.getBytes();
        return this.defineClass(fullName, classData, 0, classData.length);
    }
}

 

 

DynaCompTest.java(测试类,从myclass文件中读出源码并在内存中编译)

 

package com.ths.platform.framework.dynamic;

import sun.misc.IOUtils;

import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;

public class DynaCompTest
{
    public static void main(String[] args) throws Exception {
        String fullName = "com.seeyon.proxy.MyClass";
        File file = new File("/Users/yangyu/Downloads/myclass");
        InputStream in = new FileInputStream(file);
        byte[] bytes = IOUtils.readFully(in, -1, false);
        String src = new String(bytes);
        in.close();
 
        System.out.println(src);
        DynamicEngine de = DynamicEngine.getInstance();
        Object instance =  de.javaCodeToObject(fullName,src.toString());
        System.out.println(instance);
    }
}

 

/Users/yangyu/Downloads/myclass文件(这里使用文件,实际也可以在程序中直接拼凑String)

 

 
package com.seeyon.proxy;

public class MyClass {

    public String say(String str){
        return "hello"+str;
    }
}

 

非常感谢您使用有问必答服务,为了后续更快速的帮您解决问题,现诚邀您参与有问必答体验反馈。您的建议将会运用到我们的产品优化中,希望能得到您的支持与协助!

速戳参与调研>>>https://t.csdnimg.cn/Kf0y

问题已解决,感谢感谢