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/抽象化:
- AEM
- Sling
- JCR
- OSGi
如果API是由AEM提供,則偏好使用API而非Sling、JCR和OSGi。 如果AEM不提供API,則偏好使用Sling而非JCR和OSGi。
此順序為一般規則,表示存在例外。 可以接受中斷此規則的原因是:
-
眾所周知的例外,如下所述。
-
較高層級的API中沒有必要功能。
-
在現有程式碼(自訂或AEM產品程式碼)的上下文中操作,而現有程式碼本身使用較不偏好的API,且移至新API的成本不合理。
- 一致使用低階API比建立混合更好。
AEM API
AEM API提供產品化使用案例專用的抽象概念與功能。
例如,AEM PageManager和Page API為AEM中代表網頁的cq:Page
節點提供抽象概念。
雖然這些節點可透過Sling API作為資源使用,以及透過JCR API作為節點使用,但AEM API為常見使用案例提供抽象概念。 使用AEM API可確保AEM產品與AEM的自訂和擴充功能之間的一致行為。
com.adobe.*與com.day。* API
AEM API具有套件內偏好設定,依偏好設定的順序由下列Java™套件識別:
com.adobe.cq
com.adobe.granite
com.day.cq
com.adobe.cq
套件支援產品使用案例,而com.adobe.granite
則支援跨產品平台使用案例,例如工作流程或工作(用於跨產品:AEM Assets、Sites等)。
com.day.cq
套件包含「原始」API。 這些API處理在Adobe贏取Day CQ之前和/或前後存在的核心抽象概念與功能。 這些API受到支援,應避免使用,除非com.adobe.cq
或com.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來建構、執行和擷取查詢的結果,並提供下列專案:
Sling API
Apache Sling是支援AEM的RESTful Web架構。 Sling提供HTTP要求路由、將JCR節點建模為資源、提供安全性內容等等。
Sling API具有為擴充功能建置的額外優點,這表示與較不容易擴充的JCR API相比,使用Sling API建置的應用程式行為更容易且更安全。
Sling API的常見用法
-
以Sling Resources身分存取JCR節點,並透過ValueMaps存取其資料。
-
透過ResourceResolver提供安全性內容。
-
透過ResourceResolver的建立/移動/複製/刪除方法來建立和移除資源。
-
正在透過ModifiableValueMap更新內容。
-
建置請求處理建置區塊
-
非同步處理建置區塊
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,因為這些提供更好的抽象化。
JCR API的常見用法
-
JCR觀察(接聽JCR事件)
-
建立深層節點結構
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服務和元件的OSGi註解。
- 優先使用OSGi Declarative Services (DS) 1.2註解而非Felix SCR註解,以宣告OSGi服務和元件
-
動態內建程式碼解除註冊OSGi服務/元件的OSGi API。
- 當不需要條件式OSGi服務/元件管理(最常見的情況)時,偏好使用OSGi DS 1.2註解。
規則的例外
以下是上述定義規則的常見例外。
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 servlet註冊,偏好使用含@SlingServletResourceTypes的OSGi DS 1.2註解,而非
@SlingServlet
Sling篩選器註冊 sling-filter-registration
- Sling篩選器註冊,偏好使用含@SlingServletFilter的OSGi DS 1.2註解,而非
@SlingFilter
有用的程式碼片段
以下是有用的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();