Springboot Custom Annotation

2025. 2. 17. 14:13·개발/SpringBoot

항상 서두에 글을 쓰지만 이 글은 Spring Boot Custom Annotation에 대한 공식적인 정의를 다루기보다는,
제가 실제 개발 경험을 바탕으로 Custom Annotation을 어떻게 활용하고 있는지 그 활용법과 생각의 확장을 공유하는 글입니다.
'이런 방식으로도 사용할 수 있구나!' 하고 가볍게 읽어주시면 좋을 것 같습니다.

1. Custom Annotation이란? 그리고 왜 사용할까요?

Spring에서 공식적으로 제공하는 Annotation 이외에 개발자가 특정 목적을 위해 직접 정의하여 사용하는 Annotation을 말하여
아래와 같이 작성해서 사용할 수 있습니다.

@Description("Service Method 관리 Custom Annotation")
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface ExecutableMethod {
String methodName(); // methodName {
}


위와 같이 작성 후 특정 서비스의 메소드에 아래와 같이 표시하여 사용할 수 있습니다.

    @ExecutableMethod(methodName = "customAnnotationTest") // Custom Annotation
    @Description("Custom Annotation Test")
    @Scheduled(cron = "15 19 * * * *")
    public void customAnnotationTest() {
        log.info("customAnnotationTest Start!!!");
    }

 

보통 Custom Annotation에 대해 검색해 보면 관심사 분리,
데이터 유효성 검사(Validation), 코드 가독성 향상, 중복 관리 등에 대한 내용이 많이 나옵니다.
저 또한 이러한 장점들을 인지하고 있지만, 

저는 주로 custom Annoatation을 아래의 상황에 맞다면 사용을 고려합니다.

  - 공통적으로 처리할 로직에 표시를 해두고 싶을 때
  - 특정 기능을 가진 대상을 쉽고 빠르게 찾고 싶을 때
  - 암묵적인 처리 방식이나 특정 상태를 표식으로 지정하고 싶을 때

즉, 개발 과정에서 위와 유사한 특성을 지닌 문제점이나 어떤 비즈니스 요구사항을 해결해야 할 때
Custom Annotation을 하나의 해결책으로 고려하곤 합니다.

2. Custom Annotation을 활용한 동적 메서드 제어 (실전 예시)


최근에 위와 같은 특성을 풀어야하는 문제가 있어 활용하는 방법을 코드와 함께 설명 하도록 하겠습니다.
요건 예시는 스케쥴로 등록된 서비스의 런타임에 동적으로 ON/OFF 처리할 수 있어야 한다는 것이었습니다.

위의 요건에서 
1. 특정 method의 동적 처리( on / off ) 필요
2. 동적 제어를 위한 상태 관리가 필요

여기서 "어떤 메서드를 동적으로 제어할 것인가?"라는 중요 요건을 위해 
Custom Annotation으로 '표식'을 해두고, 이 표식이 있는 메서드만 상태 관리의 대상이 되도록 처리하는 방법을 생각했습니다.


실제 구현
1단계: Custom Annotation이 등록된 메서드들을 전역 메모리에 등록

애플리케이션이 시작되고 Spring ApplicationContext의 컴포넌트 스캔이 완료된 후,
ExecutableMethod Annotation이 붙은 메서드들을 전역 메모리(ConcurrentHashMap)에 등록 처리했습니다.
그리고 ApplicationContext의 변경(예: 리프레시)이 있을 때마다 수행해야 하므로 ContextRefreshedEvent를 사용했습니다.

@Component
@Slf4j
public class ExecutableMethodRegistry implements ApplicationListener<ContextRefreshedEvent> {

    private final ConcurrentHashMap<String, Boolean> enableMethodCheckMap = new ConcurrentHashMap<>();

    @Override
    public void onApplicationEvent(ContextRefreshedEvent event) {
        ApplicationContext context = event.getApplicationContext();
        Map<String, Object> beans = context.getBeansWithAnnotation(Service.class);

        for (Object bean : beans.values()) {
            Class<?> targetClass = AopProxyUtils.ultimateTargetClass(bean);
            Method[] methods = targetClass.getDeclaredMethods();
            for (Method method : methods) {
                if (method.isAnnotationPresent(ExecutableMethod.class)) {
                    ExecutableMethod executableFunction = method.getAnnotation(ExecutableMethod.class);
                    enableMethodCheckMap.put(executableFunction.methodName(), true);
                }
            }
        }
        log.info("enableMethod Data Setting Check : {}" , enableMethodCheckMap);
    }

    /**
     * method 사용가능여부 확인
     * @param methodName(String) method Name
     * @return Boolean ( true : 사용가능 , false : 사용불가 )
     */
    public Boolean isMethodExecutable(String methodName) {
        return enableMethodCheckMap.getOrDefault(methodName, true);
    }

    /**
     * method 사용가능여부 셋팅
     * @param methodName(String) method Name , enable(boolean) 사용가능여부
     */
    public void setMethodExecutable(String methodName , boolean enable) {
        enableMethodCheckMap.put(methodName, enable);
    }
}


2단계: 서비스 로직에서 메서드 활성화 상태 확인

