jdk.internal.misc.SharedSecrets
describes itself as:
A repository of "shared secrets", which are a mechanism for calling implementation-private methods in another package without using reflection. A package-private class implements a public interface and provides the ability to call package-private methods within that package; the object implementing that interface is provided through a third package to which access is restricted. This framework avoids the primary disadvantage of using reflection for this purpose, namely the loss of compile-time checking.
Can someone please provide an example that demonstrates how this mechanism enables classes in one package to access package-private methods in a different package?
Quoting Andrew John Hughes:
When looking through OpenJDK for the VM project, I noticed that they have a rather interesting solution to this. This is encapsulated in sun.misc.SharedSecrets. This class provides access to instances of a number of public interfaces, such as sun.misc.JavaLangAccess. The actual implementations are provided as inner classes in the appropriate package e.g. java.lang, where it has access to the private and package-private variables and methods within.
Say that you have API classes scattered across multiple packages. You want them to be able to access each other's internals, without exposing them to end-users. What do you do?
exports main.internal to test
.This solution is especially nice because the resulting Javadoc and IDE auto-complete will look a lot cleaner.
External Users
+-- external
¦ +-- EndUser.java
+-- module-info.java
Library
+-- library
¦ +-- character
¦ ¦ +-- Character.java
¦ +-- story
¦ ¦ +-- Story.java
¦ +-- internal
¦ +-- SharedSecrets.java
¦ +-- CharacterAccess.java
+-- module-info.java
Character
's internals to Story
without EndUser
gaining access.End-user code
external/EndUser.java:
package external;
import library.character.Character;
import library.story.Story;
public class EndUser
{
public static void main(String[] args)
{
Story story = new Story();
story.introduce(Character.HARRY_POTTER);
story.introduce(Character.RON_WEASLEY);
story.introduce(Character.HERMIONE_GRANGER);
}
}
module-info.java:
module external
{
requires library;
}
Library code
library/story/Story.java
package library.story;
import library.character.Character;
import library.internal.CharacterAccess;
import library.internal.SharedSecrets;
public final class Story
{
public void introduce(Character character)
{
System.out.println(character.name() + " enters the room and says: " +
SharedSecrets.getPhrase(character));
}
}
library/character/Character.java:
package library.character;
import library.internal.CharacterAccess;
import library.internal.SharedSecrets;
public enum Character
{
HARRY_POTTER
{
@Override
String getPhrase()
{
return "Your bird, there was nothing I could do. He just caught fire.";
}
},
RON_WEASLEY
{
@Override
String getPhrase()
{
return "Who are you and what have you done with Hermione Granger?";
}
},
HERMIONE_GRANGER
{
@Override
String getPhrase()
{
return "I'm not an owl!";
}
};
static
{
SharedSecrets.setCharacterAccess(new CharacterAccess()
{
@Override
public String getPhrase(Character character)
{
return character.getPhrase();
}
});
}
abstract String getPhrase();
}
library/internal/SharedSecrets.java:
package library.internal;
public final class SharedSecrets
{
private static final Lookup lookup = MethodHandles.lookup();
private static CharacterAccess characterAccess;
public static void setCharacterAccess(CharacterAccess characterAccess)
{
SharedSecrets.characterAccess = characterAccess;
}
public static String getPhrase(Character character)
{
CharacterAccess access = characterAccess;
if (access == null)
{
initialize(Character.class);
access = characterAccess;
assert access != null;
}
return access.getPhrase(character);
}
/**
* Initializes a class. If the class is already initialized, this method has no effect.
*
* @param c the class
*/
private static void initialize(Class<?> c)
{
try
{
lookup.ensureInitialized(c);
}
catch (IllegalAccessException e)
{
throw new AssertionError(e);
}
}
}
library/internal/CharacterAccess.java:
package library.internal;
import library.character.Character;
public interface CharacterAccess
{
String getPhrase(Character character);
}
module-info.java:
module library
{
exports library.character;
exports library.story;
}
Output
HARRY_POTTER enters the room and says: Your bird, there was nothing I could do. He just caught fire.
RON_WEASLEY enters the room and says: Who are you and what have you done with Hermione Granger?
HERMIONE_GRANGER enters the room and says: I'm not an owl!
Notice
Character.getPhrase()
is package-protected.Story
is located in a different package.Story
wouldn't be able to invoke Character.getPhrase()
; however, SharedSecrets
allows Character
to share access with classes that it trusts.Story
invokes SharedSecrets.getPhrase(Character)
which uses an anonymous nested class to access Character
's internals.Story
can access SharedSecrets
because the two are located in the same module, but external users cannot access it because module-info.java
does not export that package.