2010-07-17 111 views
45

我正在開發一個Java企業應用程序,目前正在執行Java EE安全性內容以限制特定用戶對特定功能的訪問。我配置了應用服務器和一切,現在我使用的RolesAllowed的註釋,以確保方法:使用枚舉類型作爲@ RolesAllowed-Annotation的值參數

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

當我使用這樣的註解,它工作正常:

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

但這不是我想要的,因爲我必須在這裏使用字符串,重構變得困難,並且可能發生錯字。因此,我不想使用字符串,而是使用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; 
    } 
} 

於是,我就用枚舉作爲這樣的參數:

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

但後來我得到以下編譯器錯誤,儘管Enum.name只返回一個字符串(這總是不變的,不是嗎?)。

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

我想接下來的事情是一個額外的最終字符串添加到我的枚舉:

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參數給你的枚舉構造函數;您可以通過在枚舉上調用.name()來訪問相同的值。我相信toString()方法委託給name()。 – I82Much 2010-07-17 15:40:24

+0

可能的重複[如何提供枚舉值從Java常量註釋](http://stackoverflow.com/questions/13253624/how-to-supply-enum-value-to-an-annotation-from- a-constant-in-java) – 2015-07-31 09:26:36

回答

29

我不認爲你使用枚舉的方法會起作用。我發現,編譯器錯誤走了,如果我在你的最後一個例子改變了STUDENT_ROLE領域的一個常量字符串,而不是一個表達式:

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

然而,這則意味着枚舉值將不會被使用任何地方,因爲你會在註釋中使用字符串常量。

在我看來,如果你的RoleType類只包含一堆靜態最終的String常量,那麼你會更好。


要知道爲什麼你的代碼是沒有編制,我看看到Java Language Specification(JLS)。所述JLS爲annotations指出,對於與類型T和值V的參數的註釋,

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被定義as

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

+4

感謝您的努力!有趣的事實。尤其是最後一個。總是認爲決賽已經是常數。好吧,這就是爲什麼它不能工作。在我看來,這已經是答案。雖然我對它並不滿意)(順便說一句,我確實需要Enum,不僅僅用於註釋) – ifischer 2010-07-17 19:15:05

+0

我確實遇到過[示例](http://java.dzone.com/articles/glassfish- 3-30分鐘),聲明'public interface Roles {String REGISTERED =「registered」; }'然後使用@RolesAllowed({Roles.REGISTERED})'。當然,一個例子*不使用'enum'並不意味着'enum'產生問題,但是好的;-) – Arjan 2012-01-15 21:10:22

7

這裏'這是一個使用額外界面和元註釋的解決方案。我已經包含了一個實用工具類來幫助做反射,從一組註釋中獲取角色類型,並對其進行一些測試:

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

謝謝,我真的很喜歡這個,我會用它。最初我遇到這個問題是因爲我正在尋找一種更簡潔的方式來指定Jackson的'@ JsonSubTypes.Type'註釋中的'name'屬性,使得定義這些邏輯名稱的枚舉可以在註釋中使用以及可用於應用程序的其他部分。 – Trevor 2018-01-25 10:25:29

+0

很高興你喜歡@Trevor – Samiron 2018-01-27 15:43:57