Existe-t-il un moyen de simuler le concept «ami» C ++ en Java?

196

Je voudrais pouvoir écrire une classe Java dans un package qui peut accéder aux méthodes non publiques d'une classe dans un autre package sans avoir à en faire une sous-classe de l'autre classe. Est-ce possible?

Matthew Murdoch
la source

Réponses:

466

Voici une petite astuce que j'utilise dans JAVA pour répliquer le mécanisme ami C ++.

Disons que j'ai une classe Romeoet une autre classe Juliet. Ils sont dans des packages différents (famille) pour des raisons de haine.

Romeoveut cuddle Julietet Julietveut seulement la laisser Romeo cuddle.

En C ++, Julietse déclarer Romeocomme (amoureux) friendmais il n'y a rien de tel en java.

Voici les cours et l'astuce:

Les dames d'abord :

package capulet;

import montague.Romeo;

public class Juliet {

    public static void cuddle(Romeo.Love love) {
        Objects.requireNonNull(love);
        System.out.println("O Romeo, Romeo, wherefore art thou Romeo?");
    }

}

Donc, la méthode Juliet.cuddleest publicmais vous avez besoin d'un Romeo.Lovepour l'appeler. Il l'utilise Romeo.Lovecomme une "sécurité de signature" pour s'assurer que seul Romeopeut appeler cette méthode et vérifie que l'amour est réel afin que le runtime lance un NullPointerExceptionsi c'est le cas null.

Maintenant les garçons:

package montague;

import capulet.Juliet;

public class Romeo {
    public static final class Love { private Love() {} }
    private static final Love love = new Love();

    public static void cuddleJuliet() {
        Juliet.cuddle(love);
    }
}

La classe Romeo.Loveest publique, mais son constructeur l'est private. Par conséquent, n'importe qui peut le voir, mais seul Romeopeut le construire. J'utilise une référence statique afin Romeo.Loveque celle qui n'est jamais utilisée ne soit construite qu'une seule fois et n'impacte pas l'optimisation.

Par conséquent, ne Romeopeut cuddle Julietet ne peut - il parce que lui seul peut construire et accéder à une Romeo.Loveinstance, qui est requis par Julietla cuddlelui (ou bien elle va te frapper avec NullPointerException).

Salomon BRYS
la source
107
+1 pour "vous gifler avec une NullPointerException". Très impressionnant.
Nickolas
2
@Steazy Il y a: recherchez les annotations NotNull, NonNull et CheckForNull. Consultez la documentation de votre IDE pour savoir comment utiliser et appliquer ces annotations. Je sais qu'IntelliJ intègre cela par défaut et qu'eclipse a besoin d'un plugin (tel que FindBugs).
Salomon BRYS
27
Vous pouvez faire Romeo« s Lovepour Juliaéternelle en changeant le lovechamp pour être final;-).
Matthias
5
@Matthias Le champ de l'amour est statique ... Je vais modifier la réponse pour la rendre définitive;)
Salomon BRYS
12
Toutes les réponses doivent être comme ceci (Y) +1 pour l'humour et le bon exemple.
Zia Ul Rehman Mughal
54

Les concepteurs de Java ont explicitement rejeté l'idée de l'ami car elle fonctionne en C ++. Vous mettez vos "amis" dans le même paquet. La sécurité privée, protégée et intégrée est appliquée dans le cadre de la conception du langage.

James Gosling voulait que Java soit C ++ sans les erreurs. Je pense qu'il pensait que cet ami était une erreur parce qu'elle violait les principes de la POO. Les packages fournissent un moyen raisonnable d'organiser les composants sans être trop puristes sur la POO.

NR a souligné que vous pouviez tricher en utilisant la réflexion, mais même cela ne fonctionne que si vous n'utilisez pas SecurityManager. Si vous activez la sécurité standard Java, vous ne pourrez pas tricher avec réflexion à moins que vous n'écriviez une stratégie de sécurité pour l'autoriser spécifiquement.

