Q1: Spring Boot에서 의존성 주입(Dependency Injection)이란 무엇이며, 왜 중요한가요?

A1:
"의존성 주입은 객체 간의 의존 관계를 애플리케이션 외부에서 주입하여 관리하는 디자인 패턴입니다. Spring Boot에서는 IoC(Inversion of Control) 컨테이너가 이를 담당하며, @Autowired, @Component, @Service 등 애노테이션을 통해 Bean을 자동 등록 및 관리합니다. 이를 통해 결합도를 낮추고, 테스트 및 유지보수를 용이하게 할 수 있습니다."

 

 

Q2: Spring Boot의 Auto Configuration 기능에 대해 설명해 주세요.

A2:
"Spring Boot의 Auto Configuration은 클래스패스에 포함된 라이브러리와 설정 파일을 기반으로 적절한 Bean들을 자동으로 등록하여 개발자가 최소한의 설정만으로 애플리케이션을 구축할 수 있도록 도와줍니다. 예를 들어, 데이터베이스 관련 라이브러리가 포함되면 기본 DataSource를 자동으로 설정합니다. 또한, 필요에 따라 개발자가 설정을 오버라이드하여 커스터마이징할 수 있습니다."

 

 

Q3: application.properties와 application.yml 파일의 차이와 활용 방법을 설명해 주세요.

A3:
"application.properties는 key-value 쌍으로 단순하게 설정을 작성할 수 있는 반면, application.yml은 YAML 형식을 사용하여 계층적이고 구조적인 설정이 가능합니다. 복잡한 설정이나 여러 환경(개발, 테스트, 운영 등)에 따라 다른 설정을 적용할 때 yml 파일이 더 가독성이 좋습니다. 두 파일 모두 스프링 프로파일을 활용해 환경별로 다른 설정을 쉽게 관리할 수 있습니다."

 

 

Q4: Spring MVC 아키텍처의 주요 구성 요소와 역할에 대해 설명해 주세요.

A4:
"Spring MVC는 웹 요청을 처리하기 위한 아키텍처로, 주요 구성 요소로는 DispatcherServlet, Controller, Service, Repository 등이 있습니다. DispatcherServlet은 모든 요청을 중앙에서 받아 적절한 컨트롤러로 분배하며, 컨트롤러는 요청을 처리하고 결과를 반환합니다. Service는 비즈니스 로직을 수행하고, Repository는 데이터베이스와의 상호작용을 담당합니다. 이러한 계층적 구조는 코드의 재사용성과 유지보수를 용이하게 만듭니다."

 

 

Q5: RESTful API 설계의 기본 원칙과 고려해야 할 요소에 대해 설명해 주세요.

A5:
"RESTful API 설계에서는 리소스 중심의 URL 설계, HTTP 메서드(GET, POST, PUT, DELETE 등)의 올바른 사용, 그리고 HTTP 상태 코드의 적절한 사용이 중요합니다. 또한, 클라이언트와 서버 간의 상태 비저장을 유지하고, 버전 관리 및 API 문서화(Swagger 등)를 통해 API의 변경 사항을 명확하게 관리하는 것이 필요합니다."

'기술면접준비' 카테고리의 다른 글

면접 예상 질문과 답변  (0) 2025.04.28
WAS와 WS  (1) 2023.12.20
기술면접준비(1)  (0) 2023.12.19

의존성? 의존성이 뭘까?

어떤 물건을 만들거나 사용할 때 다른 것이 꼭 필요할때 우리는 그것을 의존한다고 한다.

예를 들어 자동차는 엔진이 꼭 필요하다.

  • 자동차는 혼자서는 움직일 수 없다.
  • 반드시 엔진이 있어야 움직일 수 있다.
  • 자동차는 엔진에 의존하고 있다고 말할 수 있다.

이처럼 어떤 객체가 다른 객체를 필요로 할 때, 의존성이 있다고 말한다.

 

그렇다면 의존성 주입(Dependency Injection) 이란 무엇일까?

 

만약 자동차가 엔진을 직접 만들 필요 없이, 공장에서 엔진을 가져와 조립할 수 있다면 더 편리하지 않을까?? 

>>> 이것이 의존성 주입니다.

 

