Java11 中基于嵌套关系的访问控制优化问题

目录
  • Java11 之前的实现方式
  • 技术债务
  • Java11 中的实现
  • Nestmate 新增的 API
    • getNestHost
    • getNestMembers
    • isNestmateOf
  • 后续的改进

你好,我是看山。

Java 语言很强大,但是,有人的地方就有江湖,有猿的地方就有 bug,Java 的核心代码并非十全十美。比如在 JDK 中居然也有反模式接口常量 中介绍的反模式实现,以及本文说到的这个技术债务:嵌套关系(NestMate)调用方式。

在 Java 语言中,类和接口可以相互嵌套,这种组合之间可以不受限制的彼此访问,包括访问彼此的构造函数、字段、方法等。即使是private私有的,也可以彼此访问。比如下面这样定义:

public class Outer {
    private int i;
    public void print1() {
        print11();
        print12();
    }
    private void print11() {
        System.out.println(i);
    }
    private void print12() {
        System.out.println(i);
    }
    public void callInnerMethod() {
        final Inner inner = new Inner();
        inner.print4();
        inner.print5();
        System.out.println(inner.j);
    }
    public class Inner {
        private int j;
        public void print3() {
            System.out.println(i);
            print1();
        }
        public void print4() {
            System.out.println(i);
            print11();
            print12();
        }
        private void print5() {
            System.out.println(i);
            print11();
            print12();
        }
    }
}

上例中,Outer类中的字段i、方法print11print12都是私有的,但是可以在Inner类中直接访问,Inner类的字段j、方法print5是私有的,也可以在Outer类中使用。这种设计是为了更好的封装,在用户看来,这几个彼此嵌套的类/接口是一体的,分开定义是为了更好的封装自己,隔离不同特性,但是有因为彼此是一体,所以私有元素也应该是共有的。

Java11 之前的实现方式

我们使用 Java8 编译,然后借助javap -c命令分别查看OuterInner的结果。

$ javap -c Outer.class
Compiled from "Outer.java"
public class cn.howardliu.tutorials.java8.nest.Outer {
  public cn.howardliu.tutorials.java8.nest.Outer();
    Code:
       0: aload_0
       1: invokespecial #4                  // Method java/lang/Object."<init>":()V
       4: return
  public void print1();
    Code:
       0: aload_0
       1: invokespecial #2                  // Method print11:()V
       4: aload_0
       5: invokespecial #1                  // Method print12:()V
       8: return
  public void callInnerMethod();
    Code:
       0: new           #7                  // class cn/howardliu/tutorials/java8/nest/Outer$Inner
       3: dup
       4: aload_0
       5: invokespecial #8                  // Method cn/howardliu/tutorials/java8/nest/Outer$Inner."<init>":(Lcn/howardliu/tutorials/java8/nest/Outer;)V
       8: astore_1
       9: aload_1
      10: invokevirtual #9                  // Method cn/howardliu/tutorials/java8/nest/Outer$Inner.print4:()V
      13: aload_1
      14: invokestatic  #10                 // Method cn/howardliu/tutorials/java8/nest/Outer$Inner.access$000:(Lcn/howardliu/tutorials/java8/nest/Outer$Inner;)V
      17: getstatic     #5                  // Field java/lang/System.out:Ljava/io/PrintStream;
      20: aload_1
      21: invokestatic  #11                 // Method cn/howardliu/tutorials/java8/nest/Outer$Inner.access$100:(Lcn/howardliu/tutorials/java8/nest/Outer$Inner;)I
      24: invokevirtual #6                  // Method java/io/PrintStream.println:(I)V
      27: return
  static int access$200(cn.howardliu.tutorials.java8.nest.Outer);
    Code:
       0: aload_0
       1: getfield      #3                  // Field i:I
       4: ireturn
  static void access$300(cn.howardliu.tutorials.java8.nest.Outer);
    Code:
       0: aload_0
       1: invokespecial #2                  // Method print11:()V
       4: return
  static void access$400(cn.howardliu.tutorials.java8.nest.Outer);
    Code:
       0: aload_0
       1: invokespecial #1                  // Method print12:()V
       4: return
}

