의존성 주입과 스프링 전체 흐름

Dear·2025년 6월 23일

TIL

목록 보기
46/74

💙 @Autowired

Spring에서 의존성 주입(DI)을 자동으로 처리해주는 어노테이션

객체 간의 의존 관계를 설정할 때 직접 생성하는 대신, 스프링이 자동으로 의존 객체를 주입해준다.

// 개발자가 직접 의존성 주입 (spring 없이)
public class Dog {
    private final Animal animal;

    public Dog(Animal animal) { // 직접 주입
        this.animal = animal;
    }
}

// 사용하는 쪽에서 직접 주입
public class Main {
    public static void main(String[] args) {
        Animal animal = new Animal();       // 직접 생성
        Dog dog = new Dog(animal);          // 직접 주입
    }
}

// 수동 세터 주입(spring 없이)
public class Dog {
    private Animal animal;

    public void setAnimal(Animal animal) {
        this.animal = animal;
    }
}

// 사용하는 쪽
public class Main {
    public static void main(String[] args) {
        Animal animal = new Animal();       // 직접 생성
        Dog dog = new Dog();
        dog.setAnimal(animal);              // 직접 세터로 주입
    }
}

// @Autowired 사용해서 의존성 자동 주입
@Component
public class Dog {
    private Animal animal;

    @Autowired // 생성자 주입
    public Dog(Animal animal) {
        this.animal = animal;
    }

    // 또는 필드 주입
    // @Autowired
    // private Animal animal;
}

빈 등록과 주입

  • @Component → 등록
  • @Autowired → 주입
역할설명
@Component, @Service, @Repository, @Controller등해당 클래스를 스프링 빈으로 등록
@Autowired등록된 빈을 다른 클래스에 주입
@Component
public class Animal {
    // 이 클래스는 스프링 빈으로 등록됨
}

@Component
public class Dog {

    private final Animal animal;

    @Autowired // 등록된 Animal 빈을 자동으로 주입받음
    public Dog(Animal animal) {
        this.animal = animal;
    }
}

스프링 4.3부터는 생성자가 1개일 경우 @Autowired 생략 가능

💙 @Qualifier

동일한 타입의 빈이 여러 개 있을 때, 스프링에게 그중 어떤 걸 주입할지 지정해주는 어노테이션

동일한 인터페이스를 구현한 클래스가 여러 개 있을 때, 스프링은 어떤 구현체를 주입해야 할지 모호해서 오류가 발생한다.

public interface Animal {
    void sound();
}

@Component("dog") // 이름을 지정할 수도 있음
public class Dog implements Animal {
    public void sound() {
        System.out.println("멍멍");
    }
}

@Component("cat")
public class Cat implements Animal {
    public void sound() {
        System.out.println("야옹");
    }
}

// Animal 타입 빈이 2개(Dob, Cat) 생김 -> 모호성 발생
// @Qualifier로 특정 빈 지정

@Component
public class Zoo {

    private final Animal animal;

	// "dog"는 @Component("dog")에서 지정한 이름과 같음
    @Autowired
    public Zoo(@Qualifier("dog") Animal animal) {
        this.animal = animal;
    }

    public void makeSound() {
        animal.sound(); // 멍멍
    }
}

// 필드 주입에서 @Qualifier 사용
@Component
public class Zoo {

    @Autowired
    @Qualifier("cat")
    private Animal animal;

    public void makeSound() {
        animal.sound(); // 야옹
    }
}

// 에러 예시 
@Component
public class Zoo {

    @Autowired
    private Animal animal; // ❌ 오류: Animal 타입이 여러 개라서 선택 불가
}

💙 Java 기반의 수동 빈 등록 방식

@Configuration@Bean 으로 XML 설정을 대체하고, 코드 기반으로 의존성 주입을 구성할 수 있게 해준다.

@Configuration

스프링 설정 클래스임을 나타낸다.
이 클래스 안에서 @Bean 메소드를 사용해 직접 객체를 생성하고 빈으로 등록할 수 있다.

@Bean

메소드에 붙여서 해당 메소드가 반환하는 객체를 스프링 빈으로 등록하게 한다.
직접 new 해서 만든 객체를 스프링이 관리하게 만든다.

// 스프링 컨테이너 안에서 수동 주입
// @Bean으로 수동 설정

@Configuration
public class AppConfig {

    @Bean
    public Animal animal() {
        return new Animal(); //  수동 생성
    }

    @Bean
    public Dog dog() {
        return new Dog(animal()); // 직접 주입
    }
}

사용하는 시기

  1. 외부 라이브러리 클래스를 빈으로 등록하고 싶을때
    외부 라이브러리는 @Component 불가
  2. 테스트 용도 혹은 특정 조건에 따라 객체 구성 할 때
  3. 컴포넌트 스캔이 어려운 상황일 때

