Java™ API最佳作法

Adobe Experience Manager (AEM)是以豐富的開放原始碼軟體棧疊為基礎,在開發期間公開許多Java™ API以供使用。 本文會探索主要的API,以及何時應該使用這些API以及其使用原因。

AEM建置在四個主要的Java™ API集上。

  • Adobe Experience Manager (AEM)

    • 產品抽象概念,例如頁面、資產、工作流程等。
  • Apache Sling Web Framework

    • REST和以資源為基礎的抽象,例如資源、值對應和HTTP要求。
  • JCR (Apache Jackrabbit Oak)

    • 資料和內容抽象概念,例如,節點、屬性和工作階段。
  • OSGi (Apache Felix)

    • OSGi應用程式容器抽象概念,例如服務和(OSGi)元件。

Java™ API偏好設定「經驗法則」

一般規則是依下列順序偏好API/抽象化:

  1. AEM
  2. Sling
  3. JCR
  4. OSGi

如果API是由AEM提供,則偏好使用API而非Sling、JCR和OSGi。 如果AEM不提供API,則偏好使用Sling而非JCR和OSGi。

此順序為一般規則,表示存在例外。 可以接受中斷此規則的原因是:

  • 眾所周知的例外,如下所述。

  • 較高層級的API中沒有必要功能。

  • 在現有程式碼(自訂或AEM產品程式碼)的上下文中操作,而現有程式碼本身使用較不偏好的API,且移至新API的成本不合理。

    • 一致使用低階API比建立混合更好。

AEM API

AEM API提供產品化使用案例專用的抽象概念與功能。

例如,AEM PageManagerPage API為AEM中代表網頁的cq:Page節點提供抽象概念。

雖然這些節點可透過Sling API作為資源使用,以及透過JCR API作為節點使用,但AEM API為常見使用案例提供抽象概念。 使用AEM API可確保AEM產品與AEM的自訂和擴充功能之間的一致行為。

com.adobe.*與com.day。* API

AEM API具有套件內偏好設定,依偏好設定的順序由下列Java™套件識別:

  1. com.adobe.cq
  2. com.adobe.granite
  3. com.day.cq

com.adobe.cq套件支援產品使用案例,而com.adobe.granite則支援跨產品平台使用案例,例如工作流程或工作(用於跨產品:AEM Assets、Sites等)。

com.day.cq套件包含「原始」API。 這些API處理在Adobe贏取Day CQ之前和/或前後存在的核心抽象概念與功能。 這些API受到支援,應避免使用,除非com.adobe.cqcom.adobe.granite套件未提供(較新的)替代方案。

新的抽象概念(例如Content Fragments和Experience Fragments)是建置在com.adobe.cq空間中,而非如下所述的com.day.cq

查詢API

AEM支援多種查詢語言。 三種主要語言為JCR-SQL2、XPath和AEM Query Builder

最重要的考量是在程式碼庫中維持一致的查詢語言,以降低複雜度和理解成本。

所有查詢語言實際上都有相同的效能設定檔,因為Apache Oak會將其轉儲至JCR-SQL2以進行最終查詢執行,而且與JCR-SQL2的查詢時間本身相比,轉換時間可忽略不計。

偏好的API是AEM Query Builder,這是最高級別的抽象化,提供健全API來建構、執行和擷取查詢的結果,並提供下列專案:

CAUTION
AEM QueryBuilder API洩漏ResourceResolver物件。 若要減少這個洩露,請遵循此程式碼範例

Sling API

Apache Sling是支援AEM的RESTful Web架構。 Sling提供HTTP要求路由、將JCR節點建模為資源、提供安全性內容等等。

Sling API具有為擴充功能建置的額外優點,這表示與較不容易擴充的JCR API相比,使用Sling API建置的應用程式行為更容易且更安全。

Sling API的常見用法

JCR API

JCR (Java™ Content Repository) 2.0 API是JCR實作規格的一部分(在AEM的情況下,Apache Jackrabbit Oak)。 所有JCR實作都必須符合併實作這些API,因此,是與AEM內容互動的最低層級API。

