Adobe

代码重用自定义吊索模型注入器

Reuse@1x.jpg
在我的博客里用Sling模型在AEM中编写更少的Java代码,我谈到了使用Sling Models编写更少的代码。我接着说用Sling模型和Lombok在AEM中编写更少的Java代码.我谈到了代码生成器通过不编写冗余代码来帮助节省时间。你可以说我很懒。我喜欢使用我所掌握的工具和框架。
但有时这是不可避免的。我们必须写代码。我的效率很低。当我写代码时,我喜欢让它尽可能简洁易懂。在这篇博客中,我将讨论代码的可重用性,以帮助编写更少的代码。

标签ID

作为一个例子,我将从这里重新讨论Tag id模型第一篇博客在该示例中,多字段项有一个名为tag ids的属性。/标签.我把这个注入到模型中ItemTags

@ChildResource ItemTags ();

ItemTags实现Iterable <标记>

interface ItemTags extends Iterable {} @Model(adaptables = Resource.class, adapters = ItemTags.class) final类ItemTagsImpl实现ItemTags {private final List标签;@Inject public ItemTagsImpl(@Self @Via("resourceResolver") final TagManager TagManager, @Self final String[] ids) {this. .tags = Arrays.stream(ids) .map(tagManager::resolve) .filter(Objects::nonNull) .collect(Collectors.toUnmodifiableList());} @覆盖公共迭代器<标记>迭代器(){返回this.tags.iterator();} @覆盖公共无效forEach(最终消费者 action) {this.tags.forEach(action);} @覆盖公共Spliterator<标记> Spliterator(){返回this.tags.spliterator();}}

Sling模型代码的代码数量尽可能少。然而,标签在AEM中无处不在。我可能需要放置另一个同样需要解析标签的模型,这种可能性很高。让我们来研究一下我们拥有的一些代码重用选项。

实用工具类

的条件反射可能会创建一个类TagUtil.java.Lombok的@UtilityClass,这是很容易的

@UtilityClass公共类TagUtil{公共列表<标签> resolve(最终TagManager TagManager,最终字符串…返回Arrays.stream(ids) .map(tagManager::resolve) .filter(Objects::nonNull) .collect(collections . tounmodifiablelist ());}}

读了@UtilityClass文档。实用程序类是最终的,它的函数是静态的,也可能不是被实例化.因此,在单元测试中很难模拟。要隔离要测试的代码,您需要PowerMockito.这很糟糕。如果不得不求助于PowerMockito,那么就不是在编写可测试代码。你应该这样做的原因还有很多避免使用实用工具类.我像躲避瘟疫一样躲避他们。

继承的基类

我是在一个面向对象的世界里长大的。我的第二个想法可能是面向对象的方法。继承与Sling模型一起工作。

@ConsumerType接口标签{列表<标签> getTags();} @ConsumerType接口项目扩展标签{字符串getTitle();页面getPage ();} @Model(adapters = Tagged.class, adaptables = Resource.class)类TaggedImpl实现Tagged {@Getter private List标签;@ValueMapValue(name = "tags", injectionStrategy = injectionStrategy . optional) private String[] id;@Self @Via("resourceResolver") private TagManager;@PostConstruct公共无效激活(){此。tags = Arrays.stream(ArrayUtils.nullToEmpty(this.ids)) .map(this.tagManager::resolve) .filter(Objects::nonNull) .collect(Collectors.toUnmodifiableList());}} @Model(adapters = Item.class, adaptables = Resource.class) final class ItemImpl extends TaggedImpl implements Item {@Getter @ValueMapValue(name = "jcr:title") private String title;@Getter @ResourcePath私有Page页面; }

在上面的例子中,我已经放弃了只使用接口的方法因为我必须进行扩展TaggedImpl.OOP在某些场景下可能是有意义的,比如你有一组相关的对象。但在本例中,“标记”的概念非常灵活,几乎可以应用于任何东西,比如页面、组件或资产。如果您要添加另一个概念,如“链接”,就像在组件中可以有一个链接一样,会怎样?然后将延长标记这将会延伸有关

OSGi组件

我可能是在面向对象的世界中长大的,但在过去的几年里,我一直在OSGi容器中摸索。我的第三个想法是OSGi组件。

