AEM開発者ブログ by YAMATO

アドビ社のデリバリーパートナー大和株式会社のAEM開発者ブログです。

Sling Modelsに出てくるAnnotationって何!? Part.01

AEM Developerの皆様、どうも。大和株式会社の狩野です。

Sling Modelsにおいて重要な概念であるAnnotationについて解説いたします。 1つ1つの解説とサンプルコードが意外と分量があり、何度かに渡って更新したいと思います。 今回がPart.01となります。

Annotationという言葉の意味、概要

辞書的にいうと、「Annotation」という単語には「注釈」という意味があります。 転じて、プログラミング的には、変数やメソッドに注釈を付けて意味をもたせるという役割があります。

コードで説明すると、以下のようになります。

@Component(
    service = Servlet.class,
    property = {
            "sling.servlet.methods=post",
            "sling.servlet.paths=" + "/bin/any/path",
    }
)
public class TutorialClass extends SlingAllMethodsServlet {
    // any code
}

TutorialClass@Component というAnnotationを付与することによって、このクラスはComponentとして扱われます。 丸かっこ内に書かれた記述によって、この TutorialClass はServletであり、Postメソッドで /bin/any/path というパスに対してリクエストを受けたときに動作しますという意味です。

更に、以下のようなこともできます。

@Model(
    adaptables = {SlingHttpServletRequest.class},
    defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL
)public class TutorialClass2 {
    @ValueMapValue
    private String rootPath;
}

2つ目の例では、 @ValueMapValue Annotationにより、ダイアログに入力した値をこれだけの記述で取得することができます。 一旦 properties などで取ってきて、そこから properties.get() とかする必要すら無く、記述量も少なく済むので是非覚えましょう。

Sling Modelsで使われる各Annotationについての説明

@Model

org.apache.sling.models.annotations.Model @Model Annotationは、付与したクラスをSling Modelとして定義します。 以下の引数を与えます。

引数名 効果 必須 or 任意
adaptables 任意のクラスの配列 適応可能なクラス一覧。多くのオブジェクトをカバーしているSlingHttpServletRequest.class を指定するのが望ましい 必須
adapters 任意のクラスの配列 付与したクラスのフーパークラスやインターフェースを設定する 任意
defaultInjectionStrategy DefaultInjectionStrategy.class Sling Modelに挿入されたフィールドが必須かオプションかを決める。デフォルトは必須 任意
resourceType 文字列 Sling Modelsのリソースタイプ 任意

サンプルコード

よくある設定。

@Model(
    adaptables = { SlingHttpServletRequest.class },
    adapters = { TutorialModellClass.class },
    resourceType = { TutorialModelClasImpl.RESOURCE_TYPE },
    defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL)
public class TutorialModelClassImpl implements TutorialModelClass {
    protected static final String RESOURCE_TYPE = "myapp/any/sling/resourcetype";
    // any code
}

@Inject

javax.inject.Inject @Inject Annotationは、付与したフィールドにプロパティを挿入します。

代替のAnnotationが存在しているので、基本的に使わないほうがよい。

サンプルコード

以下のように @Inject Annotationを付与すると、 resourceResolver を以降のコードで使うことができるようになる。 しかし、 resourceResolver を使う場合は下の @SlingObject アノテーションを使ったコードを使うほうが望ましい。

@Inject
private ResourceResolver resourceResolver;

@SlingObject
private ResourceResolver resourceResolver;

@SlingObject

org.apache.sling.models.annotations.injectorspecific.SlingObject SlingHttpServletRequest, ResourceResolverもしくはResourceから派生する共通のSlingレベルのオブジェクトを挿入する。 @Model Annotationの引数 adaptablesSlingHttpServletRequest を設定すると、以下すべてがこのAnnotationで使用可能。

  • ResourceResolver
  • Resource
  • SlingHttpServletRequest
  • SlingHttpServletResponse
  • SlingScriptHelper

@ScriptVariable

org.apache.sling.models.annotations.injectorspecific.ScriptVariable @ScriptVariable Annotationは、HTL Global Object を変数に挿入する。

一部、上で紹介した @SlingObject と挿入できるオブジェクトが被っているが、こちらの @ScriptVariable の方が挿入できるオブジェクトの種類が多い。 一覧はここには書かないので、上記の「HTL Global Object」のリンクを参照。

@Via

org.apache.sling.models.annotations.Via @Via Annotationは @Self@Inject@ValueMapValue など他のAnnotationとセットで使われ、どういうクラスを経由して挿入するかを指定します。

サンプルコード

@Model(
    adaptables = { SlingHttpServletRequest.class },
    adapters = { TutorialModellClass.class },
    resourceType = { TutorialModelClasImpl.RESOURCE_TYPE },
    defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL)