JCR本身是階層式/樹狀結構的NoSQL資料存放區,AEM會將其當作內容存放庫。 JCR具有大量受支援的API,範圍從內容CRUD到查詢內容。 儘管有這個強大的API,但很少比更高層級AEM和Sling抽象更偏好。

與Apache Jackrabbit Oak API相比,總是偏好JCR API。 JCR API適用於與JCR存放庫互動​ ​**,而Oak API則適用於​ 實作 JCR存放庫。

JCR API的常見誤解

雖然JCR是AEM的內容存放庫,但其API並非與內容互動的偏好方法。 相反地,他們偏好AEM API (Page、Assets、Tag等)或Sling Resource API,因為這些提供更好的抽象化。

CAUTION
在AEM應用程式中,廣泛使用JCR API的工作階段和節點介面會導致程式碼異味。 請確定應該改用Sling API。

JCR API的常見用法

OSGi API

OSGi API與較高層級API (AEM、Sling和JCR)之間幾乎沒有重疊,而且很少需要使用OSGi API,需要高層次的AEM開發專業知識。

OSGi與Apache Felix API

OSGi會定義所有OSGi容器都必須實作並遵循的規格。 AEM的OSGi實作Apache Felix也提供自己的數個API。

  • 偏好使用OSGi API (org.osgi)而非Apache Felix API (org.apache.felix)。

OSGi API的常見用法

規則的例外

以下是上述定義規則的常見例外。

OSGi API

處理低階OSGi抽象化(例如在OSGi元件屬性中定義或讀取)時,org.osgi提供的較新抽象化會優先於較高層級的Sling抽象化。 競爭的Sling抽象化尚未標示為@Deprecated並建議替代的org.osgi

另請注意,OSGi設定節點定義偏好cfg.json而非sling:OsgiConfig格式。

AEM資產API

  • 偏好com.day.cq.dam.api而非com.adobe.granite.asset.api

    • 雖然com.day.cq Assets API為AEM的資產管理使用案例提供更免費的工具。
    • Granite Assets API支援低階資產管理使用案例(版本、關係)。

查詢API

  • AEM QueryBuilder不支援某些查詢函式,例如建議、拼字檢查,以及其他不太常見的函式中的索引提示。 若要使用這些函式進行查詢,建議使用JCR-SQL2。

Sling Servlet註冊 sling-servlet-registration

Sling篩選器註冊 sling-filter-registration

有用的程式碼片段

以下是有用的Java™程式碼片段,說明使用已討論API的常見使用案例的最佳實務。 這些片段也說明如何從較不偏好的API移至較偏好的API。

Sling ResourceResolver的JCR工作階段

自動關閉Sling ResourceResolver

自AEM 6.2起,try-with-resources陳述式中的Sling ResourceResolver為AutoClosable。 使用此語法時,不需要明確呼叫resourceResolver .close()

@Reference
ResourceResolverFactory rrf;
...
Map<String, Object> authInfo = new HashMap<String, Object>();
authInfo.put(JcrResourceConstants.AUTHENTICATION_INFO_SESSION, jcrSession);

try (ResourceResolver resourceResolver = rrf.getResourceResolver(authInfo)) {
    // Do work with the resourceResolver
} catch (LoginException e) { .. }

手動關閉Sling ResourceResolver

如果無法使用上述的自動關閉技術,則必須在finally區塊中手動關閉ResourceResolvers。

@Reference
ResourceResolverFactory rrf;
...
Map<String, Object> authInfo = new HashMap<String, Object>();
authInfo.put(JcrResourceConstants.AUTHENTICATION_INFO_SESSION, jcrSession);

ResourceResolver resourceResolver = null;

try {
    resourceResolver = rrf.getResourceResolver(authInfo);
    // Do work with the resourceResolver
} catch (LoginException e) {
   ...
} finally {
    if (resourceResolver != null) { resourceResolver.close(); }
}

Sling的JCR路徑Resource

Resource resource = ResourceResolver.getResource("/path/to/the/resource");

JCR節點至Sling Resource

Resource resource = resourceResolver.getResource(node.getPath());

Sling Resource至AEM資產

建議做法

DamUtil.resolveToAsset(..)函式會視需要向上瀏覽樹狀結構,將dam:Asset下的任何資源解析為Asset物件。

