스프링 이전의 Bean 관리 방법
Spring이 등장하기 전, Java 애플리케이션에서 객체(Bean) 관리는 개발자가 직접 수행해야 했습니다.
대표적인 방식으로는 1) new 키워드를 이용한 수동 객체 생성, 2) Factory 패턴을 이용한 객체 관리 를 활용한 객체 관리 등이 있었습니다.
1. new 키워드를 이용한 객체 생성 (수동 관리)
가장 기본적인 방식은 개발자가 직접 객체를 생성하고 관리하는 것이었습니다.
예제: 전통적인 객체 생성 방식
public class UserService {
private UserRepository userRepository;
public UserService() {
this.userRepository = new UserRepository(); // 직접 객체 생성
}
public void doSomething() {
userRepository.findUser();
}
}
public class UserRepository {
public void findUser() {
System.out.println("사용자 조회 로직 실행...");
}
}
⚠️ 문제점
- 객체 간 강한 결합 (Tightly Coupled)
- UserService가 UserRepository의 구현에 직접 의존하므로, 변경이 필요할 때 UserService 코드도 수정해야 함.
- 재사용성 및 테스트 어려움
- UserRepository의 다른 구현체(JdbcUserRepository, JpaUserRepository 등)를 사용하려면 기존 UserService 코드를 수정해야 함.
- 유닛 테스트 시 UserRepository를 Mock 객체로 대체하기 어려움.
- 객체 생명주기 관리 어려움
- 개발자가 직접 객체를 생성하고 파괴해야 하므로, 메모리 관리 및 라이프사이클 제어가 번거로움.
2. Factory 패턴을 이용한 객체 관리
이 문제를 해결하기 위해 Factory 패턴을 사용하여 객체 생성을 분리하는 방식이 등장했습니다.
예제: Factory 패턴 적용
public class UserRepositoryFactory {
public static UserRepository createUserRepository() {
return new UserRepository(); // 객체 생성 로직을 Factory가 담당
}
}
public class UserService {
private UserRepository userRepository;
public UserService() {
this.userRepository = UserRepositoryFactory.createUserRepository(); // Factory에서 객체를 생성
}
public void doSomething() {
userRepository.findUser();
}
}
✅ 개선된 점
- UserService가 UserRepository의 구체적인 구현을 직접 생성하지 않음 → 결합도를 낮출 수 있음
- 객체 생성을 Factory가 담당하여 객체 생성 로직을 중앙 집중화할 수 있음
⚠️ 여전히 남아있는 문제
- UserService가 UserRepositoryFactory에 의존하고 있어, 완전한 DI(의존성 주입)가 아님
- 여전히 객체의 라이프사이클을 관리해야 함
Spring 등장 후의 변화
Spring이 등장하면서 IoC(Inversion of Control)와 DI(Dependency Injection) 개념을 기반으로 객체 관리가 훨씬 쉬워졌습니다.
Spring의 등장 전후 비교
구분 | Spring 이전 (수동 관리) | Spring 이후 (자동 관리) |
객체 생성 | 개발자가 직접 new 사용 | Spring 컨테이너가 Bean을 관리 |
의존성 주입 | 직접 코드에서 생성 | @Autowired, @Bean 등을 이용한 자동 주입 |
결합도 | 강한 결합 (Tightly Coupled) | 낮은 결합 (Loosely Coupled) |
테스트 용이성 | 테스트 어려움 (Mock 객체 주입 어려움) | @MockBean, @Profile 등으로 테스트 용이 |
설정 방식 | XML, JNDI 설정 필요 | Java 기반 설정 (@Configuration, @ComponentScan) |
Spring의 IoC / DI 적용 예제
import org.springframework.stereotype.Component;
@Component
public class UserRepository {
public void findUser() {
System.out.println("사용자 조회...");
}
}
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class UserService {
private final UserRepository userRepository;
@Autowired
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
public void doSomething() {
userRepository.findUser();
}
}
Spring으로 개선된 점
✅ 개발자가 객체 생성을 직접 관리할 필요 없음 → 유지보수성 향상
✅ 의존성 주입(DI)을 통해 결합도가 낮아짐 → 테스트 및 확장성 향상
✅ 라이프사이클 관리가 자동으로 이루어짐 → 메모리 관리 최적화
✅ 설정이 간결해지고 @ComponentScan, @Bean, @Autowired 등을 활용하여 관리 용이
스프링 프레임워크의 특징
제어의 역행 (IoC)
IoC (Inversion of Control )애플리케이션의 느슨한 결합을 도모. 컨트롤의 제어권이 사용자가 아니라 프레임워크에 있어 필요에 따라 스프링에서 사용자의 코드를 호출한다.
- 팩토리 패턴을 사용하여 객체 간의 결합도를 줄여 가독성을 좋게 하고, 코드 중복을 최소화하며, 유지보수를 편하게 할 수 있습니다.
- 기존에 사용자가 모든 작업을 제어하던 것을 컨테이너에게 위임하여 객체의 생성부터 생명주기 등 모든 객체에 대한 제어권이 넘어 간 것을 IoC라고 합니다.
BeanFactory
BeanFactory 인터페이스는 IoC컨테이너의 기능을 정의하고 있는 인터페이스이며, Bean의 생성 및 의존성 주입, 생명주기(lifecycle) 관리 등의 기능을 제공한다.
클라이언트의 요청에 의해서 Bean 객체가 사용되는 시점(Lazy Loading)에 객체를 생성
ApplicationContext(Spring 컨테이너, IoC 컨테이너)
BeanFactory 인터페이스를 상속받는 ApplicationContext는 BeanFactory가 제공하는 기능 외에 AOP, 트랜잭션 관리 등의 기능을 제공한다.
컨테이너가 구동되는 시점에 객체들을 생성하는 Pre-Loading 방식
애플리케이션 실행 시점에 오류를 즉시 발견할 수 있다.
- 트랜잭션 이란 쉽게 말해 처리 단위를 뜻한다.
의존성 주입(DI)
제어의 역행이 일어나는 것을 전제로 하여 스프링 내부의 객체들간의 관계를 만들어줄 때 사용합니다.
외부에서 두 객체 간의 관계를 결정해주는 디자인 패턴으로, 인터페이스를 사이에 둬서 클래스 레벨에서는 의존관계가 고정되지 않도록 하고 런타임 시에 관계를 동적으로 주입하여 유연성을 확보하고 결합도를 낮출 수 있게 해준다.
빈(Bean) 설정파일에 의존관계가 필요한 정보를 추가해주면 컨테이너가 자동적으로 연결해준다.
- 각각의 계층이나 서비스들 간에 의존성이 존재할 경우 프레임워크가 서로 연결시켜준다.
- 의존성 주입은 말 그대로 특정 객체가 필요로 하는 객체를 외부에서 결정하여 연결시키는 것을 말합니다.
POJO 기반의 구성
POJO는 Plain Old Java Object로, 평범한 자바 객체를 말합니다.
이 단어는 마틴 파울러가 2000년에 컨퍼런스 발표를 준비하다가 만들어낸 용어인데, 단순히 발표 중의 "간단한 자바 오브젝트를 사용하는데요~"라고 하는 것보다 "POJO 방식의 기술을 사용하는데요~"라고 하면 왠지 세련되고 첨단 기술을 쓰는 것처럼 느껴진다는 심리를 이용하여 만들어진 것이라고 합니다. 그래서 우리가 자바에서 개발하는 지극히 평범한 객체를 POJO라고 합니다.
다만, POJO는 특정 규약과 특정 환경에 종속되어서는 안 되고 객체지향 설계를 잘 지켜야한다는 조건이 있습니다.
AOP 지원
AOP(Aspect Oriented Programming)는 관점 지향 프로그래밍을 뜻합니다.
대부분의 시스템에서 비즈니스 로직은 아니지만 보안, 로그, 트랜잭션과 같이 반드시 처리가 필요한 부분을 횡단 관심사라고 합니다. 스프링에서는 이러한 관심사를 비즈니스 로직과 분리하여 중복된 코드를 줄이고 개발자가 비즈니스 로직에 집중하도록 만들어 줍니다.
- 관점지향 프로그래밍(AOP : Aspect-Oriented Programming) : 트랜잭션이나 로깅, 보안과 같이 여러 모듈에서 공통적으로 사용하는 기능의 경우 해당 기능을 분리하여 관리할 수 있다.
오버라이딩(Overriding)
부모 클래스에서 상속받은 자식 클래스에서 부모클래스에서 만들어진 메서드를 자식 클래스에서 자신의 입맛대로 다시 재정의해서 사용하는 것을 말한다.
- 인터페이스를 구현한 객체 인스턴스를 실행 시점에 유연하게 변경할 수 있다
- 로미오와 줄리엣의 역할 → 구현체로 장동건과 원빈, 그리고 김태희와 송혜교 등
- 자동차라는 역할 → 구현체로 K3, 아반떼, 테슬라 모델3 등이 있습니다.
의존성 주입 DI (@Autowired , @Component)
컨테이너에서 필요한 의존 객체의 타입에 해당하는 빈 객체를 찾아 주입한다.
생성자, 필드, setter에 Autowired를 사용할 수 있다.
1)생성자 주입
생성자를 통해 의존 관계를 주입하는 방법
생성자 주입은 생성자의 호출 시점에 1회 호출 되는 것이 보장된다. 그렇기 때문에 주입받은 객체가 변하지 않거나, 반드시 객체의 주입이 필요한 경우에 강제하기 위해 사용할 수 있다. 또한 생성자가 1개만 있을 경우에 @Autowired를 생략해도 주입이 가능하도록 편의성을 제공한다.
2) 세터 주입
필드 값을 변경하는 Setter를 통해서 의존 관계를 주입하는 방법
Setter 주입은 생성자 주입과 다르게 주입받는 객체가 변경될 가능성이 있는 경우에 사용한다.
3)필드 주입
필드에 바로 의존 관계를 주입하는 방법
필드 주입을 이용하면 코드가 간결해져서 과거에 상당히 많이 이용되었던 주입 방법이다. 하지만 필드 주입은 외부에서 변경이 불가능하다는 단점이 존재하는데, 점차 테스트 코드의 중요성이 부각됨에 따라 필드의 객체를 수정할 수 없는 필드 주입은 거의 사용되지 않게 되었다. 또한 필드 주입은 반드시 DI 프레임워크가 존재해야 하므로 반드시 사용을 지양해야 한다.
생성자 주입(Constructor Injection)을 사용해야 하는 이유
1)객체의 불변성 확보
의존 관계 주입의 변경이 필요한 상황은 거의 없다. 하지만 수정자 주입이나 일반 메소드 주입을 이용하면 불필요하게 수정의 가능성을 열어두게 되며, 이는 OOP의 5가지 개발 원칙 중 OCP(Open-Closed Principal, 개방-폐쇄의 법칙)를 위반하게 된다. 그러므로 생성자 주입을 통해 변경의 가능성을 배제하고 불변성을 보장하는 것이 좋다.
2)테스트 코드의 작성
필드 주입으로 작성된 경우 테스트 코드가 Spring과 같은 DI 프레임워크 위에서 동작하지 않으므로 의존 관계 주입이 되지 않기 때문에 순수한 자바 코드로 단위 테스트를 작성하는 것이 불가능하다.
3)final 키워드 작성 및 Lombok과의 결합
생성자 주입을 사용하면 필드 객체에 final 키워드를 사용할 수 있으며, 컴파일 시점에 누락된 의존성을 확인할 수 있다. 반면에 생성자 주입을 제외한 다른 주입 방법들은 객체의 생성(생성자 호출) 이후에 호출되므로 final 키워드를 사용할 수 없다.
또한 final 키워드를 붙임으로써 Lombok과 결합되어 코드를 간결하게 작성할 수 있다. Lombok에는 final 변수를 위한 생성자를 대신 생성해주는 @RequiredArgsConstructor가 있다.
Spring Bean
Spring 컨테이너가 관리하는 객체를 빈(Bean)이라고 부릅니다. IoC 컨테이너 안에 들어있는 객체를 필요할 때 IoC 컨테이너에서 가져와서 사용합니다.
스프링 Bean을 IoC 컨테이너에 등록하는 방법
1)Component scan
- @ComponentScan 어노테이션과 @Component 어노테이션을 사용해서 빈을 등록하는 방법
- @ComponentScan: 어느 지점부터 컴포넌트를 찾으라고 알려주는 역할
- @Component: 실제로 찾아서 빈으로 등록할 클래스
2) 빈 설정 파일에 직접 등록
- XML 파일이나 설정 파일로 작성할 수 있음
- 자바 설정 파일: @Configuration 어노테이션을 붙인 클래스에 @Bean 어노테이션을 사용해 직접 빈을 정의할 수 있음
@Component
@Component는 Spring에서 Bean(객체)을 자동으로 생성하고 관리하도록 지정하는 애너테이션입니다.
즉, 개발자가 new 키워드를 사용하지 않고도, Spring이 직접 객체를 만들고 관리하도록 할 수 있습니다.
- IoC 컨테이너에 Bean을 등록하기 위해 사용
- 개발자가 작성한 class를 기반으로 런타임 시에 컴포넌트 스캔하여 인스턴스 객체를 생성합니다.
- @Controller, @Service, @Repository 모두 @Component이며 실행시점에 자동으로 의존성을 주입합니다.
애너테이션 | 역할 |
@Component | 일반적인 Bean 등록 |
@Service | 서비스 계층 (비즈니스 로직) Bean 등록 |
@Repository | 데이터 액세스 계층 (DAO, Repository) Bean 등록 |
@Controller | Spring MVC 컨트롤러 등록 |
@Bean
- IoC 컨테이너에 Bean을 등록하기 위해 사용
- 개발자가 작성한 메소드를 기반으로 메서드에서 반환하는 객체를 인스턴스 객체로 생성합니다.
@Component vs @Bean 차이점
구분 | @Component | @Bean |
자동 등록 여부 | Spring이 자동으로 스캔하여 등록 | 개발자가 직접 등록 |
사용 위치 | 클래스 선언부 | @Configuration 클래스 내부의 메서드 |
유연성 | 클래스 레벨에서 간편하게 사용 | 객체 생성 로직을 세밀하게 제어 가능 |
스프링 Bean의 Scope
빈 스코프는 빈이 존재할 수 있는 범위를 뜻하며 싱글톤, 프로토타입, request, session 등이 있다.
@Scope
- singleton: 기본 스코프로 스프링 컨테이너의 시작과 종료까지 유지되는 가장 넓은 범위의 스코프
- prototype : 애플리케이션 요청시( getBean() 메서드가 호출될 때마다) 스프링이 새 인스턴스를 생성합니다.
- request : HTTP 요청별로 인스턴스화 되며 요청이 끝나면 소멸됩니다.
- session : HTTP 세션별로 인스턴스화 되며 세션이 끝나면 소멸됩니다.
- thread : 새 스레드에서 요청하면 새로운 bean 인스턴스를 생성합니다. 같은 스레드의 요청에는 항상 같은 인스턴스가 반환됩니다.
- custom : org.pringframework.beans.factory.config.Scope 인터페이스를 구현하고 커스텀 스코프를 스프링의 설정에 등록하여 사용합니다.
연관된 글:
[CS] 좋은 객체 지향 설계의 5대 원칙: SOLID
참고 :
@Controller, @Service, @Repository 차이
[Spring] 스프링 빈(Bean)의 개념과 생성 원리
[Spring] 다양한 의존성 주입 방법과 생성자 주입을 사용해야 하는 이유 - (2/2)
'개발 > Spring' 카테고리의 다른 글
[Spring] @Tansactional (0) | 2023.04.17 |
---|---|
[Spring] JPA와 ORM (0) | 2023.03.22 |
[Spring] 어노테이션 모음집 (0) | 2023.03.08 |
[Spring] QueryDSL (0) | 2023.02.20 |
[Spring] Spring Boot (0) | 2023.02.10 |
스프링 이전의 Bean 관리 방법
Spring이 등장하기 전, Java 애플리케이션에서 객체(Bean) 관리는 개발자가 직접 수행해야 했습니다.
대표적인 방식으로는 1) new 키워드를 이용한 수동 객체 생성, 2) Factory 패턴을 이용한 객체 관리 를 활용한 객체 관리 등이 있었습니다.
1. new 키워드를 이용한 객체 생성 (수동 관리)
가장 기본적인 방식은 개발자가 직접 객체를 생성하고 관리하는 것이었습니다.
예제: 전통적인 객체 생성 방식
public class UserService {
private UserRepository userRepository;
public UserService() {
this.userRepository = new UserRepository(); // 직접 객체 생성
}
public void doSomething() {
userRepository.findUser();
}
}
public class UserRepository {
public void findUser() {
System.out.println("사용자 조회 로직 실행...");
}
}
⚠️ 문제점
- 객체 간 강한 결합 (Tightly Coupled)
- UserService가 UserRepository의 구현에 직접 의존하므로, 변경이 필요할 때 UserService 코드도 수정해야 함.
- 재사용성 및 테스트 어려움
- UserRepository의 다른 구현체(JdbcUserRepository, JpaUserRepository 등)를 사용하려면 기존 UserService 코드를 수정해야 함.
- 유닛 테스트 시 UserRepository를 Mock 객체로 대체하기 어려움.
- 객체 생명주기 관리 어려움
- 개발자가 직접 객체를 생성하고 파괴해야 하므로, 메모리 관리 및 라이프사이클 제어가 번거로움.
2. Factory 패턴을 이용한 객체 관리
이 문제를 해결하기 위해 Factory 패턴을 사용하여 객체 생성을 분리하는 방식이 등장했습니다.
예제: Factory 패턴 적용
public class UserRepositoryFactory {
public static UserRepository createUserRepository() {
return new UserRepository(); // 객체 생성 로직을 Factory가 담당
}
}
public class UserService {
private UserRepository userRepository;
public UserService() {
this.userRepository = UserRepositoryFactory.createUserRepository(); // Factory에서 객체를 생성
}
public void doSomething() {
userRepository.findUser();
}
}
✅ 개선된 점
- UserService가 UserRepository의 구체적인 구현을 직접 생성하지 않음 → 결합도를 낮출 수 있음
- 객체 생성을 Factory가 담당하여 객체 생성 로직을 중앙 집중화할 수 있음
⚠️ 여전히 남아있는 문제
- UserService가 UserRepositoryFactory에 의존하고 있어, 완전한 DI(의존성 주입)가 아님
- 여전히 객체의 라이프사이클을 관리해야 함
Spring 등장 후의 변화
Spring이 등장하면서 IoC(Inversion of Control)와 DI(Dependency Injection) 개념을 기반으로 객체 관리가 훨씬 쉬워졌습니다.
Spring의 등장 전후 비교
구분 | Spring 이전 (수동 관리) | Spring 이후 (자동 관리) |
객체 생성 | 개발자가 직접 new 사용 | Spring 컨테이너가 Bean을 관리 |
의존성 주입 | 직접 코드에서 생성 | @Autowired, @Bean 등을 이용한 자동 주입 |
결합도 | 강한 결합 (Tightly Coupled) | 낮은 결합 (Loosely Coupled) |
테스트 용이성 | 테스트 어려움 (Mock 객체 주입 어려움) | @MockBean, @Profile 등으로 테스트 용이 |
설정 방식 | XML, JNDI 설정 필요 | Java 기반 설정 (@Configuration, @ComponentScan) |
Spring의 IoC / DI 적용 예제
import org.springframework.stereotype.Component;
@Component
public class UserRepository {
public void findUser() {
System.out.println("사용자 조회...");
}
}
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class UserService {
private final UserRepository userRepository;
@Autowired
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
public void doSomething() {
userRepository.findUser();
}
}
Spring으로 개선된 점
✅ 개발자가 객체 생성을 직접 관리할 필요 없음 → 유지보수성 향상
✅ 의존성 주입(DI)을 통해 결합도가 낮아짐 → 테스트 및 확장성 향상
✅ 라이프사이클 관리가 자동으로 이루어짐 → 메모리 관리 최적화
✅ 설정이 간결해지고 @ComponentScan, @Bean, @Autowired 등을 활용하여 관리 용이
스프링 프레임워크의 특징
제어의 역행 (IoC)
IoC (Inversion of Control )애플리케이션의 느슨한 결합을 도모. 컨트롤의 제어권이 사용자가 아니라 프레임워크에 있어 필요에 따라 스프링에서 사용자의 코드를 호출한다.
- 팩토리 패턴을 사용하여 객체 간의 결합도를 줄여 가독성을 좋게 하고, 코드 중복을 최소화하며, 유지보수를 편하게 할 수 있습니다.
- 기존에 사용자가 모든 작업을 제어하던 것을 컨테이너에게 위임하여 객체의 생성부터 생명주기 등 모든 객체에 대한 제어권이 넘어 간 것을 IoC라고 합니다.
BeanFactory
BeanFactory 인터페이스는 IoC컨테이너의 기능을 정의하고 있는 인터페이스이며, Bean의 생성 및 의존성 주입, 생명주기(lifecycle) 관리 등의 기능을 제공한다.
클라이언트의 요청에 의해서 Bean 객체가 사용되는 시점(Lazy Loading)에 객체를 생성
ApplicationContext(Spring 컨테이너, IoC 컨테이너)
BeanFactory 인터페이스를 상속받는 ApplicationContext는 BeanFactory가 제공하는 기능 외에 AOP, 트랜잭션 관리 등의 기능을 제공한다.
컨테이너가 구동되는 시점에 객체들을 생성하는 Pre-Loading 방식
애플리케이션 실행 시점에 오류를 즉시 발견할 수 있다.
- 트랜잭션 이란 쉽게 말해 처리 단위를 뜻한다.
의존성 주입(DI)
제어의 역행이 일어나는 것을 전제로 하여 스프링 내부의 객체들간의 관계를 만들어줄 때 사용합니다.
외부에서 두 객체 간의 관계를 결정해주는 디자인 패턴으로, 인터페이스를 사이에 둬서 클래스 레벨에서는 의존관계가 고정되지 않도록 하고 런타임 시에 관계를 동적으로 주입하여 유연성을 확보하고 결합도를 낮출 수 있게 해준다.
빈(Bean) 설정파일에 의존관계가 필요한 정보를 추가해주면 컨테이너가 자동적으로 연결해준다.
- 각각의 계층이나 서비스들 간에 의존성이 존재할 경우 프레임워크가 서로 연결시켜준다.
- 의존성 주입은 말 그대로 특정 객체가 필요로 하는 객체를 외부에서 결정하여 연결시키는 것을 말합니다.
POJO 기반의 구성
POJO는 Plain Old Java Object로, 평범한 자바 객체를 말합니다.
이 단어는 마틴 파울러가 2000년에 컨퍼런스 발표를 준비하다가 만들어낸 용어인데, 단순히 발표 중의 "간단한 자바 오브젝트를 사용하는데요~"라고 하는 것보다 "POJO 방식의 기술을 사용하는데요~"라고 하면 왠지 세련되고 첨단 기술을 쓰는 것처럼 느껴진다는 심리를 이용하여 만들어진 것이라고 합니다. 그래서 우리가 자바에서 개발하는 지극히 평범한 객체를 POJO라고 합니다.
다만, POJO는 특정 규약과 특정 환경에 종속되어서는 안 되고 객체지향 설계를 잘 지켜야한다는 조건이 있습니다.
AOP 지원
AOP(Aspect Oriented Programming)는 관점 지향 프로그래밍을 뜻합니다.
대부분의 시스템에서 비즈니스 로직은 아니지만 보안, 로그, 트랜잭션과 같이 반드시 처리가 필요한 부분을 횡단 관심사라고 합니다. 스프링에서는 이러한 관심사를 비즈니스 로직과 분리하여 중복된 코드를 줄이고 개발자가 비즈니스 로직에 집중하도록 만들어 줍니다.
- 관점지향 프로그래밍(AOP : Aspect-Oriented Programming) : 트랜잭션이나 로깅, 보안과 같이 여러 모듈에서 공통적으로 사용하는 기능의 경우 해당 기능을 분리하여 관리할 수 있다.
오버라이딩(Overriding)
부모 클래스에서 상속받은 자식 클래스에서 부모클래스에서 만들어진 메서드를 자식 클래스에서 자신의 입맛대로 다시 재정의해서 사용하는 것을 말한다.
- 인터페이스를 구현한 객체 인스턴스를 실행 시점에 유연하게 변경할 수 있다
- 로미오와 줄리엣의 역할 → 구현체로 장동건과 원빈, 그리고 김태희와 송혜교 등
- 자동차라는 역할 → 구현체로 K3, 아반떼, 테슬라 모델3 등이 있습니다.
의존성 주입 DI (@Autowired , @Component)
컨테이너에서 필요한 의존 객체의 타입에 해당하는 빈 객체를 찾아 주입한다.
생성자, 필드, setter에 Autowired를 사용할 수 있다.
1)생성자 주입
생성자를 통해 의존 관계를 주입하는 방법
생성자 주입은 생성자의 호출 시점에 1회 호출 되는 것이 보장된다. 그렇기 때문에 주입받은 객체가 변하지 않거나, 반드시 객체의 주입이 필요한 경우에 강제하기 위해 사용할 수 있다. 또한 생성자가 1개만 있을 경우에 @Autowired를 생략해도 주입이 가능하도록 편의성을 제공한다.
2) 세터 주입
필드 값을 변경하는 Setter를 통해서 의존 관계를 주입하는 방법
Setter 주입은 생성자 주입과 다르게 주입받는 객체가 변경될 가능성이 있는 경우에 사용한다.
3)필드 주입
필드에 바로 의존 관계를 주입하는 방법
필드 주입을 이용하면 코드가 간결해져서 과거에 상당히 많이 이용되었던 주입 방법이다. 하지만 필드 주입은 외부에서 변경이 불가능하다는 단점이 존재하는데, 점차 테스트 코드의 중요성이 부각됨에 따라 필드의 객체를 수정할 수 없는 필드 주입은 거의 사용되지 않게 되었다. 또한 필드 주입은 반드시 DI 프레임워크가 존재해야 하므로 반드시 사용을 지양해야 한다.
생성자 주입(Constructor Injection)을 사용해야 하는 이유
1)객체의 불변성 확보
의존 관계 주입의 변경이 필요한 상황은 거의 없다. 하지만 수정자 주입이나 일반 메소드 주입을 이용하면 불필요하게 수정의 가능성을 열어두게 되며, 이는 OOP의 5가지 개발 원칙 중 OCP(Open-Closed Principal, 개방-폐쇄의 법칙)를 위반하게 된다. 그러므로 생성자 주입을 통해 변경의 가능성을 배제하고 불변성을 보장하는 것이 좋다.
2)테스트 코드의 작성
필드 주입으로 작성된 경우 테스트 코드가 Spring과 같은 DI 프레임워크 위에서 동작하지 않으므로 의존 관계 주입이 되지 않기 때문에 순수한 자바 코드로 단위 테스트를 작성하는 것이 불가능하다.
3)final 키워드 작성 및 Lombok과의 결합
생성자 주입을 사용하면 필드 객체에 final 키워드를 사용할 수 있으며, 컴파일 시점에 누락된 의존성을 확인할 수 있다. 반면에 생성자 주입을 제외한 다른 주입 방법들은 객체의 생성(생성자 호출) 이후에 호출되므로 final 키워드를 사용할 수 없다.
또한 final 키워드를 붙임으로써 Lombok과 결합되어 코드를 간결하게 작성할 수 있다. Lombok에는 final 변수를 위한 생성자를 대신 생성해주는 @RequiredArgsConstructor가 있다.
Spring Bean
Spring 컨테이너가 관리하는 객체를 빈(Bean)이라고 부릅니다. IoC 컨테이너 안에 들어있는 객체를 필요할 때 IoC 컨테이너에서 가져와서 사용합니다.
스프링 Bean을 IoC 컨테이너에 등록하는 방법
1)Component scan
- @ComponentScan 어노테이션과 @Component 어노테이션을 사용해서 빈을 등록하는 방법
- @ComponentScan: 어느 지점부터 컴포넌트를 찾으라고 알려주는 역할
- @Component: 실제로 찾아서 빈으로 등록할 클래스
2) 빈 설정 파일에 직접 등록
- XML 파일이나 설정 파일로 작성할 수 있음
- 자바 설정 파일: @Configuration 어노테이션을 붙인 클래스에 @Bean 어노테이션을 사용해 직접 빈을 정의할 수 있음
@Component
@Component는 Spring에서 Bean(객체)을 자동으로 생성하고 관리하도록 지정하는 애너테이션입니다.
즉, 개발자가 new 키워드를 사용하지 않고도, Spring이 직접 객체를 만들고 관리하도록 할 수 있습니다.
- IoC 컨테이너에 Bean을 등록하기 위해 사용
- 개발자가 작성한 class를 기반으로 런타임 시에 컴포넌트 스캔하여 인스턴스 객체를 생성합니다.
- @Controller, @Service, @Repository 모두 @Component이며 실행시점에 자동으로 의존성을 주입합니다.
애너테이션 | 역할 |
@Component | 일반적인 Bean 등록 |
@Service | 서비스 계층 (비즈니스 로직) Bean 등록 |
@Repository | 데이터 액세스 계층 (DAO, Repository) Bean 등록 |
@Controller | Spring MVC 컨트롤러 등록 |
@Bean
- IoC 컨테이너에 Bean을 등록하기 위해 사용
- 개발자가 작성한 메소드를 기반으로 메서드에서 반환하는 객체를 인스턴스 객체로 생성합니다.
@Component vs @Bean 차이점
구분 | @Component | @Bean |
자동 등록 여부 | Spring이 자동으로 스캔하여 등록 | 개발자가 직접 등록 |
사용 위치 | 클래스 선언부 | @Configuration 클래스 내부의 메서드 |
유연성 | 클래스 레벨에서 간편하게 사용 | 객체 생성 로직을 세밀하게 제어 가능 |
스프링 Bean의 Scope
빈 스코프는 빈이 존재할 수 있는 범위를 뜻하며 싱글톤, 프로토타입, request, session 등이 있다.
@Scope
- singleton: 기본 스코프로 스프링 컨테이너의 시작과 종료까지 유지되는 가장 넓은 범위의 스코프
- prototype : 애플리케이션 요청시( getBean() 메서드가 호출될 때마다) 스프링이 새 인스턴스를 생성합니다.
- request : HTTP 요청별로 인스턴스화 되며 요청이 끝나면 소멸됩니다.
- session : HTTP 세션별로 인스턴스화 되며 세션이 끝나면 소멸됩니다.
- thread : 새 스레드에서 요청하면 새로운 bean 인스턴스를 생성합니다. 같은 스레드의 요청에는 항상 같은 인스턴스가 반환됩니다.
- custom : org.pringframework.beans.factory.config.Scope 인터페이스를 구현하고 커스텀 스코프를 스프링의 설정에 등록하여 사용합니다.
연관된 글:
[CS] 좋은 객체 지향 설계의 5대 원칙: SOLID
참고 :
@Controller, @Service, @Repository 차이
[Spring] 스프링 빈(Bean)의 개념과 생성 원리
[Spring] 다양한 의존성 주입 방법과 생성자 주입을 사용해야 하는 이유 - (2/2)
'개발 > Spring' 카테고리의 다른 글
[Spring] @Tansactional (0) | 2023.04.17 |
---|---|
[Spring] JPA와 ORM (0) | 2023.03.22 |
[Spring] 어노테이션 모음집 (0) | 2023.03.08 |
[Spring] QueryDSL (0) | 2023.02.20 |
[Spring] Spring Boot (0) | 2023.02.10 |