이제 실제 비즈니스 로직을 담고 있는 서비스 클래스에서 ExecutableMethodRegistry를 주입받아,
@ExecutableMethod Annotation이 붙은 메서드의 실제 서비스로직이 실행되기 전에 활성화 상태를 확인하고 제어할 수 있습니다.

@Service
@RequiredArgsConstructor
@Slf4j
public class TestService {

    private final ExecutableMethodRegistry executableMethodRegistry;

    @ExecutableMethod(methodName = "customAnnotationTest")
    @Description("Custom Annotation Test")
    @Scheduled(cron = "15 19 * * * *")
    public void customAnnotationTest() {
        log.info("customAnnotationTest Start!!!");
        if(executableMethodRegistry.isMethodExecutable("customAnnotationTest")) {
            log.info("customAnnotationTest is executable");
        } else {
            log.info("customAnnotationTest is not executable");
        }
    }
}

 

마지막으로, 외부에서 ExecutableMethodRegistry의 setMethodExecutable 메서드를 호출하여 맵의 값을 변경하고,
특정 메서드의 ON/OFF 상태를 동적으로 제어할 수 있도록 Controller를 작성하였습니다.

package com.sample.customannotationsample.controller;

import com.sample.customannotationsample.common.service.ExecutableMethodRegistry;
import com.sample.customannotationsample.service.TestService;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

@RequiredArgsConstructor
@RestController
public class TestController {
    private final ExecutableMethodRegistry executableMethodRegistry;

    @PutMapping("/{methodName}/status")
    public ResponseEntity<String> setMethodStatus(@PathVariable String methodName, @RequestParam boolean enable) {
        executableMethodRegistry.setMethodExecutable(methodName, enable);
        return ResponseEntity.ok(methodName + " method status set to " + (enable ? "enabled" : "disabled"));
    }

    @GetMapping("/{methodName}/status")
    public ResponseEntity<Boolean> getMethodStatus(@PathVariable String methodName) {
        boolean isExecutable = executableMethodRegistry.isMethodExecutable(methodName);
        return ResponseEntity.ok(isExecutable);
    }
}



Custom Annotation, 언제 정답이 될까?


이런 경우 반드시 Custom Annotation 을 사용해야하느냐?
사실 Spring Property 를 만들어서 거기에 관리해야할 Method를 적고 Reflection으로 처리를 할 수 도 있고 
애초에 관리해야할 서비스들을 package 단위로 구분짓는 방법등 다른 다양한 방법이 많이 있습니다.

Custom Annotation은 이러한 문제에 대한 하나의 강력한 선택지일 뿐, 유일한 정답은 아닙니다.
하지만 개인적으로는 "공통적으로 사용할 곳에 표시", "잘 찾고 싶다", "표식으로 암묵적인 처리 지정"이라는
요구사항이 명확할 때 사용시 코드의 직관성이 높아지기에 선호하는 방법 중 하나일뿐

현재 시스템의 구성 또는 팀의 코딩 컨벤션에 맞춰 적절하게 선택 구현하시면 됩니다.

위의 코드는 아래의 GitHub에서 확인 할 수 있습니다.
https://github.com/jeroschoi/exampleSource/tree/customAnnotation

To be Continued~

 

SpringBoot AOP 1편..

중복 코드를 줄여주는 SpringBoot  AOP를 알아보자 Aop 란   - Aspect Oriented Programming 으로 사전적 정의를 찾아보면    "횡단 관심사(cross-cutting concern)의 분리를 허용함으로써 모듈성을 증가

crazy-code.tistory.com

'개발 > SpringBoot' 카테고리의 다른 글

ApplicationEventPublisher의 @Async 사용 시 주의점  (0) 2025.05.13
SpringBoot의 ApplicationEventPublisher 를 활용한 비즈니스 분리  (0) 2025.04.25
SpringBoot AOP 의 아이러니  (0) 2025.03.11
'개발/SpringBoot' 카테고리의 다른 글
  • ApplicationEventPublisher의 @Async 사용 시 주의점
  • SpringBoot의 ApplicationEventPublisher 를 활용한 비즈니스 분리
  • SpringBoot AOP 의 아이러니
개발자아저씨
개발자아저씨
그냥 오랫동안 개발하고 싶은 아저씨...
  • 개발자아저씨
    코딩하는 아저씨
    개발자아저씨
  • 전체
    오늘
    어제
    • 분류 전체보기 (17)
      • 개발자 (5)
      • 개발 (11)
        • SpringBoot (4)
        • AI (5)
        • MSA (2)
  • 블로그 메뉴

    • 홈
    • 태그
    • 미디어로그
    • 위치로그
    • 방명록
  • 링크

  • 공지사항

  • 인기 글

  • 태그

    MCP
    local llm 연동
    ApplicationEventPublisher
    claude desktop
    backend
    aop 사용 기준
    은탄환은 없다
    백엔드
    springboot
    생각과 경험
    ai서빙
    로그인 발전
    회고
    claudecode
    MSA
    이직
    결재연동
    ai coding
    Session Cluster
    글쓰기
    Claude.md
    AI컨벤션
    비지니스 분리
    AOP
    AI
    annotation 활용
    springai
    ai 서빙
    simpleasynctaskexecutor
    경량llm
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.3
개발자아저씨
Springboot Custom Annotation

티스토리툴바