Java 使用枚举类型作为@RolesAllowed-Annotation 的值参数
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/3271659/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Use Enum type as a value parameter for @RolesAllowed-Annotation
提问by Ingo Fischer
I'm developing a Java enterprise application, currently doing Java EE security stuff to restrict access for particular functions to specific users. I configured the application server and everything, and now I'm using the RolesAllowed-annotation to secure the methods:
我正在开发一个 Java 企业应用程序,目前正在执行 Java EE 安全性工作,以限制特定用户对特定功能的访问。我配置了应用程序服务器和所有东西,现在我使用 RolesAllowed-annotation 来保护这些方法:
@Documented
@Retention (RUNTIME)
@Target({TYPE, METHOD})
public @interface RolesAllowed {
String[] value();
}
When I use the annotation like this, it works fine:
当我使用这样的注释时,它工作正常:
@RolesAllowed("STUDENT")
public void update(User p) { ... }
But this is not what I want, as I have to use a String here, refactoring becomes hard, and typos can happen. So instead of using a String, I would like to use an Enum value as a parameter for this annotation. The Enum looks like this:
但这不是我想要的,因为我必须在这里使用 String,重构变得困难,并且可能会发生拼写错误。因此,我想使用 Enum 值作为此注释的参数,而不是使用 String。枚举看起来像这样:
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;
}
}
So I tried to use the Enum as a parameter like this:
所以我尝试使用 Enum 作为这样的参数:
@RolesAllowed(RoleType.DEANERY.name())
public void update(User p) { ... }
But then I get the following compiler error, although Enum.name just returns a String (which is always constant, isn't it?).
但是后来我得到以下编译器错误,虽然 Enum.name 只返回一个字符串(它总是常量,不是吗?)。
The value for annotation attribute RolesAllowed.value must be a constant expression`
注释属性 RolesAllowed.value 的值必须是常量表达式`
The next thing I tried was to add an additional final String to my Enum:
我尝试的下一件事是向我的 Enum 添加一个额外的最终字符串:
public enum RoleType {
...
public static final String STUDENT_ROLE = STUDENT.toString();
...
}
But this also doesn't work as a parameter, resulting in the same compiler error:
但这也不能用作参数,导致相同的编译器错误:
// The value for annotation attribute RolesAllowed.value must be a constant expression
@RolesAllowed(RoleType.STUDENT_ROLE)
How can I achieve the behavior I want? I even implemented my own interceptor to use my own annotations, which is beautiful, but far too much lines of codes for a little problem like this.
我怎样才能实现我想要的行为?我什至实现了我自己的拦截器来使用我自己的注释,这很漂亮,但是对于这样的小问题,代码行太多了。
DISCLAIMER
免责声明
This question was originally a Scalaquestion. I found out that Scala is not the source of the problem, so I first try to do this in Java.
这个问题最初是一个Scala问题。我发现 Scala 不是问题的根源,所以我首先尝试在 Java 中执行此操作。
采纳答案by Luke Woodward
I don't think your approach of using enums is going to work. I found that the compiler error went away if I changed the STUDENT_ROLE
field in your final example to a constant string, as opposed to an expression:
我认为您使用枚举的方法行不通。我发现如果我将STUDENT_ROLE
最后一个示例中的字段更改为常量字符串,而不是表达式,编译器错误就会消失:
public enum RoleType {
...
public static final String STUDENT_ROLE = "STUDENT";
...
}
However, this then means that the enum values wouldn't be used anywhere, because you'd be using the string constants in annotations instead.
但是,这意味着枚举值不会在任何地方使用,因为您将在注释中使用字符串常量。
It seems to me that you'd be better off if your RoleType
class contained nothing more than a bunch of static final String constants.
在我看来,如果你的RoleType
类只包含一堆静态最终字符串常量,你会更好。
To see why your code wasn't compiling, I had a look into the Java Language Specification(JLS). The JLS for annotationsstates that for an annotation with a parameter of type Tand value V,
为了了解为什么您的代码无法编译,我查看了Java 语言规范(JLS)。注释的 JLS指出,对于具有类型T和值V的参数的注释,
if Tis a primitive type or
String
, Vis a constant expression.
如果T是原始类型或
String
,则V是常量表达式。
A constant expressionincludes, amongst other things,
甲常量表达式包括,除其他外,
Qualified names of the form TypeName. Identifierthat refer to constant variables
形式为TypeName 的限定名称。 引用常量变量的标识符
and a constant variableis defined as
一个常量变量定义为
a variable, of primitive type or type
String
, that is final and initialized with a compile-time constant expression
一个原始类型或类型的变量,
String
它是最终的并用编译时常量表达式初始化
回答by anomolos
Here's a solution using an additional interface and a meta-annotation. I've included a utility class to help do the reflection to get the role types from a set of annotations, and a little test for it:
这是使用附加接口和元注释的解决方案。我已经包含了一个实用程序类来帮助进行反射以从一组注释中获取角色类型,并对其进行了一些测试:
/**
* 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());
}
}
回答by Samiron
How about this?
这个怎么样?
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;
}
}
And in annotation you can use it like
在注释中你可以像这样使用它
@RolesAllowed(RoleType.Names.DEANERY)
public void update(User p) { ... }
One little concern is, for any modification, we need to change in two places. But since they are in same file, its quite unlikely to be missed. In return, we are getting the benefit of not using raw strings and avoiding the sophisticated mechanism.
一个小问题是,对于任何修改,我们需要在两个地方进行更改。但由于它们在同一个文件中,因此不太可能被遗漏。作为回报,我们获得了不使用原始字符串和避免复杂机制的好处。
Or this sounds totally stupid? :)
或者这听起来很愚蠢?:)
回答by John B
I solved this problem by adding an annotation @RoleTypesAllowed
and adding a metadata source. This works really well if there is just one enum type that needs to be supported. For multiple enum types, see anomolos's post.
我通过添加注释@RoleTypesAllowed
和添加元数据源解决了这个问题。如果只需要支持一种枚举类型,这会非常有效。对于多种枚举类型,请参阅 anomolos 的帖子。
In the below RoleType
is my role enum.
下面RoleType
是我的角色枚举。
@Documented
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface RoleTypesAllowed {
RoleType[] value();
}
Then I added the following metadata source to spring...
然后我将以下元数据源添加到 spring...
@Slf4j
public class CemsRolesAllowedMethodSecurityMetadataSource
extends AbstractFallbackMethodSecurityMetadataSource {
protected Collection<ConfigAttribute> findAttributes(Class<?> clazz) {
return this.processAnnotations(clazz.getAnnotations());
}
protected Collection<ConfigAttribute> findAttributes(Method method, Class<?> targetClass) {
return this.processAnnotations(AnnotationUtils.getAnnotations(method));
}
public Collection<ConfigAttribute> getAllConfigAttributes() {
return null;
}
private List<ConfigAttribute> processAnnotations(Annotation[] annotations) {
if (annotations != null && annotations.length != 0) {
List<ConfigAttribute> attributes = new ArrayList();
for (Annotation a : annotations) {
if (a instanceof RoleTypesAllowed) {
RoleTypesAllowed ra = (RoleTypesAllowed) a;
RoleType[] alloweds = ra.value();
for (RoleType allowed : alloweds) {
String defaultedAllowed = new RoleTypeGrantedAuthority(allowed).getAuthority();
log.trace("Added role attribute: {}", defaultedAllowed);
attributes.add(new SecurityConfig(defaultedAllowed));
}
return attributes;
}
}
}
return null;
}
}