在Java中使用注解来制定策略

wufei123 2025-01-26 阅读:7 评论:0
我在工作中遇到了一个非常有趣的情况,想在这里分享解决方案。 想象一下您需要处理一组数据。为了处理这组数据,您有几种不同的策略。例如,我需要创建如何从 s3 获取数据集合、本地存储库中的示例或作为输入传递的策略。 决定这一策略的人就是提出...

我在工作中遇到了一个非常有趣的情况,想在这里分享解决方案。

想象一下您需要处理一组数据。为了处理这组数据,您有几种不同的策略。例如,我需要创建如何从 s3 获取数据集合、本地存储库中的示例或作为输入传递的策略。

决定这一策略的人就是提出请求的人:

我想获取s3中的数据。取 x 天 h1 和 h2 之间生成的数据,该数据来自 abóbora 客户端。获取最近3000条符合此条件的数据。

或者:

拿你那里的示例数据,复制10000次来进行压力测试。

或者甚至:

我有这个目录,你也可以访问它。获取该目录中的所有内容并递归到子目录中。

最后:

获取输入中的这个数据单元并使用它。

如何实施?

我的第一个想法是:“如何在 java 中定义输入的形状?”

我得出了第一个结论,对于该项目非常重要:“你知道吗?我不会定义形状。添加一个可以处理它的 map。”

最重要的是,由于我没有在 dto 中放置任何形状,因此我可以完全自由地尝试输入。

因此,在建立概念验证后,我们遇到了这样的情况:我们需要摆脱 poc 压力,转向接近实际使用的东西。

我所做的服务是验证规则。基本上,当更改规则时,我需要采用该规则并将其与生产应用程序中发生的事件进行匹配。或者,如果应用程序发生更改并且没有错误,则期望对相同数据的相同规则的决策将保持相同;现在,如果使用相同数据集的相同规则的决策发生变化……那么,这就是潜在的麻烦。

所以,我需要这个应用程序来运行规则的回测。我需要点击真实的应用程序发送数据以进行评估和相关规则。它的用途相当多样:

  • 验证更新应用程序时的潜在偏差
  • 验证更改后的规则是否保持相同的行为
    • 例如,优化规则执行时间
  • 检查规则的变化是否产生了预期的决策变化
  • 验证应用程序中的更改确实提高了效率
    • 例如,使用新版本的 graalvm 并启用 jvmci 是否会增加我可以发出的请求数量?

因此,为此,我需要一些关于事件起源的策略:

  • 从s3获取真实数据
  • 获取存储库中的数据作为样本并复制多次
  • 从本地计算机上的特定位置获取数据

而且我还需要与我的规则不同的策略:

  • 通过输入传递
  • 使用快速运行的存根
  • 使用基于生产规则的样本
  • 在我的机器上使用此路径

如何处理这个问题?好吧,让用户提供数据吧!

策略 api

你知道关于 json-schema 总是引起我注意的一些事情吗?这里:

{
    "$schema": "https://json-schema.org/draft/2020-12/schema",
    "$id": "https://json-schema.org/draft/2020-12/schema",
    "$vocabulary": {
        //...
    }
}

这些字段以 $ 开头。在我看来,它们是用来表示元数据的。那么为什么不在数据输入中使用它来指示正在使用哪种策略的元数据呢?

{
    "dados": {
        "$strategy": "sample",
        "copias": 15000
    },
    //...
}

例如,我可以订购 15000 份我拥有的数据作为样本。或者从 s3 请求一些东西,在 athena 中进行查询:

{
    "dados": {
        "$strategy": "athena-query",
        "limit": 15000,
        "inicio": "2024-11-25",
        "fim": "2024-11-26",
        "cliente": "abóbora"
    },
    //...
}

或者在本地路径中?

{
    "dados": {
        "$strategy": "localpath",
        "cwd": "/home/jeffque/random-project-file",
        "dir": "../payloads/esses-daqui/top10-hard/"
    },
    //...
}

这样我就可以委托选择未来的策略。

代码审查和外观

我处理策略的第一个方法是:

public dataloader getdataloader(map<string, object> inputdados) {
    final var strategy = (string) inputdados.get("$strategy");
    return switch (strategy) {
        case "localpath" -> new localpathdataloader();
        case "sample" -> new sampledataloader(resourcepatternresolver_spring);
        case "athena-query" -> new athenaquerydataloader(athenaclient, s3client);
        default -> new athenaquerydataloader(athenaclient, s3client);
    }
}

