Java™ API最佳实践

Adobe Experience Manager (AEM)构建在丰富的开源软件栈栈上,该栈栈公开许多Java™ 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提供,则首选它而不是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的查询时间本身相比,转换到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资源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。

  • 与Apache Felix API (org.apache.felix)相比,首选OSGi API (org.osgi)。

OSGi API的常见用法

规则的例外

以下是上述定义的规则的常见例外。

OSGi API

在处理低级OSGi抽象概念(例如在OSGi组件属性中定义或读取)时,org.osgi提供的较新抽象概念优先于较高级别的Sling抽象概念。 竞争的Sling抽象尚未标记为@Deprecated并建议org.osgi替代方案。

另请注意,与sling:OsgiConfig格式相比,OSGi配置节点定义更倾向于使用cfg.json

AEM资源API

  • com.adobe.granite.asset.api相比,优先选择com.day.cq.dam.api

    • com.day.cq Assets API为AEM的资源管理用例提供了更多免费工具。
    • Granite Assets API支持低级资产管理用例(版本、关系)。

查询API

  • AEM QueryBuilder不支持某些查询函数,例如建议、拼写检查和索引提示以及其他不太常见的函数。 首选使用JCR-SQL2函数进行查询。

Sling Servlet注册 sling-servlet-registration

  • Sling servlet注册,与@SlingServlet相比,更喜欢带有@SlingServletResourceTypes🔗的OSGi DS 1.2注释

Sling筛选器注册 sling-filter-registration

  • Sling筛选器注册,首选带有🔗的OSGi DS 1.2@SlingServletFilter注释而非@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下的任何资源解析为页面对象。

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页面属性

使用页面对象的getter获取已知属性(getTitle()getDescription()等)和page.getProperties()获取[cq:Page]/jcr:content值映射以检索其他属性。

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

读取AEM资源元数据属性

资产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(Page、Asset)无法直接访问的位置(属性或相对资源)中时,Sling资源和值映射可用于获取数据。

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

在这种情况下,可能必须将AEM对象转换为Sling Resource才能有效地找到所需的属性或子资源。

到Sling的AEM页面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