David G
la source
11
Je ne veux pas être un pédant, mais les modificateurs d'accès ne sont pas un mécanisme de sécurité.
Greg D
6
Les modificateurs d'accès font partie du modèle de sécurité java. Je faisais spécifiquement référence à java.lang.RuntimePermission pour la réflexion: accessDeclaredMembers et accessClassInPackage.
David G
54
Si Gosling pensait vraiment que cela friendviolait la POO (en particulier, plus que l'accès aux packages), il ne le comprenait vraiment pas (tout à fait possible, beaucoup de gens le comprennent mal).
Konrad Rudolph
8
Les composants de classe doivent parfois être séparés (par exemple, l'implémentation et l'API, l'objet principal et l'adaptateur). La protection au niveau du package est à la fois trop permissive et trop restrictive pour le faire correctement.
dhardy
2
@GregD Ils peuvent être considérés comme un mécanisme de sécurité dans le sens où ils aident à empêcher les développeurs d'utiliser incorrectement un membre de la classe. Je pense qu'il vaut mieux les appeler un mécanisme de sécurité .
écraser
45

Le concept «ami» est utile en Java, par exemple, pour séparer une API de son implémentation. Il est courant que les classes d'implémentation aient besoin d'accéder aux composants internes de classe API, mais ceux-ci ne doivent pas être exposés aux clients API. Ceci peut être réalisé en utilisant le modèle 'Friend Accessor' comme détaillé ci-dessous:

La classe exposée via l'API:

package api;

public final class Exposed {
    static {
        // Declare classes in the implementation package as 'friends'
        Accessor.setInstance(new AccessorImpl());
    }

    // Only accessible by 'friend' classes.
    Exposed() {

    }

    // Only accessible by 'friend' classes.
    void sayHello() {
        System.out.println("Hello");
    }

    static final class AccessorImpl extends Accessor {
        protected Exposed createExposed() {
            return new Exposed();
        }

        protected void sayHello(Exposed exposed) {
            exposed.sayHello();
        }
    }
}

La classe offrant la fonctionnalité «ami»:

package impl;

public abstract class Accessor {

    private static Accessor instance;

    static Accessor getInstance() {
        Accessor a = instance;
        if (a != null) {
            return a;
        }

        return createInstance();
    }

    private static Accessor createInstance() {
        try {
            Class.forName(Exposed.class.getName(), true, 
                Exposed.class.getClassLoader());
        } catch (ClassNotFoundException e) {
            throw new IllegalStateException(e);
        }

        return instance;
    }

    public static void setInstance(Accessor accessor) {
        if (instance != null) {
            throw new IllegalStateException(
                "Accessor instance already set");
        }

        instance = accessor;
    }

    protected abstract Exposed createExposed();

    protected abstract void sayHello(Exposed exposed);
}

Exemple d'accès à partir d'une classe dans le package d'implémentation «ami»:

package impl;

public final class FriendlyAccessExample {
    public static void main(String[] args) {
        Accessor accessor = Accessor.getInstance();
        Exposed exposed = accessor.createExposed();
        accessor.sayHello(exposed);
    }
}
Matthew Murdoch
la source
1
Parce que je ne savais pas ce que le "statique" signifie dans la classe "Exposed": Le bloc statique, est un bloc de déclaration à l'intérieur d'une classe Java qui sera exécuté quand une classe sera chargée pour la première fois dans la JVM. En savoir plus sur javatutorialhub. com /…
Guy L
Modèle intéressant, mais il requiert que les classes Exposed et Accessor soient publiques tandis que les classes implémentant une API (c'est-à-dire un ensemble de classes Java implémentant un ensemble d'interfaces Java publiques) seraient mieux "protégées par défaut" et donc inaccessibles au client pour séparer les types de leurs implémentations.
Yann-Gaël Guéhéneuc
8
Je suis assez rouillé sur mon Java, pardonnez donc mon ignorance. Quel est l'avantage de cela sur la solution "Roméo et Juliette" que Salomon BRYS a publiée? Cette implémentation me ferait peur si je tombais dessus dans une base de code (sans votre explication jointe, c'est-à-dire de lourds commentaires). L'approche Roméo et Juliette est très simple à comprendre.
Steazy
1
Cette approche ne rendra les problèmes visibles qu'au moment de l'exécution, tandis qu'une mauvaise utilisation de Roméo et Juliette les rendrait visibles au moment de la compilation, lors du développement.
ymajoros
1
@ymajoros L'exemple de Roméo et Juliette ne rend pas la mauvaise utilisation visible au moment de la compilation. Il repose sur un argument passé correctement et une exception levée. Ce sont deux actions d'exécution.
Radiodef
10

