티스토리 뷰

⭐ 컴파일타임(Compiletime)과 런타임(Runtime)의 차이

컴파일타임(Compiletime)과 런타임(Runtime)은 소프트웨어 프로그램개발의 서로 다른 두 계층의 차이를 설명하기 위한 용어이다. 프로그램을 생성하기 위해 개발자는 첫째로 소스코드를 작성하고 컴파일이라는 과정을 통해 기계어코드로 변환 되어 실행 가능한 프로그램이 되며, 이러한 편집 과정을 컴파일타임(Compiletime) 이라고 부른다. 컴파일과정을 마친 프로그램은 사용자에 의해 실행되어 지며, 이러한 응용프로그램이 동작되어지는 때를 런타임(Runtime)이라고 부른다.

⭐ 컴파일 타임 에러(Compile Time Error)

소스코드가 컴파일 되는 과정 중에 발생하는 Syntax Error. 파일 참조 오류 등과 같은 문제들로 인해 컴파일이 방해되어 발생하는 오류들을 의미한다.

⭐ 런타임 에러(Run Time Error)

이미 컴파일이 완료되어 프로그램이 실행 중임에도 불구하고 의도치 않은 예외 상황으로 인하여 프로그램 실행 중에 발생하는 오류 형태를 의미한다.

⭐ 오류 유형

컴파일 타임 에러 런타임 에러
- Syntax Error
- Type Check Error
- 0 나누기 오류
- NullPointException
- 메모리 부족 오류

⭐ 컴파일 타임 의존성(Compile Time Dependancy)

컴파일타임 의존성이란 코드를 컴파일하는 시점에 결정되는 의존성이며, 클래스 사이의 의존성에 해당한다. 일반적으로 추상화된 클래스나 인터페이스가 아닌 구체 클래스에 의존하면 컴파일타임 의존성을 갖게된다.

@Service
@RequiredArgsConstructor
public class AuthServiceImpl implements AuthService {
    private final UserRepository userRepository;
    private final NaverSmsService naverSmsService;

    @Override
    public void sendSms(SmsMessageDto smsMessageDto) throws UnsupportedEncodingException, NoSuchAlgorithmException, InvalidKeyException, JsonProcessingException {
        userValidator.isPhoneValid(smsMessageDto.getTo());
        SmsResponseDto smsResponseDto = naverSmsService.sendSms(smsMessageDto);
    }
}

AuthServiceImpl은 컴파일 될 때 NaverSmsService 클래스를 참조한다. 이는 AuthServiceImpl이 Sms 서비스를 이용할 때 Naver Vender을 이용하고 있음을 의미한다. 그러므로 의존성 결합도는 높다고 할 수 있다. 결합도를 낮추고 바람직한 의존성을 갖기 위해서는 결국 런타임 의존성을 가져야 한다.

⭐ 런타임 의존성(Run Time Dependancy)

런타임 의존성이란 애플리케이션을 실행하는 시점에 결정되는 의존성이며, 객체 사이의 의존성에 해당한다. 일반적으로 추상화된 클래스나 인터페이스에 의존 할 때 런타임 의존성을 갖게 된다.

@Service
@RequiredArgsConstructor
public class AuthServiceImpl implements AuthService {
    private final UserRepository userRepository;
    private final SmsService smsService;

    @Override
    public void sendSms(SmsMessageDto smsMessageDto) throws UnsupportedEncodingException, NoSuchAlgorithmException, InvalidKeyException, JsonProcessingException {
        userValidator.isPhoneValid(smsMessageDto.getTo());
        SmsResponseDto smsResponseDto = smsService.sendSms(smsMessageDto);
    }
}
public interface SmsService {
    String getSignature(String time);
    SmsResponseDto sendSms(SmsMessageRequestDto smsMessageRequestDto, String content); 
    void verifySms(String key);
}
@RequiredArgsConstructor
@Service
public class NaverSmsServiceImpl implements SmsService {
    private final RedisUtil redisUtil;

