>Java >java지도 시간 >Java에서 주석을 사용하여 전략 만들기

Java에서 주석을 사용하여 전략 만들기

Susan Sarandon
Susan Sarandon원래의
2025-01-10 12:13:43276검색

Usando annotations em Java para fazer um strategy

직장에서 매우 흥미로운 상황을 겪었고 여기에 해결책을 공유하고 싶었습니다.

일련의 데이터를 처리해야 한다고 상상해 보세요. 그리고 이 데이터 세트를 처리하기 위해 이에 대한 몇 가지 다른 전략이 있습니다. 예를 들어, S3에서 데이터 컬렉션을 가져오거나 로컬 저장소 내의 예제를 가져오거나 입력으로 전달하는 방법에 대한 전략을 만들어야 했습니다.

그리고 이 전략을 지시하는 사람은 요청하는 사람입니다.

S3에서 데이터를 가져오고 싶습니다. X일에 H1 시간과 H2 시간 사이에 Abóbora 클라이언트에서 생성된 데이터를 가져옵니다. 이를 충족하는 마지막 3000개의 데이터를 가져옵니다.

그 외의 경우:

여기에 있는 예시 데이터를 10,000번 복사하여 스트레스 테스트를 수행하세요.

또는

제가 이 디렉토리를 갖고 있으니 여러분도 이 디렉토리에 액세스하실 수 있습니다. 해당 디렉토리의 모든 항목을 재귀적으로 하위 디렉토리로 가져옵니다.

그리고 마지막으로:

입력에 있는 이 데이터 단위를 가져와서 사용하세요.

구현 방법은 무엇입니까?

첫 번째 생각은 "Java에서 입력의 형태를 어떻게 정의할 수 있을까?"였습니다.

그리고 저는 프로젝트에 매우 중요한 첫 번째 결론에 도달했습니다. "그거 알아요? 저는 모양을 정의하지 않을 거예요. 이를 처리할 수 있는 Map를 추가하세요."

게다가 DTO에 어떤 모양도 넣지 않았기 때문에 입력을 자유롭게 실험할 수 있었습니다.

그래서 개념 증명을 확립한 후에는 스트레스 POC에서 벗어나 실제 사용에 가까운 것으로 넘어가야 하는 상황에 도달합니다.

제가 하고 있던 서비스는 규칙을 검증하는 것이었습니다. 기본적으로 규칙을 변경할 때 해당 규칙을 가져와 프로덕션 애플리케이션에서 발생한 이벤트와 일치시켜야 했습니다. 또는 애플리케이션이 변경되고 버그가 없는 경우 동일한 규칙에 대한 결정이 동일한 데이터에 대해 동일하게 유지될 것으로 기대됩니다. 이제 동일한 데이터 세트를 사용하여 동일한 규칙에 대한 결정이 변경되면... 음, 문제가 발생할 수 있습니다.

그래서 규칙 백테스팅을 실행하려면 이 애플리케이션이 필요했습니다. 평가를 위해 데이터와 문제의 규칙을 보내는 실제 애플리케이션을 실행해야 합니다. 이것의 용도는 매우 다양합니다:

  • 애플리케이션 업데이트 시 잠재적인 편차 확인
  • 변경된 규칙이 동일한 동작을 유지하는지 확인
    • 예: 규칙 실행 시간 최적화
  • 규칙 변경으로 인해 예상되는 결정 변경이 발생했는지 확인
  • 애플리케이션의 변경으로 인해 실제로 더 효율적이었는지 확인
    • 예를 들어 JVMCI가 활성화된 GraalVM의 새 버전을 사용하면 요청할 수 있는 수가 늘어나나요?

그러므로 이벤트 발생에 대한 몇 가지 전략이 필요합니다.

  • 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
    },
    //...
}

예를 들어, 제가 가지고 있는 데이터의 사본 15,000개를 샘플로 주문할 수 있습니다. 또는 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);

현재:

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

그래서 내 컨트롤러 레이어에서는 이를 관리할 필요가 없습니다. 파사드에 맡겨보세요.

