Backend

Spring DI(의존주입), @Autowired (의존 자동주입)

연_우리 2022. 4. 2. 15:59
반응형

목차

     

     

    의존주입 방법

    설정 클래스에서 직접 의존 주입

    설정클래스에서 의존객체를 직접 주입해주는 방법이다. 

    @Configuration
    public class AppCtx {
        @Bean
        public MemberDao memberDao(){
            return new MemberDao();
        }
    
        @Bean
        public MemberRegisterService memberRegisterService(){
            return new MemberRegisterService(memberDao());
        }
    
        @Bean
        public ChangePasswordService changePasswordService(){
            ChangePasswordService changePasswordService = new ChangePasswordService();
            changePasswordService.setMemberDao(memberDao());
            return changePasswordService;
        }
    }

     

     

    자동 의존 주입

    설정클래스에서 직접 주입하지 않고, 스프링이 자동으로 의존 객체를 주입해주는 방법이다.

     

    설정클래스에서 생성자, setter로 memberDao를 주입해주는 코드를 모두 주석처리한다.

    @Configuration
    public class AppCtx {
        @Bean
        public MemberDao memberDao(){
            return new MemberDao();
        }
    
        @Bean
        public MemberRegisterService memberRegisterService(){
    //        return new MemberRegisterService(memberDao());
            return new MemberRegisterService();
        }
    
        @Bean
        public ChangePasswordService changePasswordService(){
            ChangePasswordService changePasswordService = new ChangePasswordService();
    //        changePasswordService.setMemberDao(memberDao());
            return changePasswordService;
        }
    
    }

     

    의존을 주입받는 클래스에서 필드 또는 Setter메서드에 @Autowired를 붙인다.

    (여기서 생성자에 @Autowired를 붙이면 AppCtx에서 @Bean을 생성할 때 에러가 발생한다. )

    public class MemberRegisterService {
        @Autowired	//⭐
        private MemberDao memberDao;
    
    //    constructor DI
    //    public MemberRegisterService(MemberDao memberDao) {
    //        this.memberDao = memberDao;
    //    }
    
        public Long regist(RegisterDTO req){
            //회원가입 로직
            ...
        }
    }
    public class ChangePasswordService {
        @Autowired	//⭐
        private MemberDao memberDao;
    
    //    @Autowired //⭐ 여기에 붙여도된다.
    //    public void setMemberDao(MemberDao memberDao) {
    //        this.memberDao = memberDao;
    //    }
    
        public void changePassword(String email, String oldpwd, String newpwd){
            //비밀번호 변경로직
            ...
        }
    }

     

    @Autowired 어노테이션을 필드나 setter메서드에 붙이면 

    스프링 컨테이너는 타입이 일치하는 Bean객체를 찾아 주입해준다.

     

     

    @Autowired 의존 주입 시 타입 일치하는 Bean이 없는 경우

    UnsatisfiedDependencyException 발생

     

     

    @Autowired 의존 주입 시 타입 일치하는 Bean이 2개 이상인 경우 -> @Qualifier 어노테이션 사용

    @Configuration
    public class AppCtx {
        @Bean
        public MemberDao memberDao1(){	//⭐
            return new MemberDao();
        }
    
        @Bean
        public MemberDao memberDao2(){	//⭐
            return new MemberDao();
        }
    
        @Bean
        public MemberRegisterService memberRegisterService(){
            return new MemberRegisterService();
        }
    
        @Bean
        public ChangePasswordService changePasswordService(){
            return new ChangePasswordService();
        }
    }
    public class MemberRegisterService {
        @Autowired
        private MemberDao memberDao;
        ...
    }
    
    public class ChangePasswordService {
        @Autowired
        private MemberDao memberDao;
        ...
    }

     

    그냥 실행하면 NoUniqueBeanDefinitionException 발생

     

     

    @Qualifier를 사용하여 해결할 수 있다.

    @Configuration
    public class AppCtx {
        @Bean
        @Qualifier("memberDaoForRegister") //⭐
        public MemberDao memberDao1(){
            return new MemberDao();
        }
    
        @Bean
        public MemberDao memberDao2(){
            return new MemberDao();
        }
    
        @Bean
        public MemberRegisterService memberRegisterService(){
            return new MemberRegisterService();
        }
    
        @Bean
        public ChangePasswordService changePasswordService(){
            return new ChangePasswordService();
        }
    }
    public class MemberRegisterService {
        @Autowired
        @Qualifier("memberDaoForRegister") //⭐
        private MemberDao memberDao;
    
        public Long regist(RegisterDTO req){
            //회원가입 로직
        }
    }

     

    AppCtx의 @Bean 등록 시 @Qualifier로 이름 지정, @Autowired로 빈 주입 시 @Qualifier로 이름 지정

    양쪽에 적어주어서 빈의 이름으로 구분한다.

    (동일 타입 빈이 2개일때, 하나에만 Qualifier를 붙여도 두 빈을 구분할 수 있기 때문에 오류없이 실행 가능하다)

     

     

     

    상위/하위 타입 관계와 자동주입

    public class MemberParentPrinter {
    }
    
    
    public class MemberChildPrinter extends MemberParentPrinter{
    }

    상속관계에서 @Qualifier 애노테이션을 안붙인다면 이것도 NoUniqueBeanDefinitionException이 발생한다.

     

     

     

    @Autowired 필수 여부

    @Autowired는 기본적으로 타입에 해당하는 빈이 존재하지 않으면 Exception이 발생한다.

     

    @Autowired(required = false)
    public void setDateFormatter(DateTimeFormatter dateTimeFormatter){
    	this.dateTimeFormatter = dateTimeFormatter;
    }
    
    @Autowired
    public void setDateFormatter(Optional<DateTimeFormatter> dateTimeFormatter){
    	this.dateTimeFormatter = dateTimeFormatter;
    }
    
    @Autowired
    public void setDateFormatter(@Nullable DateTimeFormatter dateTimeFormatter){
    	this.dateTimeFormatter = dateTimeFormatter;
    }

    자동 주입할 대상이 필수가 아닌 경우에는

    @Autowired 애노테이션의 required 속성을 false로 지정하거나,

    Optional을 사용하거나, @Nullable을 사용하여 자동주입을 수행하지 않으면된다.

     

    위의 방식은 필드에도 그대로 적용된다.

    @Autowired(required = false)
    private DateTimeFormatter dateTimeFormatter;
    
    @Autowired
    private Optional<DateTimeFormatter> dateTimeFormatter;
    
    @Autowired
    @Nullable
    private DateTimeFormatter dateTimeFormatter;

     

     

     

    생성자 초기화와 필수 여부 지정

    public class MemberPrinter{
    	private DateTimeFormatter dateTimeFormatter;
        
        public MemberPrinter(){
        	dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy년 MM월 dd일");
        }
        
        @Autowired(required = false)
        public void setDateTimeFormatter(DateTimeFormatter dateTimeFormatter){
        	this.dateTimeFormatter = dateTimeFormatter;
        }
    }

    위의 코드를 실행하면 DateTimeFormatter는 기본생성자에서 초기화 된 값("yyyy년 MM월 dd일")을 사용한다.

     => required = false는 매칭되는 빈이 없다면 아무것도 하지 않는다.

     => 기본생성자의 초기화 값을 변경하지 않는다. 

     

     

    public class MemberPrinter{
    	private DateTimeFormatter dateTimeFormatter;
        
        public MemberPrinter(){
        	dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy년 MM월 dd일");
        }
        
        @Autowired
        public void setDateTimeFormatter(@Nullable DateTimeFormatter dateTimeFormatter){
        	this.dateTimeFormatter = dateTimeFormatter;
        }
    }

    위의 코드를 실행하면 DateTimeFormatter는 기본생성자가 아닌 Setter에서 설정된 값을 사용한다.

     => @Nullable은 매칭되는 빈이 없다면 값을 null로 설정한다.

     => 기본생성자의 초기화 값을 설정한 후, Setter를 실행함으로써 null이 설정된다.

     

    Optional도 마찬가지로 Optional을 할당한다.

     

     

    자동 주입과 명시적 의존 주입

    @Configuration
    public class AppCtx{
    
        @Bean @Qualifier("printer")
        public MemberPrinter memberPrinter(){
        	return new MemberPrinter();
        }
        
        @Bean @Qualifier("summaryPrinter")
        public MemberSummaryPrinter memberPrinter2(){
        	return new MemberSummaryPrinter();
        }
        
        @Bean
        public MemberInfoPrinter infoPrinter(){
        	MemberInfoPrinter infoPrinter = new MemberInfoPrinter();
            //MemberInfoPrinter의 setPrinter메서드에는 @Autowired가 붙어있어서
            //memberPrinter(=MemberPrinter)를 설정해준다.
            
            infoPrinter.setPrinter(memberPrinter2());
            //그 다음 setter로 다시 memberPrinter2(=MemberSummaryPrinter)를 설정해준다.
        }
    }
    public class MemberInfoPrinter{
        @Autowired @Qualifier("printer")
        public void setPrinter(MemberPrinter memberPrinter){
        	this.printer = memberPrinter;
        }
    }

     

     

    결과는 MemberPrinter의 내용으로 실행된다.

    즉, setter DI로 외부에서 값을 넣어주어도

    해당 setter에 @Autowired가 붙어있으면 @Autowired가 우선권을 갖는다.

     

    우선순위 : @Autowired > Setter DI 주입

     

     

     

    정리

    • 의존 주입 방법
     - 직접의존주입 : Bean을 생성할 때 setter로 의존을 주입하는 방법
     - 자동의존주입 : Bean을 생성할 때 setter를 사용하지 않고,

                           각 클래스들의 필드/setter에 @Autowired를 붙여서 의존을 주입하는 방법


    • @Autowired는 타입이 일치하는 Bean객체를 찾아 주입해준다.
    타입 일치 Bean이 없으면 -> UnsatisfiedDependencyException 발생
    타입 일치 Bean이 2개 이상이면 -> NoUniqueBeanDefinitionException 발생. 

                                                 @Qualifier를 사용하여 각각 빈의 이름을 지정하여 해결 가능

     


    • @Qualifier를 사용하면 설정클래스와 주입되는 클래스에 모두 적어주어야한다.

     


    • 상위/하위 타입 관계에서 @Qualifier는 안붙이면 -> NoUniqueBeanDefinitionException 발생. @Qualifier로 해결 가능


    • 자동 주입할 대상이 필수가 아니면 3가지 방법 사용
     - @Autowired(required = false) : 매칭되는 빈이 없다면 값을 할당하지 않는다
     - Optional : 매칭되는 빈이 없으면 Optional 할당
     - @Nullable : 매칭되는 빈이 없으면 Null을 할당한다.


    • 의존 주입받는 클래스의 setter에 @Autowired가 있어서 A를 주입받을 수 있는데
    설정클래스에서 setter를 호출해 B를 주입해주면?
    @Autowired가 setter DI보다 우선순위가 높기때문에 A로 동작하게된다.

     

     

     

     

     

     

     

    반응형
    • 네이버 블러그 공유하기
    • 페이스북 공유하기
    • 트위터 공유하기
    • 구글 플러스 공유하기
    • 카카오톡 공유하기