지난시간에 Jersey 를 사용하면서 굉장히 Restful API 만드는 일이 쉽다고 느껴졌다. 이렇게 설정이 없고 이렇게나 간단한 프레임워크가 있나.. 하면서.. 굉장히 좋아했었던 기억이 난다. (지금은 다소 시간이 흘렀지만) 회사에서 진행하는 프로젝트를 세팅하면서 Jersey 를 적극 사용하여 보기로 어느정도 합의를 하고 시작을 했는데.. 결국은 뭔가 커다란 장벽에 부딫히는 느낌이었다. 대부분은 레퍼런스들은 여러 커뮤니티들을 통해서 살펴보면 대부분이 Jersey 만 홀로 이용하는 경우는 별로 찾아볼 수 없다. 다들 Spring DI 가 주는 잇점을 사용하여 진행을 하거나 Google Guice 와 같은 또 다른 DI 라이브러리들을 사용해서 쓰고 있었다. 그중에서도 Spring 이 당연히 압도적으로 많았다. 이렇다보니 이를 통해서 진행을 안하자니 뭔가가 허전한 느낌이라. Spring + Jersey 를 연동하는 작업을 시작하게 되었다.
일단 가장 먼저 찾아 볼 수 있는 에러는 FileNotFoundException 이다. classpath 에 applicationContext.xml 파일이 없다고 자꾸 난리를 친다. 물론 이 파일을 추가해주면 끝날 일이지만 Spring Annotation 을 사용하여 Java Configuration 을 시도하고 있는 중이기 때문에 다소 불필요하다고 여겨지는 xml 파일을 철저하게 배척하고 있었다. 분명 필요한
것들을 모두 했는데 지속적으로 applicationContext.xml 파일이 없다고 앵무새 처럼 짖어댈 뿐이었다. 이때 정말 콘솔창을 터트려 버리고 싶었다.
정답은 web.xml 을 대신하는 WebApplicationInitializer 의 구현체에 있었다. 이 모든 상황이 arguably 나의 실수라는 것은 자명한 일이 되어버렸다. 정작 Jersey servlet contatiner 에는 별로 관심을 안두고 있었는데, spring 과 jersey 를 연동시켜주는 라이브러리는 spring 3 버전에 대해서만 지원하고 있었을 뿐 아니라.. spring 의 dispatcher servlet 이 아니라 jersey 의 servlet 을 사용한다는 점을 약간 잊고 있었다.
http://www.codingpedia.org/ama/restful-web-services-example-in-java-with-jersey-spring-and-mybatis/
처음엔 위의 페이지를 참고해가면서 세팅을 진행하고 있는데.. 이분은 xml 파일들을 적극 사용하면서 세팅을 하고 있지만 이를 Java Configuration 으로 수행하려고 시도를 했다. 그리고 나서 여러가지 삽질을 하게 되었다. 위에서 언급한 에러들 뿐만아니라 많은 삽질로 뭐가 맞고 틀리는지 구분을 잘 못했다.
그리고 나서 발견한 jersey 개발팀이 운영하고 있는 듯한 Jira 를 살펴본 결과.. 다음과 같은 충격적인 글을 보게 되었다. (https://java.net/jira/browse/JERSEY-2038) 10여개의 코멘트가 달려있는 해당 이슈는 2013년 8월 14일에 생성되어 약 1년동안 해결되지 않은 "Minor" 이슈였다. spring 과 jersey 를 연동하는 과정중에서 AnnotationConfigWebApplicationContext 를 사용하여 Configuration 클래스들을 등록해주는데 영향을 주지 않는 다는 것, 그리고 나처럼 계속 applicationContext.xml 토해낸다는 점.
억장이 무너지는 느낌이었다. 더러운(?) xml 을 작성하면서 세팅을 해야하나?! 하지만 밥숟가락이 달려있는 문제니까 아주 쉽게 태세 전환을 하고 xml 을 슬금슬금 작성하고 있더라.. 빡치게도 xml 로 세팅을 하고 나면 매우 잘되었다. spring 3, 4 이든 원래 annotation configuration 을 지원하는건 매한가지 이니까 특별하게 문제 삼을 부분이 없이 잘 되더라는 것이다. 그렇구나... 그런거였어.. xml 로 하면 쉬운거였는데 나는 왜 xml 을 싫어했을까... 이렇게 다 설정을 마치고 나니 왜이렇게 마음이 허전하고 서운하던지.. 개발자라서 그런거 같다. 어떤 특정 상황에 마주치면 해결책을 찾아서 돌파해야 속이 시원한데.. 이걸 "우회" 할려다보니까 뭔가 영혼을 잃은 느낌이랄까? 아무튼 개똥 철학이 작용해서 다시 원점으로 돌아왔다.
이렇게 이틀을 날려버리게 되었다. 밥을 먹으면서 집에서 누워서도 이 생각이 계속 들더라는 것이었다. 그러다가 찾아낸 이 곳..
Sleepless in Salt Lake City : http://sleeplessinslc.blogspot.kr/2014/04/jerseyjaxrs-2x-example-using-spring.html
잠못드는 솔트레이크시티.. 어디서 많이 들어봤는데.. 2002 년에 동계 올림픽을 개최했던 곳이었다. 미국 유타주에 있는 Salt Lake City.. 잘 모르는 동네니까 위키피디아에 가서 물어보시길 (http://en.wikipedia.org/wiki/Salt_Lake_City)
캬.. 나랑 거의 80% 일치하는 환경에서의 개발환경 설정을 하고 계셨더라는.. 그리고 이미 완료 했다는 것을!! 직감했다. 이 페이지에 들어온 직후 뭔가 해결될 것 같은 기분. 결국 이 아제 홈페이지를 통해서 모든것을 해결하고 원하는 형태로 개발을 완료했다. ㅠㅠ 감격!!
import javax.servlet.FilterRegistration; import javax.servlet.ServletContext; import javax.servlet.ServletException; import javax.servlet.ServletRegistration; import org.glassfish.jersey.servlet.ServletContainer; import org.springframework.core.Ordered; import org.springframework.core.annotation.Order; import org.springframework.web.WebApplicationInitializer; import org.springframework.web.context.ContextLoader; import org.springframework.web.context.ContextLoaderListener; import org.springframework.web.context.support.AnnotationConfigWebApplicationContext; import org.springframework.web.filter.CharacterEncodingFilter; @Order(Ordered.HIGHEST_PRECEDENCE) public class ApplicationInitializer implements WebApplicationInitializer { @Override public void onStartup(ServletContext ctx) throws ServletException { // add listener ctx.addListener(ContextLoaderListener.class); // *important* using for annotation based config ctx.setInitParameter(ContextLoader.CONTEXT_CLASS_PARAM, AnnotationConfigWebApplicationContext.class.getName()); // register root context bean ctx.setInitParameter(ContextLoader.CONFIG_LOCATION_PARAM, "com.smilegate.balin.config"); // Register Jersey 2.0 servlet ServletRegistration.Dynamic servletRegistration = ctx.addServlet("Notes", ServletContainer.class.getName()); servletRegistration.addMapping("/*"); servletRegistration.setLoadOnStartup(1); servletRegistration.setInitParameter("javax.ws.rs.Application", ApplicationResourceConfig.class.getName()); // utf-8 encoding FilterRegistration charEncodingfilterReg = ctx.addFilter("CharacterEncodingFilter", CharacterEncodingFilter.class); charEncodingfilterReg.setInitParameter("encoding", "UTF-8"); charEncodingfilterReg.setInitParameter("forceEncoding", "true"); charEncodingfilterReg.addMappingForUrlPatterns(null, false, "/*"); } }
중요한 항목은 24,26번 라인인데 이부분에서 기존의 XML 기반의 Context 대신 AnnotationConfigWebApplicationContext 를 추가해주고 해당 설정 정보에 대해 패키지 경로를 지정해 주면서 해결이 되었다. ㅠㅠ 이 얼마나 힘든? 작업이었나 회고를 해보면 ApplicationContext 에 대한 기반 지식이 별로 없었기 때문이기도 하다. 이번 기회를 빌어서 잘 배운 것같다. 근데 이 직업 정말.. 단 하루도 배움이 없는 날이 없다.