Il existe deux solutions à votre question qui n'impliquent pas de conserver toutes les classes dans le même package.

La première consiste à utiliser le modèle Friend Accessor / Friend Package décrit dans (Practical API Design, Tulach 2008).

La seconde consiste à utiliser OSGi. Il y a un article ici expliquant comment OSGi y parvient.

Questions connexes: 1 , 2 et 3 .

Jeff Axelrod
la source
7

Pour autant que je sache, ce n'est pas possible.

Peut-être pourriez-vous nous donner plus de détails sur votre conception. De telles questions sont probablement le résultat de défauts de conception.

Considérez juste

  • Pourquoi ces classes sont-elles dans des packages différents, si elles sont si étroitement liées?
  • A a-t-il accès aux membres privés de B ou l'opération devrait-elle être déplacée en classe B et déclenchée par A?
  • Est-ce vraiment un appel ou la gestion des événements est-elle meilleure?
Noir
la source
3

La réponse d'eirikma est simple et excellente. Je pourrais ajouter encore une chose: au lieu d'avoir une méthode accessible au public, getFriend () pour obtenir un ami qui ne peut pas être utilisé, vous pouvez aller plus loin et interdire d'obtenir l'ami sans jeton: getFriend (Service.FriendToken). Ce FriendToken serait une classe publique interne avec un constructeur privé, de sorte que seul le service pourrait en instancier une.

AriG
la source
3

Voici un exemple de cas d'utilisation clair avec une Friendclasse réutilisable . L'avantage de ce mécanisme est la simplicité d'utilisation. Peut-être bon pour donner aux classes de tests unitaires plus d'accès que le reste de l'application.

Pour commencer, voici un exemple d'utilisation de la Friendclasse.

public class Owner {
    private final String member = "value";

    public String getMember(final Friend friend) {
        // Make sure only a friend is accepted.
        friend.is(Other.class);
        return member;
    }
}

Ensuite, dans un autre package, vous pouvez le faire:

public class Other {
    private final Friend friend = new Friend(this);

    public void test() {
        String s = new Owner().getMember(friend);
        System.out.println(s);
    }
}

La Friendclasse est la suivante.

public final class Friend {
    private final Class as;

    public Friend(final Object is) {
        as = is.getClass();
    }

    public void is(final Class c) {
        if (c == as)
            return;
        throw new ClassCastException(String.format("%s is not an expected friend.", as.getName()));
    }

    public void is(final Class... classes) {
        for (final Class c : classes)
            if (c == as)
                return;
        is((Class)null);
    }
}

Cependant, le problème est qu'il peut être abusé comme suit:

public class Abuser {
    public void doBadThings() {
        Friend badFriend = new Friend(new Other());
        String s = new Owner().getMember(badFriend);
        System.out.println(s);
    }
}

Maintenant, il peut être vrai que la Otherclasse n'a pas de constructeurs publics, ce qui rend le Abusercode ci-dessus impossible. Toutefois, si votre classe ne un constructeur public alors il est probablement préférable de dupliquer la classe ami comme une classe interne. Prenez ce Other2cours comme exemple:

