Since update 9-u175, java permits illegal access by default thus allowing all the old reflection tricks. Works fine, except when it comes to classes in control.skin (maybe others as well, didn't check) - to reproduce, run the example below, click the button and see how access succeeds until the line that tries to access a private field in ButtonSkin. The stacktrace:
Exception in thread "JavaFX Application Thread" java.lang.reflect.InaccessibleObjectException:
Unable to make field private final com.sun.javafx.scene.control.behavior.BehaviorBase javafx.scene.control.skin.ButtonSkin.behavior accessible:
module javafx.controls does not "opens javafx.scene.control.skin" to unnamed module @537fb2
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.Field.checkCanSetAccessible(Field.java:176)
at java.base/java.lang.reflect.Field.setAccessible(Field.java:170)
My context: jdk9-u175, eclipse-oxygen-R with patch for java9, access rules in the project are set to allow javafx/**
The question is: who's the culprit? FX, Eclipse, the ea or ..?
The example:
import java.lang.reflect.Field;
import java.util.logging.Logger;
import javafx.application.Application;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.SkinBase;
import javafx.scene.control.skin.ButtonSkin;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;
import com.sun.javafx.scene.control.LambdaMultiplePropertyChangeListenerHandler;
public class AccessFieldFX extends Application {
private Parent getContent() {
Button button = new Button("something to click on");
// okay
Object def = invokeGetFieldValue(Button.class, button, "defaultButton");
button.setOnAction(e -> {
ButtonSkin skin = (ButtonSkin) button.getSkin();
// okay
LambdaMultiplePropertyChangeListenerHandler cl =
(LambdaMultiplePropertyChangeListenerHandler) invokeGetFieldValue(SkinBase.class, skin, "lambdaChangeListenerHandler");
// okay
Object clField = invokeGetFieldValue(LambdaMultiplePropertyChangeListenerHandler.class, cl, "EMPTY_CONSUMER");
// failure
Object beh = invokeGetFieldValue(ButtonSkin.class, skin, "behavior");
});
BorderPane pane = new BorderPane(button);
return pane;
}
@Override
public void start(Stage primaryStage) throws Exception {
primaryStage.setScene(new Scene(getContent(), 600, 400));
// primaryStage.setTitle(FXUtils.version());
primaryStage.show();
}
public static Object invokeGetFieldValue(Class declaringClass, Object target, String name) {
try {
Field field = declaringClass.getDeclaredField(name);
field.setAccessible(true);
return field.get(target);
} catch (NoSuchFieldException | SecurityException | IllegalArgumentException | IllegalAccessException e) {
e.printStackTrace();
}
return null;
}
public static void main(String[] args) {
launch(args);
}
@SuppressWarnings("unused")
private static final Logger LOG = Logger
.getLogger(AccessFieldFX.class.getName());
}
To prevent accidental dependencies on new APIs, illegal access is only granted to packages that existed before Java 9 - I hence assume com.sun.javafx.scene.control.behavior
is new.
In his mail with the revised proposal for --illegal-access
Mark Reinhold writes (emphasis mine):
--illegal-access=permit
This mode opens each package in each module in the run-time image to code in all unnamed modules, i.e., code on the class path, if that package existed in JDK 8. This enables both static access, i.e., by compiled bytecode, and deep reflective access, via the platform's various reflection APIs.
The first reflective-access operation to any such package causes a warning to be issued, but no warnings are issued after that point. This single warning describes how to enable further warnings.
This mode will be the default for JDK 9. It will be removed in a future release.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With