그럼 파사드는 어떻게 할 건가요? 글쎄, 시작하려면 모든 개체를 여기에 삽입해야 합니다.

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

네, 기본 DataLoader의 경우 @Service 외에 @Primary로 작성합니다. 나머지는 그냥 @Service로 적어두겠습니다.

Spring이 생성자를 호출하고 작동하는 방식을 시험해 보기 위해 getDataLoader를 null로 반환하도록 설정하여 여기에서 테스트하세요. 이제 각 서비스가 어떤 전략을 사용하는지 메타데이터와 함께 기록해야 합니다...

이를 수행하는 방법...

자, 보세요! Java에는 주석이 있습니다! 해당 구성 요소에서 사용되는 전략이 포함된 런타임 주석을 만들 수 있습니다!

따라서 AthenaQueryDataLoader에서 다음과 같은 것을 가질 수 있습니다.

{
    "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/"
    },
    //...
}

그리고 보여주세요!

그런데 이 주석을 만드는 방법은 무엇일까요? 글쎄요, 문자열 벡터인 속성이 필요합니다(Java 컴파일러는 이미 단일 문자열을 제공하고 이를 위치 1의 벡터로 변환하는 작업을 처리하고 있습니다). 기본값은 값입니다. 다음과 같습니다:

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);
    }
}

주석 필드의 값이 아닌 경우 명시적으로 지정해야 하며 이는 EstrategiaFeia 주석에서처럼 보기 흉하게 보일 것입니다.

final var dataLoader = getDataLoader(inputDados)
dataLoader.loadData(inputDados, workingPath);

제 생각에는 그다지 자연스럽지 않은 것 같아요.

그렇습니다. 그래도 다음이 필요합니다.

  • 전달된 객체에서 클래스 주석 추출
  • 문자열 맵 만들기 오른쪽 화살표 데이터 로더(또는 문자열 오른쪽 화살표 ㅜㅜ)

주석 추출 및 지도 조립

주석을 추출하려면 객체 클래스에 대한 액세스 권한이 필요합니다.

dataLoaderFacade.loadData(inputDados, workingPath);

게다가 이 클래스에 Strategy:
와 같은 주석이 추가되었는지 물어봐도 될까요?

@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...
    }
}

values ​​필드가 있다는 것을 기억하시나요? 음, 이 필드는 문자열 벡터를 반환합니다.

@Service
@Primary
@Estrategia("athena-query")
public class AthenaQueryDataLoader implements DataLoader {
    // ...
}

쇼! 하지만 한 가지 문제가 있습니다. 전에는 T 유형의 개체가 있었지만 이제는 동일한 개체를 (T, String)[]에 매핑하려고 하기 때문입니다. 스트림에서 이를 수행하는 고전적인 작업은 flatMap입니다. 그리고 Java에서는 갑자기 그런 튜플을 반환하는 것을 허용하지 않지만 이를 사용하여 레코드를 만들 수 있습니다.

다음과 같습니다.

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

전략이 주석으로 추가되지 않은 개체가 있으면 어떻게 되나요? NPE를 제공합니까? 더 나은 방법은 NPE 전에 필터링해 보겠습니다.

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

그래도 지도를 만들어야 해요. 그리고 보세요. Java는 이미 이를 위한 수집기를 제공합니다! Collector.toMap(keyMapper, valueMapper)

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

지금까지는 괜찮습니다. 그러나 flatMap은 특히 나를 괴롭혔다. 다음과 같은 가능성을 지닌 mapMulti라는 새로운 Java API가 있습니다.

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

뷰티. DataLoader용으로 얻었지만 RuleLoader에서도 동일한 작업을 수행해야 합니다. 아니면 아닐 수도 있나요? 알고 보면 이 코드에는 DataLoader에만 해당되는 내용이 없습니다. 이 코드를 추상화할 수 있습니다!!

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);
    }
}

정면 아래

순전히 실용적인 이유로 다음 알고리즘을 주석에 배치했습니다.

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...
    }
}

위 내용은 Java에서 주석을 사용하여 전략 만들기의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.