public class Other2 {
    private final Friend friend = new Friend();

    public final class Friend {
        private Friend() {}
        public void check() {}
    }

    public void test() {
        String s = new Owner2().getMember(friend);
        System.out.println(s);
    }
}

Et puis la Owner2classe serait comme ça:

public class Owner2 {
    private final String member = "value";

    public String getMember(final Other2.Friend friend) {
        friend.check();
        return member;
    }
}

Notez que la Other2.Friendclasse a un constructeur privé, ce qui en fait un moyen beaucoup plus sûr de le faire.

intrepidis
la source
2

La solution proposée n'était peut-être pas la plus simple. Une autre approche est basée sur la même idée qu'en C ++: les membres privés ne sont pas accessibles en dehors de la portée package / private, à l'exception d'une classe spécifique que le propriétaire se fait un ami.

La classe qui a besoin d'un accès ami à un membre doit créer une "classe ami" abstraite publique intérieure à laquelle la classe possédant les propriétés masquées peut exporter l'accès, en renvoyant une sous-classe qui implémente les méthodes d'implémentation d'accès. La méthode "API" de la classe d'amis peut être privée, elle n'est donc pas accessible en dehors de la classe qui a besoin d'un accès ami. Sa seule instruction est un appel à un membre protégé abstrait que la classe exportatrice implémente.

Voici le code:

D'abord le test qui vérifie que cela fonctionne réellement:

package application;

import application.entity.Entity;
import application.service.Service;
import junit.framework.TestCase;

public class EntityFriendTest extends TestCase {
    public void testFriendsAreOkay() {
        Entity entity = new Entity();
        Service service = new Service();
        assertNull("entity should not be processed yet", entity.getPublicData());
        service.processEntity(entity);
        assertNotNull("entity should be processed now", entity.getPublicData());
    }
}

Ensuite, le service qui a besoin d'un accès ami à un package membre privé de l'entité:

package application.service;

import application.entity.Entity;

public class Service {

    public void processEntity(Entity entity) {
        String value = entity.getFriend().getEntityPackagePrivateData();
        entity.setPublicData(value);
    }

    /**
     * Class that Entity explicitly can expose private aspects to subclasses of.
     * Public, so the class itself is visible in Entity's package.
     */
    public static abstract class EntityFriend {
        /**
         * Access method: private not visible (a.k.a 'friendly') outside enclosing class.
         */
        private String getEntityPackagePrivateData() {
            return getEntityPackagePrivateDataImpl();
        }

        /** contribute access to private member by implementing this */
        protected abstract String getEntityPackagePrivateDataImpl();
    }
}

Enfin: la classe Entity qui fournit un accès convivial à un membre privé du package uniquement à la classe application.service.Service.

package application.entity;

import application.service.Service;

public class Entity {

    private String publicData;
    private String packagePrivateData = "secret";   

    public String getPublicData() {
        return publicData;
    }

    public void setPublicData(String publicData) {
        this.publicData = publicData;
    }

    String getPackagePrivateData() {
        return packagePrivateData;
    }

    /** provide access to proteced method for Service'e helper class */
    public Service.EntityFriend getFriend() {
        return new Service.EntityFriend() {
            protected String getEntityPackagePrivateDataImpl() {
                return getPackagePrivateData();
            }
        };
    }
}

D'accord, je dois admettre que c'est un peu plus long que "friend service :: Service;" mais il pourrait être possible de le raccourcir tout en conservant la vérification à la compilation en utilisant des annotations.

eirikma
la source
Cela ne fonctionne pas tout à fait car une classe normale dans le même package pourrait simplement obtenir GetFriend () puis appeler la méthode protégée en contournant la méthode privée.
user2219808
1