再来看看Inner的编译结果,这里需要注意的是,内部类会使用特殊的命名方式定义Inner类,最终会将编译结果存储在两个文件中:

$ javap -c Outer\$Inner.class
Compiled from "Outer.java"
public class cn.howardliu.tutorials.java8.nest.Outer$Inner {
  final cn.howardliu.tutorials.java8.nest.Outer this$0;
  public cn.howardliu.tutorials.java8.nest.Outer$Inner(cn.howardliu.tutorials.java8.nest.Outer);
    Code:
       0: aload_0
       1: aload_1
       2: putfield      #3                  // Field this$0:Lcn/howardliu/tutorials/java8/nest/Outer;
       5: aload_0
       6: invokespecial #4                  // Method java/lang/Object."<init>":()V
       9: return
  public void print3();
    Code:
       0: getstatic     #5                  // Field java/lang/System.out:Ljava/io/PrintStream;
       3: aload_0
       4: getfield      #3                  // Field this$0:Lcn/howardliu/tutorials/java8/nest/Outer;
       7: invokestatic  #6                  // Method cn/howardliu/tutorials/java8/nest/Outer.access$200:(Lcn/howardliu/tutorials/java8/nest/Outer;)I
      10: invokevirtual #7                  // Method java/io/PrintStream.println:(I)V
      13: aload_0
      14: getfield      #3                  // Field this$0:Lcn/howardliu/tutorials/java8/nest/Outer;
      17: invokevirtual #8                  // Method cn/howardliu/tutorials/java8/nest/Outer.print1:()V
      20: return
  public void print4();
    Code:
       0: getstatic     #5                  // Field java/lang/System.out:Ljava/io/PrintStream;
       3: aload_0
       4: getfield      #3                  // Field this$0:Lcn/howardliu/tutorials/java8/nest/Outer;
       7: invokestatic  #6                  // Method cn/howardliu/tutorials/java8/nest/Outer.access$200:(Lcn/howardliu/tutorials/java8/nest/Outer;)I
      10: invokevirtual #7                  // Method java/io/PrintStream.println:(I)V
      13: aload_0
      14: getfield      #3                  // Field this$0:Lcn/howardliu/tutorials/java8/nest/Outer;
      17: invokestatic  #9                  // Method cn/howardliu/tutorials/java8/nest/Outer.access$300:(Lcn/howardliu/tutorials/java8/nest/Outer;)V
      20: aload_0
      21: getfield      #3                  // Field this$0:Lcn/howardliu/tutorials/java8/nest/Outer;
      24: invokestatic  #10                 // Method cn/howardliu/tutorials/java8/nest/Outer.access$400:(Lcn/howardliu/tutorials/java8/nest/Outer;)V
      27: return
  static void access$000(cn.howardliu.tutorials.java8.nest.Outer$Inner);
    Code:
       0: aload_0
       1: invokespecial #2                  // Method print5:()V
       4: return
  static int access$100(cn.howardliu.tutorials.java8.nest.Outer$Inner);
    Code:
       0: aload_0
       1: getfield      #1                  // Field j:I
       4: ireturn
}

我们可以看到,OuterInner中多出了几个方法,方法名格式是access$*00

Outer中的access$200方法返回了属性iaccess$300access$400分别调用了print11print12方法。这些新增的方法都是静态方法,作用域是默认作用域,即包内可用。这些方法最终被Inner类中的print3print4调用,相当于间接调用Outer中的私有属性或方法。

我们称这些生成的方法为“桥”方法(Bridge Method),是一种实现嵌套关系内部互相访问的方式。

在编译的时候,Java 为了保持类的单一特性,会将嵌套类编译到多个 class 文件中,同时为了保证嵌套类能够彼此访问,自动创建了调用私有方法的“桥”方法,这样,在保持原有定义不变的情况下,又实现了嵌套语法。

技术债务

