在最终公共类Java中修改最终静态变量

5

关于修改final public类的问题,我有一个快速的疑问。根据一些研究,似乎final public类不能被继承或实现。我的目标是在这个final public类中更改一个final static变量。

类名为: public final class Utils

private static final Set<String> DISALLOWED_HEADERS_SET = Set.of(
    "authorization", "connection", "cookie", "content-length",
    "date", "expect", "from", "host", "origin", "proxy-authorization",
    "referer", "user-agent", "upgrade", "via", "warning");

我希望从这个 DISALLOWED_HEADERS_SET 中移除 authorization 字段。有没有做到这一点的方法?
我听说反射是修改类的一种方式。这里有一个指向 GitHub 的链接 Apress/java-9-revealed,它似乎揭示了类的内部情况。
这个问题已被确认为XY问题。我将尝试通过更多信息解释为什么我想要一个解决方案。在深入探讨驱使我提出这个问题的原因之前,我将介绍当前这个问题所处的情况。
重要的是要了解Clevertap已经向Oracle提出了这个问题。如果您跟随Oracle链接,您会看到这个问题已经得到确认并更新到Jdk 11。希望Oracle将这个修复源代码应用于即将发布的Java 10,但鉴于Jdk 9代表Java 9的事实,这是极不可能的。也就是说,唯一的解决方案是使用反射,clevertap中的开放线程建议使用它。
现在,我将简要解释我已经实现和正在努力解决的问题。 我一直在开发一个框架,使用Java语言发送推送通知到APNs。除了一个功能之外,一切正常运作。
[我将在不久的将来通过GitHub分享这个框架,以便那些想要发送通知到APNs而不依赖于第三方框架(如Jetty,Netty或okhttp)的人使用。]
当我尝试使用令牌作为身份验证的方式发送通知时,问题出现了。我已经成功地按照Apple提供的说明创建了令牌。我所要做的就是使用authorization键和bearer <token>值设置请求头。然而,当我使用从jdk9.incubator.httpclient模块派生的.setHeader来设置这些值时,它自动省略了这个字段。如上所述,authorizationDISALLOWED_HEADERS_SET的一部分,显然不允许使用。如果用户尝试将"authorization"设置为请求头字段的键值,它将被删除。如果您有任何解决这个问题的建议,那将对其他面临同样问题的人非常有用。

坏消息,各位...jdk 9.0.4已经移除了setSystemHeader方法,所以如果你想要使用reflection,你需要使用Jdk 9.0.1


如之前承诺的,我创建了一个基于纯Java代码发送通知的Java库,并将其上传到github。我为发布的应用程序使用了基于jdk10的旧版本。旧版本仅支持tls连接。现在,基于jdk11的当前版本支持使用令牌进行身份验证以向APNs发送推送通知。


1
你能改变源代码并重新编译吗?为什么要更改这个常量,你想做什么? - Mick Mnemonic
2
@Mick Mnemonic 感谢阅读我的问题。我无法更改源代码,因为它是Java 9的一部分。我正在尝试摆脱“授权”字段,以便我可以将标题值设置为“授权”。 - John
1
好的。如果您在研究中加入了这些信息(即“为什么”),那么问题会更好,包括您似乎已经做了相当多的工作,包括针对此问题提交的Java 9错误 - Mick Mnemonic
1
@MickMnemonic,正如您所建议的那样,我刚刚编辑了帖子,以包含更多关于这个问题的信息来源。 - John
1
@John:引用你的话:“我将在不久的将来通过GitHub分享这个框架……”,这正是我的目标——没有外部依赖。支持JDK 9.0.1的版本可以在此处找到:https://github.com/CleverTap/apns-http2/tree/java9 - judepereira
显示剩余12条评论
4个回答

2
您是否只需要从集合中删除该值?类似于这样的操作:
        Field f = Utils.class.getDeclaredField("DISALLOWED_HEADERS_SET");
        f.setAccessible(true);

        @SuppressWarnings("unchecked")
        Set<String> headers = (Set<String>)f.get(null);        
        headers.remove("authorization"); 

谢谢你的回答。哦,我不认为我应该这样做。这个类是 jdk9.incubator 源代码的一部分,它限制了头文件的值。我知道他们在 jdk 11 中修复了这个问题,但那离现在还很遥远。我只是想看看是否有解决这个问题的临时方案。 - John
那个特定的Set是不可变的,所以我怀疑调用remove方法除了抛出异常之外不会有任何作用。 - Lothar
@Lothar 哎呀,utils 不可访问。我会尝试弄清楚是否能访问这个类以测试你的代码 :) 谢谢你的回复 - John
同意。不确定是否使用Java9 API。 - tsolakp
@tsolakp 我之前也不知道,直到我在我的Java8环境中尝试调用Set.of(...)方法时才发现的。;-) - Lothar