En Java, il est possible d'avoir une "convivialité liée au package". Cela peut être utile pour les tests unitaires. Si vous ne spécifiez pas privé / public / protégé devant une méthode, ce sera "ami dans le package". Une classe dans le même package pourra y accéder, mais elle sera privée en dehors de la classe.

Cette règle n'est pas toujours connue, et c'est une bonne approximation d'un mot-clé "ami" C ++. Je le trouve un bon remplacement.

daitangio
la source
1
C'est vrai, mais je posais vraiment des questions sur le code résidant dans différents packages ...
Matthew Murdoch
1

Je pense que les classes d'amis en C ++ sont comme le concept de classe interne en Java. En utilisant des classes internes, vous pouvez réellement définir une classe englobante et une classe fermée. La classe fermée a un accès complet aux membres publics et privés de sa classe enfermante. voir le lien suivant: http://docs.oracle.com/javase/tutorial/java/javaOO/nested.html

Sephiroth
la source
Euh, non, ils ne le sont pas. Cela ressemble plus à de l'amitié dans la vraie vie: elle peut, mais n'a pas besoin d'être, mutuelle (A étant un ami de B ne signifie pas que B est considéré comme un ami de A), et vous et vos amis peuvent être totalement différents familles et avoir vos propres cercles d'amis qui se chevauchent éventuellement (mais pas nécessairement). (Non pas que j'aimerais voir des cours avec beaucoup d'amis. Cela peut être une fonctionnalité utile, mais doit être utilisé avec prudence.)
Christopher Creutzig
1

Je pense que l'approche de l'utilisation du modèle d'accesseur ami est beaucoup trop compliquée. J'ai dû faire face au même problème et j'ai résolu en utilisant le bon vieux constructeur de copie, connu de C ++, en Java:

public class ProtectedContainer {
    protected String iwantAccess;

    protected ProtectedContainer() {
        super();
        iwantAccess = "Default string";
    }

    protected ProtectedContainer(ProtectedContainer other) {
        super();
        this.iwantAccess = other.iwantAccess;
    }

    public int calcSquare(int x) {
        iwantAccess = "calculated square";
        return x * x;
    }
}

Dans votre application, vous pouvez écrire le code suivant:

public class MyApp {

    private static class ProtectedAccessor extends ProtectedContainer {

        protected ProtectedAccessor() {
            super();
        }

        protected PrivateAccessor(ProtectedContainer prot) {
            super(prot);
        }

        public String exposeProtected() {
            return iwantAccess;
        }
    }
}

L'avantage de cette méthode est que seule votre application a accès aux données protégées. Ce n'est pas exactement une substitution du mot-clé ami. Mais je pense que cela convient parfaitement lorsque vous écrivez des bibliothèques personnalisées et que vous devez accéder à des données protégées.

Chaque fois que vous devez gérer des instances de ProtectedContainer, vous pouvez envelopper votre ProtectedAccessor et vous y avez accès.

Il fonctionne également avec des méthodes protégées. Vous les définissez protégés dans votre API. Plus loin dans votre application, vous écrivez une classe wrapper privée et exposez la méthode protégée comme publique. C'est tout.

Chris
la source
1
Mais ProtectedContainerpeut être sous-classé en dehors du package!
Raphael
0