所以我的架构师在代码审查期间问了两个问题:

  • “为什么你实例化所有东西而不让 spring 为你工作?”
  • 他在代码中创建了一个dataloaderfacade并放弃了它半成品

从中我明白了什么?使用外观将处理委托给正确的角落并......放弃手动控制是一个好主意?

嗯,很多魔法都是因为春天而发生的。既然我们在一个拥有 java 专业知识的 java 之家,为什么不使用惯用的 java/spring,对吧?仅仅因为作为一个个体觉得有些事情难以理解并不一定意味着它们很复杂。那么,让我们拥抱 java 依赖注入魔法的世界。

创建 façade 对象

曾经是:

final var dataloader = getdataloader(inputdados)
dataloader.loaddata(inputdados, workingpath);

变成:

dataloaderfacade.loaddata(inputdados, workingpath);

所以我的控制器层不需要管理这个。把它留给门面。

那么,我们要如何做立面呢?好吧,首先,我需要将所有对象注入其中:

@service // para o spring gerenciar esse componente como um serviço
public class dataloaderfacade implements dataloader {

    public dataloaderfacade(dataloader primarydataloader,
                            list<dataloader> dataloaderwithstrategies) {
        // armazena de algum modo
    }

    @override
    public completablefuture<void> loaddata(map<string, object> input, path workingpath) {
        return getdataloader(input).loaddata(input, workingpath);
    }

    private dataloader getdataloader(map<string, object> input) {
        final var strategy = input.get("$strategy");
        // magia...
    }
}

好的,对于主 dataloader,除了 @service 之外,我还将其写为 @primary。剩下的我就用@service写下来。

在这里测试一下,设置 getdataloader 返回 null 只是为了尝试 spring 如何调用构造函数......它起作用了。现在我需要用元数据注释每个服务他们使用什么策略...

如何做到这一点...

嗯,看!在 java 中,我们有

注释!我可以创建一个 runtime 注释,其中包含该组件使用的策略!

所以我可以在 athenaquerydataloader 中拥有类似的东西:


@service
@primary
@estrategia("athena-query")
public class athenaquerydataloader implements dataloader {
    // ...
}

我也可以有别名,为什么不呢?


@service
@estrategia({"local", "path", "localpath"})
public class localpathdataloader implements dataloader {
    // ...
}

并展示!

但是如何创建这个注释呢?好吧,我需要它有一个字符串向量的属性(java 编译器已经处理提供一个单独的字符串并将其转换为具有 1 个位置的向量)。默认值为值。看起来像这样:


@retention(retentionpolicy.runtime) // posso usar isso em runtime, não só em análise de bytecode
@target(elementtype.type)           // é intenção que eu só possa anotar tipos com essa anotação
public @interface estrategia {
    string[] value();
}

如果注释字段没有值,我需要将其明确化,这看起来很难看,如 estrategiafeia 注释中所示:


@service
@estrategiafeia(estrategia = {"local", "path", "localpath"})
public class localpathdataloader implements dataloader {
    // ...
}

我认为这听起来不太自然。

好吧,鉴于此,我们仍然需要:

    从传递的对象中提取类注释
  • 创建字符串映射
  • css">右箭头→ 数据加载器(或字符串 右箭头→ t)
提取注释并组装地图 要提取注释,我需要访问对象类:


o.getclass();

除此之外,我可以问一下这个类是否带有像strategy这样的注解:


o.getclass().getdeclaredannotation(estrategia.class)

你还记得它有values字段吗?好吧,这个字段返回一个字符串向量:


string[] estrategias = o.getclass().getdeclaredannotation(estrategia.class).values();

表演!但我有一个挑战,因为之前我有一个 t 类型的对象,现在我想将同一对象映射到 (t, string)[]。在流中,执行此操作的经典操作是 flatmap。 java 也不允许我突然返回这样的元组,但我可以用它创建一条记录。

它看起来像这样:


record dataloadercomestrategia(dataloader dataloader, string estrategia) {}

list<dataloaders> dataloaders = ...;

dataloaders.stream()
    .flatmap(o ->
        stream.of(o.getclass().getdeclaredannotation(estrategia.class).values())
            .map(s -> new dataloadercomestrategia(o, s)
        )
    )  //...

如果有一个对象没有标注策略怎么办?会给npe吗?最好不要,我们在 npe 之前过滤掉它:


record dataloadercomestrategia(dataloader dataloader, string estrategia) {}

list<dataloaders> dataloaders = ...;

