使用枚举类型作为@ RolesAllowed-Annotation的值参数

Ing*_*her 55 java enums annotations java-ee java-ee-6

我正在开发一个Java企业应用程序,目前正在使用Java EE安全性来限制特定用户对特定功能的访问.我配置了应用程序服务器和所有内容,现在我使用RolesAllowed-annotation来保护方法:

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

当我使用这样的注释时,它工作正常:

@RolesAllowed("STUDENT")
public void update(User p) { ... }
Run Code Online (Sandbox Code Playgroud)

但这不是我想要的,因为我必须在这里使用String,重构变得困难,并且可能发生拼写错误.因此,我想使用Enum值作为此注释的参数,而不是使用String.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;
    }
}
Run Code Online (Sandbox Code Playgroud)

所以我尝试使用Enum作为这样的参数:

@RolesAllowed(RoleType.DEANERY.name())
public void update(User p) { ... }
Run Code Online (Sandbox Code Playgroud)

但是后来我得到了以下编译器错误,虽然Enum.name只返回一个String(它总是不变的,不是吗?).

注释属性RolesAllowed.value的值必须是常量表达式`

我尝试的下一件事是在我的枚举中添加一个额外的最终字符串:

public enum RoleType {
    ...
    public static final String STUDENT_ROLE = STUDENT.toString();
    ...
}
Run Code Online (Sandbox Code Playgroud)

但这也不能作为参数,导致相同的编译器错误:

// The value for annotation attribute RolesAllowed.value must be a constant expression
@RolesAllowed(RoleType.STUDENT_ROLE)
Run Code Online (Sandbox Code Playgroud)

我怎样才能实现我想要的行为?我甚至实现了自己的拦截器来使用我自己的注释,这是很漂亮的,但是对于像这样的小问题,代码行太多了.

免责声明

这个问题最初是一个Scala问题.我发现Scala不是问题的根源,所以我首先尝试用Java做到这一点.

Luk*_*ard 31

我认为你使用枚举的方法不会起作用.我发现如果我将STUDENT_ROLE最后一个示例中的字段更改为常量字符串,而不是表达式,编译器错误就消失了:

public enum RoleType { 
  ...
  public static final String STUDENT_ROLE = "STUDENT";
  ...
}
Run Code Online (Sandbox Code Playgroud)

但是,这意味着枚举值不会在任何地方使用,因为您将在注释中使用字符串常量.

在我看来,如果你的RoleType类只包含一堆静态的最终String常量,那么你会更好.


为了了解您的代码未编译的原因,我查看了Java语言规范(JLS).注释的JLS 指出对于具有类型T和值V的参数的注释,

如果Ť是原始类型或String,V是一个常量表达式.

常量表达式包括,除其他外,

TypeName表单的限定名称. 引用常量变量的标识符

并且常量变量定义为

一个原始类型或类型的变量,String它是最终的,并用编译时常量表达式初始化

  • 谢谢你的努力!有趣的事实.特别是最后一个.一直认为决赛已经是常数.好的,这就是它无法工作的原因.在我看来,这已经是答案.虽然我对它并不满意;)(顺便说一句,我确实需要Enum,不仅仅是注释) (4认同)

Sam*_*ron 22

这个怎么样?

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;
    }
}
Run Code Online (Sandbox Code Playgroud)

在注释中你可以像使用它一样

@RolesAllowed(RoleType.Names.DEANERY)
public void update(User p) { ... }
Run Code Online (Sandbox Code Playgroud)

一个小问题是,对于任何修改,我们需要在两个地方进行更改.但由于它们属于同一档案,因此不太可能被遗漏.作为回报,我们正在获得不使用原始字符串和避免复杂机制的好处.

或者这听起来完全愚蠢?:)

  • 谢谢,我真的很喜欢这个,我会用它。我最初提出这个问题是因为我正在寻找一种更清晰的方法来指定 Jackson 的“@JsonSubTypes.Type”注释中的“名称”属性,这样定义这些逻辑名称的枚举可以在注释中使用,如下所示以及可用于应用程序的其他部分。 (3认同)

小智 15

我通过使用 Lombok 注释解决了这个问题FieldNameConstants

@FieldNameConstants(onlyExplicitlyIncluded = true)
public enum EnumBasedRole {
    @FieldNameConstants.Include ADMIN,
    @FieldNameConstants.Include EDITOR,
    @FieldNameConstants.Include READER;
}
Run Code Online (Sandbox Code Playgroud)

接下来您可以按如下方式使用它:

@RestController
@RequestMapping("admin")
@RolesAllowed(EnumBasedRole.Fields.ADMIN)
public class MySecuredController {

   @PostMapping("user")
   public void deleteUser(...) {
       ...
   }
}
Run Code Online (Sandbox Code Playgroud)


小智 9

这是使用附加接口和元注释的解决方案.我已经包含了一个实用程序类来帮助进行反射以从一组注释中获取角色类型,并对其进行一些测试:

/**
 * 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());
    }
}
Run Code Online (Sandbox Code Playgroud)