2010-07-17 6 views
45

Ich entwickle eine Java-Unternehmensanwendung, die derzeit Java EE-Sicherheitsfunktionen ausführt, um den Zugriff für bestimmte Funktionen auf bestimmte Benutzer zu beschränken. Ich baute den Anwendungsserver und alles, und jetzt bin ich mit der RolesAllowed-Annotation, die Methoden zu sichern:Enum-Typ als Wertparameter für @ RolesAllowed-Annotation verwenden

@Documented 
@Retention (RUNTIME) 
@Target({TYPE, METHOD}) 
public @interface RolesAllowed { 
    String[] value(); 
} 

Wenn ich die Anmerkung wie diese, es funktioniert gut:

@RolesAllowed("STUDENT") 
public void update(User p) { ... } 

Aber das ist nicht was ich will, da ich hier einen String verwenden muss, Refactoring wird schwer, und Tippfehler können passieren. Anstatt einen String zu verwenden, möchte ich einen Enum-Wert als Parameter für diese Annotation verwenden. Die Enum sieht wie folgt aus:

public enum RoleType { 
    STUDENT("STUDENT"), 
    TEACHER("TEACHER"), 
    DEANERY("DEANERY"); 

    private final String label; 

    private RoleType(String label) { 
     this.label = label; 
    } 

    public String toString() { 
     return this.label; 
    } 
} 

Also habe ich versucht, die Enum als Parameter wie folgt zu verwenden:

@RolesAllowed(RoleType.DEANERY.name()) 
public void update(User p) { ... } 

Aber dann bekomme ich die folgende Compiler-Fehler, obwohl Enum.name gibt nur einen String (Das ist immer konstant, oder?).