dataloaders.stream()
    .filter(o -> o.getclass().getdeclaredannotation(estrategia.class) != null)
    .flatmap(o ->
        stream.of(o.getclass().getdeclaredannotation(estrategia.class).values())
            .map(s -> new dataloadercomestrategia(o, s)
        )
    )  //...

鉴于此,我还需要整理一张地图。而且,好吧,看:java 已经为此提供了一个收集器! collector.tomap(keymapper, valuemapper)


record dataloadercomestrategia(dataloader dataloader, string estrategia) {}

list<dataloaders> dataloaders = ...;

dataloaders.stream()
    .filter(o -> o.getclass().getdeclaredannotation(estrategia.class) != null)
    .flatmap(o ->
        stream.of(o.getclass().getdeclaredannotation(estrategia.class).values())
            .map(s -> new dataloadercomestrategia(o, s)
        )
    ).collect(collectors.tomap(dataloadercomestrategia::estratgia, dataloadercomestrategia::dataloader));

到目前为止,还好。但 flatmap 特别困扰我。有一个名为 mapmulti 的新 java api,它具有倍增的潜力:


record dataloadercomestrategia(dataloader dataloader, string estrategia) {}

list<dataloaders> dataloaders = ...;

dataloaders.stream()
    .filter(o -> o.getclass().getdeclaredannotation(estrategia.class) != null)
    .<dataloadercomestrategia<t>>mapmulti((o, c) -> {
        for (final var estrategia: o.getclass().getdeclaredannotation(strategized.class).value()) {
            c.accept(new dataloadercomestrategia<>(o, estrategia));
        }
    })
    .collect(collectors.tomap(dataloadercomestrategia::estratgia, dataloadercomestrategia::dataloader));

美丽。我为 dataloader 获取了它,但我还需要为 ruleloader 做同样的事情。或者也许不是?如果您注意到,此代码中没有任何特定于 dataloader 的内容。我们可以抽象这段代码!!


record objetocomestrategia<t>(t objeto, string estrategia) {}

list<t> objetos = ...;

objetos.stream()
    .filter(o -> o.getclass().getdeclaredannotation(estrategia.class) != null)
    .<objetocomestrategia<t>>mapmulti((o, c) -> {
        for (final var estrategia: o.getclass().getdeclaredannotation(strategized.class).value()) {
            c.accept(new objetocomestrategia<>(o, estrategia));
        }
    })
    .collect(collectors.tomap(objetocomestrategia::estratgia, objetocomestrategia::objeto));

立面之下 纯粹出于功利的原因,我将这个算法放在注释中:


@retention(retentionpolicy.runtime) // posso usar isso em runtime, não só em análise de bytecode
@target(elementtype.type)           // é intenção que eu só possa anotar tipos com essa anotação
public @interface estrategia {
    string[] value();

    public static class util {

        // nenhum motivo especial o record estar aqui, apenas para facilidade de uso
        private record objetocomestrategia<t>(t objeto, string estrategia) {}

        public static <t> map<string, t> mapaestrategia(list<t> objetoscomestrategia) {
            return objetoscomestrategia.stream()
                    .filter(o -> o.getclass().getdeclaredannotation(estrategia.class) != null)
                    .<objetocomestrategia<t>>mapmulti((o, c) -> {
                        for (final var estrategia: o.getclass().getdeclaredannotation(strategized.class).value()) {
                            c.accept(new objetocomestrategia<>(o, estrategia));
                        }
                    })
                    .collect(collectors.tomap(objetocomestrategia::estratgia, objetocomestrategia::objeto));
        }
    }
}

立面呢?好吧,这份工作几乎也是这么说的。我决定抽象一下:


class fachadaselector<t> {
    final t primario;
    final map<string, t> estrategia;

    fachadaselector(t primario, list<t> outros) {
        this.primario = primario;
        this.estrategia = estrategia.util.mapaestrategia(outros);
    }

    t objetousado(map<string, object> input) {
        if (input == null) return primario;
        final var estrategiainput = input.get("$strategy"); // aqui o tipo vai ser object
        if (estrategiainput == null) return primario;

        // mas tudo bem ser object porque a chave do mapa é object e ele casa no final com .equals
        return estrategia.getordefault(estrategiainput, primario);
    }
}

立面看起来像这样:


@Service // para o Spring gerenciar esse componente como um serviço
public class DataLoaderFacade implements DataLoader {

    private final FachadaSelector<DataLoader> selector;