    public String getSignature(String time) {
    ...
    }
    public SmsResponseDto sendSms(SmsMessageRequestDto smsMessageRequestDto, String content){
    ...
    }; 
    public void verifySms(String key){
    ...
    };

위의 코드에서 AuthServiceImpl 컴파일될 때 SmsService 인터페이스를 참조한다. 이는 컴파일 시점에 AuthServiceImpl이 어떠한 플랫폼을 사용하여 Sms 메세징 서비스를 사용하는 지 알 수 없으며, 애플리케이션이 실행 될 때 어떠한 SmsService 구현체를 참조하는지 알 수 있다.
즉, 런타임 의존성은 추상클래스 또는 인터페이스에 의존하므로 컴파일 시점에 어느 객체에 의존하는지 알지 못한다. 컴파일 시점에는 딱 메세지를 전송해야 한다는 것만 알고 있을 뿐, 실행될 때 어떠한 객체를 주입받아서 어떤 SmsService 결합되는지 알 수 있다. 이러한 이유로 런타임 의존성은 결합도가 낮으며 다른 객체들과 협력할 가능성을 열어두므로 변경에 유연한 설계를 갖는다.

⭐ 의존관계 자동 주입시 Bean이 2개 이상 존재한다면?

@Autowired는 타입으로 Bean을 조회한다.

  • 타입으로 Bean을 조회하므로 @Autowired로 빈을 주입하기 위해 ac.getBean(SmsService.class)와 같이 Bean을 조회할 때, SmsService의 하위 타입인 NaverSmsServicePurrioSmsService가 스프링 빈으로 선언되어 있으면 오류가 발생한다.\
@Component
public class NaverSmsService implements SmsService {} 
@Component
public class PurrioSmsService implements SmsService {} 
@Service
public class AuthServiceImpl implements AuthService {
    private final UserRepository userRepository;
    private final SmsService smsService; // NoUniqueBeanDefinitionException 오류 발생

    @Autowires
    public AuthServiceImpl(UserRepository userRepository, SmsService smsService) {
        this.userRepository = userRepository;
        this.smsService = smsService;
    }
}

SmsService 구조

 

코드는 위와 같은 계층 구조를 보이고, NaverSmsServicePurrioSmsService 모두 빈에 등록된다. 여기서 문제는 AuthServiceImpl에서 SmsService를 자동 주입할 때 NaverSmsServicePurrioSmsService 둘 중 어느 빈을 등록해야할 지 몰라 에러를 뱉어내는 것이다.

⭐ NoUniqueBeanDefinitionException 해결 방법

  1. @Autowired를 주입하는 곳에서 필드명을 사용할 빈과 일치시킨다.
@Service public class AuthServiceImpl implements AuthService { 
	private final UserRepository userRepository; 
    private final SmsService naverSmsService; // 필드명 변경 
    
    @Autowires 
    public AuthServiceImpl(UserRepository userRepository, SmsService smsService) { 
    this.userRepository = userRepository; this.smsService = smsService; 
    } 
}
  • @Autowired 어노테이션은 타입으로 Bean을 조회할 뿐만 아니라, 조회한 결과가 여러개일 때 이름으로 Bean을 조회한다.
  1. @Qualifer 사용
    @Service
    public class AuthServiceImpl implements AuthService {
     private final UserRepository userRepository;
     private final SmsService naverSmsService; // 필드명 변경
    
     @Autowires
     public AuthServiceImpl(UserRepository userRepository, @Qulifer("naver") SmsService smsService) {
         this.userRepository = userRepository;
         this.smsService = smsService;
     }
    }
  2. @Component @Qulifer("naver") public class NaverSmsService implements SmsService {}
  • @Qualifier가 Bean의 이름을 따로 바꿔주는 것은 아니며, 매칭할 기준을 정해주는 것이다.
  • 매칭할 Bean과 Bean을 주입하는 곳 둘 다 @Qualifier로 연결시키는 것이 좋다.
  1. @Primary 사용
    @Component
    @Primary
    public class NaverSmsService implements SmsService {}
    @Component
    public class PurrioSmsService implements SmsService {} 
  • 여러개 등록된 빈 중에 우선순위를 주는 것이다. 사용할 빈에 @Primary 어노테이션을 붙이기만 하면 인터페이스로 주입해도 @Primary로 선언한 빈이 주입된다.



참고 자료
참고 자료2

댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/11   »
1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30
글 보관함