[Spring] 1.스프링 핵심원리
Updated:
스프링 핵심원리
여러 스프링의 부가적인 기능들을 알아보기에 앞서 스프링의 기반이 되는 핵심원리에 대해 알아보자

배경지식
스프링은 결국
Java즉, 객체지향 언어의 특징을 최대한 살릴수 있도록 도와주는 프레임워크이다
좋은 객체지향 프로그래밍은 무엇일까?
- 객체지향 프로그래밍은 추상화, 캡슐화, 상속, 다형성 등 다양한 특징을 지니지만 그 중에서도 가장 중요한 것을 하나 꼽으라면 다형성이다
- 그렇다면 다형성이란 무엇일까?

- 역할과 구현으로 나눠 위 예시를 통해 살펴보도록 하자
- 어떤 운전자가 K3를 타다가 어느 정도 돈이 모여 테슬라 모델3를 구입하였다고 가정해보자
- K3와 테슬라 모델3 모두 자동차이기 때문에 바뀌었다고 해서 운전법이 변하지 않는다. 즉, 운전자에게 영향을 주지 않는다
- 이를 프로그래밍적으로 설명하면 클라이언트가 interface에만 의존하도록 설정한다면 이후 변경사항들이 생겨도 해당 interface만을 구현하도록 하면 되는 식이다
- 클라이언트는 interface를 의존하기만 하고 구현체의 내부 구조를 몰라도 된다. 또한, 구현체를 바꾼다고 해서 영향을 받지도 않는다
- 즉, interface를 우선적으로 만들고 이를 구현하는 클래스를 만들어 사용한다
간단한 그림을 통해 좀 더 살펴보자