1

我不清楚你通过继承会实现什么,因为这个值是静态的。但如果你可以重新编译你的应用程序,你考虑过对象组合吗?这可以作为继承的替代方案,并且通常用于模拟多态行为,特别是在最终类或“多重”继承的情况下。如果你可以“注入”新类而不是原始类(它是最终类),你可以组合一个包含最终类实例并将其方法委托给它们的新类。没有必要创建相同的最终静态变量。


谢谢您的建议,但我不知道如何利用对象组合来解决这个问题。我所说的最终类是jdk9源代码的一部分。如果我要操纵它,那么很多其他类都会依赖于它,我不知道如何找出所有这些依赖关系。我只能从上述私有静态最终变量中删除“授权”,或者找出其他解决此问题的方法。 - John

-1

如果你希望利用反射,这可能会有所帮助 -

Class reqClz = request.getClass();

Method setHeaderMethod = reqClz.getDeclaredMethod("setSystemHeader", String.class, String.class);
setHeaderMethod.setAccessible(true);

setHeaderMethod.invoke(request, "authorization", "<token>");

你可能需要使用VM参数打开孵化器模块的包:

--add-opens jdk.incubator.httpclient/jdk.incubator.http=<your-package x.y.z>

或者

我还在考虑,这里可能还有另一种方法,就是修补模块内容

--patch-module <module>=<file>(<pathsep><file>)*

对于 jdk.incubator.http.internal.common.Utils 类,但建议仅用于测试或调试目的。


谢谢您的回答。显然,clevertap.com是解决这个问题的一种方法。使用反射方法有什么不利之处吗? - John
@John 如果该类的内部实现发生变化(声明方法或字段为“private”是一种表明“我们在这里有权做任何事情”的方式),你的代码将停止工作。 - Lothar
@Lothar 35 谢谢您的回答!!!这样会有任何安全问题吗?根据我的研究,可能会存在一些性能问题。我只是想确保我编写的代码在任何情况下都是安全的。 - John
@John 反射调用方法直接调用方法的速度大概会慢上千倍(或更多),除非你缓存 getDeclaredMethod/getDeclaredField 返回的 Method/Field 对象。至于安全方面的问题,我无法回答你的问题,因为这需要我知道你的应用程序具体做了什么以及如何做,但我可以想出可能与此相关的一般性安全问题。 - Lothar
@Lothar 这是否意味着上面的代码将会非常慢?我只是想设置一个被DISALLOWED_HEADERS_SET阻止的“authorization”头值。我想避免使用反射,因为你提供的原因。由于示例显示我调用的方法是公共的,无论如何,我认为这比直接访问私有方法更好... - John
1
@nullpointer,你应该引用从clevertap.com复制的代码和VM arg设置的原始来源。 - Mick Mnemonic

-2
以下代码可以让您更改私有静态字段(非final)的值:
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;

public class SetFinalField {

    @SuppressWarnings("unchecked")
    public final static void main(String[] args) throws Exception {
        System.out.println("elements before: " + SetTarget.DISALLOWED_HEADERS_SET);
        Field f = SetTarget.class.getDeclaredField("DISALLOWED_HEADERS_SET");
        f.setAccessible(true);
        ArrayList<String> l = new ArrayList<String>();
        l.addAll((Set<String>) f.get(null));
        l.remove("authorization");
        HashSet<String> hs = new HashSet<>();
        hs.addAll(l);
        f.set(null, Collections.unmodifiableSet(hs));
        System.out.println("elements after: " + SetTarget.DISALLOWED_HEADERS_SET);
    }

    public final static class SetTarget {
        private static Set<String> DISALLOWED_HEADERS_SET;

        static {
            HashSet<String> l = new HashSet<>();
            Collections.addAll(l, new String[]{"authorization", "connection", "cookie", "content-length",
                    "date", "expect", "from", "host", "origin", "proxy-authorization",
                    "referer", "user-agent", "upgrade", "via", "warning"});
            DISALLOWED_HEADERS_SET = Collections.unmodifiableSet(l);
        }
    }

}

如果你将字段更改为final,这将被阻止,因此回答你的问题:使用反射无法实现你当前尝试做的事情。虽然有一种方法可以使用JNI,但是你不想走那条路。无论你想要完成什么任务,都应该尝试找到一个不同的解决方案,例如通过检查使用此标题的类是否可以使用替代的一组不允许的标题值进行配置,或者按照 @nullpointer 描述的方式进行处理。

1
谢谢你的出色回答!如果可以的话,我会将最终类更改为非最终类,但该类是jdk9.incubator的一部分...我认为反射是目前唯一可行的方法,就像@nullpointer所描述的那样。 - John

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接