Quoting http://blog.fuseyism.com/index.php/2008/05/26/sharing-secrets/:
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.
Here is a concrete example:
External Users
├── external
│ └── EndUser.java
└── module-info.java
Library
├── library
│ ├── character
│ │ └── Character.java
│ ├── story
│ │ └── Story.java
│ └── secrets
│ ├── SharedSecrets.java
│ └── SecretCharacter.java
└── module-info.java
- We want to expose
Character's internals to Story without EndUser gaining access.
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/story/Story.java
package library.story;
import library.character.Character;
import library.secrets.SecretCharacter;
import library.secrets.SharedSecrets;
public final class Story
{
private static final SecretCharacter secretCharacter =
SharedSecrets.INSTANCE.secretCharacter;
public void introduce(Character character)
{
System.out.println(character.name() + " enters the room and says: " +
secretCharacter.getPhrase(character));
}
}
character/Character.java:
package library.character;
import library.secrets.SecretCharacter;
import library.secrets.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.INSTANCE.secretCharacter = new SecretCharacter()
{
@Override
public String getPhrase(Character character)
{
return character.getPhrase();
}
};
}
abstract String getPhrase();
}
library/secrets/SharedSecrets.java:
package library.secrets;
public enum SharedSecrets
{
INSTANCE;
public SecretCharacter secretCharacter;
}
library/secrets/SecretCharacter.java:
package library.secrets;
import library.character.Character;
public interface SecretCharacter
{
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!
Explanation
Character.getPhrase() is package-protected.
Story is located in a different package.
- Normally
Story wouldn't be able to invoke Character.getPhrase(); however, SharedSecrets allows Character to share access with classes that it trusts.
Story invokes SharedSecrets.INSTANCE.secretCharacter 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.
If you want to export SharedSecrets to trusted external modules, see https://stackoverflow.com/a/53653651/14731.