반응형

자바와 스프링 프레임워크를 공부하다 보면 '핸들러(Handler)'라는 용어를 자주 접하게 됩니다. 이름만 들어서는 무슨 일을 하는지 감이 잘 오지 않을 수 있는데요. 핸들러는 특정 요청이나 이벤트를 전담해서 처리하는 역할을 하는 객체를 의미합니다.


1. 핸들러, 왜 사용할까요?

핸들러의 가장 큰 목적은 *관심사 분리(Separation of Concerns)*입니다. 복잡한 로직을 하나의 거대한 클래스에 몰아넣지 않고, 기능별로 나누어 관리하는 것이죠. 예를 들어, 웹 요청을 처리할 때 로그인, 결제, 상품 조회 등 각기 다른 요청들을 하나의 클래스가 모두 처리한다면 코드가 매우 복잡해지고, 수정이 필요할 때마다 전체 코드를 분석해야 하는 어려움이 있습니다.

핸들러를 사용하면 다음과 같은 장점을 얻을 수 있습니다.

  • 높은 확장성: 새로운 기능을 추가할 때 기존 코드를 수정하지 않고 새로운 핸들러만 만들면 됩니다.
  • 쉬운 유지보수: 특정 기능의 로직을 수정할 때 해당 핸들러만 보면 되므로 다른 코드에 영향을 줄 가능성이 줄어듭니다.
  • 명확한 책임: 각 핸들러는 자신의 역할에만 집중하므로 코드를 이해하기 쉽습니다.

2. 스프링에서의 핸들러 (컨트롤러)

스프링 프레임워크에서 가장 흔히 접하는 핸들러는 바로 **컨트롤러(Controller)**입니다. @Controller 어노테이션이 붙은 클래스가 바로 핸들러의 역할을 수행합니다.

예시 코드:

@Controller
public class MyController {

    @GetMapping("/hello")
    public String sayHello() {
        // "hello"라는 요청이 들어오면 이 메소드가 처리합니다.
        return "hello"; // hello.html 같은 뷰를 반환
    }
}

위 코드에서 sayHello() 메소드는 /hello라는 URL 요청을 전담해서 처리하는 핸들러 메소드입니다. 스프링은 @GetMapping과 같은 어노테이션을 보고 어떤 요청을 어떤 핸들러 메소드에 연결할지 자동으로 매핑해줍니다.


3. 전략 패턴을 활용한 핸들러

컨트롤러 외에도, 여러 핸들러를 만들고 상황에 따라 적절한 핸들러를 선택하여 실행하는 전략 패턴(Strategy Pattern) 방식의 핸들러 사용법도 매우 중요합니다. 이것이 바로 질문하신 이미지에서 설명하는 방식입니다.

단계별 구현 방법:

  1. 핸들러 인터페이스 정의: 모든 핸들러가 공통으로 가져야 할 메소드를 정의합니다.

    public interface MyHandler {
        void handle(String data);
    }
  2. 핸들러 구현 클래스 생성: 인터페이스를 구현하는 여러 클래스를 만들고, @Component 어노테이션으로 이름을 부여하여 스프링 컨테이너에 등록합니다.

    @Component("typeA")
    public class TypeAHandler implements MyHandler {
        @Override
        public void handle(String data) {
            System.out.println("타입 A 핸들러가 데이터를 처리합니다: " + data);
        }
    }
    
    @Component("typeB")
    public class TypeBHandler implements MyHandler {
        @Override
        public void handle(String data) {
            System.out.println("타입 B 핸들러가 데이터를 처리합니다: " + data);
        }
    }
  3. 핸들러를 관리하는 클래스: 모든 핸들러를 주입받아 보관하고, 요청에 따라 적절한 핸들러를 찾아 실행하는 클래스를 만듭니다.

    @Service
    public class HandlerService {
    
        private final Map<String, MyHandler> handlers;
    
        // 생성자를 통해 스프링 컨테이너의 모든 핸들러를 자동으로 주입받습니다.
        public HandlerService(Map<String, MyHandler> handlers) {
            this.handlers = handlers;
        }
    
        public void executeHandler(String type, String data) {
            MyHandler handler = handlers.get(type); // 요청 타입에 맞는 핸들러를 찾습니다.
            if (handler != null) {
                handler.handle(data); // 찾은 핸들러의 메소드를 실행합니다.
            } else {
                // 핸들러가 없을 경우의 처리 로직
                System.out.println("해당 타입의 핸들러를 찾을 수 없습니다.");
            }
        }
    }
  4. 사용: 컨트롤러에서 HandlerService를 주입받아 사용자의 요청에 따라 핸들러를 호출합니다.

    @RestController
    public class MyApiController {
    
        private final HandlerService handlerService;
    
        public MyApiController(HandlerService handlerService) {
            this.handlerService = handlerService;
        }
    
        @PostMapping("/process")
        public String processData(@RequestParam String type, @RequestBody String data) {
            handlerService.executeHandler(type, data);
            return "ok";
        }
    }

4. 정리: 언제 핸들러를 사용해야 할까요?

  • 요청의 종류가 다양할 때: 웹 요청, 메시지 큐 이벤트 등 처리해야 할 요청의 종류가 많고 각각의 로직이 다를 때
  • 확장 가능성이 높을 때: 앞으로 새로운 유형의 요청이 추가될 가능성이 높을 때
  • 유지보수가 중요할 때: 복잡한 로직을 여러 사람이 함께 개발하고 관리해야 할 때

핸들러를 잘 활용하면 깔끔하고 효율적인 코드를 작성할 수 있습니다. 처음에는 조금 복잡해 보일 수 있지만, 이러한 구조를 이해하고 나면 유지보수와 확장이 훨씬 쉬운 애플리케이션을 만들 수 있을 거예요.

반응형

+ Recent posts