Si vous souhaitez accéder aux méthodes protégées, vous pouvez créer une sous-classe de la classe que vous souhaitez utiliser qui expose les méthodes que vous souhaitez utiliser en tant que public (ou interne à l'espace de noms pour être plus sûr), et avoir une instance de cette classe dans votre classe (utilisez-le comme proxy).

En ce qui concerne les méthodes privées (je pense), vous n'avez pas de chance.

Omar Kooheji
la source
0

Je suis d'accord que dans la plupart des cas, le mot-clé ami n'est pas nécessaire.

  • Package-private (aka. Default) est suffisant dans la plupart des cas où vous avez un groupe de classes fortement entrelacées
  • Pour les classes de débogage qui souhaitent accéder aux internes, je rend généralement la méthode privée et y accède via la réflexion. La vitesse n'est généralement pas importante ici
  • Parfois, vous implémentez une méthode qui est un "hack" ou autre qui est susceptible de changer. Je le rends public, mais utilisez @Deprecated pour indiquer que vous ne devez pas vous fier à cette méthode existante.

Et enfin, si c'est vraiment nécessaire, il y a le modèle d'accesseur ami mentionné dans les autres réponses.

Casebash
la source
0

Ne pas utiliser de mot-clé.

Vous pouvez "tricher" en utilisant la réflexion, etc., mais je ne recommanderais pas de "tricher".

NR.
la source
3
je considérerais cela comme une si mauvaise idée que même le suggérer est odieux pour moi. Évidemment, c'est au mieux un cludge, et ne devrait faire partie d'aucune conception.
shsteimer
0

Une méthode que j'ai trouvée pour résoudre ce problème est de créer un objet accesseur, comme ceci:

class Foo {
    private String locked;

    /* Anyone can get locked. */
    public String getLocked() { return locked; }

    /* This is the accessor. Anyone with a reference to this has special access. */
    public class FooAccessor {
        private FooAccessor (){};
        public void setLocked(String locked) { Foo.this.locked = locked; }
    }
    private FooAccessor accessor;

    /** You get an accessor by calling this method. This method can only
     * be called once, so calling is like claiming ownership of the accessor. */
    public FooAccessor getAccessor() {
        if (accessor != null)
            throw new IllegalStateException("Cannot return accessor more than once!");
        return accessor = new FooAccessor();
    }
}

Le premier code à appeler getAccessor()"revendique la propriété" de l'accesseur. Habituellement, c'est du code qui crée l'objet.

Foo bar = new Foo(); //This object is safe to share.
FooAccessor barAccessor = bar.getAccessor(); //This one is not.

Cela a également un avantage sur le mécanisme ami de C ++, car il vous permet de limiter l'accès à un niveau par instance , par opposition à un niveau par classe . En contrôlant la référence de l'accesseur, vous contrôlez l'accès à l'objet. Vous pouvez également créer plusieurs accesseurs et donner un accès différent à chacun, ce qui permet un contrôle précis sur quel code peut accéder à quoi:

class Foo {
    private String secret;
    private String locked;

    /* Anyone can get locked. */
    public String getLocked() { return locked; }

    /* Normal accessor. Can write to locked, but not read secret. */
    public class FooAccessor {
        private FooAccessor (){};
        public void setLocked(String locked) { Foo.this.locked = locked; }
    }
    private FooAccessor accessor;

    public FooAccessor getAccessor() {
        if (accessor != null)
            throw new IllegalStateException("Cannot return accessor more than once!");
        return accessor = new FooAccessor();
    }

    /* Super accessor. Allows access to secret. */
    public class FooSuperAccessor {
        private FooSuperAccessor (){};
        public String getSecret() { return Foo.this.secret; }
    }
    private FooSuperAccessor superAccessor;

    public FooSuperAccessor getAccessor() {
        if (superAccessor != null)
            throw new IllegalStateException("Cannot return accessor more than once!");
        return superAccessor = new FooSuperAccessor();
    }
}

Enfin, si vous souhaitez que les choses soient un peu plus organisées, vous pouvez créer un objet de référence, qui tient tout ensemble. Cela vous permet de revendiquer tous les accesseurs avec un seul appel de méthode, ainsi que de les conserver avec leur instance liée. Une fois que vous avez la référence, vous pouvez passer les accesseurs au code qui en a besoin:

class Foo {
    private String secret;
    private String locked;

    public String getLocked() { return locked; }

    public class FooAccessor {
        private FooAccessor (){};
        public void setLocked(String locked) { Foo.this.locked = locked; }
    }
    public class FooSuperAccessor {
        private FooSuperAccessor (){};
        public String getSecret() { return Foo.this.secret; }
    }
    public class FooReference {
        public final Foo foo;
        public final FooAccessor accessor;
        public final FooSuperAccessor superAccessor;

        private FooReference() {
            this.foo = Foo.this;
            this.accessor = new FooAccessor();
            this.superAccessor = new FooSuperAccessor();
        }
    }

    private FooReference reference;

    /* Beware, anyone with this object has *all* the accessors! */
    public FooReference getReference() {
        if (reference != null)
            throw new IllegalStateException("Cannot return reference more than once!");
        return reference = new FooReference();
    }
}

Après beaucoup de coups de tête (pas du bon genre), ce fut ma solution finale, et je l'aime beaucoup. Il est flexible, simple à utiliser et permet un très bon contrôle sur l'accès aux classes. (L' accès avec référence uniquement est très utile.) Si vous utilisez protégé au lieu de privé pour les accesseurs / références, les sous-classes de Foo peuvent même renvoyer des références étendues à partir de getReference. Il ne nécessite également aucune réflexion, il peut donc être utilisé dans n'importe quel environnement.

jpfx1342
la source
0

Depuis Java 9, des modules peuvent être utilisés pour en faire un problème dans de nombreux cas.

Raphael
la source
0

Je préfère la délégation ou la composition ou la classe d'usine (en fonction du problème qui entraîne ce problème) pour éviter d'en faire une classe publique.

S'il s'agit d'un problème de "classes d'interface / d'implémentation dans différents packages", j'utiliserais une classe de fabrique publique qui serait dans le même package que le package impl et empêcherait l'exposition de la classe impl.

Si c'est un problème "Je déteste rendre cette classe / méthode publique juste pour fournir cette fonctionnalité à une autre classe dans un autre package", alors j'utiliserais une classe déléguée publique dans le même package et n'exposerais que cette partie de la fonctionnalité nécessaire par la classe "outsider".

Certaines de ces décisions sont dictées par l'architecture de chargement de classe du serveur cible (bundle OSGi, WAR / EAR, etc.), le déploiement et les conventions de dénomination des packages. Par exemple, la solution proposée ci-dessus, le modèle «Friend Accessor» est intelligente pour les applications java normales. Je me demande s'il est difficile de l'implémenter dans OSGi en raison de la différence de style de chargement de classe.

Suraj Rao
la source
0

Je ne sais pas si cela est utile à personne, mais je l'ai traité de la manière suivante:

J'ai créé une interface (AdminRights).

Chaque classe qui devrait pouvoir appeler lesdites fonctions devrait implémenter AdminRights.

J'ai ensuite créé une fonction HasAdminRights comme suit:

private static final boolean HasAdminRights()
{
    // Gets the current hierarchy of callers
    StackTraceElement[] Callers = new Throwable().getStackTrace();

    // Should never occur with me but if there are less then three StackTraceElements we can't check
    if (Callers.length < 3)
    {
        EE.InvalidCode("Couldn't check for administrator rights");
        return false;

    } else try
    {

        // Now we check the third element as this function is the first and the function wanting to check for the rights the second. We try to use it as a subclass of AdminRights.
        Class.forName(Callers[2].getClassName()).asSubclass(AdminRights.class);

        // If everything worked up to now, it has admin rights!
        return true;

    } catch (java.lang.ClassCastException | ClassNotFoundException e)
    {
        // In the catch, something went wrong and we can deduce that the caller has no admin rights

        EE.InvalidCode(Callers[1].getClassName() + " doesn't have administrator rights");
        return false;
    }
}
mdre
la source
-1

J'ai vu une fois une solution basée sur la réflexion qui effectuait une "vérification d'amis" au moment de l'exécution en utilisant la réflexion et la vérification de la pile d'appels pour voir si la classe appelant la méthode était autorisée à le faire. Étant un contrôle d'exécution, il présente l'inconvénient évident.

Ran Biron
la source