Asset asset = DamUtil.resolveToAsset(resource);

替代方法

將資源調整為適合資產,資源本身必須是dam:Asset節點。

Asset asset = resource.adaptTo(Asset.class);

Sling資源至AEM頁面

建議做法

pageManager.getContainingPage(..)會視需要向上瀏覽樹狀結構,將cq:Page下的任何資源解析為Page物件。

PageManager pageManager = resourceResolver.adaptTo(PageManager.class);
Page page = pageManager.getContainingPage(resource);
Page page2 = pageManager.getContainingPage("/content/path/to/page/jcr:content/or/component");

替代方法 alternative-approach-1

將資源調整成頁面需要資源本身成為cq:Page節點。

Page page = resource.adaptTo(Page.class);

讀取AEM頁面屬性

使用Page物件的getter取得已知屬性(getTitle()getDescription()等)和page.getProperties()取得[cq:Page]/jcr:content ValueMap,以擷取其他屬性。

Page page = resource.adaptTo(Page.class);
String title = page.getTitle();
Calendar value = page.getProperties().get("cq:lastModified", Calendar.getInstance());

讀取AEM資產中繼資料屬性

Asset API提供從[dam:Asset]/jcr:content/metadata節點讀取屬性的便利方法。 這不是ValueMap,不支援第二個引數(預設值和自動型別轉換)。

Asset asset = resource.adaptTo(Asset.class);
String title = asset.getMetadataValue("dc:title");
Calendar lastModified = (Calendar) asset.getMetadata("cq:lastModified");

讀取Sling Resource屬性 read-sling-resource-properties

當屬性儲存在AEM API (頁面、資產)無法直接存取的位置(屬性或相對資源)時,可以使用Sling資源和ValueMaps來取得資料。

ValueMap properties = resource.getValueMap();
String value = properties.get("jcr:title", "Default title");
String relativeResourceValue = properties.get("relative/propertyName", "Default value");

在此情況下,可能必須將AEM物件轉換為Sling Resource,才能有效找到所需的屬性或子資源。

AEM頁面至Sling Resource

Resource resource = page.adaptTo(Resource.class);

AEM資產到Sling Resource

Resource resource = asset.adaptTo(Resource.class);

使用Sling的ModifiableValueMap寫入內容

使用Sling的ModifiableValueMap將屬性寫入節點。 這只能寫入至立即節點(不支援相對屬性路徑)。

請注意,呼叫.adaptTo(ModifiableValueMap.class)需要資源的寫入許可權,否則會傳回null。

ModifiableValueMap properties = resource.adaptTo(ModifiableValueMap.class);

properties.put("newPropertyName", "new value");
properties.put("propertyNameToUpdate", "updated value");
properties.remove("propertyToRemove");

resource.getResourceResolver().commit();

建立AEM頁面

您必須一律使用PageManager建立頁面,就像使用「頁面範本」一樣,才能在AEM中正確定義和初始化「頁面」。

String templatePath = "/conf/my-app/settings/wcm/templates/content-page";
boolean autoSave = true;

PageManager pageManager = resourceResolver.adaptTo(PageManager.class);
pageManager.create("/content/parent/path", "my-new-page", templatePath, "My New Page Title", autoSave);

if (!autoSave) { resourceResolver.commit(); }

建立Sling資源

ResourceResolver支援建立資源的基本作業。 建立較高層級的抽象概念(AEM Pages、Assets、Tags等等)時,請使用其各自管理員提供的方法。

resourceResolver.create(parentResource, "my-node-name", new ImmutableMap.Builder<String, Object>()
           .put("jcr:primaryType", "nt:unstructured")
           .put("jcr:title", "Hello world")
           .put("propertyName", "Other initial properties")
           .build());

resourceResolver.commit();

刪除Sling資源

ResourceResolver支援移除資源。 建立較高層級的抽象概念(AEM Pages、Assets、Tags等等)時,請使用其各自管理員提供的方法。

resourceResolver.delete(resource);

resourceResolver.commit();
recommendation-more-help
c92bdb17-1e49-4e76-bcdd-89e4f85f45e6