3 Minuten Lesezeit (671 Worte)

Hier kommst du nicht dran! InaccessibleObjectException mit Java 9

Java 9 bringt eine neue Exception Klasse mit. Ist das Grund zur Freude? Ich finde ja! Mit diesem Blogpost möchte ich euch erklären, wie wir mit der neu dazugekommenen Ausnahme InaccessibleObjectException umgehen.

Zu allererst ein wenig Theorie. Wie man der offiziellen API entnehmen kann, gibt es die InaccessibleObjectException Klasse erst seit Java 9. Grund der Einführung ist das neue Modulsystem "Jigsaw". Die spannende Frage ist nun: Warum gibt es diese Ausnahme und wann kann diese auftreten? Das erkläre ich am besten mit einem kurzen Beispiel.

Folgende Klasse befindet sich im Projekt "ServiceProject". Diese besitzt zwei Methoden. Eine öffentlich erreichbare Methode und eine von außen nicht sichtbare private  Methode.

package de.ordix.service;

/**
 * Service.java is part of ServiceProject.
 */
public class Service {

	public void doSomethingPublic() {
		System.out.println("Public Method!");
	}

	private void doSomethingPrivate() {
		System.out.println("Private Method!");
	}

}
 

Da es sich um ein auf Java 9 aufbauendes Projekt handelt, nutzen wir natürlich auch die Möglichkeit unsere Software mittels Modulen zu strukturieren. Die Modul-Definition sieht folgendermaßen aus:

/**
 * module-info.java is part of ServiceProject.
 */
module serviceModul {
	exports de.ordix.service;
} 

Kein Service ohne Konsument. Das zweite Java Projekt trägt den Namen ConsumerProject. Dieses Projekt besitzt ebenfalls eine Modul-Definition mittels einer modul-info.java. Hier ist angegeben, dass das serviceModul benötigt wird. Wir möchten die Funktionalität des bereitgestellten Services im Konsumenten aufrufen.

/**
 * module-info.java is part of ConsumerProject.
 */
module consumerModul {
	requires serviceModul;
} 

In der main Methode wird ein Service Objekt erzeugt und die doSomethingPublic() Methode aufgerufen. Das ist natürlich kein Problem, da es sich hier um eine public Methode handelt. Weiter soll auch die doSomethingPrivate() Methode ausgeführt werden. Da diese von außen nicht sichtbar ist, müssen wir Reflection einsetzen, um diese Methode aufrufbar zu machen und sie dann ausführen zu können. Das Exception-Handling, welches aufgrund des Einsatzes von Reflection-Funktionalität notwendig ist, habe ich aus Gründen der Übersichtlichkeit weggelassen.

package de.ordix.consumer;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

import de.ordix.service.Service;

/**
 * Consumer.java is part of ConsumerProject.
 */
public class Consumer {

	public static void main(String[] args) throws ... /* some Exceptions because of reflection */,
			IllegalArgumentException, InvocationTargetException {
		Service service = new Service();
		service.doSomethingPublic();

		Method m = service.getClass().getDeclaredMethod("doSomethingPrivate", null);
		m.setAccessible(true);
		m.invoke(service);

	}

}
 

Ab jetzt wird es spannend, wenn wir versuchen die main Methode auszuführen. Ein Problem mit der doSomethingPublic() gibt es erwartungsgemäß natürlich nicht. Aber die doSomethingPrivate() Methode per Reflection aufzurufen, führt zu einem Fehler. Und diese Ausnahme wird durch die InaccessibleObjectException signalisiert. Hier der Inhalt der Konsole inklusive der Exception:

Public Method!
Exception in thread "main" java.lang.reflect.InaccessibleObjectException: Unable to make private void de.ordix.service.Service.doSomethingPrivate() accessible: module serviceModul does not "opens de.ordix.service" to module consumerModul
	at java.base/java.lang.reflect.AccessibleObject.checkCanSetAccessible(AccessibleObject.java:337)
	at java.base/java.lang.reflect.AccessibleObject.checkCanSetAccessible(AccessibleObject.java:281)
	at java.base/java.lang.reflect.Method.checkCanSetAccessible(Method.java:198)
	at java.base/java.lang.reflect.Method.setAccessible(Method.java:192)
	at consumerModul/de.ordix.consumer.Consumer.main(Consumer.java:19)
 

Nun stellt sich allerdings die Frage, warum es zu dieser Exception kommt. Grund dafür ist das Standard-Verhalten des Modul-Systems in Java 9. Wird ein Modul per requires eingebunden, bedeutet es nicht automatisch, dass per Reflection nicht zugreifbare Typen erreichbar gemacht werden können. Um das zu können, braucht es das neue Schlüsselwort opens. Dieses muss von dem Service Entwickler in der Modul-Definition gesetzt werden.Vor Java 9 konnte man als Entwickler eines Projektes nicht verhindern, dass per Reflection nicht zugreifbare Typen und Methoden sichtbar und somit ausführbar gemacht wurden.

Hier die angepasste module-info.java des Service Projektes inklusive der opens Angabe.

/**
 * module-info.java is part of ServiceProject.
 */
module serviceModul {
	exports de.ordix.service;
	opens de.ordix.service;
} 

Nachdem das erledigt ist, kann die main Methode erneut ausgeführt werden. Und siehe da! Die private doSomethingPrivate() Methode kann nun wieder per Reflection ausgeführt werden. Hier die Konsolenausgabe:

Public Method!
Private Method!
 

Und nun könnt ihr meine eingangs erwähnte Euphorie bezüglich der Exception besser nachvollziehen. Denn das Modul-System alleine ist bereits ein Feature, welches ich nicht mehr missen möchte. Dass es aber gleichzeitig noch möglich ist, Reflection aktiv zu verhindern, ist eine sinnvolle und dringend notwendige Neuerung. Zugriffe auf z. B. interne Java-APIs können somit unterbunden werden, was einen erheblichen Sicherheitsgewinn darstellt.

Einziger Wermutstropfen ist aktuell noch, dass man die Verhinderung des Einsatzes von Reflection an internen APIs umgehen kann. Oracle erreicht dadurch, dass viele Projekte noch weiterhin funktionieren. Dadurch wird eine gewisse Abwärtskompatibilität gewährleistet. Allerdings wird es diesen Kill Switch nicht für immer geben.

Senior Consultant bei ORDIX

 

Kommentare

Derzeit gibt es keine Kommentare. Schreibe den ersten Kommentar!
Freitag, 19. April 2024

Sicherheitscode (Captcha)

×
Informiert bleiben!

Bei Updates im Blog, informieren wir per E-Mail.

Weitere Artikel in der Kategorie