web-dev-qa-db-ja.com

@ RolesAllowed-Annotationの値パラメーターとして列挙型を使用する

私はJavaエンタープライズアプリケーションを開発しています。現在はJava特定の機能へのアクセスを特定のユーザーに制限するEEセキュリティ関連。アプリケーションサーバーとすべてを設定しました、そして今、私はRolesAllowed-annotationを使用してメソッドを保護しています:

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

このような注釈を使用すると、正常に機能します。

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

しかし、これは私が望んでいるものではありません。ここでは文字列を使用する必要があるため、リファクタリングが難しくなり、タイプミスが発生する可能性があります。したがって、文字列を使用する代わりに、この注釈のパラメーターとして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;
    }
}

だから私はこのようなパラメータとして列挙型を使用しようとしました:

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

しかし、Enum.nameはStringを返すだけですが、次のコンパイラエラーが発生します(常に定数です)。

アノテーション属性RolesAllowed.valueの値は定数式でなければなりません `

次に試したのは、最後の文字列を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でこれを実行しようとしました。

55
Ingo Fischer

列挙型を使用するあなたのアプローチがうまくいくとは思わない。最後の例のSTUDENT_ROLEフィールドを式ではなく定数文字列に変更すると、コンパイラエラーがなくなることがわかりました。

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

ただし、これは、代わりに注釈で文字列定数を使用するため、列挙値はどこでも使用されないことを意味します。

RoleTypeクラスに静的な最終文字列定数の束しか含まれていない方が良いと思われます。


コードがコンパイルされなかった理由を確認するために、 Java Language Specification (JLS)を調べました。 annotations のJLSは、タイプ[〜#〜] t [〜#〜]のパラメーターを持つアノテーションについて述べています。および値[〜#〜] v [〜#〜]

if[〜#〜] t [〜#〜]はプリミティブ型またはString[〜#〜] v [〜#〜]は定数式です。

定数式 には、とりわけ、

形式の修飾名TypeName定数変数を参照する識別子

定数変数 は次のように定義されます

プリミティブ型またはString型の変数。最終変数であり、コンパイル時の定数式で初期化されます

31
Luke Woodward

これはどう?

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) { ... }

少し心配なのは、どんな変更でも、2つの場所で変更する必要があるということです。しかし、それらは同じファイルにあるため、見落とされることはほとんどありません。その見返りとして、生の文字列を使用せず、洗練されたメカニズムを回避するという利点を得ています。

それともまったくばかげているように聞こえますか? :)

20
Samiron

追加のインターフェイスとメタ注釈を使用したソリューションを次に示します。一連の注釈からロールタイプを取得するためのリフレクションを支援するユーティリティクラスと、そのための小さなテストを含めました。

/**
 * 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());
    }
}
9
anomolos

注釈@RoleTypesAllowedを追加し、メタデータソースを追加することで、この問題を解決しました。サポートする必要がある列挙型が1つしかない場合、これは非常にうまく機能します。複数の列挙型については、anomolosの投稿を参照してください。

以下のRoleTypeは私の役割の列挙です。

@Documented
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface RoleTypesAllowed {
  RoleType[] value();
}

次に、次のメタデータソースを春に追加しました...

@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;
  }
}
0
John B