public interface TagService {List resolve(TagManager TagManager, String…ids);} @Component(service = TagService.class) public final class TagServiceImpl实现TagService {@Override public List resolve(final TagManager TagManager, final String…返回Arrays.stream(ids) .map(tagManager::resolve) .filter(Objects::nonNull) .collect(collections . tounmodifiablelist ());}}

您可以将接口与实现分离,使其具有可比性。这有一个恼人的缺点。它没有集成到Sling Model的注入管道中。我仍然需要编写自定义代码来调用它解决(TagManager,弦…)函数,在模型的构造函数中

@注入公共ItemTagsImpl(@Self @Via("resourceResolver") final TagManager TagManager, @OSGiService final TagService TagService, @Self final String[] id){此。tags = tagService。解决(tagManager, ids);}

自定义注入器

喷射器是Sling Models的核心。看一下 GitHub上的标准注入器 .ACS Commons已经实现了一些 喷油器 .源代码如下 在这里 您应该查看所有这些实现。当然,你会学到一些东西。但在你离开这个博客之前,请继续阅读。我将简单解释一下注射是如何工作的。
你已经这样做过一千次了:MyModel model = resource.adaptTo(MyModel.class) 你有没有想过为什么会这样?如何注入模型中的所有这些属性?看一下ModelAdapterFactory。createObject(对象,ModelClass < ModelType >) 注入是通过滚动所有注入器直到其中一个返回一个值来实现的。你需要更明确一点。调用注入器by 的名字 或使用 injector-specific注释
//通过name调用注入器@Inject @Source("valuemap")//或者使用注入器特定的注释@ValueMapValue私有字符串名;

我们来创建定制的注射器.如果您喜欢上面的OSGi组件解决方案,那么这并没有什么不同。注入器只是一个OSGi组件,用于ModelAdapterFactory

@ServiceRanking(Integer.MAX_VALUE) @Component(service = {Injector.class})公共最终类TagIdInjector实现了Injector {@Override public String getName(){返回"tag-id";} @覆盖公共对象getValue(最终对象可适应,最终字符串名称,最终类型类型,最终AnnotatedElement元素,最终DisposalCallbackRegistry callbackRegistry){最终var tags =新ArrayList<标签>();final var resource =(资源)适应性强;final var ids = resource.getValueMap() .get(name, String[].class);if (ids != null) {Optional.ofNullable(resource.getResourceResolver() .adaptTo(tagManager .class)) .ifPresent(tagManager -> Arrays.stream(ids) .map(tagManager::resolve) .filter(Objects::nonNull) .forEach(tags::add));} return Collections.unmodifiableList(tags);}}

当然,这是一个非常简单的例子。的getValue函数是做一些假设。它假设适应性是资源.它总是返回一个非空值<标记>列表.这很好,因为我们的模型正在适应一个资源,我们将通过它的名字来调用它:标签id

下面是最终的模型代码。

@Model(adaptables = {Resource.class})公共接口MyModel {@ValueMapValue String getText();@ChildResource(injectionStrategy = injectionStrategy . optional) List getItems();@Model(adaptables = Resource.class) interface项目{@ValueMapValue(name = "jcr:title") String getTitle();@ResourcePath Page getPage();@Inject @Source(" Tag -id") List getTags();}}

这比原来的代码要小很多。我回到了接口,这意味着我不需要用单元测试覆盖这段代码。我唯一要讲的就是注射器。

结论

如果你看ModelAdapterFactory。createObject(对象,ModelClass < ModelType >)代码,现在你知道了。@ inject不是魔法。实用类很糟糕。自定义注入器是创建可重用注入器的最佳选择。

我们使用注入器名称@Source(“标签id”).我会把它留给你们作为练习,让你们自己去做自定义注释.看看开源注入器的实现。还要留意StaticInjectAnnotationProcessorFactory接口。

留下回复

这个网站使用Akismet来减少垃圾邮件。了解如何处理您的评论数据

胡安·阿亚拉

Juan Ayala是Perficient, Inc. Adobe实践的首席开发人员,专注于Adobe体验平台和围绕它的事情。

更多来自作者

订阅每周博客文摘:

报名
关注我们
推特 Linkedin 脸谱网 Youtube Instagram