MemberService는MemberRepository라는 interface를 의존하는 형태이다MemoryMemberRepository와JdbcMemberRepository는 모두MemberRepository를 구현하는 클래스이다MemberService는 언제든지JdbcMemberRepository와MemberRepository둘 중에 하나를 선택하여 바꿀 수 있다- 즉, 인터페이스를 구현한 객체를 실행 시점에 유연하게 변경할 수 있다는 것이 다형성의 핵심이다
- 결국 모든 것의 기반이 되는 인터페이스를 안정적으로 잘 설계하는 것이 가장 중요하다
SOLID
좋은 객체지향 설계 5가지 원칙의 앞글자를 따서 SOLID라고 부른다
-
SRP 단일 책임 원칙
- 하나의 클래스는 하나의 책임만 가져야 한다
- 추후에 변경할 일이 생겼을때 파급효과가 적다면 이 원칙을 잘 따른 것이다
-
OCP 개방-폐쇄 원칙
-
확장에는 열려있으나 변경에는 닫혀있어야 한다
-
위에서 설명한 다형성을 생각해보면 확장을 위해 새로운 구현 클래스를 만들어도 interface를 구현하기만 한다면 기존 코드에서 변경되는 부분은 없다
-
사실 Java의 다형성만으로는 OCP를 완벽히 지키기 어렵지만 Spring을 이용한다면 확실히 OCP를 지킬 수 있다
public class HelloService { // HelloRepository repository = new MemoryHelloRepository(); HelloRepository repository = new JdbcHelloRepository(); }- 위의 코드처럼
MemoryHelloRepository를 사용하다가JdbcHelloRepository로 변경할 경우 코드 변경이 불가피하다 - 하지만 Spring의 도움을 받는다면
HelloService의 코드 변경 없이HelloRepository의 구현체 변경이 가능하다
- 위의 코드처럼
-
-
LSP 리스코프 치환 원칙
- 인터페이스를 구현한 객체는 인터페이스의 기능을 제대로 수행해야 한다
-
ISP 인터페이스 분리 원칙
- 하나의 인터페이스에 여러가지 기능을 담기보단 각각의 기능을 담는 인터페이스를 여러개 만들어야 한다
-
DIP 의존관계 역전 원칙
- 구현 클래스에 의존하는 것이 아니라 인터페이스에 의존해야 한다
- 클라이언트 코드는 인터페이스에 대해서만 알면 되지 구체적인 구현체의 내부구조에 대해서 알 필요가 없게 설계해야 한다
- OCP 파트에서 예시로 든 코드는 사실
HelloService가 인터페이스가 아닌 구현 클래스에 의존하기 때문에 DIP 원칙에 위배된다
=> 결국 다형성만으로는 OCP, DIP를 지킬 수 없고 여기서 등장한 것이 바로 스프링이다
1. 스프링 핵심원리
코드의 변화과정을 살펴보며 스프링의 핵심원리인 IoC와 DI에 대해 알아보자

- 위 구조도를 코드로 작성하면 아래와 같다
public interface MemberRepository {
void save(Member member);
Member findById(Long memberId);
}
public interface MemberService {
void join(Member member);
Member findMember(Long memberId);
}
public class MemoryMemberRepository implements MemberRepository{
private static Map<Long, Member> store = new HashMap<>();
@Override
public void save(Member member) {
store.put(member.getId(), member);
}
@Override
public Member findById(Long memberId) {
return store.get(memberId);
}
}
public class MemberServiceImpl implements MemberService{
private MemberRepository memberRepository = new MemoryMemberRepository();
@Override
public void join(Member member) {
memberRepository.save(member);
}
@Override
public Member findMember(Long memberId) {
return memberRepository.findById(memberId);
}
}
MemberServiceImpl이MemberRepository라는 인터페이스가 아닌MemoryMemberRepository라는 구현 클래스를 의존하는 형태이다MemoryMemberRepository라는 클래스를MemberServiceImpl에서 직접 생성하여 사용하므로 이런 경우 개발자에게 제어권이 있다고 말한다
이번에는 스프링 코드를 살펴보자
다른 코드들은 동일하지만 MemberServiceImpl에 변화가 발생하고 AppConfig라는 클래스를 새로 생성한다
public class MemberServiceImpl implements MemberService{
private final MemberRepository memberRepository;
public MemberServiceImpl(MemberRepository memberRepository) {
this.memberRepository = memberRepository;
}
@Override
public void join(Member member) {
memberRepository.save(member);
}
@Override
public Member findMember(Long memberId) {
return memberRepository.findById(memberId);
}
}
@Configuration
public class AppConfig {
@Bean
public MemberRepository memberRepository() {
return new MemoryMemberRepository();
}
@Bean
public MemberService memberService() {
return new MemberServiceImpl(memberRepository());
}
}
MemberServiceImpl의 코드를 살펴보면 인터페이스에만 의존하고 생성자를 통해 해당 필드값을 넣어줌을 확인할 수 있다- 이를 생성자를 통한 의존관계 주입이라고 부르는데 자세한건 아래에서 알아보기로 하자
AppConfig라는 설정 클래스는 빈을 만들어 스프링 컨테이너에 담는 역할을 한다- 빈은 간단히 말해 개발자가 비즈니스 로직에 필요하여 작성한 클래스 객체를 의미한다
- 스프링 컨테이너는 간단히 말해 이 빈들의 모든 것을 관리해주는 역할을 한다
@Configuration은 스프링 빈 설정 정보들을 담고 있으므로 스프링 실행 시에 하위@Bean이 붙은 메서드 이름으로 빈을 등록시키라고 명령을 내린다- 위의 코드는 결국 비즈니스 로직은 비즈니스 관련 업무를 처리하도록 하고 그 과정에서 필요한 객체들의 생성은 설정 파일에서 따로 관리한다
MemberServiceImpl은 의존관계에 있는MemberRepository로 어떤 구체 클래스가 주입될지 모르는 상황이다
1-1. IoC
- Inversion of Control이라고 해서 제어의 역전을 의미한다
- 위에서 스프링을 이용하기 전 기존 코드에서는 제어권이 개발자에게 있다
- 스프링을 이용한 코드에서는 개발자가 해당 클래스에서 의존관계에 있는 클래스를 직접 생성하지 않는다
AppConfig라는 설정 파일을 이용하여 필요한 빈들을 등록하기만 스프링이 자동으로 런타임 시점에 알맞은 클래스를 생성자 주입해준다- 이는 제어권이 스프링 쪽에 넘어간 상황으로 제어의 역전이 발생했다고 얘기한다
- 참고로 라이브러리와 프레임워크의 차이가 여기에 있다
- 라이브러리: 개발자가 만든 코드가 직접 제어의 흐름을 담당
- 프레임워크: 프레임워크에게 제어권이 있어 빈만 등록한다면 프레임워크가 자체 사이클에 따라 제어
1-2. DI
- 위의 스프링을 사용한 코드에서
MemberServiceImpl은MemberRepository를 의존할 뿐 실제로 어떤 구체 클래스가 들어올지 모른다 - 실제 런타임 시점에 스프링 컨테이너에 의해 해당 인터페이스를 구현한
MemoryMemberRepository가 생성자를 통해 주입되는데 이를 의존관계 주입(Dependency Injection)이라고 한다
2. 스프링 컨테이너와 빈
스프링 컨테이너가 무엇이고 빈을 어떻게 등록하고 조회하는지 등에 대해 알아보자
-
위와 같이
AppConfig를 작성하고 해당 클래스를@Configuration, 하위 메서드를@Bean으로 적어주면ApplicationContext라는 스프링 컨테이너를 통해 빈들에 접근하여 사용가능하다 -
참고로 스프링 컨테이너에 빈 등록 시에는 반드시 다른 이름이어야 한다
-
ApplicationContext를 통해memberService라는 이름의 빈을 가져오는 실제 코드는 아래와 같다ApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class); MemberService memberService = ac.getBean("memberService", MemberService.class);- 여기서
ApplicationContext라는 인터페이스를 구현한AnnotationConfigApplicationContext를 사용하는 이유는 우리가 만든AppConfig설정 파일이 어노테이션 기반으로 작동하기 때문이다 ApplicationContext라는 스프링 컨테이너로AppConfig라는 구성정보가 든 파일을 넘긴다getBean()메서드는 첫번째 파라미터로 빈이름이, 두번째 파라미터로 타입을 받는다- 참고로
getBean()메서드의 파라미터로Object타입을 넣는다면 모든 빈을 조회한다
- 참고로
- 여기서
-
스프링 컨테이너는 크게
BeanFactory와ApplicationContext2가지로 나뉜다
1. BeanFactory
-
스프링 컨테이너의 최상위 인터페이스
-
스프링의 기본적인 빈 조회 및 관리를 담당한다
- 대표적으로
getBean()메서드를 제공한다
- 대표적으로
2. ApplicationContext
- 기본 기능을 제공하기 위한
BeanFactory와 부가기능을 제공하기 위한EnvironmentCapable,MessageSource,ApplicationEventPublisher,ResourcePatternResolver를 상속받은 인터페이스EnvironmentCapable: 실무에서 로컬, 개발, 운영 등을 분류하여 처리하는 기능을 제공한다MessageSource: 메세징 및 국제화 기능을 제공한다ApplicationEventPublisher: 등록된 리스너에게 이벤트를 발행하는 기능을 제공한다ResourcePatternResolver: 클래스패스나 파일시스템 등의 리소스를 조회하는 기능을 제공한다
BeanFactory는 거의 사용하지 않고 대부분ApplicationContext를 사용한다
3. ApplicationContext 구현 클래스
-
스프링은 자바 코드, XML, Groovy 등등 다양한 형식의 설정 정보를 읽어들일 수 있도록
ApplicationContext를 실제 구현한 클래스들을 제공한다
-
위에서 사용한
AnnotationConfigApplicationContext는 자바 코드로 된 설정 정보를 받아들인다 -
GenericXmlApplicationContext는 XML로 된 설정 정보를 받아들인다-
참고로 XML로 설정된 설정 파일 예시는 아래와 같다
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <!-- 빈 등록 시작 --> <!-- 생성자 주입 위해 constructor-arg 태그 사용 --> <bean id="memberService" class="springcore.core.member.MemberServiceImpl"> <constructor-arg name="memberRepository" ref="memberRepository" /> </bean> <bean id="memberRepository" class="springcore.core.member.MemoryMemberRepository" /> <bean id="discountPolicy" class="springcore.core.discount.RateDiscountPolicy" /> <bean id="orderService" class="springcore.core.order.OrderServiceImpl"> <constructor-arg name="memberRepository" ref="memberRepository" /> <constructor-arg name="discountPolicy" ref="discountPolicy" /> </bean> <!-- 빈 등록 마무리 --> </beans>
-
-
해당 클래스를 생성하면서 파라미터값으로 설정 정보가 들어있는 파일을 넘겨주면 된다
-
3. 싱글톤과 싱글톤 컨테이너
디자인 패턴 중 하나인 싱글톤 방식과 스프링이 싱글톤 방식의 장점만을 어떻게 뽑아내는지에 대해 알아보자
싱글톤 방식을 이해하기 위해 먼저 아래 코드를 살펴보자
@Configuration
public class AppConfig {
@Bean
public MemberRepository memberRepository() {
return new MemoryMemberRepository();
}
@Bean
public MemberService memberService() {
return new MemberServiceImpl(memberRepository());
}
}
//테스트 코드
public class SingletonTest {
@Test
@DisplayName("스프링 없이 순수한 DI 컨테이너를 이용하면 싱글톤이 적용되지 않는다.")
void pureContainer() {
AppConfig appConfig = new AppConfig();
MemberRepository memberRepository1 = appConfig.memberRepository();
MemberRepository memberRepository2 = appConfig.memberRepository();
assertThat(memberRepository1).isNotSameAs(memberRepository2);
}
}
AppConfig에서MemberRepository와MemberService타입의 빈을 등록할 것이라고@Bean을 통해 지정한다- 테스트 코드에서
AppConfig라는 순수한 DI 컨테이너만을 사용하면 빈 조회시마다 매번 새로운 인스턴스값을 가져온다는 점을 확인할 수 있다 - 무수히 많은 사용자가 접근한다는 특징을 지니는 웹서비스에서 같은 객체임에도 불구하고 매번 접근시마다 새로운 인스턴스를 생성하여 가져온다는 것은 문제를 일으킬 수 있다
3-1. 싱글톤 방식
임의의 객체에 대해 인스턴스는 초기에 하나만 생성하고 모든 클라이언트들이 해당 인스턴스를 공유하도록 하는 방식
public class SingletonService {
private static final SingletonService instance = new SingletonService();
//모든 사용자가 생성된 하나의 인스턴스만을 사용하도록 싱글톤 방식으로 설정
public static SingletonService getInstance() {
return instance;
}
//생성자를 통한 새로운 인스턴스 생성을 막음
private SingletonService() {
}
}
- 실제 싱글톤 동작과정을 이해하기 위해서 위의 코드를 살펴보자
- 초기에 Static 메모리 영역에 해당 객체를 올리고 이후부터는
getInstance()메서드를 통해서만 접근가능하도록 한다- 생성자를 통한 새로운 인스턴스 생성은 기본 생성자의 접근제어자를
private으로 설정하여 막는다
- 생성자를 통한 새로운 인스턴스 생성은 기본 생성자의 접근제어자를
- 하지만, DIP를 위반하였고, OCP 역시 위반할 확률이 높은데 더해 매번 이런 코드를 작성해야한다는 번거로움 또한 존재한다
3-2. 싱글톤 컨테이너
싱글톤 방식의 단점을 보완하기 위해 스프링은 싱글톤 컨테이너(스프링 컨테이너) 개념을 제시한다
위에서 작성한 SingletonTest 클래스에 아래 코드를 추가해보자
//테스트 코드
@Test
@DisplayName("스프링 컨테이너에 의해 관리되는 빈은 인스턴스가 동일해야한다.")
void containerSameInstance() {
ApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);
MemberService memberService1 = ac.getBean("memberService", MemberService.class);
MemberService memberService2 = ac.getBean("memberService", MemberService.class);
assertThat(memberService1).isSameAs(memberService2);
}
- 스프링이 제공하는
ApplicationContext라는 스프링 컨테이너를 통해 빈을 조회하면 모두 같은 인스턴스를 반환한다- 즉, 스프링 컨테이너를 통해 접근하면 어떤 경우에도 싱글톤이 보장된다
3-3. 주의점
결론부터 말하자면 스프링 컨테이너에 등록한 빈들은 모두 무상태(Stateless)로 설계해야한다!!!
싱글톤 방식은 여러 클라이언트가 단지 하나의 인스턴스를 공유한다는 특징을 지닌다
이는 치명적인 단점을 불러일으킬 수 있는데 아래 코드를 통해 살펴보자
public class StatefulService {
private int price; //상태를 유지하는 필드
public void order(String name, int price) {
System.out.println("name = " + name + ", price = " + price);
this.price = price; //문제가 되는 코드
}
public int getPrice() {
return price;
}
}
//테스트 코드
class StatefulServiceTest {
static class TestConfig {
@Bean
public StatefulService statefulService() {
return new StatefulService();
}
}
@Test
void statefulServiceSingleton() {
ApplicationContext ac = new AnnotationConfigApplicationContext(TestConfig.class);
StatefulService statefulService1 = ac.getBean("statefulService", StatefulService.class);
StatefulService statefulService2 = ac.getBean("statefulService", StatefulService.class);
//ThreadA: 사용자A가 10000원 주문
statefulService1.order("userA", 10000);
//ThreadB: 사용자B가 20000원 주문
statefulService2.order("userB", 20000);
//ThreadA: 사용자A가 주문 금액 조회
int price = statefulService1.getPrice();
System.out.println("price = " + price);
assertThat(statefulService1.getPrice()).isEqualTo(20000);
}
}
StatefulService클래스는 값을 유지하는 Stateful한 코드이다StatefulServiceTest에서 내부에서 사용할 빈을 등록한다- 참고로 예리한 사람이면 여기서 왜
@Configuration을 붙이지 않고도 싱글톤이 보장되는지 의문이 들 수 있는데 이는 아래서 추후에 알아보자
- 참고로 예리한 사람이면 여기서 왜
- 테스트 코드에서
statefulService1와statefulService2는 같은 인스턴스이기 때문에order()메서드 실행시에StatefulService클래스의price필드값에 값이 유지되는 문제가 발생한다- 사용자A가 20000원을 주문했는데 사용자B의 상태가 남아 사용자A 주문 금액 조회 시에 사용자B의 주문 금액이 조회되는 문제는 심각하다
- 특정 클라이언트에게 의존적인 코드가 존재해서는 안되며 값 수정을 막고 읽기만 가능하도록 설정해야 한다
3-4. @Configuration
싱글톤을 보장받기 위해선 반드시
@Configuration을 붙여줘야 한다!!!
설명을 위해 다시 AppConfig 클래스의 @Configuration을 주석처리하고 MemoryMemberRepository 클래스의 생성자 부분을 수정하고 MemberServiceImpl 클래스에 Getter를 추가해주자
//@Configuration
public class AppConfig {
@Bean
public MemberRepository memberRepository() {
return new MemoryMemberRepository();
}
@Bean
public MemberService memberService() {
return new MemberServiceImpl(memberRepository());
}
}
public class MemoryMemberRepository implements MemberRepository{
//변경 코드
public MemoryMemberRepository() {
System.out.println("MemoryMemberRepository 빈 등록");
}
private static Map<Long, Member> store = new HashMap<>();
@Override
public void save(Member member) {
store.put(member.getId(), member);
}
@Override
public Member findById(Long memberId) {
return store.get(memberId);
}
}
public class MemberServiceImpl implements MemberService{
private final MemberRepository memberRepository;
public MemberServiceImpl(MemberRepository memberRepository) {
this.memberRepository = memberRepository;
}
@Override
public void join(Member member) {
memberRepository.save(member);
}
@Override
public Member findMember(Long memberId) {
return memberRepository.findById(memberId);
}
//Getter 추가
public MemberRepository getMemberRepository() {
return memberRepository;
}
}
//테스트 코드
@Test
@DisplayName("@Configuration이 붙지 않으면 싱글톤이 보장되지 않는다.")
void containerSameInstance() {
ApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);
MemberServiceImpl memberService = ac.getBean("memberService", MemberServiceImpl.class);
MemberRepository memberRepository = ac.getBean("memberRepository", MemberRepository.class);
assertThat(memberService.getMemberRepository()).isNotSameAs(memberRepository);
}
"MemoryMemberRepository 빈 등록"라는 문구가 몇번이나 출력될지 예상해본 후 위에서 사용한 테스트 코드를 이용하여 다시 테스트 해보자- 정답은 2번인데, 빈 등록 과정을 천천히 생각해보면 이해할 수 있다
- 스프링 컨테이너가 빈 등록을 위해
AppConfig클래스를 확인한다 @Bean이 붙은memberRepository()메서드를 실행해 해당 인스턴스값을 빈으로 등록한다- 마찬가지로
@Bean이 붙은memberService()메서드를 실행해 해당 인스턴스값을 빈으로 등록한다- 이 과정에서 의존관계 주입을 위해
memberRepository()메서드를 다시 호출하게 된다 - 하지만
@Configuration이 붙어있지 않으므로 싱글톤이 보장되지 않고 어쩔 수 없이 새로운 인스턴스를 생성하게 된다
- 이 과정에서 의존관계 주입을 위해
- 스프링 컨테이너가 빈 등록을 위해
@Configuration이 붙지 않은 설정파일들은 싱글톤이 보장되지 않음을 확인할 수 있다
이번에는 @Configuration을 붙여 작성해보자
@Configuration
public class AppConfig {
@Bean
public MemberRepository memberRepository() {
return new MemoryMemberRepository();
}
@Bean
public MemberService memberService() {
return new MemberServiceImpl(memberRepository());
}
}
//테스트 코드
@Test
@DisplayName("@Configuration이 붙지 않으면 싱글톤이 보장되지 않는다.")
void containerSameInstance() {
ApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);
MemberServiceImpl memberService = ac.getBean("memberService", MemberServiceImpl.class);
MemberRepository memberRepository = ac.getBean("memberRepository", MemberRepository.class);
assertThat(memberService.getMemberRepository()).isSameAs(memberRepository);
}
"MemoryMemberRepository 빈 등록"라는 문구는 한번만 출력될 것이고 위의 테스트 코드는 성공할 것이다- 즉, 싱글톤을 보장받기 위해선 반드시
@Configuration을 붙여줘야함을 알 수 있다 - 사실
@Configuration을 붙이면 스프링 빈 등록 시에AppConfig가 아닌 해당 클래스를 상속받은 CGLIB 관련 클래스가 등록된다- 해당 코드를 까보면
@Bean이 붙은 메서드를 무작정 호출하기 전에 스프링 컨테이너에 해당 빈이 존재한다면 조회해오고, 없을 경우에만 호출하는 식으로 작동함을 알 수 있다
- 해당 코드를 까보면
+α) 위의 StatefulServiceTest 코드 상의 의문점
TestConfig설정파일에@Configuration을 붙이지 않고도 같은 인스턴스값을 보장받았던 이유가 궁금할 것이다- 이는 애초에 스프링 컨테이너로
TestConfig의 빈 등록 시에@Bean이 붙은 메서드가 하나라 한번만 호출되어 빈으로 등록되었기 때문이다 statefulService()메서드를 의존관계로서의 필요에 의해 호출하는 다른@Bean메서드가 존재했다면StatefulService타입의 인스턴스가 해당 개수만큼 생성되는 즉, 싱글톤을 보장받지 못했을 것이다
4. 컴포넌트 스캔
이전까지는 빈을 수동등록하는 방법이라면 이번에는 빈을 자동등록하는 방법인
@ComponentScan에 대해 알아보자
4-1. 동작과정
그렇다면 컴포넌트 스캔이 어떻게 동작할 수 있는지 아래 코드를 통해 살펴보자
@Configuration
@ComponentScan
public class AutoAppConfig {
}
public class AutoAppConfigTest {
@Test
void basicScan() {
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(AutoAppConfig.class);
String[] beanDefinitionNames = ac.getBeanDefinitionNames();
for (String beanDefinitionName : beanDefinitionNames) {
Object bean = ac.getBean(beanDefinitionName);
System.out.println("bean.getClass() = " + bean.getClass());
}
}
}
AutoAppConfig라는 클래스를 만든후@Configuration과@ComponentScan을 달아준다- 참고로 빈으로 등록하고자 하는 클래스에는 클래스 레벨에
@Component를 붙여줘야 빈 등록시에 인식하여 자동으로 스프링 컨테이너에 등록해준다- 이렇게 되면 기본적으로 빈 등록시에 해당 클래스가 속한 패키지부터 하위 패키지를 모두 훑어
@Component가 붙은 클래스를 모두 빈으로 자동등록한다 @Component가 붙은 클래스에서 필요한 의존관계 주입은 생성자 주입이든setter를 이용한 주입이든 해당 코드에@Autowired를 붙여주면 자동으로 해결된다- 참고로
@Component를 내포하고 있는@Controller,@Service,@Repository,@Configuration까지 스캔 대상에 포함된다
- 이렇게 되면 기본적으로 빈 등록시에 해당 클래스가 속한 패키지부터 하위 패키지를 모두 훑어
AutoAppConfigTest테스트 코드에서 위와 같이 작성하면 컴포넌트 스캔에 의해 자동으로 등록되는 모든 빈들을 확인가능하다
4-2. @ComponentScan 파라미터
다양한 파라미터들이 존재하는데 주로 사용하는 것은
basePackages와excludeFilters정도이다
-
basePackages
- 컴포넌트 스캔이 시작될 위치를 지정하여 해당 패키지 하위에서만 빈들이 자동등록 되도록 설정한다
- 기본값은
@ComponentScan이 붙은 클래스가 속한 패키지이다
-
excludeFilters
-
컴포넌트 스캔의 대상에서 제외할 대상을 설정한다
-
제외할 대상에게 적용할 어노테이션을 만든 후에
@ComponentScan의excludeFilters값에 설정해주는 방식이다@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface ExcludeComponent { }@Configuration @ComponentScan( excludeFilters = @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = ExcludeComponent.class) ) public class AutoAppConfig { }- 제외를 원하는 클래스에 위에서 만든
@ExcludeComponent를 붙여주고@ComponentScan에excludeFilters값으로 설정해주면 된다
- 제외를 원하는 클래스에 위에서 만든
-
물론 이 역시도 그렇게 자주 이용하는 설정은 아니다
-
4-3. 수동과 자동빈 동시 등록시
- 과거에는 스프링에서 수동과 자동으로 모두 동일한 빈을 등록한 경우에 수동빈이 자동빈을 덮어쓰도록 설정하였다
- 최근에는 두 방법으로 동시에 등록할 시에 충돌이 발생하여 에러가 발생하도록 한다
Leave a comment