简体   繁体   中英

Modifying final static variable within final public class Java

I have a quick question regarding modifying a final public class. Based on some researches, it seems like final public class cannot be inherited or implemented. My goal is to change one final static variable within this final public class.

class name is : 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");

I want to get rid of authorization field from this DISALLOWED_HEADERS_SET . Is there any ways to doing this?

I heard reflection is one way to modify classes. This is a Apress/java-9-revealed to a github that seems to reveal what's inside of the class


This thread (question) has been identified as XY problem. I will try to explain with more information on why I want a solution for the above problem. Before digging into the reason that drove me to ask this question, I will cover the current situation of where this problem is at as of now.

It is important to understand that this problem has been already posed by Clevertap to Oracle . If you follow Oracle link, you can see that this problem has been acknowledged and updated to Jdk 11 . Hopefully, Oracle applies this fixed source code to the coming Java 10 , but it is highly unlikely given the fact that Jdk 9 represents Java 9 . This being said, only solution there is to use reflection which the open thread in clevertap suggests.

Now, I will briefly explain what I have achieved and am trying to figure out. I have been working on a framework that I have been developing for sending Push Notification to APNs using Java language. Everything works except one functionality.

[ I will share this framework through GitHub in the near future for those trying to send notification to APNs without depending on third party frameworks, such as Jetty, Netty, or okhttp. ]

The problem rises when I try to use token as a way of authentication to send notification. I have successfully created token following through the instruction provided by Apple . All I have to do is to set request header with authorization key and bearer <token> value. However, when I use .setHeader derived from jdk9.incubator.httpclient module to set these values, it automatically omits this field. As aforementioned, authorization is part of DISALLOWED_HEADERS_SET and it is clearly not allowed. If a user attempts to set "authorization" as a key value for the request header field, it is deleted. If you have any suggestions to work around this problem. It will be awesome and helpful for others facing the same problem.


Bad news folks... jdk 9.0.4 removed setSystemHeader method, so if you want to use reflection , you need to use Jdk 9.0.1


As promised before, I created java library for sending notification using pure java code and pushed it to the github . I used older version that was based on jdk10 for my published app. Older version only supported tls connection. Now the current version based on jdk11 is supporting both tls and token based authentication for sending push notification to APNs.

Will just removing that value from the set work for you? Something like this:

        Field f = Utils.class.getDeclaredField("DISALLOWED_HEADERS_SET");
        f.setAccessible(true);

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

There is no clear for me what are you going achieve by inheritance since this value is static. But if you possible to recompile your application, have you considered of object composition? This can be an alternate to inheritance and is often used to simulate polymorphic behavior in case of final class or 'multiply' inheritance. If you can 'inject' the new class instead of original one (which is final), you can compose a new class that contains instance of final class and delegates its methods to them. There is no need to create same final static variable.

If you wish to bank upon reflection , this might help -

Class reqClz = request.getClass();

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

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

for which you might have to open the incubator module's package with the use of VM arg:-

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

OR

Was also thinking that, another way here possibly could be patch the module content

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

for the jdk.incubator.http.internal.common.Utils class but as recommended not more than testing or debugging purpose.

The following code allows you to change the value of private static fields (non-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);
        }
    }

}

If you change the field to final this will be prevented, so to answer your question: It's not possible to do what you currently try to do using reflection. There is a way, though by using JNI, but you don't want to go that way. Whatever you try to accomplish you should try to find a different solution, eg by checking the class that is using this header if it can be configured with an alternate set of disallowed header values or go the way @nullpointer described.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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