기술과 산업/언어 및 프레임워크

Spring Framework 시리즈 10화 – 실무 예제: 간단한 DI 기반 계산기 시스템 구현

B컷개발자 2025. 5. 29. 16:30
728x90

스프링 핵심 개념인 @Component, @Autowired, @Value, @EventListener 등을 실제 프로젝트에 통합하여 DI 기반 계산기 시스템을 구현해봅니다. 학습한 이론을 코드로 연결하고 구조적으로 정리합니다.

프로젝트 개요

우리는 간단한 계산기 시스템을 만들 겁니다.
사용자가 두 수와 연산자(+, -, *, /)를 입력하면, 계산을 수행하고 결과를 출력하며, 이벤트 리스너를 통해 로그 또는 통계 처리를 따로 분리합니다.


📁 프로젝트 구조

src/
 └── main/
     ├── java/
     │   └── com.example.calculator/
     │        ├── AppConfig.java
     │        ├── Calculator.java
     │        ├── OperatorService.java
     │        ├── ResultPrinter.java
     │        ├── CalculationEvent.java
     │        ├── CalculationEventListener.java
     │        └── CalculatorApp.java
     └── resources/
         └── application.properties

1. 설정값 주입 – application.properties

calculator.name=스프링 계산기

2. OperatorService – 연산 수행 컴포넌트

@Component
public class OperatorService {

    public double calculate(double a, double b, String op) {
        return switch (op) {
            case "+" -> a + b;
            case "-" -> a - b;
            case "*" -> a * b;
            case "/" -> b != 0 ? a / b : 0;
            default -> throw new IllegalArgumentException("지원하지 않는 연산자입니다.");
        };
    }
}

3. CalculationEvent – 이벤트 객체

public class CalculationEvent {
    private final double a;
    private final double b;
    private final String operator;
    private final double result;

    public CalculationEvent(double a, double b, String operator, double result) {
        this.a = a;
        this.b = b;
        this.operator = operator;
        this.result = result;
    }

    // Getter 생략
}

4. Calculator – 주입 구조 구성

@Component
public class Calculator {

    private final OperatorService operatorService;
    private final ApplicationEventPublisher publisher;

    @Value("${calculator.name}")
    private String name;

    public Calculator(OperatorService operatorService, ApplicationEventPublisher publisher) {
        this.operatorService = operatorService;
        this.publisher = publisher;
    }

    public void calculate(double a, double b, String op) {
        System.out.println("[" + name + "] 연산 수행: " + a + " " + op + " " + b);
        double result = operatorService.calculate(a, b, op);
        publisher.publishEvent(new CalculationEvent(a, b, op, result));
    }
}

5. ResultPrinter – 이벤트 리스너를 통한 결과 처리

@Component
public class ResultPrinter {

    @EventListener
    public void handleResult(CalculationEvent event) {
        System.out.println("결과: " + event.getResult());
    }
}

6. AppConfig – 설정 클래스

@Configuration
@ComponentScan(basePackages = "com.example.calculator")
public class AppConfig {}

7. CalculatorApp – 실행 메인 클래스

public class CalculatorApp {
    public static void main(String[] args) {
        ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
        Calculator calculator = context.getBean(Calculator.class);

        calculator.calculate(5, 3, "+");
        calculator.calculate(10, 2, "/");
        calculator.calculate(7, 8, "*");
    }
}

실행 결과 예시

[스프링 계산기] 연산 수행: 5.0 + 3.0
결과: 8.0

[스프링 계산기] 연산 수행: 10.0 / 2.0
결과: 5.0

[스프링 계산기] 연산 수행: 7.0 * 8.0
결과: 56.0

이 예제를 통해 확인한 개념 정리

적용 기능 사용 위치

DI 및 컴포넌트 주입 @Component, @Autowired, 생성자 주입
외부 설정값 주입 @Value, application.properties
ApplicationContext 구성 AnnotationConfigApplicationContext
이벤트 퍼블리싱 ApplicationEventPublisher 사용
이벤트 리스닝 @EventListener 로 결과 처리

마무리 – 실전은 연결이다

이전 회차까지 개별적으로 배운 기능들을 하나의 프로젝트로 통합해 보았습니다.
의존성 주입, 설정 분리, 이벤트 처리 등은 개념 그 자체보다 서로 어떻게 연결되고 설계되는지가 실무 핵심입니다.

이 계산기 프로젝트처럼 작고 명확한 구조를 설계하면서 핵심 요소를 조립해보면, 더 복잡한 서비스에서도 유사한 패턴으로 확장할 수 있습니다.


다음 11화에서는 테스트 가능한 구조 만들기를 주제로, 이 프로젝트를 단위 테스트 및 통합 테스트 가능한 구조로 리팩토링하고, @TestConfiguration, @MockBean 등을 활용해 실전 테스트 전략을 소개할 예정입니다.

 

728x90