The value for annotation attribute RolesAllowed.value must be a constant expression`

Das nächste, was ich versuchte, einen zusätzlichen final String in meine Enum hinzuzufügen:

public enum RoleType { 
    ... 
    public static final String STUDENT_ROLE = STUDENT.toString(); 
    ... 
} 

Aber das ist auch nicht als Parameter arbeitet, in dem gleichen Compiler Fehler führt:

// The value for annotation attribute RolesAllowed.value must be a constant expression 
@RolesAllowed(RoleType.STUDENT_ROLE) 

Wie kann ich das gewünschte Verhalten erreichen? Ich habe sogar meinen eigenen Abfangjäger implementiert, um meine eigenen Anmerkungen zu verwenden, was schön ist, aber viel zu viele Codezeilen für ein kleines Problem wie dieses.

HAFTUNGSAUSSCHLUSS

Diese Frage war ursprünglich eine Scala Frage. Ich habe herausgefunden, dass Scala nicht die Ursache des Problems ist, also versuche ich es zuerst in Java.

+1

Sorry das ist ein bisschen irrelevant, um Ihr Problem zu lösen, aber ich dachte, ich würde erwähnen, dass Sie auf das String-Argument zu Ihren Enum-Konstruktoren verzichten können, wenn Sie sie genauso haben, wie Sie sie nennen ; Sie können auf denselben Wert zugreifen, indem Sie für die Enumeration .name() aufrufen. Ich glaube, die Methode toString() delegiert trotzdem an name(). – I82Much

+0

mögliches Duplikat von [So liefern Sie Enum-Werte zu einer Annotation von einer Constant in Java] (http://stackoverflow.com/questions/13253624/how-to-supply-enum-value-to-annannotation-from- a-constant-in-java) –

Antwort

29

Ich glaube nicht, dass Ihr Ansatz der Verwendung von Enums funktioniert. Ich fand, dass der Compiler-Fehler ging weg, wenn ich das STUDENT_ROLE Feld in Ihrem letzten Beispiel auf einen konstanten String geändert, wie zu einem Ausdruck im Gegensatz:

public enum RoleType { 
    ... 
    public static final String STUDENT_ROLE = "STUDENT"; 
    ... 
} 

Dies bedeutet aber dann, dass die ENUM-Werte nicht verwendet werden überall, weil Sie stattdessen die String-Konstanten in Annotationen verwenden würden.

Es scheint mir, dass Sie besser dran wäre, wenn Ihre RoleType Klasse nichts mehr als eine Reihe von statischen endgültigen String-Konstanten enthielt.


Um zu sehen, warum der Code nicht kompiliert wurde, hatte ich einen Blick in die Java Language Specification (JLS).Die JLS für annotations besagt, daß für eine Annotation mit einem Parameter des Typs T und Wert V,

if T is a primitive type or String , V is a constant expression.

A constant expression umfasst unter anderem

Qualified names of the form TypeName . Identifier that refer to constant variables

und ein constant variable definiert sind wie

a variable, of primitive type or type String , that is final and initialized with a compile-time constant expression

+4

Danke für Ihre Bemühungen! Interessante Fakten. Vor allem der letzte. Ich dachte immer, dass Finale bereits Konstanten sind. Ok, deshalb kann es nicht funktionieren. Es scheint mir, dass dies bereits die Antwort ist. Obwohl ich nicht wirklich glücklich damit bin;) (BTW ich brauche wirklich die Enum, nicht nur für die Annotation) – ifischer

+0

Ich lief tatsächlich in [ein Beispiel] (http://java.dzone.com/articles/glassfish- 3-30 Minuten), die 'public interface Roles {String REGISTERED =" registriert "deklariert; } 'und verwendet dann' @RolesAllowed ({Roles.REGISTERED}) ''. Natürlich bedeutet ein Beispiel * nicht * mit 'enum' nicht, dass' enum' Probleme bringt, aber gut ;-) – Arjan

7

Hier ' s eine Lösung mit einer zusätzlichen Schnittstelle und einer Meta-Annotation. Ich habe eine Utility-Klasse enthalten, um die Reflexion zu tun, um die Rollentypen aus einer Reihe von Anmerkungen und einen kleinen Test dafür zu bekommen:

/** 
* empty interface which must be implemented by enums participating in 
* annotations of "type" @RolesAllowed. 
*/ 
public interface RoleType { 
    public String toString(); 
} 

/** meta annotation to be applied to annotations that have enum values implementing RoleType. 
* the value() method should return an array of objects assignable to RoleType*. 
*/ 
@Retention(RetentionPolicy.RUNTIME) 
@Target({ANNOTATION_TYPE}) 
public @interface RolesAllowed { 
    /* deliberately empty */ 
} 

@RolesAllowed 
@Retention(RetentionPolicy.RUNTIME) 
@Target({TYPE, METHOD}) 
public @interface AcademicRolesAllowed { 
    public AcademicRoleType[] value(); 
} 

public enum AcademicRoleType implements RoleType { 
    STUDENT, TEACHER, DEANERY; 
    @Override 
    public String toString() { 
     return name(); 
    } 
} 


public class RolesAllowedUtil { 

    /** get the array of allowed RoleTypes for a given class **/ 
    public static List<RoleType> getRoleTypesAllowedFromAnnotations(
      Annotation[] annotations) { 
     List<RoleType> roleTypesAllowed = new ArrayList<RoleType>(); 
     for (Annotation annotation : annotations) { 
      if (annotation.annotationType().isAnnotationPresent(
        RolesAllowed.class)) { 
       RoleType[] roleTypes = getRoleTypesFromAnnotation(annotation); 
       if (roleTypes != null) 
        for (RoleType roleType : roleTypes) 
         roleTypesAllowed.add(roleType); 
      } 
     } 
     return roleTypesAllowed; 
    } 

    public static RoleType[] getRoleTypesFromAnnotation(Annotation annotation) { 
     Method[] methods = annotation.annotationType().getMethods(); 
     for (Method method : methods) { 
      String name = method.getName(); 
      Class<?> returnType = method.getReturnType(); 
      Class<?> componentType = returnType.getComponentType(); 
      if (name.equals("value") && returnType.isArray() 
        && RoleType.class.isAssignableFrom(componentType)) { 
       RoleType[] features; 
       try { 
        features = (RoleType[]) (method.invoke(annotation, 
          new Object[] {})); 
       } catch (Exception e) { 
        throw new RuntimeException(
          "Error executing value() method in " 
            + annotation.getClass().getCanonicalName(), 
          e); 
       } 
       return features; 
      } 
     } 
     throw new RuntimeException(
       "No value() method returning a RoleType[] type " 
         + "was found in annotation " 
         + annotation.getClass().getCanonicalName()); 
    } 

} 

public class RoleTypeTest { 

    @AcademicRolesAllowed({DEANERY}) 
    public class DeaneryDemo { 

    } 

    @Test 
    public void testDeanery() { 
     List<RoleType> roleTypes = RolesAllowedUtil.getRoleTypesAllowedFromAnnotations(DeaneryDemo.class.getAnnotations()); 
     assertEquals(1, roleTypes.size()); 
    } 
} 
3

Wie wäre das?

public enum RoleType { 
    STUDENT(Names.STUDENT), 
    TEACHER(Names.TEACHER), 
    DEANERY(Names.DEANERY); 

    public class Names{ 
     public static final String STUDENT = "Student"; 
     public static final String TEACHER = "Teacher"; 
     public static final String DEANERY = "Deanery"; 
    } 

    private final String label; 

    private RoleType(String label) { 
     this.label = label; 
    } 

    public String toString() { 
     return this.label; 
    } 
} 

Und in Anmerkung Sie es wie

@RolesAllowed(RoleType.Names.DEANERY) 
public void update(User p) { ... } 

Ein wenig Sorge verwenden können, ist, für jede Änderung, müssen wir an zwei Stellen ändern. Aber da sie sich in derselben Datei befinden, ist es sehr unwahrscheinlich, dass sie übersehen wird. Im Gegenzug erhalten wir den Vorteil, keine rohen Strings zu verwenden und den ausgefeilten Mechanismus zu vermeiden.

Oder das klingt total dumm? :)

+0

Danke, das gefällt mir wirklich und ich werde es benutzen. Ich kam ursprünglich zu dieser Frage, weil ich nach einer saubereren Möglichkeit suche, die 'name'-Eigenschaft in Jacksons' @ JsonSubTypes.Type'-Annotation so anzugeben, dass die Enums, die diese logischen Namen definieren, in der Annotation als verwendet werden können und für andere Teile der Anwendung verfügbar sein. – Trevor

+0

Froh, dass Sie @Trevor gemocht haben – Samiron

Verwandte Themen