简体   繁体   English

如何在Java中热交换java.lang类

[英]How to hot swap java.lang Class in Java

I know it's not a good demand. 我知道这不是一个很好的需求。 But I need to do that now. 但是我现在需要这样做。

I need to replace the method of Class.getResourceAsStream(String) 我需要替换Class.getResourceAsStream(String)的方法

But ClassLoader.preDefineClass(String , ProtectionDomain) checks whether the class name of the full name starts with java. 但是ClassLoader.preDefineClass(String , ProtectionDomain)检查全名的类名是否以java.开头java. , and if so, throws an exception and is not allowed to load. ,如果是,则抛出异常,并且不允许加载。

Is there a way to bypass runtime security checks instead of compile time ? 有没有一种方法可以绕过运行时安全性检查,而不是编译时

Update 更新资料

The real requirement is that many older projects need to migrate to new environments, because some of these projects may be out of date and some of them may not be able to find the source code. 真正的要求是,许多较旧的项目需要迁移到新环境,因为其中一些项目可能已过时,而其中一些可能无法找到源代码。 I am required to simulate the old environment in a new environment so that these ancient projects can continue to function. 我需要在新环境中模拟旧环境,以便这些古老的项目可以继续运行。

Upadte (GhostCat) Upadte(GhostCat)

I tried the way which you given. 我尝试了你给的方式。

ClassLoader Class ClassLoader类

public class ClassLoaderTest extends ClassLoader {
    @Override
    public InputStream getResourceAsStream(String name) {
        System.out.println("getResourceAsStream " + name);
        return new ByteArrayInputStream(new byte[]{1, 3, 5, 7, 9});
    }
    @Override
    public Class<?> loadClass(String name) throws ClassNotFoundException {
        System.out.println("Load class " + name);
        String fileName = name.substring(name.lastIndexOf(".") + 1) + ".class";
        InputStream is = getClass().getResourceAsStream(fileName);

        if (is == null) {
            return super.loadClass(name);
        }

        byte[] b = new byte[0];
        try {
            b = new byte[is.available()];
            is.read(b);
        } catch (IOException e) {
            e.printStackTrace();
        }

        return defineClass(name, b, 0, b.length);
    }
}

Invoke Class 调用类

public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException, InstantiationException {
    ClassLoaderTest classLoaderTest = new ClassLoaderTest();
    Class clazz = Class.forName("com.Client", true, classLoaderTest);
    Object client = clazz.newInstance();
    Method method = clazz.getDeclaredMethod("method");
    method.invoke(client);
}

Client Class 客户类别

public class Client {
    public void method() {
        System.out.println(this.getClass().getResourceAsStream("aaa"));
        System.out.println(Class.class.getResourceAsStream("bbb"));
        System.out.println("".getClass().getResourceAsStream("ccc"));
    }
}

It really works with this.getClass().getResourceAsStream(String) , but Class.class.getResourceAsStream(String) / "".getClass().getResourceAsStream(String) . 它确实与this.getClass().getResourceAsStream(String) ,但与Class.class.getResourceAsStream(String) / "".getClass().getResourceAsStream(String) Because I cannot load class using my custom ClassLoader which name start with java. 因为我无法使用我的自定义ClassLoader加载类,该类名称以java.开头java.

You can't. 你不能

This class gets loaded by the bootstrap class loader. 该类由引导类加载器加载。 And you can't change what this guy is doing - it is written in native code. 而且您无法更改此人在做什么-它是用本机代码编写的。 In other words: this is at the core of the JVM - and you are not allowed to tamper with that. 换句话说:这是JVM的核心-不允许您篡改。

That is the "whole idea" of that the java security concept: the user can't "get" into things that are guaranteed by the JVM platform. 这就是Java安全性概念的“整体思路”:用户无法“进入” JVM平台所保证的事物。

Given the edit by the OP that this is about legacy code: it seems that you want to hook into Class.getResourceAsStream(String) for classes that are not part of the JVM - but that belong to your own code. 鉴于OP所做的编辑是关于遗留代码的:似乎您希望针对属于JVM的类-属于您自己的代码的类加入Class.getResourceAsStream(String)