쉽게 말해

  • 자동차(클래스)는 엔진을 직접 만들지 않음
  • 필요한 엔진을 공장에서(스프링)  가져옴
  • 스프링이 자동차에 엔진을 자동으로 넣어줌(의존성 주입)!

 

오호라..스프링이 있으면 편리해 보인다. 그럼 만약에 의존성 주입이 없다면 어떤 문제가 생길까???

class Car {
    private Engine engine = new Engine();  // 자동차가 직접 엔진을 생성
    
    public void start() {
        engine.run();
    }
}

 

이런 식으로 코드를 작성할 경우

  1. 자동차(Car)가 직접 엔진을 생성하고 있다.
  2. 나중에 엔진을 바꾸고 싶다면 자동차 코드를 직접 수정해야 한다.
  3. 코드가 유연하지 않고, 새로운 기능 추가도 어려워진다.

즉, 자동차가 직접 엔진을 만들게되면, 나중에 다른 엔진(전기 엔진, 하이브리드 엔진)으로 바꾸기 어려워진다.

 

 

그렇다면 의존성 주입을 사용하려면 어떻게 해야할까???

 

Spring Boot에서는 자동으로 필요한 객체(엔진)를 주입해줄 수 있다.

class Car {
    private Engine engine;

    public Car(Engine engine) {  // 외부에서 엔진을 넣어줌
        this.engine = engine;
    }

    public void start() {
        engine.run();
    }
}

 

이렇게 되면 자동차는 아주 쉽게 엔진을 교체할 수  있다.

  • Engine engine = new GasEngine();  (가솔린 엔진)
  • Engine engine = new ElectricEngine(); (전기 엔진)
  • Engine engine = new HybridEngine(); (하이브리드 엔진)

즉, 자동차 스스로 엔진을 직접 만들 필요 없이, 외부에서 받아서 사용할 수 있다.

 

 

너무 신기하다 근데 스프링은 어떻게 의존성을 주입하는 걸까?

 

스프링은 자동으로 필요한 객체를 찾아 주입(Injection) 해준다.

@Component
class Engine {
    public void run() {
        System.out.println("엔진이 가동됩니다.");
    }
}

@Component
class Car {
    private final Engine engine;

    @Autowired  // 스프링이 자동으로 Engine을 넣어줌!
    public Car(Engine engine) {
        this.engine = engine;
    }

    public void start() {
        engine.run();
    }
}

 

@Autowired를 사용하면 스프링이 알아서 엔진을 찾아서(Car에) 넣어준다.

이제 Car 객체를 만들 때 자동으로 Engine이 들어간다.

 

 

의존성 주입 3가지 방법

 

1. 생성자 주입(추천)

class Car {
    private final Engine engine;

    @Autowired
    public Car(Engine engine) {  // 생성자를 통해 의존성 주입
        this.engine = engine;
    }
}

 

장점 : 

  • 반드시 필요한 값이 주입된다 (final 사용 가능)
  • 테스트하기 쉽고 유지보수도 편리하다

 

2. 필드 주입(사용 지양)

class Car {
    @Autowired
    private Engine engine;  // 필드에 직접 주입
}

 

단점 : 

  • 필수값이 빠질 수 있다.
  • 테스트하기 어렵다
  • Spring Context 없이 사용할 수 없다.

 

3. Setter 주입

class Car {
    private Engine engine;

    @Autowired
    public void setEngine(Engine engine) {  // Setter를 통해 주입
        this.engine = engine;
    }
}

 

장점 : 필요할 때 객체를 바꿀 수 있다.

단점 : 의존성이 필수가 아닐 수도 있음(Setter를 호출하지 않으면 값이 없다)

 

 

 

의존성에 대해 알아보았는데 의존성 주입에 대해 요약하자면

 

중요한 이유 !

  1. 코드의 재사용성이 높아짐 -> Car 클래스를 수정하지 않고 다양한 Engine을 사용가능
  2. 유지보수가 쉬워짐 -> Engine을 변경할 때 Car 클래스를 수정할 필요가 없다.
  3. 테스트하기 쉬움 -> 테스트할 때 가자(Mock) 객체를 쉽게 주입할 수 있다.
  4. 스프링이 자동으로 객체를 관리 ->  개발자가 직접 객체를 만들 필요가 없다.

 

