ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 스프링 핵심 원리 (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는 수동으로 빈 등록함
    // 모든 빈 생성 후 의존관계 주입시작
    // 수동 > 자동 우선순위 인 것 기억하자

     

    참고자료: 스프링 핵심 원리 (김영한)

    https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-%ED%95%B5%EC%8B%AC-%EC%9B%90%EB%A6%AC-%EA%B8%B0%EB%B3%B8%ED%8E%B8

     

    스프링 핵심 원리 - 기본편 - 인프런 | 강의

    스프링 입문자가 예제를 만들어가면서 스프링의 핵심 원리를 이해하고, 스프링 기본기를 확실히 다질 수 있습니다., 스프링 핵심 원리를 이해하고, 성장하는 백엔드 개발자가 되어보세요! 📢

    www.inflearn.com

     

Designed by Tistory.