최근에 Spring Framework에 기여하게 되었습니다.
이번 글에서는 프레임워크 내부 코드를 탐구하다가 오픈소스 기여까지 이어지게 된 과정을 설명해드리려 합니다.
블로그 설명에도 나와있지만, 저는 기술을 사용할 때 단순히 주어지는 대로 쓰기보다는, "이건 내부적으로 어떻게 동작할까?" 하고 그 원리를 파헤치고 탐구하는 것을 좋아합니다.
이런 성향 덕분에, 저는 Spring MVC의 내부 동작 원리에도 관심이 많았습니다.
Interceptor가 요청을 어떻게 가로채는지, DispatcherServlet이 어떻게 요청을 분배하는지, Controller의 메서드 파라미터로 어떻게 객체들이 바인딩되는지 종종 내부 코드를 열어보며 확인하곤 했습니다.
이러한 호기심으로 인해 우아한테크코스에서 같은 뜻을 가진 크루들과 "Spring 내부 코드 탐험 스터디"를 진행했고, 그 과정에서 HandlerMethodReturnValueHandlerComposite 이라는 클래스를 자세히 살펴보게 되었습니다.
이 클래스는 Controller(HandlerMethod)에서 반환된 객체를 지원하는 적절한 HandlerMethodReturnValueHandler 를 찾아 처리를 위임하는 역할을 합니다. 이때 분석했던 내용이 꽤 흥미로워서, 이를 주제로 테코톡 발표를 진행하기도 했습니다.
테코톡 발표 영상 링크 : https://youtu.be/JOLwv6Btayg?si=N2N019VSeGo3RL08
몇 개월 후, 프로젝트를 진행하다 커스텀 Handler를 만드는 과정에서 HandlerMethodReturnValueHandlerComposite 내부 코드를 다시 읽다가 흥미로운 점을 발견했습니다.
코드를 다시 살펴본 이유는 이 클래스에 캐시를 도입해 보면 어떨까? 하는 생각 때문이었습니다.
🤔 배경지식
HandlerMethodReturnValueHandler란
HandlerMethod(컨트롤러의 메서드)가 반환한 객체를 사용해 Http 응답을 구성하기 위해 사용하는 "응답 값 핸들러"입니다.
HandlerMethodReturnValueHandlerComposite란
다양한HandlerMethodReturnValueHandler를 하나의 묶음으로 구성해서, HandlerMethod(컨트롤러의 메서드)가 반환한 객체를 처리(Handle)할 수 있는 적절한HandlerMethodReturnValueHandler를 탐색하고 호출합니다.
HandlerMethodReturnValueHandlerComposite 는 현재 반환된 객체를 처리할 수 있는 적절한 HandlerMethodReturnValueHandler를 찾기 위해 내부적으로 반복문을 돕니다.
@Nullable
private HandlerMethodReturnValueHandler getReturnValueHandler(MethodParameter returnType) {
for (HandlerMethodReturnValueHandler handler : this.returnValueHandlers) {
if (handler.supportsReturnType(returnType)) {
return handler;
}
}
return null;
}
매번 루프를 도는 대신 캐시를 도입한다면 큰 성능 최적화가 가능하지 않을까? 하는 호기심이 생겼습니다.
캐싱 로직을 추가해 볼 수 있는지 알아보기 위해 클래스를 자세히 살펴보기 시작했는데, 상단의 JavaDoc을 읽다가 의아한 부분을 발견했습니다.
/**
* Handles method return values by delegating to a list of registered
* {@link HandlerMethodReturnValueHandler HandlerMethodReturnValueHandlers}.
* Previously resolved return types are cached for faster lookups.
*
* @author Rossen Stoyanchev
* @since 3.1
*/
public class HandlerMethodReturnValueHandlerComposite implements HandlerMethodReturnValueHandler {
Javadoc 일부를 보면, Previously resolved return types are cached for faster lookups. 라는 설명이 존재합니다.
분명 코드 로직 상으로는 매번 루프를 돌며 Handler를 찾고 있었는데, JavaDoc에는 "이 클래스는 빠른 탐색을 위해 Handler를 캐싱한다"는 설명이 남아있었습니다.
코드와 주석의 내용이 일치하지 않는 이유를 찾기 위해 해당 클래스의 Git History를 거슬러 올라가 보았습니다.
그 과정에서, 2012년에 작성된 하나의 커밋을 발견했습니다.
문제의 커밋 : https://github.com/spring-projects/spring-framework/commit/cfe2af76906039e42b12dc24cf4fca7b91c9b910
내용을 살펴보니, 2012년 이전에는 실제로 캐싱 로직이 존재했습니다.
하지만 HandlerMethod가 반환한 객체의 내부 상태에 따라 분기 처리를 해야 하는 요구사항이 생기면서, 단순히 반환 타입만으로는 특정 Handler를 고정해서 캐싱할 수 없게 된 것입니다.
결국 캐싱 로직은 해당 커밋으로 인해 제거되었지만, Javadoc 주석은 함께 지워지지 않고 누락되어 있었습니다.
그리고 그 상태로 2012년부터 2026년인 지금까지 그대로 남아있었던 것입니다.
히스토리를 분석해 보니 그 Javadoc이 잘못되었다는 것을 파악했고, 실제 HandlerMethodReturnValueHandlerComposite의 동작과 일치하도록 수정하여 PR을 제출했습니다.
PR : https://github.com/spring-projects/spring-framework/pull/36555
단순한 문서 수정이었기에 리뷰 과정은 비교적 짧게 끝났고, 제 PR은 Spring Framework의 7.0.7 마일스톤에 성공적으로 반영되어 머지되었습니다.

거창한 코드 기여나 엄청난 성능 개선을 이룬 것은 아닙니다.
하지만 평소에 프레임워크의 내부 코드를 열어보고 원리를 궁금해하던 습관 덕분에, 오래된 문서 오류를 바로잡는 소소한 기여를 할 수 있었습니다.
이번 경험을 통해 멀게만 느껴졌던 오픈소스 생태계와의 심리적 거리가 한결 가까워졌고, 제 목표인 공통 플랫폼 개발자에 한 걸음 더 다가간 것 같아 뿌듯한 마음도 듭니다.
프로젝트를 진행하시다가 "이건 내부적으로 어떻게 돌아갈까?"라는 궁금증이 든다면, 라이브러리 코드를 슬쩍 열어보는건 어떨까요?
혹시 아시나요? 작은 궁금증이 오픈소스 기여로 이어질지도 모릅니다.