Spring Context 외부에서 Bean 주입받기: ServletContext와 BeanFinder 유틸리티
Filter, Listener, 혹은 레거시 코드 등 Spring 컨테이너의 제어를 받지 않는 곳에서 스프링 빈(Bean)을 사용해야 할 때가 있습니다. ServletContext를 활용해 ApplicationContext에 접근하고 빈을 동적으로 조회하는 BeanFinder 유틸리티 구현 방법을 알아봅니다.
1. 문제 상황: Spring 관리 영역 밖에서의 빈 접근
Spring MVC 패턴 안에서는 @Autowired나 생성자 주입을 통해 손쉽게 빈을 사용할 수 있습니다. 하지만 서블릿 필터(Filter), 리스너(Listener), 또는 일반 POJO 클래스에서는 이러한 자동 주입(DI)이 동작하지 않습니다.
이러한 상황에서는 ServletContext를 통해 Spring의 루트 컨테이너인 WebApplicationContext를 직접 찾아내고, 필요한 빈을 수동으로 꺼내오는 전략이 필요합니다.
2. 핵심 구현: BeanFinder 유틸리티 클래스
아래 코드는 WebApplicationContextUtils를 사용하여 컨텍스트에 접근하고, ID 또는 Class 타입을 기반으로 빈을 검색하는 유틸리티 클래스입니다.
import javax.servlet.ServletContext;
import org.springframework.context.ApplicationContext;
import org.springframework.web.context.support.WebApplicationContextUtils;
public class BeanFinder {
// 1. ServletContext로부터 Spring ApplicationContext 획득
private static ApplicationContext getApplicationContext(ServletContext context) {
return WebApplicationContextUtils.getWebApplicationContext(context);
}
// 2. Bean ID로 조회
public static Object getBean(ServletContext context, String id) {
return getApplicationContext(context).getBean(id);
}
// 3. Class 타입으로 조회
public static T getBean(ServletContext context, Class clazz) {
ApplicationContext appContext = getApplicationContext(context);
return appContext.getBean(clazz);
}
// (옵션) 타입으로 조회 시 정확한 매칭을 위한 헬퍼 메소드
public static String getBeanName(ApplicationContext context, Class clazz) {
for (String beanName : context.getBeanNamesForType(clazz)) {
// 프록시 객체 등을 고려하여 원본 클래스명 비교
if (getBeanClassName(context, beanName).equals(clazz.getName())) {
return beanName;
}
}
return null;
}
// AOP Proxy 처리를 위한 클래스명 파싱
private static String getBeanClassName(ApplicationContext context, String beanName) {
String className = context.getBean(beanName).getClass().getName();
int pos = className.indexOf("$$"); // CGLIB Proxy 식별
return pos == -1 ? className : className.substring(0, pos);
}
}
3. 주요 로직 상세 분석
위 코드에서 눈여겨봐야 할 핵심 기술 요소는 다음과 같습니다.
- WebApplicationContextUtils: Spring이 제공하는 유틸리티로, 서블릿 컨테이너가 시작될 때
ServletContext속성(Attribute)에 저장해둔 Spring 컨테이너 객체를 꺼내옵니다. - 프록시(Proxy) 처리: Spring AOP나 트랜잭션이 적용된 빈은 원본 클래스가 아닌 프록시 객체(예:
UserService$$EnhancerBySpringCGLIB)로 래핑됩니다.
따라서 클래스 타입으로 빈을 조회할 때, $$ 키워드를 통해 프록시 여부를 확인하고 원본 클래스 이름을 추적하는 로직이 포함되어야 정확한 조회가 가능합니다.
4. 실무 활용 시나리오
이 BeanFinder 클래스는 다음과 같은 상황에서 매우 유용하게 사용됩니다.
- Legacy JSP 마이그레이션: Spring 컨트롤러를 거치지 않고 직접 호출되는 레거시 JSP 파일에서 Service 빈을 호출해야 할 때.
- Servlet Filter: 인코딩이나 보안 처리를 담당하는 필터 내에서 DB 접근이 필요한 DAO/Repository 빈을 사용해야 할 때.
- Third-party 라이브러리 연동: Spring을 인지하지 못하는 외부 라이브러리의 콜백 함수 내부.
💡 Tip: Spring 4.3 이상을 사용한다면 컴포넌트 내부에서는 생성자 주입이 권장되지만, 컨테이너 외부에서의 접근에는 이 방식이 가장 확실한 솔루션입니다.
댓글
댓글 쓰기