    public DataLoaderFacade(DataLoader primaryDataLoader,
                            List<DataLoader> dataLoaderWithStrategies) {
        this.selector = new FachadaSelector<>(primaryDataLoader, dataLoaderWithStrategies);
    }

    @Override
    public CompletableFuture<Void> loadData(Map<String, Object> input, Path workingPath) {
        return getDataLoader(input).loadData(input, workingPath);
    }

    private DataLoader getDataLoader(Map<String, Object> input) {
        return selector.objetoUsado(input);
    }
}

以上就是在Java中使用注解来制定策略的详细内容,更多请关注知识资源分享宝库其它相关文章!

版权声明

本站内容来源于互联网搬运,
仅限用于小范围内传播学习,请在下载后24小时内删除,
如果有侵权内容、不妥之处,请第一时间联系我们删除。敬请谅解!
E-mail:dpw1001@163.com

分享:

扫一扫在手机阅读、分享本文

发表评论
热门文章
  • 华为 Mate 70 性能重回第一梯队 iPhone 16 最后一块遮羞布被掀

    华为 Mate 70 性能重回第一梯队 iPhone 16 最后一块遮羞布被掀
    华为 mate 70 或将首发麒麟新款处理器,并将此前有博主爆料其性能跑分将突破110万,这意味着 mate 70 性能将重新夺回第一梯队。也因此,苹果 iphone 16 唯一能有一战之力的性能,也要被 mate 70 拉近不少了。 据悉,华为 Mate 70 性能会大幅提升,并且销量相比 Mate 60 预计增长40% - 50%,且备货充足。如果 iPhone 16 发售日期与 Mate 70 重合,销量很可能被瞬间抢购。 不过,iPhone 16 还有一个阵地暂时难...
  • 酷凛 ID-COOLING 推出霜界 240/360 一体水冷散热器,239/279 元

    酷凛 ID-COOLING 推出霜界 240/360 一体水冷散热器,239/279 元
    本站 5 月 16 日消息,酷凛 id-cooling 近日推出霜界 240/360 一体式水冷散热器,采用黑色无光低调设计,分别定价 239/279 元。 本站整理霜界 240/360 散热器规格如下: 酷凛宣称这两款水冷散热器搭载“自研新 V7 水泵”,采用三相六极马达和改进的铜底方案,缩短了水流路径,相较上代水泵进一步提升解热能力。 霜界 240/360 散热器的水泵为定速 2800 RPM 设计,噪声 28db (A)。 两款一体式水冷散热器采用 27mm 厚冷排,...
  • 惠普新款战 99 笔记本 5 月 20 日开售:酷睿 Ultra / 锐龙 8040,4999 元起

    惠普新款战 99 笔记本 5 月 20 日开售:酷睿 Ultra / 锐龙 8040,4999 元起
    本站 5 月 14 日消息,继上线官网后,新款惠普战 99 商用笔记本现已上架,搭载酷睿 ultra / 锐龙 8040处理器,最高可选英伟达rtx 3000 ada 独立显卡,售价 4999 元起。 战 99 锐龙版 R7-8845HS / 16GB / 1TB:4999 元 R7-8845HS / 32GB / 1TB:5299 元 R7-8845HS / RTX 4050 / 32GB / 1TB:7299 元 R7 Pro-8845HS / RTX 2000 Ada...
  • python中def什么意思

    python中def什么意思
    python 中,def 关键字用于定义函数,这些函数是代码块,执行特定任务。函数语法为 def (参数列表)。函数可以通过其名字和圆括号调用。函数可以接受参数作为输入,并在函数体中使用参数名访问。函数可以使用 return 语句返回一个值,它将成为函数调用的结果。 Python 中 def 关键字 在 Python 中,def 关键字用于定义函数。函数是代码块,旨在执行特定任务。 语法 def 函数定义的语法如下: def (参数列表): # 函数体 示例 定义...
  • python怎么调用其他文件函数

    python怎么调用其他文件函数
    在 python 中调用其他文件中的函数,有两种方式:1. 使用 import 语句导入模块,然后调用 [模块名].[函数名]();2. 使用 from ... import 语句从模块导入特定函数,然后调用 [函数名]()。 如何在 Python 中调用其他文件中的函数 在 Python 中,您可以通过以下两种方式调用其他文件中的函数: 1. 使用 import 语句 优点:简单且易于使用。 缺点:会将整个模块导入到当前作用域中,可能会导致命名空间混乱。 步骤:...