“桥”方法的实现是比较巧妙的,但是这会造成源码与编译结果访问控制权限不一致,比如,我们可以在Inner中调用Outer中的私有方法,按照道理来说,我们可以在Inner中通过反射调用Outer的方法,但实际上不行,会抛出IllegalAccessException异常。我们验证一下:

public class Outer {
    // 省略其他方法
    public void callInnerReflectionMethod()
            throws InvocationTargetException, NoSuchMethodException, IllegalAccessException {
        final Inner inner = new Inner();
        inner.callOuterPrivateMethod(this);
    }

    public class Inner {
        // 省略其他方法
        public void callOuterPrivateMethod(Outer outer)
                throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
            final Method method = outer.getClass().getDeclaredMethod("print12");
            method.invoke(outer);
        }
    }
}

定义测试用例:

@Test
void gotAnExceptionInJava8() {
    final Outer outer = new Outer();

    final Exception e = assertThrows(IllegalAccessException.class, outer::callInnerReflectionMethod);
    e.printStackTrace();

    assertDoesNotThrow(outer::callInnerMethod);
}

打印的异常信息是:

java.lang.IllegalAccessException: class cn.howardliu.tutorials.java8.nest.Outer$Inner cannot access a member of class cn.howardliu.tutorials.java8.nest.Outer with modifiers "private"
    at java.base/jdk.internal.reflect.Reflection.newIllegalAccessException(Reflection.java:361)
    at java.base/java.lang.reflect.AccessibleObject.checkAccess(AccessibleObject.java:591)
    at java.base/java.lang.reflect.Method.invoke(Method.java:558)
    at cn.howardliu.tutorials.java8.nest.Outer$Inner.callOuterPrivateMethod(Outer.java:62)
    at cn.howardliu.tutorials.java8.nest.Outer.callInnerReflectionMethod(Outer.java:36)

通过反射直接调用私有方法会失败,但是可以直接的或者通过反射访问这些“桥”方法,这样就比较奇怪了。所以提出 JEP181 改进,修复这个技术债务的同时,为后续的改进铺路。

Java11 中的实现

我们再来看看 Java11 编译之后的结果:

$ javap -c Outer.class
Compiled from "Outer.java"
public class cn.howardliu.tutorials.java11.nest.Outer {
  public cn.howardliu.tutorials.java11.nest.Outer();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public void print1();
    Code:
       0: aload_0
       1: invokevirtual #2                  // Method print11:()V
       4: aload_0
       5: invokevirtual #3                  // Method print12:()V
       8: return

  public void callInnerMethod();
    Code:
       0: new           #7                  // class cn/howardliu/tutorials/java11/nest/Outer$Inner
       3: dup
       4: aload_0
       5: invokespecial #8                  // Method cn/howardliu/tutorials/java11/nest/Outer$Inner."<init>":(Lcn/howardliu/tutorials/java11/nest/Outer;)V
       8: astore_1
       9: aload_1
      10: invokevirtual #9                  // Method cn/howardliu/tutorials/java11/nest/Outer$Inner.print4:()V
      13: aload_1
      14: invokevirtual #10                 // Method cn/howardliu/tutorials/java11/nest/Outer$Inner.print5:()V
      17: getstatic     #4                  // Field java/lang/System.out:Ljava/io/PrintStream;
      20: aload_1
      21: getfield      #11                 // Field cn/howardliu/tutorials/java11/nest/Outer$Inner.j:I
      24: invokevirtual #6                  // Method java/io/PrintStream.println:(I)V
      27: return
}

是不是很干净,与Outer类的源码结构是一致的。我们再看看Inner有没有什么变化:

$ javap -c Outer\$Inner.class
Compiled from "Outer.java"
public class cn.howardliu.tutorials.java11.nest.Outer$Inner {
  final cn.howardliu.tutorials.java11.nest.Outer this$0;

  public cn.howardliu.tutorials.java11.nest.Outer$Inner(cn.howardliu.tutorials.java11.nest.Outer);
    Code:
       0: aload_0
       1: aload_1
       2: putfield      #1                  // Field this$0:Lcn/howardliu/tutorials/java11/nest/Outer;
       5: aload_0
       6: invokespecial #2                  // Method java/lang/Object."<init>":()V
       9: return