1. 그리디 알고리즘의 정의

그리디 알고리즘은 문제를 해결할 때 현재 상황에서 가장 최선의 선택을 하여 최종 해답을 도출하는 방법이다. 이 때, 각 단계마다 선택하는 것이 최적해를 보장하는 경우에만 효과적이다.

 

 

2. 국부 최적 선택(Local Optimal Choice) 이란??

국부 최적선택이란 각 단계에서 당장 가장 이득이 큰 선택을 하는 것을 의미한다. 예를 들어, 쇼핑할 때 할인율이 가장 높은 상품을 먼저 고르는 것과 비슷하다.

 

 

3. 그리디 알고리즘의 장단점

장점 : 

  • 구현이 단순하고 빠르며, 계산 비용이 적다.
  • 문제의 크기가 커져도 각 단계의 선택만 고려하면 되므로 효율적이다.

단점 : 

  • 매 순간 최선의 선택이 전체 최적해를 보장하지 않을 수 있다.
  • 문제에 따라 그리디 알고리즘이 최적해를 찾지 못하는 경우가 있다.

 

4. 대표 문제 : 동전 거스름돈 문제

 - 문제 설명 : 정당한 화폐 단위 (예: 500원, 100원, 50원, 10원)가 주어지고, 거슬러 줘야 하는 금액이 주어졌을 때, 가장 적은 수의 동전으로 거스름돈을 지급하는 방법을 구하는 문제다.

 - 힌트 : 그리디로 접근하면 거슬러 줄 금액이 있을 때, 가장 큰 단위의 동전을 최대한 많이 사용한다. 그 후, 남은 금액에 대해 같은 방식으로 반복한다.

 - 조건 : 이 접근법은 화폐 단위가 "정당한 화폐 단위"일 때 최적해를 보장한다.

 

 

Kotlin을 이용한 동전 거스름돈 문제 구현

fun coinChange(amount: Int, coins: Array<Int>): Int {
    var remaining = amount
    var count = 0

    // 동전 배열은 큰 단위부터 정렬되어 있다고 가정합니다.
    for (coin in coins) {
        if (remaining >= coin) {
            val numCoins = remaining / coin  // 해당 동전으로 몇 개를 사용할 수 있는지 계산
            count += numCoins                 // 총 동전 개수에 더합니다.
            remaining %= coin                 // 남은 금액을 계산합니다.
            println("동전 $coin원: 사용 개수 = $numCoins, 남은 금액 = $remaining")
        }
    }
    return count
}

fun main() {
    // 예제: 거스름돈 1260원, 화폐 단위는 500, 100, 50, 10원
    val amount = 1260
    val coins = arrayOf(500, 100, 50, 10)
    println("총 동전 개수: ${coinChange(amount, coins)}")
}

 

  • 초기 변수 설정:
    • remaining 변수는 아직 거슬러 줘야 하는 금액을 저장한다.
    • count 변수는 사용한 동전의 총 개수를 저장한다.
  • 동전 단위 순회:
    • 배열 coins는 큰 단위부터 정렬되어 있으므로, 500원부터 차례로 처리한다.
    • 만약 remaining 금액이 현재 동전보다 크거나 같다면,
      remaining / coin으로 해당 동전이 몇 개 필요한지 계산한다.
  • 동전 개수 갱신 및 남은 금액 계산:
    • 계산한 동전 개수를 count에 더하고,
    • remaining %= coin으로 남은 금액을 갱신한다.
    • 각 단계에서 현재 동전 단위, 사용 개수, 남은 금액을 출력하여 진행 과정을 확인할 수 있다.
  • 최종 결과 반환:
    • 모든 동전 단위를 처리한 후, count를 반환한다.

 

실행결과

동전 500원: 사용 개수 = 2, 남은 금액 = 260
동전 100원: 사용 개수 = 2, 남은 금액 = 60
동전 50원: 사용 개수 = 1, 남은 금액 = 10
동전 10원: 사용 개수 = 1, 남은 금액 = 0
총 동전 개수: 6

 

+ Recent posts