java9 opens与exports的区别

codecraft

本文主要研究下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

阅读 1.6k

code-craft
spring boot , docker and so on 欢迎关注微信公众号: geek_luandun

当一个代码的工匠回首往事时,不因虚度年华而悔恨,也不因碌碌无为而羞愧,这样,当他老的时候,可以很自豪告诉世人,我曾经将代码注入生命去打造互联网的浪潮之巅,那是个很疯狂的时代,我在一波波的浪潮上留下了或重如泰山或轻如鸿毛的几笔。

11.3k 声望
1.1k 粉丝
0 条评论

当一个代码的工匠回首往事时,不因虚度年华而悔恨,也不因碌碌无为而羞愧,这样,当他老的时候,可以很自豪告诉世人,我曾经将代码注入生命去打造互联网的浪潮之巅,那是个很疯狂的时代,我在一波波的浪潮上留下了或重如泰山或轻如鸿毛的几笔。

11.3k 声望
1.1k 粉丝
宣传栏