  public void print3();
    Code:
       0: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;
       3: aload_0
       4: getfield      #1                  // Field this$0:Lcn/howardliu/tutorials/java11/nest/Outer;
       7: getfield      #4                  // Field cn/howardliu/tutorials/java11/nest/Outer.i:I
      10: invokevirtual #5                  // Method java/io/PrintStream.println:(I)V
      13: aload_0
      14: getfield      #1                  // Field this$0:Lcn/howardliu/tutorials/java11/nest/Outer;
      17: invokevirtual #6                  // Method cn/howardliu/tutorials/java11/nest/Outer.print1:()V
      20: return

  public void print4();
    Code:
       0: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;
       3: aload_0
       4: getfield      #1                  // Field this$0:Lcn/howardliu/tutorials/java11/nest/Outer;
       7: getfield      #4                  // Field cn/howardliu/tutorials/java11/nest/Outer.i:I
      10: invokevirtual #5                  // Method java/io/PrintStream.println:(I)V
      13: aload_0
      14: getfield      #1                  // Field this$0:Lcn/howardliu/tutorials/java11/nest/Outer;
      17: invokevirtual #7                  // Method cn/howardliu/tutorials/java11/nest/Outer.print11:()V
      20: aload_0
      21: getfield      #1                  // Field this$0:Lcn/howardliu/tutorials/java11/nest/Outer;
      24: invokevirtual #8                  // Method cn/howardliu/tutorials/java11/nest/Outer.print12:()V
      27: return
}

同样干净。

我们在通过测试用例验证一下反射调用:

@Test
void doesNotGotAnExceptionInJava11() {
    final Outer outer = new Outer();

    assertDoesNotThrow(outer::callInnerReflectionMethod);
    assertDoesNotThrow(outer::callInnerMethod);
}

结果是正常运行。

这就是 JEP181 期望的结果,源码和编译结果一致,访问控制一致。

Nestmate 新增的 API

在 Java11 中还新增了几个 API,用于嵌套关系的验证:

getNestHost

这个方法是返回嵌套主机(NestHost),转成普通话就是找到嵌套类的外层类。对于非嵌套类,直接返回自身(其实也算是返回外层类)。

我们看下用法:

@Test
void checkNestHostName() {
    final String outerNestHostName = Outer.class.getNestHost().getName();
    assertEquals("cn.howardliu.tutorials.java11.nest.Outer", outerNestHostName);
    final String innerNestHostName = Inner.class.getNestHost().getName();
    assertEquals("cn.howardliu.tutorials.java11.nest.Outer", innerNestHostName);
    assertEquals(outerNestHostName, innerNestHostName);
    final String notNestClass = NotNestClass.class.getNestHost().getName();
    assertEquals("cn.howardliu.tutorials.java11.nest.NotNestClass", notNestClass);
}

对于OuterInner都是返回了cn.howardliu.tutorials.java11.nest.Outer

getNestMembers

这个方法是返回嵌套类的嵌套成员数组,下标是 0 的元素确定是 NestHost 对应的类,其他元素顺序没有给出排序规则。我们看下使用:

@Test
void getNestMembers() {
    final List<String> outerNestMembers = Arrays.stream(Outer.class.getNestMembers())
            .map(Class::getName)
            .collect(Collectors.toList());
    assertEquals(2, outerNestMembers.size());
    assertTrue(outerNestMembers.contains("cn.howardliu.tutorials.java11.nest.Outer"));
    assertTrue(outerNestMembers.contains("cn.howardliu.tutorials.java11.nest.Outer$Inner"));
    final List<String> innerNestMembers = Arrays.stream(Inner.class.getNestMembers())
            .map(Class::getName)
            .collect(Collectors.toList());
    assertEquals(2, innerNestMembers.size());
    assertTrue(innerNestMembers.contains("cn.howardliu.tutorials.java11.nest.Outer"));
    assertTrue(innerNestMembers.contains("cn.howardliu.tutorials.java11.nest.Outer$Inner"));
}

