2010-07-17 3 views
45

Я занимаюсь разработкой корпоративного приложения Java, которое в настоящее время использует материалы безопасности Java EE для ограничения доступа к определенным функциям для конкретных пользователей. Я настроил сервер приложений и все, и теперь я использую RolesAllowed-аннотацию, чтобы обеспечить методы:Использовать тип Enum в качестве параметра значения для @ RolesAllowed-Annotation

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

Когда я использую аннотации, как это, это работает отлично:

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

Но это не то, что я хочу, так как я должен использовать String здесь, рефакторинг становится тяжелым, и опечатки могут произойти. Поэтому вместо использования String я хотел бы использовать значение Enum в качестве параметра для этой аннотации. Enum выглядит следующим образом:

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; 
    } 
} 

Так что я попытался использовать Enum в качестве параметра, например так:

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

Но я получаю следующую ошибку компиляции, хотя Enum.name просто возвращает строку (который всегда постоянный, не так ли?).

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

Следующая вещь, которую я попытался было добавить еще окончательный строки в моей Enum:

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

Но это также не работает в качестве параметра, в результате чего та же ошибка компилятора:

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

Как достичь желаемого поведения? Я даже реализовал свой собственный перехватчик, чтобы использовать свои собственные аннотации, что красиво, но слишком много строк кода для такой небольшой проблемы.

ОТКАЗ

Этот вопрос был первоначально Scala вопрос. Я узнал, что Scala не является источником проблемы, поэтому я сначала попытаюсь сделать это на Java.

+1

Извините, что это немного не имеет отношения к решению вашей проблемы, но я думал, что упомянул, что вы можете обойтись без аргумента String для своих конструкторов enum, если вы просто будете иметь их такими же, как вы их называете ; вы можете получить доступ к этому же значению, вызвав имя .name() в перечислении. Я считаю, что метод toString() делегирует имя() в любом случае. – I82Much

+0

Возможный дубликат [Как указать значение Enum для аннотации из константы в Java] (http://stackoverflow.com/questions/13253624/how-to-supply-enum-value-to-an-annotation-from- a-constant-in-java) –

ответ

29

Я не думаю, что ваш подход к использованию перечислений будет работать. Я обнаружил, что ошибка компилятора ушел, если я изменил STUDENT_ROLE поле в последнем примере с постоянной строкой, в отличие от выражения:

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

Однако, это тогда означает, что значения перечислений не будут использоваться в любом месте, потому что вместо этого вы должны использовать строковые константы в аннотациях.

Мне кажется, что вам будет лучше, если ваш класс RoleType содержит не более, чем кучу статических окончательных строковых констант.


Чтобы понять, почему ваш код не компиляции, я должен был заглянуть в Java Language Specification (JLS).ПСБ для annotations утверждает, что для аннотации с параметром типа T и значением В,

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

constant expression включает в себя, среди прочего,

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

и constant variable определен как

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

+4

Спасибо за ваши усилия! Интересные факты. Особенно последний. Всегда думал, что финалы уже являются константами. Хорошо, поэтому он не может работать. Мне кажется, что это уже ответ. Хотя я не очень доволен этим;) (BTW мне действительно нужен Enum, а не только для аннотации) – ifischer

+0

Я действительно столкнулся с [примером] (http://java.dzone.com/articles/glassfish- 3-30-минутный), объявляющий «public interface Roles {String REGISTERED =« зарегистрированный »; } ', а затем использует' @RolesAllowed ({Roles.REGISTERED}) '. Конечно, один пример * not * using 'enum' не означает, что' enum' дает проблемы, но хорошо ;-) – Arjan

7

Адрес: s решение с использованием дополнительного интерфейса и мета-аннотации. Я включил служебный класс, чтобы помочь сделать отражение, чтобы получить типы ролей из набора аннотаций и небольшой тест для этого:

/** 
* 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

Как насчет этого?

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; 
    } 
} 

И в аннотации вы можете использовать его как

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

Один мало беспокойства есть, для каких-либо изменений, мы должны изменить в двух местах. Но поскольку они находятся в одном файле, его вряд ли удастся упустить. В свою очередь, мы получаем выгоду от использования необработанных строк и избежания сложного механизма.

Или это звучит совершенно глупо? :)

+0

Спасибо, мне это очень нравится, и я буду использовать его. Я пришел к этому вопросу изначально, потому что я ищу более чистый способ указать свойство «name» в аннотации '@ JsonSubTypes.Type' от Jackson таким образом, что перечисления, которые определяют эти логические имена, могут использоваться в аннотации как а также доступны для других частей приложения. – Trevor

+0

Рад, что вам понравилось @Trevor – Samiron

Смежные вопросы