问题 无法从不同jar中的同一个包访问超类的受保护成员


我有一个奇怪的问题,当我试图插入我的程序时,我无法弄明白这个问题。另一个问题是我无法创建一个简单的测试用例,因为每次我尝试它都有效。必须有一些我不知道的并发症。但我会尽可能清楚地描述情况,以防任何人听起来都很熟悉。

我有一个名为Seed的基类,它是主应用程序的一部分,由系统类加载器加载。我有一个插件,其中包含一个类Road,它是Seed的子类。它在运行时从单独的jar文件加载。类Road引用了Seed.garden字段,定义为:

保护最终花园;

请注意,我没有收到编译错误。当插件jar包含在系统类路径中时,我也不会遇到运行时错误。只有当我的主应用程序使用新的类加载器(系统类加载器作为其父类)加载插件时才会出现错误。错误是:

java.lang.IllegalAccessError:尝试从类package.Road $ 4访问字段package.Seed.garden

它必须与子类由超类不同的类加载器加载这一事实有关,但我找不到任何官方原因,为什么它不起作用。另外,就像我说的,当我尝试用简单的测试用例(包括单独的jar,用不同的类加载器加载子类等)重现问题时,我没有得到错误。

我也不太可能违反访问规则,因为当类由同一个类加载器加载时它工作,我不会遇到编译错误。

我没有想法!有没有人认识到这个问题,或者有一些指示我的方向可以看?帮帮我!


3461
2018-05-05 20:20


起源

我应该补充一点,Road $ 4是一个匿名的内部类Road(显然),但我也在我的测试用例中包含了这个事实,它仍然有效。 - Pepijn Schmitz
可能重复 跨不同类加载器覆盖默认访问器方法会破坏多态性 - axtavt
@axtavt:你可能是对的,虽然那篇文章是关于默认访问,而不是受保护的访问。我想这同样适用于受保护的访问。我确实看过规范,但没有抓住“运行时包”的事情。它没有解释为什么我的测试用例有效。我会看看我是否可以利用这些知识让它破裂。谢谢! - Pepijn Schmitz
@axtavt:我没有理解的原因是我检查了语言规范,但这是在VM规范中。该规范包含一个额外的有趣子句:“当且仅当满足以下任何条件时,字或方法R才可被类或接口D访问:... R受保护并在类C中声明,并且D是C或C本身的子类。“这解释了为什么允许Road访问Seed.garden(它是Seed的子类),但Road $ 4不是(它不是子类,而在同一个包中,它位于不同的运行时包中)。现在打破我的简单测试案例...... - Pepijn Schmitz
@PepijnSchmitz这可能会在下面验证我的答案。即使“规则”在语言规范中,它可能存在的原因还在于运行时如何解释匿名类在包结构中的位置。


答案:


好的,所以在axtavt和其他受访者的帮助下,我弄清楚问题是什么。其他答案有所帮助,但他们并没有完全正确,这就是为什么我回答我自己的问题。问题原来是“运行时包”的概念,定义在 Java虚拟机规范 如下:

5.3创建和加载

... 在运行时,类或接口不是由其名称单独确定,而是由一对确定:它的完全限定名称及其定义的类加载器。每个这样的类或接口都属于单个运行时包。类或接口的运行时包由包名称和类或接口的类加载器定义。 ...

5.4.4访问控制

... 当且仅当满足以下任一条件时,才能访问类或接口D的字段或方法R:...

  • R受保护并在C类中声明,D是C或C本身的子类。
  • R既可以是受保护的,也可以是包私有的(即既不公开也不受保护也不是私有),并且由与D相同的运行时包中的类声明。

第一个条款解释了为什么允许Road访问Seed.garden,因为Road是Seed的子类,第二个条款解释了为什么Road $ 4不允许访问它,尽管它与Road在同一个包中,因为它不是在相同的 运行 包,由不同的类加载器加载。限制实际上不是Java语言限制,而是Java VM限制。

所以我的情况的结论是由于Java VM的合法限制而发生异常,我将不得不解决它,可能是通过将字段公开,这在这种情况下不是问题,因为它们是最终的,而不是秘密,或者可能通过道路将Seed.garden出口到Road $ 4,它可以访问。

谢谢大家的建议和解答!


7
2018-05-06 11:45



谢谢你很有帮助。要添加的一件事 - 您不需要将这些受保护的字段公开,您只需要在子类中放置一个方法(并且它可以是私有的),它返回受保护的值。只有匿名类无法访问受保护的字段而不能访问子类。 - Michael Wiles
@MichaelWiles谢谢。这实际上就是我对第二个建议的意思。 :) - Pepijn Schmitz


听起来你有一个类身份危机,有两个不同的类加载器在类层次结构中加载相同的类或类似。阅读java类加载器上的一些内容。这是一个很好的介绍,对于“阶级认同危机”,见图2: http://www.ibm.com/developerworks/java/library/j-dyn0429/


4
2018-05-05 20:48



它不是类标识危机(每个类只由一个类加载器加载),但我相信原因是相似的,正如上面链接的问题所指出的那样,即类在不同的“运行时包”中的事实。 - Pepijn Schmitz
我不确定区别是什么,你指的是什么是“运行时包”,你指的是类加载器引入的不同名称空间吗?然后我们谈论同样的事情。 - Mattias Isegran Bergander
是。 “运行时包”是Java虚拟机规范调用包和类加载器的组合。我认为通过“阶级认同危机”你的意思是加载 相同 两次使用不同的类加载器。 - Pepijn Schmitz
啊,那么我们的意思是相同的东西,但也许应该称之为包身份危机然后:) - Mattias Isegran Bergander


我应该补充一点,道路4美元是一个匿名的内部道路......

其他人认为这也是1998年的一个错误:

http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4116802

内部类对另一个类的成员没有更大的访问权限   顶级类,除了那些成员 声明 中   封闭的,封闭的或兄弟的类。这是一种常见的误解   内部类具有对受继承成员的无限制访问权限   它的封闭课程。这不是真的。

我可能会更多地研究这个事实,因为这最初是针对Java 1.2报告的,但我似乎记得从我的阅读中看到今天也是如此。

编辑:

我确认这是真的:

http://docs.oracle.com/javase/tutorial/java/javaOO/summarynested.html

匿名内部类的范围只是定义它的范围。因此即使外部类也可以访问继承的成员。


1
2018-05-05 21:48



有趣的信息。我认为它不适用,因为protected意味着包访问,并且内部类与其封闭类的超类在同一个包中(在我的情况下),因此它应该可以访问超类的受保护成员。对?否则它甚至不应该编译,它确实如此。 - Pepijn Schmitz


这是权限错误,因此它取决于您用于运行运行时的框架。 只是为了澄清这确实是这样,让父成员公开,然后尝试运行。如果一切正常,则恢复您的代码,根据您使用的运行时,我们需要配置正确的安全访问。


0
2018-05-05 21:33



这不是一个简单的权限错误,否则甚至不会编译。但我认为我们找到了它,看到我对原始问题的评论。 - Pepijn Schmitz
通过权限 - 我的意思是运行时权限,而不是静态/编译时间。无论如何,你似乎找到了真正的问题。 - miks