isNestmateOf

这个方法是用于判断两个类是否是彼此的 NestMate,彼此形成嵌套关系。判断依据还是嵌套主机,只要相同,两个就是 NestMate。我们看下使用:

@Test
void checkIsNestmateOf() {
    assertTrue(Inner.class.isNestmateOf(Outer.class));
    assertTrue(Outer.class.isNestmateOf(Inner.class));
}

后续的改进

嵌套关系是作为 Valhalla 项目的一部分,这个项目的主要目标之一是改进 JAVA 中的值类型和泛型。后续会有更多的改进:

  • 在泛型特化(generic specialization)中,每个特化类型(specialized type)可被创建为泛型的一个 Nestmate。
  • 支持对Unsafe.defineAnonymousClass() API 的安全替换,实现将新类创建为已有类的 Nestmate。
  • 可能会影响“密封类”(sealed classes),仅允许 Nestmate 的子类作为密封类。
  • 可能会影响私有嵌套类型。私有嵌套类型当前定义为包内可访问(package-access)。

到此这篇关于Java11 中基于嵌套关系的访问控制优化的文章就介绍到这了,更多相关Java访问控制优化内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • Java11中基于嵌套关系的访问控制优化详解

    目录 前言 Java11 之前的实现方式 技术债务 Java11 中的实现 Nestmate 新增的 API getNestHost getNestMembers isNestmateOf 后续的改进 文末总结 前言 Java 语言很强大,但是,有人的地方就有江湖,有猿的地方就有 bug,Java 的核心代码并非十全十美.比如在JDK 中居然也有反模式接口常量 中介绍的反模式实现,以及本文说到的这个技术债务:嵌套关系(NestMate)调用方式. 在 Java 语言中,类和接口可以相互嵌套,这种

  • Java JDK11基于嵌套的访问控制的实现

    Java(和其他语言)通过内部类支持嵌套类.要使其正常工作,需要编译器执行一些技巧.这是一个例子: public class Outer { private int outerInt; class Inner { public void printOuterInt() { System.out.println("Outer int = " + outerInt); } } } 在执行编译之前,编译器会修改它以创建类似的东西: public class Outer { private i

  • Java访问控制符原理及具体用法解析

    Java包中的相关概念 包的概念   Java中的包类似于操作系统中的文件夹,将具有类似功能的类或者相同的类放在相同的包里面,便于组织与管理 包的作用   1.一是开发过程中避免类名重复问题:   2.二是对代码整理归类,将功能相近的类放到一个包中管理:   3.三是限定访问权限(default)就是包访问权限. 简述调用某个包中的类的三种方式   1>使用一个类的全名:即包名+类名     注意:在包中,所有类对真正名字将是包名+类名的组合,所以在运行的时候要将包层给写上,不能直接写类名.  

  • Java11 中基于嵌套关系的访问控制优化问题

    目录 Java11 之前的实现方式 技术债务 Java11 中的实现 Nestmate 新增的 API getNestHost getNestMembers isNestmateOf 后续的改进 你好,我是看山. Java 语言很强大,但是,有人的地方就有江湖,有猿的地方就有 bug,Java 的核心代码并非十全十美.比如在 JDK 中居然也有反模式接口常量 中介绍的反模式实现,以及本文说到的这个技术债务:嵌套关系(NestMate)调用方式. 在 Java 语言中,类和接口可以相互嵌套,这种组

  • 基于ElementUI中Table嵌套实现多选的示例代码

    前言: 写这个是因为帮朋友修改项目中的bug 我也是第一次写这个功能,有不对的希望大家指正,如果看完有帮助点个赞! 代码中关键是js中Tree的路径查找这个核心,有不懂的自行百度 多了不说了,有需要的可以私信找我要代码,来看下我怎么实现的 思路: 从头开始看这个需求,我们需要知道用到哪写东西 1.表格Table 2.多选&全选 3.嵌套数据(下拉操作) 正好我们可以找下ElementUI官方文档 找到了我们需要用到的API 在嵌套数据的时候需要使用tree-props 选中数据的时候使用togg

  • 基于js文件加载优化(详解)

    在js引擎部分,我们可以了解到,当渲染引擎解析到script标签时,会将控制权给JS引擎,如果script加载的是外部资源,则需要等待下载完后才能执行. 所以,在这里,我们可以对其进行很多优化工作. 放置在BODY底部 为了让渲染引擎能够及早的将DOM树给渲染出来,我们需要将script放在body的底部,让页面尽早脱离白屏的现象,即会提早触发DOMContentLoaded事件. 但是由于在IOS Safari, Android browser以及IOS webview里面即使你把js脚本放到

  • Spring Boot在Web应用中基于JdbcRealm安全验证过程

    目录 正文 01-RBAC 基于角色的访问控制 02-Shiro 中基于 JdbcRealm 实现用户认证.授权 03-集成到 Spring Boot Web 应用中 04-总结 正文 在安全领域,Subject 用来指代与系统交互的实体,可以是用户.第三方应用等,它是安全认证框架(例如 Shiro)验证的主题. Principal 是 Subject 具有的属性,例如用户名.身份证号.电话号码.邮箱等任何安全验证过程中关心的要素. Primary principal 指能够唯一区分 Subje

  • 在Spring中基于Java类进行配置的完整步骤

    前言 JavaConfig 原来是 Spring 的一个子项目,它通过 Java 类的方式提供 Bean 的定义信息,在 Spring4 的版本, JavaConfig 已正式成为 Spring4 的核心功能 . 本文将详细介绍关于Spring中基于Java类进行配置的相关内容,下面话不多说了,来一起看看详细的介绍吧 1 定义 Bean 普通的 POJO 只要标注了 @Configuration 注解,就可以为 Spring 容器提供 Bean 的定义信息. @Configuration pub

  • 解决mybatis 中collection嵌套collection引发的bug

    我就废话不多说了,大家还是直接看代码吧~ <resultMap id="ParentMap" type="org.example.mybatis.Parent"> <id column="Id" jdbcType="VARCHAR" property="id" /> <result column="Name" jdbcType="VARCHAR&q

  • python中if嵌套命令实例讲解

    一.嵌套命令计算机执行的顺序 缩进相同的命令处于同一个等级,第一步,计算机就要按顺序一条一条地执行命令. 1.先给score赋值: 2.因为if和else是只能留一个的互斥关系,if和else下的代码块只会执行一个,所以计算机要判断赋值的内容满足[score>=60]还是[score<60]的条件--如果满足if的条件,就执行if缩进下的内容. 二.if嵌套 if嵌套就是指,在已经有的if条件下在内部在写一个if条件. score=26 if score>=60: print('你已经及

  • 详解Python中4种超参自动优化算法的实现

    目录 一.网格搜索(Grid Search) 二.随机搜索(Randomized Search) 三.贝叶斯优化(Bayesian Optimization) 四.Hyperband 总结 大家好,要想模型效果好,每个算法工程师都应该了解的流行超参数调优技术. 今天我给大家总结超参自动优化方法:网格搜索.随机搜索.贝叶斯优化 和 Hyperband,并附有相关的样例代码供大家学习. 一.网格搜索(Grid Search) 网格搜索是暴力搜索,在给定超参搜索空间内,尝试所有超参组合,最后搜索出最优

  • PHP中基于perl的正则表达式处理函数

    前面我们已经学习了正则表达式的基础语法,包括了定界符.原子.元字符和模式修正 符.实际上正则表达式想要起作用的话,就必须借用正则表达式处理函数.本节我们就来介绍一下PHP中基于perl的正则表达式处理函数,主要包含了分割, 匹配,查找,替换等等处理操作,依旧是配合示例讲解,让我们开始吧. 和正则表达式一样,正则表达式处理函数不能够独立使用,而这必须相结合,才能够完成特定的功能.在前面我们也说过,基于perl的正则表达式要快于POXIS正则表达式处理函数,所以我们只介绍以preg开头的基于perl

随机推荐