If that is the case, then the "answer" is simply: you have to make sure that all your classes are loaded by your class loader. 如果是这样的话,那么“答案”很简单:你必须确保所有类都是由你的类加载器加载。 The javadoc for getResourceAsStreeam() clearly states: getResourceAsStreeam()的Javadoc明确指出:

Finds a resource with a given name. 查找具有给定名称的资源。 The rules for searching resources associated with a given class are implemented by the defining class loader of the class. 搜索与给定类关联的资源的规则由该类的定义类加载器实现。 This method delegates to this object's class loader. 该方法委托给该对象的类加载器。

Thus: learn how to use a custom classloader. 因此:了解如何使用自定义类加载器。 Start here for example. 例如从这里开始。

Throwing an exception for attempts to define classes with a qualified name starting with java. 为尝试以java.开头的合格名称定义类而引发异常java. , is part of the contract of defineClass , but that's irrelevant. ,是defineClass合同的defineClass ,但这无关紧要。 Any attempt to define a class with the name of an existing class on a ClassLoader can only have one of two possible results: 任何使用ClassLoader上现有类的名称定义类的尝试都只能产生以下两种结果之一:

  1. If the existing class has been defined by the same ClassLoader , attempting to define it again will be rejected and an exception will be thrown. 如果现有的类已由相同的ClassLoader定义,则再次定义它的尝试将被拒绝,并将引发异常。 defineClass does not change existing classes, that's simply not within its functionality. defineClass不会更改现有的类,这根本不在其功能之内。

  2. If the existing class has been loaded by a different ClassLoader (or the bootstrap loader), you may define a class with the same qualified name on this ClassLoader , but it will be an entirely different class, unrelated to the class with the same name but a different defining class loader. 如果现有类已由其他ClassLoader (或引导加载程序)加载,则可以在此ClassLoader上定义具有相同限定名称的ClassLoader ,但这将是一个完全不同的类,与具有相同名称的类无关,但一个不同的定义类加载器。

The fact that you can not define such classes with the same name and a different loader for java.* classes is just an additional limitation, but your attempt wouldn't work anyway. 您不能为java.*类使用相同的名称和不同的加载器来定义此类,这只是一个附加限制,但是您的尝试无论如何还是行不通的。

If you want to replace the class ClassLoader after it has been loaded, you can only use an Agent, eg a Java Agent using the Instrumentation API , or a debugger. 如果要在类ClassLoader后替换它,则只能使用代理,例如使用Instrumentation API的Java代理或调试器。 Alternatively, you can simply add your own version to the bootstrap class path using the -Xbootclasspath/p:… command line option. 另外,您可以使用-Xbootclasspath/p:…命令行选项将您自己的版本简单地添加到引导类路径。

Of course replacing it with a specific version only works in the environment, from which this version has been derived. 当然,用特定版本替换它只能在派生该版本的环境中起作用。 Also such applications are not allowed to be publicly deployed. 同样,不允许将此类应用程序公开部署。

A more general solution would be an Instrumentation Agent using the current version of ClassLoader and modify it on-the-fly, however, if you have to option of developing a byte code transforming Agent, it would be much easier and more reliable to transform the application code instead, just replacing all invocations of Class.getResourceAsStream(String) with an invocation of a custom method, without touching code not needing such adaptation. 一个更通用的解决方案是使用当前版本的ClassLoader并即时对其进行修改的Instrumentation Agent,但是,如果您必须选择开发字节码转换Agent,则转换该Code将会更加容易和可靠。 应用程序代码 ,只需用自定义方法的调用替换Class.getResourceAsStream(String)所有调用,而无需触摸不需要这种修改的代码。

I wrote a longish answer on how to implement runtime class transforms here: how to retransform a class at runtime 我写了一篇关于如何在此处实现运行时类转换的冗长答案: 如何在运行时重新转换类

An easier way would be my retransformer project at https://github.com/nickman/retransformer which will transform java.* classes, including native methods. 一种更简单的方法是位于https://github.com/nickman/retransformer的 retransformer项目,该项目将转换java。*类,包括本机方法。 (I used it to modify Runtime.availableProcessors ) (我用它来修改Runtime.availableProcessors)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM