How does the SharedSecrets mechanism work?

Tags:



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?

Answer

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:

  • We have two classes: Character and Story.
  • We want to expose Character‘s non-public methods to Story but not external users (classes residing outside the module).

Main.java:

package external.main;

import external.character.Character;
import external.story.Story;

public class Main
{
    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);
    }
}

Story.java

package external.story;

import external.character.Character;
import internal.secrets.SharedSecrets;

public final class Story
{
    /**
     * Introduces a character.
     *
     * @param character the character
     */
    public void introduce(Character character)
    {
        System.out.println(character.name() + " enters the room and says: " + SharedSecrets.INSTANCE.secretCharacter.getPhrase(character));
    }
}

Character.java:

package external.character;

import internal.secrets.SecretCharacter;
import internal.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();
            }
        };
    }

    /**
     * @return the character's introductory phrase
     */
    abstract String getPhrase();
}

SharedSecrets.java:

package internal.secrets;

public final class SharedSecrets
{
    public static SharedSecrets INSTANCE = new SharedSecrets();
    public SecretCharacter secretCharacter;

    /**
     * Prevent construction.
     */
    private SharedSecrets()
    {
    }
}

SecretCharacter.java:

package internal.secrets;

import external.character.Character;

public interface SecretCharacter
{
    /**
     * @param character a character
     * @return the character's introductory phrase
     */
    String getPhrase(Character character);
}

module-info.java:

module SharedSecret
{
    exports external.character;
    exports external.main;
    exports external.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

  • external.character.Character.getPhrase() is package-protected.
  • external.story.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.
  • external.story.Story can access internal.secrets.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.



Source: stackoverflow