OAuth2를 공부하던 도중에 고민거리가 생겼다.
CustomOAuth2User
dto 클래스와, CustomOAuth2UserService
서비스 레이어에서 동일한 메서드를 사용하게 되는데, 어떻게 리팩토링을 해야 좋을까???
{resultcode=00, message=success, response={id=123123123, name=이름이름}}
{resultcode=00, message=success, id=123123123, name=이름이름}
개발 시 회원 데이터를 DB에 저장해야하는데 아래와 같이 테이블을 구성할 경우 내부적으로 구분할 수 있는 username
이 존재하지 않게 된다.
따라서 아래와 같은 메서드를 구현했는데......
// 전달받은 데이터에서 username으로 지칭할 수 있는 것이 없기에 별도의 메소드를 구현한다.
public static String getUsername(OAuth2Response oAuth2Response) {
return oAuth2Response.getProvider() + " " + oAuth2Response.getProviderId();
}
아래의 클래스 다이어그램을 보면 문제의 OAuth2Response
의 경우 인터페이스이고
2개의 구현체를 가진다.
의존관계를 주입할 경우 해당 Type의 bean이 하나일 경우에는 Bean이 자동으로 주입이 되는데,
구현체를 여러개 만들 경우 해당 Type의 bean이 여러 개가 되기 때문에 해당 Type만으로 주입하려는 bean을 찾을 수 없어 오류가 발생하게 된다.
***************************
APPLICATION FAILED TO START
***************************
Description:
Parameter 0 of constructor in com.example.oauthsession.util.UsernameCreator required a single bean, but 2 were found:
- googleResponse: defined in file [경로]
- naverResponse: defined in file [경로]
This may be due to missing parameter name information
Action:
Consider marking one of the beans as @Primary, updating the consumer to accept multiple beans, or using @Qualifier to identify the bean that should be consumed
Ensure that your compiler is configured to use the '-parameters' flag.
You may need to update both your build tool settings as well as your IDE.
(See https://github.com/spring-projects/spring-framework/wiki/Upgrading-to-Spring-Framework-6.x#parameter-name-retention)
실제로 에러메시지를 살펴보면 아래와 같다.
매개변수가 하나만 필요하지만 두 개의 빈이 발견되었기 때문에 발생하는 것으로 보입니다.
따라서@Primary
혹은@Qualifier
어노테이션을 이용해주세요!
어노테이션을 사용하지 않기 위해서는 구현체의 이름으로 의존성 주입을 받는 필드 이름을 명시하는 것인데,
유지보수를 할 때, 구현체의 이름을 변경하게 되면 해당 필드의 이름을 일일히 전부 변경해 줘야 하기 때문에 좋은 방식은 아닌 것 같다.
@Primary
어노테이션을 특정 구현체에 붙여주게 되면, 여러개의 구현체 빈이 존재해도 @Primary
어노테이션이 붙은 구현체가 기본적으로 주입이 된다.
@Qualifier
어노테이션은, 스프링 컨테이너가 여러 개의 빈을 찾았을 때, 어떤 빈을 이용할지 판단할 정보를 주는 것이다.
아래와 같이 코드를 작성하면 된다.
public interface OAuth2Response {
// ...
}
@Component
@Qualifier("googleResponse")
public class GoogleResponse implements OAuth2Response {
// ...
}
@Component
@Qualifier("naverResponse")
public class NaverResponse implements OAuth2Response {
// ...
}
@Component
public class NameCreator {
private final OAuth2Response oAuth2Response;
public NameCreator(@Qualifier("naverResponse") OAuth2Response oAuth2Response) {
this.oAuth2Response = oAuth2Response;
}
public String getUsername() {
return oAuth2Response.getProvider() + " " + oAuth2Response.getProviderId();
}
}
근본적으로 문제점이 생겼다.
결국 우선순위를 부여하거나, 이름을 특정지어주어야 하는 것인데, 아래의 로직을 보자.
if (registrationId.equals("naver")) {
oAuth2Response = new NaverResponse(oAuth2User.getAttributes());
} else if (registrationId.equals("google")) {
oAuth2Response = new GoogleResponse(oAuth2User.getAttributes());
} else {
return null;
}
// DB 저장 구현
// 전달받은 데이터에서 username으로 지칭할 수 있는 것이 없기에 별도의 메소드를 구현한다.
String username = UsernameCreator.getUsername(oAuth2Response);
로그인을 진행하려고 할 때 Provider(프로바이더)
변수에 따라 OAuthResponse
의 구현체가 달라지게 된다.
따라서 @Primary
어노테이션은 사용이 불가능하다.
만약 @Qualifier
어노테이션을 사용하게 된다면 if문 내에 중복된 String username = ~~
코드가 생기게 될 것이다.
-> 만약 추후 더 많은 Provider
가 생겨 총 N
개의 Provider
가 존재한다면 N
개의 중복된 코드가 생성될 것이다.
- 자바 클래스의 정적 메소드는 정적 변수만 참조할 수 있다.
- 스프링 빈의 메소드는 다양한 외부 요소들을 참조할 수 있다. 예를 들어 스프링 빈에 작성된 메소드에서는 DB에 저장된 값을 불러와서 그것에 대한 다양한 동작을 수행할 수 있다.
현재 프로젝트 패키지가 계층형 구조인 것을 고려해 많은 사람들이 관습적으로 사용하는 util
패키지의 하위에 별도의 Java 클래스를 생성하고,
외부 환경이 어떻던 현재 메소드의 경우 파라미터로 이용되는 OAuth2Response
구현체가 같을 경우 반드시 동일한 값을 반환하기 때문에 Static 메소드
를 이용하기로 결정했다.
public class UsernameCreator {
// 전달받은 데이터에서 username으로 지칭할 수 있는 것이 없기에 별도의 메소드를 구현한다.
public static String getUsername(OAuth2Response oAuth2Response) {
return oAuth2Response.getProvider() + " " + oAuth2Response.getProviderId();
}
}