public class TutorialModelClassImpl implements TutorialModelClass {
    @Inject
    @Via("resource")
    private List<Resource> multifieldValues;
}

こうすることによって、multifieldで複数選択した項目を List<Resource> という形で勝手に挿入してくれる。 下記のようなNode構造で保存されているのを勝手に変換してくれるので便利です。

multifieldValues
    +-- item0
        +-- property0="value0"
    +-- item1
        +-- property1="value1"

上記は @Inject を使って以下のように書くこともできます。

@Inject(via = "resource")
private List<Resource> multifieldValues;

また、以下のような使い方もあります。

@Model(adaptables = { SlingHttpServletRequest.class })
public interface TutorialModelClass {
    @Inject
    @Via(value = "jcr:content", type = ChildResource.class)
    String getPropertyName();
}

この getPropertyName() メソッドは以下のメソッドと同等の動きをします。

public String getPropertyName() {
    return resource.getChild("jcr:content").getValueMap().get("propertyName", String.class);
}

@ValueMapValue

org.apache.sling.models.annotations.injectorspecific.ValueMapValue @ValueMapValue Annotationは、呼び出し元のコンポーネントのダイアログの値を挿入できます。

サンプルコード

@Model(
    adaptables = { SlingHttpServletRequest.class },
    adapters = { TutorialModellClass.class },
    resourceType = { TutorialModelClasImpl.RESOURCE_TYPE },
    defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL)
public class TutorialModelClassImpl implements TutorialModelClass {
    // 変数名がdialogのnameプロパティの値が "rootPath" と一致している必要あり
    @ValueMapValue
    private String rootPath;

    public String getRootPath() {
        return rootPath;
    }
}

@Named

javax.inject.Named @Named Annotationは、挿入する対象の名前を明示的にします。

サンプルコード

先の @ValueMapValue Annotationで「変数名がdialogのnameプロパティと一致している必要がある」としましたが、この @Name Annotationを使用すれば、変数名を自由にできます。

@Model(
    adaptables = { SlingHttpServletRequest.class },
    adapters = { TutorialModellClass.class },
    resourceType = { TutorialModelClasImpl.RESOURCE_TYPE },
    defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL)
public class TutorialModelClassImpl implements TutorialModelClass {
    @ValueMapValue
    @Named("rootPath")
    private String topPagePath;

    public String getRootPath() {
        return topPagePath;
    }
}

@PostConstruct

javax.annotation.PostConstruct @PostConstruct Annotationは、付与したメソッドを初期化するためのメソッドとして定義します。

サンプルコード

@Model(
    adaptables = { SlingHttpServletRequest.class },
    adapters = { TutorialModellClass.class },
    resourceType = { TutorialModelClasImpl.RESOURCE_TYPE },
    defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL)
public class TutorialModelClassImpl implements TutorialModelClass {
    protected static final String RESOURCE_TYPE = "myapp/any/sling/resourcetype";
    private String toHTL;

    @PostConstruct
    protected void init() {
        toHTL = "print to HTL";
    }

    public String getToHTL() {
        return toHTL;
    }
}

@ResourcePath

org.apache.sling.models.annotations.injectorspecific.ResourcePath @ResourcePath AnnotationはResourceにpathの情報を付与するAnnotationです。 以下の引数を与えます。

引数名 効果 必須 or 任意
injectionStrategy InjectionStrategy.class Sling Modelに挿入されたフィールドが必須かオプションかを決める。デフォルトは必須 任意
name String プロパティの名前を指定する 任意
path String リソースパスを含むプロパティのパスを指定します 任意
paths String[] リソースの複数のパスを指定します 任意

サンプルコード

@Model(
    adaptables = { SlingHttpServletRequest.class },
    adapters = { TutorialModellClass.class },
    resourceType = { TutorialModelClasImpl.RESOURCE_TYPE },
    defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL)
public class TutorialModelClassImpl implements TutorialModelClass {
    @SlingObject
    @ResourcePath(name = "currentResource")
    Resource componentResource;

    @ResourcePath(path = "/content/we-retail/us/en")
    Resource givenPathResource;

    @ResourcePath(paths = {"/content/we-retail/us/en", "/content/we-retail/de/de"})
    Resource[] givenPathResources;

    public String getComponentResourcePath() {
        // Sling Modelsとして呼び出されたコンポーネントのresourceのパスを返す
        return componentResource.getPath();
    }

    public String getPath() {
        // "/content/we-retail/us/en"を返す
        return givenPathResource.getPath();
    }

    public List<String> getPaths() {
        return Arrays.asList(givenPathResources).stream()
                .map(Resource::getPath)
                .collect(Collectors.toList());
    }
}

最後に

次回は、 @Self , @ChildResource , @RequestAttribute , @OSGiService について解説します!

blog.yamato-ltd.com