-
스프링 핵심 원리 (4) - 컴포넌트 스캔Web/Spring 2023. 8. 18. 03:56
1. 컴포넌트 스캔과 의존관계 자동 주입
- 스프링 빈을 등록할 때 자바코드나 Xml을 통해 설정 정보를 직접 등록했음
- 설정 정보 없이 자동으로 스프링 빈을 등록하는 컴포넌트 스캔이라는 기능 있다.
- 의존관계 자동 주입하는 @Autowired 기능도 제공한다.
@Configuration @ComponentScan public class AutoAppConfig{ }
> 컴포넌트 스캔을 사용하려면, 설정 정보에 @ComponentScan을 붙여주면 된다.
> @Component 어노테이션이 붙은 클래스를 스캔해서 스프링 빈으로 등록한다.
@Configuration도 스캔 대상이다.
@Component public class MemoryMemberRepository implements MemberRepository { //.. } @Component public class RateDiscountPolicy implements DiscountPolicy{ //.. }
> 빈을 직접 등록하면, 의존관계도 직접 명시해야하지만, 컴포넌트 스캔 이용시 의존관계 주입도 클래스 안에서
해결해야한다.
> @Autowired를 통해 의존관계를 자동으로 주입할 수 있다.
ApplicationContext ac = new AnnotationConfigApplicationContext(AutoAppConfig.class); MemberService memberService = ac.getBean(MemberService.class);
> 기존 config와 동일하게 사용가능,
> @ComponentScan 어노테이션이 붙은 클래스를 스프링 컨테이너 구현체 생성시 설정 정보로 넘긴다.
1.1 자동 스프링 빈 등록시
- 빈 이름 : 클래스명을 사용, 앞글자만 소문자 -> (@Component("이름"))으로 이름 부여 가능
- 의존관계 자동 주입: 의존관계 자동 주입의 조회 전략은 타입이다. (getBean(~.class))
1.2 스캔 탐색 위치와 기본 스캔 대상
- 컴포넌트 스캔 탐색 위치 지정
@ComponentScan( basePackages = "패키지명" // 탐색 시작 위치, 이 패키지+ 하위 패키기 탐색 basePackageClasses = ~.class // 지정 클래스 포함한 패키지를 탐색 시작 위치로 지정 ) // 탐색 위치 지정하지 않으면, @ComponentScan이 붙은 설정 정보 클래스의 패키지가 시작 위치이다.
- 패키지 지정하지 않고, 설정 정보 클래스의 위치를 프로젝트 최상단에 두는 것도 괜찮은 방법!
1.3 스캔 기본 대상
@Controller: 스프링 MVC 컨트롤러에서 사용 + 스프링 MVC 컨트롤러로 인식
@Service: 비지니스 로직
@Repository : 스프링 데이터 접근 계층에서 사용
@Configuration: 스프링 설정 정보에서 사용 + 스프링 빈 싱글톤 유지하도록 추가 처리 기능
* 어노테이션은 상속관계 없음 -> 어노테이션이 nasted되는 것을 인식하는 건, 자바가 지원하는게 아니라 스프링이 지원하는 기능
1.4 필터
- includeFilters: 스캔 대상 추가 지정
- excludeFilters: 스캔 대상 제외
@ComponentScan( includeFilters = {@Filter(type=FilterType.ANNOTATION, classes=어노테이션명.class)}, excludeFilters = {@Filter(type= FilterType.ASSIGNABLE_TYPE, classes = 클래스명.class} )
- FilterType 옵션
> ANNOTATION : 어노테이션을 인식
> ASSIGABLE_TYPE : 지정타입 + 자식타입 인식
> ASPECTJ : AspectJ패턴 사용
>REGEX: 정규 표현식 사용
>CUSTOM: TypeFilter 인터페이스 구현 처리
1.5 중복 등록과 충돌
-빈 이름과 타입 중복
> 자동 빈 등록 - 자동 빈 등록 충돌 :
컴포넌트 스캔에 의해 자동으로 빈이 등록될 때, 타입과 이름이 같은 경우 스프링 오류 발생
> 수동 빈 등록 - 자동 빈 등록:
- @Component에 의해 등록된 빈을 @Configuration에서 @Bean으로 수동 등록시
- 수동 빈 등록이 우선권을 가지게 된다. (수동 빈이 오버라이딩 함)
- 하지만, 스프링 부트 자체에서 이를 막아 두었다. 위와 같은 상황 발생 시 오류가 발생한다.
- resources에 application.properties에 아래와 같은 설정을 추가하면, 오버라이딩 할 수 있다.
spring.main.allow-bean-definition-overriding=true
*스프링 부트를 통해서 실행시에만 수동 빈과 자동 빈 등록 오류 발생함!
설정정보 넘겨서 ApplicationContext 만들때는 오버라이딩 됨
2. 의존관계 자동주입
- 기존의 bean을 수동으로 설정할 때는 의존관계 또한 수동으로 지정했다.
- 컴포넌트 스캔을 통해 bean을 자동으로 생성하면, 의존관계 또한 Autowired를 통해 의존관계를 주입해야한다.
2.1 다양한 의존관계 주입 방법
생성자 주입: - 생성자를 통해 의존 관계를 주입 받는 방법
- bean 생성시 생성자 호출 시점에 딱 1번만 호출되는 것이 보장된다.
- 불변, 필수 의존관계에서 사용
- 예외적으로 빈생성과 의존관계 주입이 동시에 일어남
- 생성자가 1개라면 @Autowired 생략해도 빈 등록시 자동으로 주입해준다.
@Component public class Test{ private final SomeObject some; @Autowired public Test(SomeObject some){ this.some = some; } }
수정자 주입: - setter라는 필드 값을 변경하는 수정자 메서드 통해서 의존관계 주입
- 선택, 변경 가능성이 있는 의존관계에 사용
- 자바빈 프로퍼티 규약의 수정자 메서드 방식을 사용한다.
@Autowired public void setDiscountPolicy(DiscountPolicy discountPolicy) { this.discountPolicy = discountPolicy; }
*Autowired시 스프링 컨테이너에 해당 빈이 등록되지 않았다면 오류발생
또한 스프링 빈사이의 의존관계를 맺어주는 것, 빈으로 등록된 객체가 아니라면, 당연히 사용할 수 없다.
필드 주입: - 필드에 주입
- 단순하지만, 테스트가 힘들고, DI 프레임워크가 없으면 아무것도 못함 (사용X)
- 단, 실제 코드와 관계 없는 테스트 코드 (스프링 전체 테스트 코드)
혹은 스프링 설정을 목적으로 하는 @Configuration에선 때에 따라 특별한 용도로 사용
*스프링 컨테이너에 빈으로 등록하여 사용하는 것과 순수하게 new 로 객체 생성하는 것은 당연히 다른 일
* 수동으로 Bean등록시 빈 메서드에 파라미터를 통해 (자동 생성된 빈과)의존관계가 자동 주입 된다.
@Bean OrderService orderService(MemberRepository memberRepoisitory, DiscountPolicy discountPolicy) { return new OrderServiceImpl(memberRepository, discountPolicy); }
일반 메서드 주입: 일반 메서드를 통해서 주입 받을 수 있다. -> 일반적으로 사용 잘 안한다.
-> 한번에 여러 필드 주입 받을 수 있다.
2.2 옵션 처리 (자동주입)
- 주입할 스프링 빈이 없어도 동작하게 만들어야 할 때 사용하는 옵션이다.
@Autowired(required = false) public void setBean1(Member member){ //.. } @Autowired public void setBean2(@Nullable Member member){ //.. } @Autowired(required = false) public void setBean3(Optional<Member> member){ //.. }
- 그냥 required = false 면 호출 자체 안됨
- 나머지는 null 혹은 Optional.empty가 들어감
2.3 생성자 주입 선택
- 과거에는 수정자 주입, 필드 주입을 많이 사용했지만, 최근에는 대부분의 DI 프레임워크에서 생성자 주입을 권장한다.
> 불변으로 만들기 위함
- 대부분의 의존관계 주입은 한번 일어나면, 애플리케이션 종료시점까지 의존관계 변경할 일이 없다.
- 생성자 주입은 객체 생성시 딱 한번만 호출되므로 이후에 호출될 일이 없다. 따라서 불변하게 설계 가능
> 누락
- 순수 자바 코드를 통해 단위 테스트 하는 경우 필드 -> 테스트 어려움, 수정자 -> 여러 셋팅 해야함 귀찮
- 또한 수정자 주입 택할 시, 순수 자바코드 테스트시 DI가 누락되어도 컴파일 오류가 없지만, 생성자는 발생
> final 키워드
- 생성자 주입시 필드에 final 키워드 사용할 수 있음! -> 값이 설정되지 않으면 컴파일 오류
따라서 위와 같은 이유로 생성자 주입이 프레임워크에 과도하게 의존하지 않고, 순수 자바 언어의 특징을 잘 살리는 방법이다.
- 생성자 주입을 기본으로 사용하되, 필수 값이 아닌 경우 수정자 주입 방식을 옵션으로 사용하자
2.4 롬복
- 생성자 주입을 필드 주입처럼 쫌 예쁘게 만들어보자
* 롬복 라이브러리가 필요
롬복 라이브러리는 어노테이션을 통해 겟터,셋터, 생성자 등을 손쉽게 만들 수 있도록 도와주는 라이브러리이다.
롬복에서 지원하는 어노테이션을 붙이면, 컴파일 시점에 필요 메서드를 추가해서 컴파일 한다.
@Getter //getter 만들어줌 @Setter //setter 만들어줌 public class Rom{ private int val1; private long val2; } public class RomTest{ Rom rom = new Rom(); rom.setVal1(10); rom.getVal1(); }
- 롬복 라이브러리가 지원하는 @RequiredArgsConstructor을 사용하면, final 붙은 필드를 모아서 생성자를 자동으로 만들어 준다.
@Component @RequiredArgsConstructor public class OrderServiceImpl implements OrderService { private final MemberRepository memberRepository; private final DiscountPolicy discountPolicy; }
2.5 조회 빈 2개 이상
- @Autowired는 타입을 통해 조회한다. getBean(~.class)와 유사하다고 보면 된다.
- 의존관계 주입시 같은 타입의 빈이 여러개 존재시 에러가 발생한다.
- 이때 하위 타입으로 자세하게 지정할 수도 있지만 이는 DIP위배, 유연성 떨어짐 or 스프링 빈 수동 등록
> 다른 방법으로 이를 해결해 보자
@Autowired 필드명
- Autowired는 1. 타입매칭시도 -> 타입 매칭 결과가 2개 이상일 때 > 필드 주입이라면 필드명 vs 빈이름
> 생성자 주입이라면, 파라미터명 vs 빈이름
위와 같은 매칭을 시도하여 자동으로 주입해준다. 따라서 파라미터명을 구체적인 빈 이름으로 지정해두면 해결할 수 있다.
@Qualifier 사용
- 빈이름 외 추가 구분자를 붙여주는 방식이다.
@Component @Qualifier("mainDiscountPolicy") public class RateDiscountPolicy implements DiscountPolicy{} @Autowired public OrderServiceimpl(MemberRepository memberRepository, @Qulifier("mainDiscountPolicy) DiscountPolicy discountPolicy){ }
- 위와 같이 추가 구분자를 지정해두면, 의존성 주입시 스프링이 이를 인식해서 처리해준다.
- 또한 Qualifier를 찾을 수 없다면, 동일한 이름의 빈을 찾는다.
@Primary
- 의존성 주입의 우선순위를 정하는 방법이다. 여러 빈이 매칭되면 위 어노테이션 붙은 빈이 우선권을 가짐
- @Qulifier는 쫌 적어줄게 많으니까 일단 가장 main은 @Primary로 두고, 서브 등록시에 @Qulifier사용
*스프링은 수동, 좁은 범위의 선택권의 우선순위가 높다. 따라서 @Primary보다 @Qulifier 우선권이 높다.
2.6 어노테이션 직접 만들기
- @Qualifier의 문제는 구분자가 문자로 지정되어서 컴파일시 타입 체크나 오류를 잡기 어렵다.
- 이를 어노테이션을 직접 만들어서 해결해보자
@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.TYPE, ElementType.ANNOTATION_TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Qualifier("mainDiscountPolicy") public @interface MainPol{ } @Component @MainPol public class RateDiscountPolicy implements DiscountPolicy{ //.. }
- @Qualifier 대신 @MainPol과 같은 직접 지정한 어노테이션을 사용할 수 있다 -> 쫌 더 편하고,타입체크도 가능
* 어노테이션은 상속이라는 개념이 없음, 어노테이션 위와 같이 모아서 사용해도 인식해주는 건 스프링이 지원해주는 기능 임
2.7 조회한 빈이 모두 필요할 때, List, Map
- 의도적으로 해당 타입의 스프링 빈이 다 필요한 경우도 있음
- ex 할인 서비스 -> 할인 종류 선택 -> 전략 패턴을 간단하게 구현할 수 있다.
static class DiscountService{ private final Map<String, DiscountPolicy> policyMap; private final List<DiscountPolicy> policies; public DiscountService(Map<String, DiscountPolicy> policyMap, List<DiscountPolicy> polices) this.policyMap = policyMap; this.policies = policies; } // DiscountService를 빈에 등록 -> DiscountService를 생성하면서 빈에 등록된 DiscountPolicy들을 찾아서 // Map과 List에 Autowired해줌 (생성자 1개일땐 @Autowired생략)
* 만약 해당 타입의 bean이 없으면, 빈 컬렉션 된다.
2.8 자동, 수동 운영 기준
- 자동 기능 기본으로 사용하자
- @Controller,@Service 등 계층에 맞춘 로직 자동 스캔 지원
- 스프링 부트는 컴포넌트 스캔 기본으로 사용
- 설정 정보 기반으로 구성과 동작을 명확하게 나누는 게 이상적, 하지만 수동으로 적는 것 보다 자동이 편하다
수동 빈은
-> 업무 로직 중 기술 지원 빈 (모든 빈이 사용하는 빈, 공통관심사) -> DB연결등을 사용할 때만 하자
- 수동으로 등록해서 설정 정보에 바로 나타나게 하는 것이 유지보수에 좋다.
-> 비지니스 로직 중 다형성을 적극 활용할 때 (여러 빈 불러서 처리 시)
- 한 눈에 의미를 파악하기 힘들 수 있다 수동 등록하자 (자동 등록할것이라면, 특정 패키지에 묶자)
* AnnotationConfigApplicationContext를 직접 생성 -> 컨테이너 생성 + 스프링 빈 등록
- 해당 타입을 new 로 만들어서 스프링 빈에 등록
- @Configuration, @Service @bean 등은 빈에 등록 중 읽으면서 여러 처리를 함(싱글톤, 추가 빈 등록 등등)
- 수동으로만 등록시, 의존관계를 나타낼 수 있도록 직접 작성
- ComponentScan은 이런 과정을 자동으로 해줌 (수동+자동 같이 사용 가능)
AnnotationConfigApplicationContext(AutoConfig.class, DiscountService.class); //AutoConfig를 통해 자동 빈등록 및 @Autowired // DiscountService는 수동으로 빈 등록함 // 모든 빈 생성 후 의존관계 주입시작 // 수동 > 자동 우선순위 인 것 기억하자
참고자료: 스프링 핵심 원리 (김영한)
'Web > Spring' 카테고리의 다른 글
스프링 핵심 원리 (3) - 싱글톤 컨테이너 (0) 2023.08.17 스프링 핵심 원리 (2) - 스프링 컨테이너와 스프링 빈 (0) 2023.08.17 스프링 핵심 원리 (1) - 예제 순수 자바 to 스프링 컨테이너 (0) 2023.08.16 나프 - 게시판 만들기(1) (0) 2023.08.02 Spring MVC02 - 다중파일 업로드 (0) 2023.06.30