java - values - Use Enum type como un parámetro de valor para @ RolesAllowed-Annotation
java enum valueof (3)
Estoy desarrollando una aplicación empresarial Java, actualmente estoy haciendo cosas de seguridad Java EE para restringir el acceso de funciones específicas a usuarios específicos. Configuré el servidor de aplicaciones y todo, y ahora estoy usando la anotación RolesAllowed para asegurar los métodos:
@Documented
@Retention (RUNTIME)
@Target({TYPE, METHOD})
public @interface RolesAllowed {
String[] value();
}
Cuando uso la anotación como esta, funciona bien:
@RolesAllowed("STUDENT")
public void update(User p) { ... }
Pero esto no es lo que quiero, ya que tengo que usar una Cadena aquí, la refacturación se vuelve difícil y pueden ocurrir errores tipográficos. Entonces, en lugar de usar un String, me gustaría utilizar un valor Enum como parámetro para esta anotación. El Enum se ve así:
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;
}
}
Así que traté de usar el Enum como un parámetro como este:
@RolesAllowed(RoleType.DEANERY.name())
public void update(User p) { ... }
Pero luego aparece el siguiente error del compilador, aunque Enum.name simplemente devuelve un String (que siempre es constante, ¿no?).
El valor para el atributo de anotación RolesAllowed.value debe ser una expresión constante`
Lo siguiente que probé fue agregar un String final adicional a mi Enum:
public enum RoleType {
...
public static final String STUDENT_ROLE = STUDENT.toString();
...
}
Pero esto tampoco funciona como un parámetro, lo que da como resultado el mismo error de compilación:
// The value for annotation attribute RolesAllowed.value must be a constant expression
@RolesAllowed(RoleType.STUDENT_ROLE)
¿Cómo puedo lograr el comportamiento que quiero? Incluso implementé mi propio interceptor para usar mis propias anotaciones, lo cual es hermoso, pero hay demasiadas líneas de códigos para un pequeño problema como este.
RENUNCIA
Esta pregunta fue originalmente una pregunta de Scala . Descubrí que Scala no es la fuente del problema, así que primero intento hacerlo en Java.
¿Qué tal esto?
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;
}
}
Y en la anotación puedes usarlo como
@RolesAllowed(RoleType.Names.DEANERY)
public void update(User p) { ... }
Una pequeña preocupación es que, para cualquier modificación, tenemos que cambiar en dos lugares. Pero dado que están en el mismo archivo, es poco probable que se pierda. A cambio, estamos obteniendo el beneficio de no usar cadenas sin formato y evitar el mecanismo sofisticado.
¿O esto suena totalmente estúpido? :)
Aquí hay una solución que usa una interfaz adicional y una meta-anotación. He incluido una clase de utilidad para ayudar a hacer la reflexión para obtener los tipos de roles de un conjunto de anotaciones, y una pequeña prueba para ello:
/**
* 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());
}
}
No creo que su enfoque de usar enumeraciones vaya a funcionar. Descubrí que el error del compilador desapareció si cambiaba el campo STUDENT_ROLE
en su ejemplo final por una cadena constante, en lugar de una expresión:
public enum RoleType {
...
public static final String STUDENT_ROLE = "STUDENT";
...
}
Sin embargo, esto significa que los valores de la enumeración no se usarán en ninguna parte, porque en su lugar usaría las constantes de cadena en las anotaciones.
Me parece que sería mejor si su clase RoleType
no contuviera más que un montón de constantes de cadena estáticas finales.
Para ver por qué su código no se estaba compilando, eché un vistazo a la Especificación del lenguaje Java (JLS). El JLS para annotations indica que para una anotación con un parámetro de tipo T y valor V ,
si T es un tipo primitivo o
String
, V es una expresión constante.
Una expresión constante incluye, entre otras cosas,
Nombres calificados de la forma TypeName . Identificador que se refiere a variables constantes
y una variable constante se define como
una variable, de tipo primitivo o tipo
String
, que es definitiva e inicializada con una expresión constante en tiempo de compilación