💙 스프링 시작 시 전체 흐름

Spring 애플리케이션이 실행될 때 내부적으로 동작하는 전체 흐름

1. 서블릿 컨테이너 시작 (Tomcat 등)

web.xml을 읽고 서블릿들을 로딩하며 Spring 애플리케이션을 부팅한다.

  • Apache Tomcat 같은 서블릿 컨테이너에 배포
  • web.xml의 설정대로 DispatcherServlet을 초기화한다.
<servlet>
  <servlet-name>spring</servlet-name>
  <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
  <init-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>/WEB-INF/spring/applicationContext.xml</param-value>
  </init-param>
  <load-on-startup>1</load-on-startup>
</servlet>

2. DispatcherServlet 초기화

Spring의 프론트 컨트롤러로 동작하며, 모든 요청을 가로채서 처리한다.

  • 서블릿 초기화 시점에 contextConfigLocation에 설정된 XML을 읽는다.
  • 내부적으로 WebApplicationContext라는 스프링 컨텍스트를 생성한다.

3. 스프링 컨테이너(Application Context) 초기화

설정 파일(XML) 기반으로 Bean을 생성 및 관리하는 핵심 컨테이너

  • applicationContext.xml파일을 읽어 Bean 등록
  • 일반적인 3가지 유형의 Bean 등록 방식
    1. XML 직접 정의 `
    1. @ComponentScan으로 클래스 스캔
    2. <context:annotation-config />로 어노테이션 DI 활성화
<context:component-scan base-package="com.example" />
<context:annotation-config />

이 단계에서 Spring은 IoC 컨테이너로 동작하기 시작한다.

4. Bean 등록 및 관리(IoC)

객체 생성, 생명주기 관리, 의존성 주입까지 모두 처리한다.

  • @Component, @Service, @Repository, @Controller가 붙은 클래스들을 자동 탐색
  • 각 클래스를 Bean으로 등록하고 관리한다.

5. 의존성 주입(DI)

Bean 간의 의존 관계를 주입해서 객체 간 결합도를 낮춘다.

  • @Autowired, @Inject, @Resource 등을 통해 의존성 주입 수행
  • 생성자/세터/필드 주입 방식이 존재한다.
@Service
public class UserService {
    @Autowired
    private UserRepository userRepository;
}

Spring 3에서는 @Autowired를 쓰려면 반드시 <context:annotation-config /> 또는 <context:component-scan />이 있어야 작동한다.

6. AOP 및 트랜잭션 설정 (선택)

횡단 관심사(로그, 보안, 트랜잭션 등)를 비즈니스 로직에서 분리해서 처리한다.

  • AOP 설정
    <aop:aspectj-autoproxy />
    - @Aspect 클래스가 활성화됨
    - 메서드 전후에 로깅, 권한체크, 예외처리 등을 끼워넣을 수 있음


  • 트랜잭션 설정
    <tx:annotation-driven transaction-manager="txManager" />
    - @Transactional을 사용 가능하게 해줌
    - 주로 DB 작업에서 롤백/커밋 처리 담당

7. HandlerMapping & HandlerAdapter 설정

어떤 URL이 어떤 Controller의 어떤 메서드에 매핑되는지를 결정한다.

  • Spring 내부적으로 여러 HandlerMapping 들이 존재
  • @RequestMapping("/hello") 같은 어노테이션을 가진 메서드들을 찾아 저장한다.

HandlerMapping이 URL → Controller 메서드 매핑을 수행하고,
HandlerAdapter가 실제 메서드를 실행시켜주는 역할을 한다.

8. ViewResolver 설정 및 반환 처리

컨트롤러가 반환한 논리적 이름을 실제 JSP 등 뷰로 매핑해준다.

<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
    <property name="prefix" value="/WEB-INF/views/"/>
    <property name="suffix" value=".jsp"/>
</bean>
  • 컨트롤러에서 "home"이라는 문자열을 반환하면 /WEB-INF/views/home.jsp를 응답으로 포워딩한다.

9. 웹 요청 처리 시작

사용자의 요청을 받아 Controller → Service → DAO → DB 등으로 요청 처리

🤍 회고

개념만 듣고는 잘 와닿지 않아, 예제를 많이 찾아보며 직접 실습해 이해하려고 노력했다. 그러다 보니 점점 내부 구조까지 궁금해져서 공부하게 되었고, 자연스럽게 스프링의 시작 흐름 전반을 정리해보게 되었다. 정리하면서 나중에 더 깊이 공부해야 할 내용도 발견했는데, 아직은 전체적으로 잘 이해되지 않는 부분이 많아 여러 번 읽고 반복하면서 익히고 외워야 할 것 같다.

profile
친애하는 개발자

0개의 댓글