이전에 주문 로직을 리팩토링하며 추상클래스를 추가하였는데 프로젝트 실행이 되지않는다
리플렉션을 사용해서 전략들을 매핑하는 OrderStrategyProvider에서 out of bounds 예외가 터졌다
오류 발생
@Component
public class OrderStrategyProvider {
private final Map<Class<?>, OrderStrategy<?>> strategyMap = new HashMap<>();
public OrderStrategyProvider(List<OrderStrategy<?>> orderStrategies) {
for (OrderStrategy<?> strategy : orderStrategies) {
Class<?> targetClass = AopUtils.getTargetClass(strategy); // 프록시에서 원본 클래스 추출
Class<?> genericType = getGenericType(targetClass);
if (genericType != null)
strategyMap.put(genericType, strategy);
}
}
private Class<?> getGenericType(Class<?> strategy) {
return (Class<?>) ((ParameterizedType) strategy.getGenericInterfaces()[0]) // <- 이 라인 예외
.getActualTypeArguments()[0];
}
@SuppressWarnings("unchecked")
public <T extends CreateOrderRequest> OrderStrategy<T> getStrategy(Class<T> requestType) {
return (OrderStrategy<T>) strategyMap.get(requestType);
}
}
주석으로 달아놓은 라인에서 예외가 터졌는데
곰곰히 생각해보니 기존엔 인터페이스 - 구현체 구조였는데
리팩토링해서 인터페이스 - 추상 클래스 - 구현체 구조로 바뀌었다
getGenericInterfaces()는 대상 클래스가 구현한 인터페이스에서 제네릭타입을 찾아 Type[] 배열로 반환한다
해당 라인을 디버거로 조회해보니 빈 값이 나온다
해결
혹시나 해서 리플랙션을 적용할때 getGenericSuperClass() 메서드도 봤던것이 생각나 한번 입력해보았는데
값이 잘나온다.. getGenericSuperClass()로 고쳐서 실행해보니 잘 실행된다
의문점
뭔가 얼렁뚱땅 고쳐졌는데 getGanericSuperClass()와 getGenericInterfaces()가 무슨 차이인지 찾아보았다
메서드 이름만 보았을땐 getGenericSuperClass()는 상위 클래스에서 제네릭타입을 추출하는것같고
getGenericInterfaces()는 인터페이스에서 제네릭타입을 추출하는것같은데
왜 빈을 조회할땐 인터페이스 타입으론 조회가되면서 인터페이스에서 제네릭타입을 찾을땐 찾을 수 없단말인가
그니까 애초에 인터페이스 - 구현체 구조일땐
인터페이스로 해당 빈 목록을 조회하고
인터페이스에서 제네릭타입을 추출하였는데
인터페이스 - 추상 클래스 - 구현체 구조일땐
인터페이스로 해당 빈 목록을 조회하는건 되고
인터페이스에서 제네릭 타입을 추출하는건 안된다
디버거로 또 확인해보았다
지금 AopUtils.getTargetClass()로 프록시 객체에서 원본 클래스를 추출한 상황이고 targetClass를 확인해보면
superclass엔 AbstractOrderStrategy가 있고
superInterfaces엔 빈 배열이 들어가있다
의문점 해결
GPT에게 물어보았다
자바에선 간접적으로 구현한 인터페이스도 직접 구현한 구현체와 똑같이 작용된다고한다
interface A {} -> abstract class B implements A {} -> class C extends B {}
일때 B instanceof A가 true로 적용된다
또 스프링에서 빈을 찾을 때 할당 가능한지를 기준으로 찾는다고한다
CartOrderStrategy와 ProductOrderStrategy는 assignableFrom OrderStrategy<?>일때 true 임으로
따라서 OrderStrategy를 구현한 모든 빈들을 찾는다
각각의 구현체들이 간접적으로 구현하였지만 직접적으로 구현한것과 차이가 없기 때문에
OrderStrategy 인터페이스로 찾을 수 있는것이다
getGenericInterfaces()는 해당 클래스가 직접 구현한 인터페이스를 반환한다고한다
해당 빈들이 OrderStrategy를 간접적으로 구현한 것이기 때문에 빈 배열이 나타나는 것이다
getGenericSuperClass()도 해당 클래스가 직접 상속한 부모 클래스만 찾을 수 있다
그래서 Out Of Bounds 예외가 발생한 것이고
해당 빈들이 직접 상속한 추상 클래스에서 제네릭 타입을 추출하는 것으로 해결할 수 있었다
따라서 스프링에서 빈을 찾을때 간접적으로 구현한 빈들은 찾을 수 있지만
리플랙션에선 직접적으로 구현하거나 상속한 바로 상위 타입만 찾을 수 있어서 발생한 문제이다