java9 opens與exports的區別

go4it發表於2018-02-27

本文主要研究下java9 opens與exports的區別

open及exports

open

  • open module

主要用於解決deep reflection問題,open的作用是表示該模組下的所有的包在runtime都允許deep reflection(包括public及private型別) 但是編譯時期,僅僅允許該module中宣告過exports的包可以訪問,如果沒有exports則該包的類在編譯時期不可讀

  • opens package

用於宣告該模組的指定包在runtime允許使用反射訪問

exports

表示允許在編譯時和執行時訪問指定包的public成員

open及exports對反射的影響

反射方法

  • 目標類
package com.packt.lib.sub1;
public class Sub1Service {
    public Sub1Service() {
        System.out.println("Sub1Service being instanced");
    }

    public void publicMethod() {
        System.out.println("public method called!");
    }

    protected void protectedMethod(){
        System.out.println("protected method called...");
    }


    private void privateMethod(){
        System.out.println("private method called...");
    }
}
複製程式碼
  • 訪問類名反射
        Sub1Service sub1Service = new Sub1Service();
        Method privateMethod = sub1Service.getClass().getDeclaredMethod("privateMethod");
        privateMethod.setAccessible(true);
        privateMethod.invoke(sub1Service);
複製程式碼
  • 通過包名反射
        Optional<Module> optional = ModuleLayer.boot().findModule("packt.lib");
        Class clz = Class.forName(optional.get(),"com.packt.lib.sub1.Sub1Service");
        Object sub1 = clz.newInstance();
        System.out.println(sub1.getClass().getMethods());
        Method publicMethod = sub1.getClass().getDeclaredMethod("publicMethod");
        publicMethod.invoke(sub1);

        Method protectedMethod = sub1.getClass().getDeclaredMethod("protectedMethod");
        protectedMethod.setAccessible(true);
        protectedMethod.invoke(sub1);

        Method privateMethod = sub1.getClass().getDeclaredMethod("privateMethod");
        privateMethod.setAccessible(true);
        privateMethod.invoke(sub1);
複製程式碼

沒有exports,也沒有opens

  • module-info.java
module packt.lib {
    exports com.packt.lib;
}
複製程式碼

這裡沒有exports及opens com.packt.lib.sub1

  • 通過類名反射(編譯報錯)
Exception in thread "main" java.lang.IllegalAccessException: class com.packt.App (in module packt.main) cannot access class com.packt.lib.sub1.Sub1Service (in module packt.lib) because module packt.lib does not export com.packt.lib.sub1 to module packt.main
	at java.base/jdk.internal.reflect.Reflection.newIllegalAccessException(Reflection.java:361)
	at java.base/jdk.internal.reflect.Reflection.ensureMemberAccess(Reflection.java:107)
	at java.base/java.lang.Class.newInstance(Class.java:553)
	at packt.main/com.packt.App.main(App.java:25)
複製程式碼
  • 通過包名反射(newInstance執行時報錯)
Exception in thread "main" java.lang.IllegalAccessException: class com.packt.App (in module packt.main) cannot access class com.packt.lib.sub1.Sub1Service (in module packt.lib) because module packt.lib does not export com.packt.lib.sub1 to module packt.main
	at java.base/jdk.internal.reflect.Reflection.newIllegalAccessException(Reflection.java:361)
	at java.base/jdk.internal.reflect.Reflection.ensureMemberAccess(Reflection.java:107)
	at java.base/java.lang.Class.newInstance(Class.java:553)
	at packt.main/com.packt.App.main(App.java:26)
複製程式碼

沒有exports,有opens

  • module-info.java
module packt.lib {
    exports com.packt.lib;
    opens com.packt.lib.sub1;
}
複製程式碼
  • 通過類名反射

由於沒有exports,則編譯不過

[ERROR] Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.6.2:compile (default-compile) on project main: Compilation failure
[ERROR] /Users/demo/java9-multi-module-demo/main/src/main/java/com/packt/App.java:[30,41] 程式包 com.packt.lib.sub1 不可見
[ERROR] (程式包 com.packt.lib.sub1 已在模組 packt.lib 中宣告, 但該模組未匯出它)
複製程式碼
  • 通過包名反射

像上面那種直接引用包名來反射的,不會報錯,因為編譯可以通過,執行正常

有exports,沒有opens

module packt.lib {
    exports com.packt.lib;
    exports com.packt.lib.sub1;
}
複製程式碼
  • 通過類名反射

可以編譯通過,執行報錯

Sub1Service being instanced
Exception in thread "main" java.lang.reflect.InaccessibleObjectException: Unable to make private void com.packt.lib.sub1.Sub1Service.privateMethod() accessible: module packt.lib does not "opens com.packt.lib.sub1" to module packt.main
	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 packt.main/com.packt.App.main(App.java:44)
[ERROR] Command execution failed.
複製程式碼
  • 通過包名反射

可以編譯通過,執行報錯

Sub1Service being instanced
[Ljava.lang.reflect.Method;@4157f54e
public method called!
Exception in thread "main" java.lang.reflect.InaccessibleObjectException: Unable to make protected void com.packt.lib.sub1.Sub1Service.protectedMethod() accessible: module packt.lib does not "opens com.packt.lib.sub1" to module packt.main
	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 packt.main/com.packt.App.main(App.java:34)
[ERROR] Command execution failed.
複製程式碼

直接open整個module,但是沒有exports

  • module-info.java
open module packt.lib {
    exports com.packt.lib;
}
複製程式碼
  • 直接訪問類反射

這種情況,如果是直接訪問該類來使用反射,由於沒有exports該package,則直接編譯報錯

  • 通過包名反射

這種情況編譯可以通過,執行正常

直接open整個module,也有exports

open module packt.lib {
    exports com.packt.lib;
    exports com.packt.lib.sub1;
}
複製程式碼

兩種訪問方式的反射均正常編譯及執行。

小結

  • open表示允許執行時通過反射使用

open的作用是表示該模組下的所有的包在runtime都允許deep reflection(包括public及private型別);opens package的作用只是允許該包在runtime都允許deep reflection

open及opens都僅僅是開放runtime時期的可以通過反射訪問(蘊含了執行時的exports)。

  • exports表示允許訪問指定包的public成員(編譯及執行時)

如果反射不直接通過類名呼叫,只是執行時通過包名使用,則只需open或opens即可 如果是通過類名來反射,由於用到了該類,需要通過exports指定可以訪問,不指定則編譯期立即報錯 如果是通過類名來反射使用public方法或newInstance,如果沒有exports,則執行時報錯 如果有exports,但是沒有open,因此編譯通過執行時報錯

  • illegal-access

--illegal-access預設是permit,表示允許unnamed modules反射(java.lang.reflect/java.lang.invoke)使用所有named modules中的類

doc

相關文章