[Day 26 | Spring] Spring Framework와 의존성 주입(DI)

y♡ding·2024년 11월 18일

데브코스 TIL - Spring

목록 보기
3/46

1. 의존성 주입 (Dependency Injection, DI)

정의:
DI는 클래스의 의존성을 해당 클래스가 직접 처리하는 대신, Spring IoC 컨테이너가 생성 및 관리하도록 위임하는 디자인 패턴입니다. 이를 통해 클래스와 의존성 간의 결합도가 낮아지고, 시스템이 더 모듈화되고 테스트하기 쉬워집니다.

핵심 개념

  • IoC(Inversion of Control, 제어의 역전):
    객체의 생성, 관리, 소멸 등을 프레임워크가 담당하며, 개발자는 이러한 작업에서 벗어날 수 있습니다.

  • ApplicationContext:
    Spring 컨테이너로, 설정을 제공하고, 빈(bean)을 관리하며 의존성을 해결합니다. 주요 예:

    • AnnotationConfigApplicationContext
    • GenericXmlApplicationContext
  • Bean:
    Spring 컨테이너에 의해 관리되는 객체입니다. 빈은 어노테이션(@Component, @Service, @Repository, @Controller) 또는 설정 클래스를 통해 정의됩니다.


2. 의존성 주입의 유형

A. 필드 주입(Field Injection)

  • @Autowired를 사용하여 의존성을 private 필드에 직접 주입합니다.

예제:

@Service("writeAction")
public class WriteAction {
    @Autowired
    private BoardDAO dao; // 컨테이너에 의해 주입됨

    public WriteAction() {
        System.out.println("WriteAction 생성자: dao = " + dao);
    }

    public void getDAO() {
        System.out.println("주입된 DAO: " + dao);
    }
}
  • 장점:
    • 코드가 간결하며, 생성자나 세터 메서드가 필요 없습니다.
  • 단점:
    • 의존성이 private 필드로 설정되므로, 테스트가 어려울 수 있습니다.

B. 생성자 주입(Constructor Injection)

  • 생성자를 통해 의존성을 주입합니다.

예제:

@Service("writeAction")
public class WriteAction {
    private final BoardDAO dao;

    @Autowired
    public WriteAction(BoardDAO dao) {
        this.dao = dao;
        System.out.println("생성자 주입: dao = " + dao);
    }
}
  • 장점:
    • 불변성을 보장하며, 필수 의존성 주입에 적합합니다.
  • 단점:
    • 여러 의존성을 가진 클래스에서는 가독성이 떨어질 수 있습니다.

C. 세터 주입(Setter Injection)

  • 세터 메서드를 통해 의존성을 주입합니다.

예제:

@Service("writeAction")
public class WriteAction {
    private BoardDAO dao;

    @Autowired
    public void setDao(BoardDAO dao) {
        this.dao = dao;
        System.out.println("세터 주입: dao = " + dao);
    }
}
  • 장점:
    • 선택적 의존성에 유연하게 대응할 수 있습니다.
  • 단점:
    • 의존성 설정을 위한 추가 메서드가 필요합니다.

3. Qualifier를 활용한 의존성 분해

  • 문제: 같은 타입의 빈이 여러 개 존재할 경우, Spring은 어떤 빈을 주입해야 할지 모릅니다.
  • 해결 방법: @Qualifier를 사용하여 특정 빈을 지정합니다.

예제:

@Bean("oracle")
public OracleDatabaseDAO oracleDatabaseDAO() {
    return new OracleDatabaseDAO();
}

@Service
public class DatabaseService {
    @Autowired
    @Qualifier("oracle") // 특정 빈 이름을 명시적으로 지정
    private DatabaseDAO databaseDAO;

    public List<String> getList() {
        return databaseDAO.list();
    }
}

4. 라이프사이클 관리

Spring은 빈의 라이프사이클을 관리합니다:

  • 생성: 컨텍스트 초기화 시 빈이 생성됩니다.
  • 초기화: @PostConstruct 또는 사용자 정의 초기화 메서드가 호출됩니다.
  • 소멸: 컨텍스트가 닫힐 때 빈이 소멸됩니다 (@PreDestroy 또는 사용자 정의 소멸 메서드).

5. Spring Boot에서 ApplicationContext

Spring Boot에서는 ApplicationContext가 자동으로 생성되고 구성됩니다:

  • @SpringBootApplication이 이를 관리합니다.
  • ctx.getBean()을 통해 빈을 가져오거나, 직접 @Autowired로 주입받을 수 있습니다.

6. AOP (Aspect-Oriented Programming)

정의:
AOP는 로깅, 보안, 트랜잭션 관리와 같은 횡단 관심사를 모듈화합니다. 이를 통해 핵심 비즈니스 로직과 이러한 관심사를 분리하여 유지보수성을 향상시킵니다.

핵심 용어

  • Advice: 실행할 코드 (@Around, @Before, @After 등).
  • Pointcut: Advice를 적용할 지점을 정의하는 표현식.
  • JoinPoint: 프로그램 흐름 중 특정 지점(예: 메서드 실행).
  • Aspect: Advice와 Pointcut을 캡슐화하는 모듈.

예제: Around Advice

@Aspect
@Configuration
public class BasicAdvice {

    @Around("execution(public * say*(..))") // "say"로 시작하는 메서드에 적용
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        System.out.println("메서드 실행 전");
        Object result = joinPoint.proceed();
        System.out.println("메서드 실행 후");
        return result;
    }
}

Spring Boot 통합

Spring Boot에서는 AOP가 자동으로 활성화되며, @Aspect@EnableAspectJAutoProxy를 통해 Aspect를 감지합니다.


7. 예제 워크플로우: 데이터베이스 접근

시나리오:

  • 두 개의 DAO: MySQLDatabaseDAOOracleDatabaseDAO.
  • 이 DAO를 사용해 리스트를 반환하는 DatabaseService.

구현:
1. 공통 인터페이스를 구현하는 DAO를 생성합니다.

public interface DatabaseDAO {
    List<String> list();
}

public class MySQLDatabaseDAO implements DatabaseDAO {
    @Override
    public List<String> list() {
        return Arrays.asList("Data 1", "Data 2");
    }
}

public class OracleDatabaseDAO implements DatabaseDAO {
    @Override
    public List<String> list() {
        return Arrays.asList("Data 3", "Data 4");
    }
}
  1. @Autowired@Qualifier를 사용하는 서비스 생성:

    @Service
    public class DatabaseService {
        @Autowired
        @Qualifier("oracle")
        private DatabaseDAO databaseDAO;
    
        public List<String> getList() {
            return databaseDAO.list();
        }
    }
  2. 설정 클래스에서 빈을 정의합니다:

    @Configuration
    public class BeanConfig {
        @Bean("mysql")
        public MySQLDatabaseDAO mySQLDatabaseDAO() {
            return new MySQLDatabaseDAO();
        }
    
        @Bean("oracle")
        public OracleDatabaseDAO oracleDatabaseDAO() {
            return new OracleDatabaseDAO();
        }
    
        @Bean
        public DatabaseService service() {
            return new DatabaseService();
        }
    }

핵심 장점

  • DI는 객체 생성 및 의존성 관리를 분리하여 코드 결합도를 줄입니다.
  • AOP는 핵심 로직과 횡단 관심사를 분리하여 유지보수성을 향상시킵니다.
  • Spring Boot는 애플리케이션 설정과 빈 관리를 간소화합니다.

0개의 댓글