When you call getClass()
on an object, you get its actual runtime type which doesn’t have to match the compile-time type, as it may be a subclass of it. Therefore, traversing the class hierarchy via getSuperclass()
is fragile.
When you know the class declaring the field beforehand, you can just use that class, e.g.
HttpURLConnection conn = (HttpURLConnection)
new URL("https://stackoverflow.com").openConnection();
try {
Field f = URLConnection.class.getDeclaredField("requests");
f.setAccessible(true);
System.out.println(f.get(conn));
}
catch(ReflectiveOperationException ex) {
ex.printStackTrace();
}
This, however, will always print null
, as this specific connection implementation doesn’t use its superclass state, but delegates to a different implementation. As you’ve found out yourself, it’s stored in a field called delegate
.
HttpURLConnection conn = (HttpURLConnection)
new URL("https://stackoverflow.com").openConnection();
for(Class<?> c = conn.getClass(); c != URLConnection.class; c = c.getSuperclass())
System.out.print(c.getName() + " > ");
System.out.println(URLConnection.class.getName());
Field delegate = conn.getClass().getDeclaredField("delegate");
delegate.setAccessible(true);
conn = (HttpURLConnection)delegate.get(conn);
for(Class<?> c = conn.getClass(); c != URLConnection.class; c = c.getSuperclass())
System.out.print(c.getName() + " > ");
System.out.println(URLConnection.class.getName());
Field requests = URLConnection.class.getDeclaredField("requests");
requests.setAccessible(true);
System.out.println(requests.get(conn));
This prints
sun.net.www.protocol.https.HttpsURLConnectionImpl > javax.net.ssl.HttpsURLConnection > java.net.HttpURLConnection > java.net.URLConnection
sun.net.www.protocol.https.DelegateHttpsURLConnection > sun.net.www.protocol.https.AbstractDelegateHttpsURLConnection > sun.net.www.protocol.http.HttpURLConnection > java.net.HttpURLConnection > java.net.URLConnection
null
Showing that these two objects have a different class hierarchy and thus, traversing it assuming a particular depth is indeed fragile.
It also prints again null
, because even this implementation class doesn’t use the requests
field inherited from URLConnection
. Instead, the class sun.net.www.protocol.http.HttpURLConnection
declares its own field, with the same name, which contains the actual MessageHeader
object. That’s why performing getSuperclass().getSuperclass()
on the implementation class of the delegate leads to the correct field, which is not URLConnection
’s.
When we know this beforehand, we can use
HttpURLConnection conn =
(HttpURLConnection) new URL("https://stackoverflow.com").openConnection();
conn.setRequestProperty("Authorization", "Basic Zm9vYmFyOnNlY3JldA==");
Field delegate = Class.forName("sun.net.www.protocol.https.HttpsURLConnectionImpl")
.getDeclaredField("delegate");
Field requests = Class.forName("sun.net.www.protocol.http.HttpURLConnection")
.getDeclaredField("requests");
AccessibleObject.setAccessible(new Field[] { delegate, requests}, true);
sun.net.www.MessageHeader headers =
(sun.net.www.MessageHeader)requests.get(delegate.get(conn));
return headers.findValue("Authorization");
Since you’re accessing the sun.net. …
package(s) anyway, you could refer to these classes directly instead of using Class.forName
, but I considered the latter more robust in this specific case, as all these classes have easy to confuse names and even sometimes the same simple name, just differing by their package.
private String getAuthorizationHeaderValue(HttpURLConnection conn) {
try {
Field delegate = sun.net.www.protocol.https.HttpsURLConnectionImpl.class.getDeclaredField("delegate");
Field requests = sun.net.www.protocol.http.HttpURLConnection.class.getDeclaredField("requests");
AccessibleObject.setAccessible(new Field[] { delegate, requests}, true);
sun.net.www.MessageHeader headers = (sun.net.www.MessageHeader)requests.get(delegate.get(conn));
return headers.findValue("Authorization");
} catch(ReflectiveOperationException ex) {
ex.printStackTrace();
return "";
}
}