기선이가 두 번째 스크린캐스트를 IBM DW 로컬 컨텐츠로 공개했다. YouTube로 가서 보면 HD급 화질로도 볼 수 있다.

내가 쓴 토비의 스프링 3은 책의 내용을 그대로 강의 교재로 쓰기에는 그리 적절한 책은 아니다. 분량도 많은 데고, 책 내용도 시간을 충분히 가지고 스스로 학습하거나 스터디 등을 통해서 토론식으로 공부하려는 사람에게 적합하다. 하지만 책의 핵심 내용을 추려서 교육과정을 만든다면 나쁘지 않다. 책은 강의나 교육을 통해서 얻은 지식을 심화해주는 보조교재로 사용될 수 있을테니까.

반대로 책의 분량에 기가 질려서 선듯 공부하기 힘들다고 느껴지는 사람이라면 짧은 교육을 통해서 먼저 감을 잡고, 그리고 나서 본격적으로 책을 보는 것도 좋겠다.

책만 가지고 공부하는 데 부담을 느끼는 사람이나 아니면 빠른 시간에 스프링 개념을 잡고 전체 내용을 살펴보기 원하는 사람들이라면 내 책의 테크니컬 리뷰어 역할을 해줬던 기선이가 강의하는 교육에 참여해봐도 괜찮을 것 같다.

기선이는 이미 두 차례 한빛교육센터에서 토비의 스프링 3의 내용을 중심으로 한 "스프링 3.0 이해와 선택"이라는 교육과정을 진행했다. 또, NHN, LGCNS, SDS 의 초청을 받아서 현장에서 같은 내용의 교육을 진행하기도 했다. 내가 직접 강의를 들어본 것은 아니지만 강의를 받은 사람들의 피드백을 보면 제법 괜찮은 내용인 것 같다. 스프링을 처음 접하는 사람뿐 아니라 이미 스프링을 이용한 개발경험이 있는 사람들도 도움이 많이 되었다고 한다.

10월에는 강남의 한빛교육센터에서 같은 내용의 교육이 진행된다고 한다. 이번이 공식적인 6번째 교육이니 그만큼 기선이의 강의실력도 많이 늘었을 것이라고 기대된다. 관심있는 사람이라면 인원이 차기 전에 얼른 신청하는게 좋을 것 같다. 모든 수강생에게는 교재인 토비의 스프링 3이 한 권씩 제공된다.

내가 이렇게 기선이 교육을 홍보하는 이유는….

나한테 떨어지는 로얄티가 있기 때문이다. 강의 한번 할 때마다 로얄티로 내가 가장 좋아하는 땅콩버터구이 오징어 한봉지씩 보내주기로 약속이 되어있다. :)

다음 오징어는 언제나 오려나… 냠냠.

왜 그런지 연재가 끝났다고 생각하는 분들도 있지만 제목이 ‘토스3이 나오기까지’인데 아직 안 나왔으니까 계속.

일단 원고를 완성하고 나니 기분도 좋아졌고 마음에 여유도 생겼다. 때마침 아내도 입덧이 좋아져서 조금씩 식사를 하고 기력을 회복하게 되었다. 끝이 보이지 않던 터널을 빠져나온 느낌이었다.

원고 리뷰를 시작했다. 어색한 문장이나 설명 고치고 최신 버전에 맞춰서 코드 점검하는 정도만 하면 될 것 같았다. 길어야 한달이면 충분하겠지라고 생각했다. 1000페이지니까 일주일에 200. 5일만 일하면 하루에 40페이지. 40페이지 리뷰라면 껌이구나.

거의 일년 3개월 전에 썼던 1장부터 다시 보기 시작했다. 맘에 안들었다. 아무래도 감을 잡기 전이라 1장은 좀 이리 저리 고민해가면서 쓰긴 했지만 이 정도일 줄은 몰랐다. 문장은 질질 늘어지고 어떤 건 너무 급하게 설명하고 넘어가고 어떤건 횡설수설. 코드를 발전시켜나가는 흐름과 그것을 이용한 스프링 설명방식은 맘에 들었지만 그 외에는 너무 한심했다.

확 다 지워버리고 다시 쓰고 싶었지만 도저히 그럴 수는 없고, 어색하다고 보이는 문장은 모두 고치고 맘에 안드는 문단은 통채로 다시 썼다. 거의 30%쯤은 다시 쓴 듯. 1장은 나중에 한번 더 봤는데 그때도 맘에 안들었다. 열번이고 맘에 들때까지 고치고 싶었지만 결국 두번 보고 "에라 모르겠다" 하면서 그냥 포기. 나름 초보자도 이해할 수 있게 쉬우면서도 독특한 방법으로 스프링의 DI 개념을 설명하고, 1부에서 계속 얘기 할 내용의 핵심 메시지를 담고 싶었는데… 과연.

코드도 모두 다시 검증해서 넣었다. 원고 있는 코드를 가지고 다시 맨땅에서 부터 프로젝트를 만들어서 테스트를 하고 검증이 되면 그것을 다시 붙여넣는 식으로 작업을 했다. 맘에 안드는 다이어그램도 다시 수정. 그리고 가을에 만났을 때 김 부사장님이 알려준 기준에 맞게 일부 구성도 수정했다. 각주(-_-;;)도 모두 본문에 넣거나 박스로 뽑아냈고.

가장 손이 많이 간 것은 리스트나 그림의 번호를 참조(그림 1-1을 보시라 같은)하는 부분이다. 한장에 수십에서 백여개까지도 나오는 리스트는 번호 자동 생성기능을 이용해서 만들었기 때문에 중간에  삽입하거나 제거한다고 해도 자동으로 번호가 갱신되서 일일이 번호를 수정하는 번거로움은 없다. 하지만 본문에서 참조한 부분은 일일이 하드-코딩(?)을 한 탓에 리스트의 번호가 달라지면 모두 찾아서 수정을 해줘야 하는 번거로움이 있었다. 자동으로 리스트 번호와 연동이 되면 좋을텐데라고 투덜거리면서 작업을 했는데, 알고보니 워드에 그런 기능이 이미 있었다. 상호참조라고.

내용도 확정했고 특별히 더 넣거나 뺄 리스트나 그림은 없으니 그냥 한번씩 확인만 하고 말까했지만, 영양가 없을 때만 꼼꼼해지는 성격상 몽땅 다 상호참조로 바꾸기로 했다. 어떤 장은 백개도 넘는 리스트와 장마다 수십 개에 달하는 그림의 참고글에 상호참조 적용하는 것만 해도 방대한 작업이었다. 그게 메뉴를 타고 들어가서 매번 제일 앞으로 돌아가는 목록을 뒤져서 번호를 삽입하는 식이다 보니 작업이 무척 더뎠다. 그런데 막상 그렇게 확인을 하면서 상호참조를 넣다보니 안했으면 큰일날 뻔 했다는 생각이 들었다. 그 사이에 글을 수정하면서 본문에서 참조한 리스트나 그림의 번호가 틀린게 많았다.

일부 코드는 아예 수정이 필요한 것도 있었다. 1장을 쓸 때는 스프링 3.0이 겨우 M1이었다. 그때는 아씨아씨(이클립스에서 ACAC라고 치고 컨트롤-스페이스를 누르면 내가 테스트 만들 때 애용하는 AnnotationConfigApplicationContext이 나온다)가 없었기 때문에 XML없이 자바코드만 가지고 @Configuration를 사용하게 하려면 여러 줄의 코드가 필요했다. 하지만 나중에 ACAC가 나온 뒤로는 컨텍스트 생성자로 충분했다. 이런 식으로 원고를 쓸 때의 스프링 버전과 달라진 점이나 개선된 내용을 찾아서 적용하는 것도 큰 작업이었다.

처음 생각엔 이틀이면 충분했을 것 같았는데 1장을 퇴고(나도 폼나는 말 좀 써보자)하고 나니 이미 2주가 지났다.

다행히 2장부터는 그나마 수정할 데가 많지는 않았다. 1장을 마무리 할 때쯤엔 글을 쓰는 감도 조금 잡히고 리듬도 타고 하니 아무래도 처음 보다는 덜 어색해졌나보다. 표현이 어색한 것은 나중에는 크게 손대지 않았다. 고쳐봐야 여전히 어색하고. 차라리 출판사에서 편집하면서 세련되게 다듬어주기를 기대하는 것이 나을 듯 싶었다. 편집과정에서 어색한 문장과 표현은 모두 깔끔하게 다듬어 준다는 얘기를 어디선가 들은게 있으니까.

대부분 내용이나 코드는 크게 바뀔 것은 없었지만 일부 테스트 코드는 좀 더 나은 아이디어가 보이면 과감히 수정하기도 했다. 스프링에 관한 모는 내용을 다 넣은 것은 아니지만, 나름 중요한데도 깜빡하고 빼먹은 것이 보이면 추가하기도 했다. 일관성 없는 코드(리스트)도 통일을 해야 했다. 코드가 조금씩 바뀌면서 진행되다 보니 클래스 전체를 보여주는 경우는 드물었다. 물론 바뀔 때마다 전체 코드를 넣을 수도 있지만 양을 늘리기 위해서 별짓 다한다는 얘기를 듣기 딱 좋다. 그래서 바뀌는 부분만 골라서 삽입했다. 전체 코드의 변화를  보려면 아무래도 52단계로 쪼개서 넣은 CD의 예제 코드를 보거나 아니면 본인이 직접 해볼 필요가 있다.

남은 건 package와 import를 넣는냐의 문제였다. 클래스의 일부분이지만 그래도 어느 패키지의 어떤 클래스인지는 알려줘야 했다. 그래서 처음 클래스가 등장할 때는 package는 넣었다. 그게 예제라고 하고 파일 경로를 적는 것보다 나아보였다. 문제는 import. 이게 한 둘이 아닌데 이걸 다 넣으면 코드의 양이 두 배는 될 테고 책도 100-200페이지는 더 늘어날 것 같았다. 그래서 정말 특별한 경우에만 한 두 개의 import를 넣고 나머지는 다 생략했다. 이 때문에 책만 보고 코드를 따라하는 경우 이클립스에서 타입 이름을 보고 import를 자동으로 삽입해주는 Organize Imports 기능을 잘 사용하지 못하는 사람이라면 import문 때문에 고생할 수도 있겠다는 생각이 들었지만 어쩔 수가 없었다. 대신 쌩 초보자를 위해서 1부의 내용을 코딩하는 동영상을 만들고 이를 공개해주면 괜찮겠다 싶었다. 물론 책이 끝날 무렵엔 에너지를 다 소진해 버려서 아직도 그 동영상은 못 만들고 있긴 하지만. –_-;;

일부 부족한 설명과 중요한데 깜빡하고 빼먹은 내용 등을 추가한데다 캡션 없이 코드를 넣었던 것도 대부분 리스트-xx를 넣달아주고, 코드에 package나 import를 생략했다는 표시를 넣다보니 20페이지 정도 더 늘었다.

 

책을 쓰는 중에는 다들 하는 베타 리딩을 할까 생각했다. 하지만 원고를 마무할 즈음엔 베타 리딩이 엄두가 안났다. 오탈자나 기술적인 문제를 지적해주면야 고맙지만 구성이나 전개방법, 내용 등에 관한 피드백이 들어오면 아마 기절할지도 모를 거란 생각이 들었다. 그래도 기술적인 설명과 코드, API 사용등에 관해서 한번은 다른 사람의 점검을 받아야 겠다고 생각했다. 7년가까이 매일 만져오던 스프링이지만 내가 자주 사용하지 않아서 익숙하지 않은 기능을 설명할 때 실수했을 수도 있고, 아니면 내가 뭔가 착각하고 있었거나, 작성한 코드에 결함이 있을 수도 있었다. 게다가 자기가 쓴 글은 틀린 게 잘 안보인다. 그래서 기술적인 내용에 관한 검증을 기선이에게 부탁했다. 기선이는 스프링에 관해서는 오랜 동안 깊이 연구했고 최신 버전의 기능에도 익숙한데다 꼼꼼하기까지 해서 제격이었다. 기선이는 자기 할 일로 바쁜데도 흔쾌히 응해줬다. 그리고 내가 리뷰 하는 내내 책의 내용을 빠짐없이 읽고 코드를 모두 테스트 해보고 기술적인 설명은 근거가 될만한 레퍼런스 문서나 API항목을 일일이 찾아보면서 검증하는 등의 수고를 해서 오탈자를 제외하고도 수십 개의 크고 작은 오류를 지적해줬다.

기선이가 없었다면 아마 내가 그런 작업을 직접 해야 했을 거고 적어도 한 두 달은 시간이 더 걸렸을 것이다. 단지 오류를 발견해서 알려주는 것이 전부가 아니라 모든 기술적인 설명에 근거를 일일이 찾아서 점검도 해야 했기 때문이다. 기선이는 최종 원고 수정을 마칠 때까지 꼬박 두 번에 걸쳐서 책을 샅샅이 읽고 점검해줬다. 중간중간에 책을 읽은 느낌이나 생각을 블로그에 적기도 했다. 아참. 그래서 하고 싶은 얘기는 테크니컬 리뷰어는 기선이니까 설명에 기술적인 오류가 있다면 기선이에게 항의를… ( ”)

주로 내용과 코드, 설명을 점검하는 1차 퇴고를 마치고 나니 두달이 조금 넘었다. 거기에 틈틈이 기선이가 체크해서 보내준 내용을 적용했고. 원래 계획은 한번 더 전체 내용을 읽으면서 문장을 다듬어보는 것이었지만 이미 에너지는 거의 바닥나 있었다. 초고를 마무리 하느라 전력질주를 한데다 리뷰과정 중에도 거의 매일 12시간 이상 눈이 빠지게 모니터만 들여다 보고 있었으니 남은 기운이 없었다. 그래서 1장과 10장 정도만 다시 조금 손을 본 뒤에 5월 20일에 책 원고를 출판사에 보낼 수 있었다. 맨날 답이 없는 유령 저자에게 저술진행 확인 메일을 보내느라 수고해왔던 황지영 과장님에게 드디어 원고를 보내드립니다라는 답장을 보냈다.

그리고 책의 탈고를 알리는 블로그 글을 썼다.

저술 계약한지 3년이 넘도록 한번도 진행중인 원고를 출판사에 보낸 적이 없었다. 그 때가 처음이자 마지막이었다. 과연 출판사는 뭘 믿고 경험도 없는데다 약속을 밥먹듯이 어기는 저자를 원고 확인도 없이 기다려 주었을까 궁금했다. 나라면 수시로 원고 쓴 거 달래서 확인을 해봤을텐데. 내 책은 이미 포기한 것은 아닐까라고도 생각했다.

한편으로는 원고를 보냈으니 편집을 마치고 내가 다시 리뷰할 때까지 푹 쉴 수 있다는 생각에 마음이 편하면서도 다른 한편으로는 편집과정이 얼마나 험난할까 하는 걱정도 들었다. 주위에선 "안드로이드 같은 최신 기술이 워낙 인기라 스프링 같은 비인기 책은 아마 편집 작업에도 밀려서 찬밥일 것이다" 또는 "편집당해서 내용을 왕창 들어내야 할 것이다"라고 겁을 주기도 했다. 그래서 긴장도 많이 됐다.

이유야 어쨌든 처음 약속과 달리 1000페이지가 넘어버렸는데 이걸 내가 먼저 줄여서 넘길까 하다가 김 부사장님 반응을 먼저 살펴봤는데, 1000페이지라고 해도 크게 놀라시는 눈치는 아니었다. 일단 보고 판단하자는 생각이셨을까. 그래서 나도 모르겠다는 심정으로 원고에 손을 대지 않고 일단 그대로 넘겼다.

출판사에서는 편집 디자인, 최종 리뷰, 인쇄, 제본 등에 최소한 한달 반 정도의 시간이 필요하다고 했다. 늦어도 7월 초면 책이 나온다고 했다.

과연?

 

이제 내일 마지막으로 편집과정과 최종 책이 나온 얘기를 하면 되겠구나. 이젠 즐거운 얘기만 남았네.

며칠전 스프링 3.0.4가 나왔다. 3.0.1 이후 마이너 업그레이드는 거의 자잘한 버그 수정이거나 최신 버전 서드-파티 툴 지원(확인)이 전부였는데 3.0.4에는 눈에 띄는 새로운 기능이 추가된 것이 있다. @MVC에서 세 가지를 찾아 볼 수 있다.  @ModelAttributge 모델이 아닌 단순 @RequestParam 등에서도 포매터를 사용하게 된 것과 mvc 스키마에 새롭게 추가된 전용 태그 두개이다.

3.0.4 내용을 모두 확인하고 추가된 기능 등을 토비의 스프링 3에 반영해서 다음 인쇄시 적용해 달라고 할 수도 있겠지만, 버전 업그레이드를 시도하는 것은 생각보다 부담이 큰 작업이다. 3.0.4만 해도 개발 스타일을 바꾸고 싶은 것이 있어서 예제도 손을 좀 봐야 하고. 그래서 일단 블로그에 그런 내용을 정리해보기로 했다.

 

3.0.4의 변경사항 중에서 그 중에서 가장 내 관심을 끈 것은 바로 <mvc:default-servlet-handler/>이다. 이를 이용하면 그동안 안쓰자니 불편하고 쓰자니 지저분해 보였던 UrlRewriteFilter를 제거할 수 있게 됐다. 그런데 UrlRewriteFilter 따위는 왜 쓰게 됐을까?

여러 가지 이유를 댈 수 있겠지만 가장 큰 것은 RESTful 스타일의 URL을 사용하고 싶기 때문이다. RESTful 스타일 URL의 특징의 하나는 확장자를 쓰지 않는 것이다.  기존엔 주로 컨트롤러에 매핑되는 URL을 login.do니 login.action이니 하는 식으로 특정 확장자를 주어서 만들었다. 그래서 스프링의 프론트 컨트롤러인 DispatcherServlet의 서블릿 매핑을 *.do나 *.action 처럼 주면 됐다. URL의 서브 디렉토리는 어떻게 잡아도 상관없어서 이 방식이 가장 편하긴 하다.

그런데 RESTful 스타일에선 이렇게 확장자를 잘 쓰지 않는다. 여러가지 스타일이 있지만 보통 리소스 이름을 경로로 쓰고 그 뒤에 의미있는 추가 경로를 붙이거나 HTTP요청 메소드(GET,POST,PUT 등등)를 바꿔서 활용하는 식이다. 그래서 URI가 보통 /user니 /user/1, /user/1/edit 이런식으로 만들어진다. 따라서 특정 확장자를 사용할 수 없다.

문제는 스프링이 이런 URL을 처리하게 하려면 웹 애플리케이션에 기본으로 등록된 다른 서블릿과 중복이 되지 않게 만들어줘야 한다. 보통 서블릿 컨테이너에는 스태틱 리소스(HTML, 자바 스크립트, CSS, 이미지 등)를 처리하는 디폴트 서블릿과 JSP를 처리하는 JSP 서블릿이 기본으로 등록되어있다. 디폴트 서블릿은 /에 매핑되어 있고 JSP는 확장자를 이용해서 *.jsp로 되어있다. 그리고 여기에 추가로 서블릿을 등록하면 URL 우선순위(매핑 조건이 긴게 우선)에 따라서 그 서블릿의 매핑에 해당하는 것은 등록된 것으로 가고 나머지는 디폴트 서블릿이나 JSP 서블릿이 담당한다. 스프링을 *.do와 같이 사용할 땐 그래서 문제가 없었다. 그런데 경로만 있는 URL로 가니 얘기가 달라졌다.

디폴트 서블릿을 안쓸 수는 없다. 따라서 스프링 서블릿(DispatcherServlet)을 /에 매핑할 수는 없다. 그러면 DispatcherServlet가 모든 스태틱 리소스까지 처리해줘야 한다. 그래서 보통 /app와 같은 서브폴더를 만들고 그 아래로 오는 요청을 스프링의 DispatcherServlet이 담당하게 만든다. DispatcherServlet의 매핑을 /app/나 /app/* 등으로 하고, URL은 /app/user, /app/user/1, /app/user/1/edit 이렇게 썼다.

그런데 app가 계속 붙으니 안이쁘다. 폼나게 RESTful하게 보이려면 도메인 이름 뒤에 바로 리소스 이름이 나오는게 좋다. 그냥 http://toby.epril.com/article/1 이런 식으로.

그래서 스프링 사용자들이 슬슬 사용하기 시작한게 UrlRewriteFilter다. 이 필터는 서버로 온 URL을 애플리케이션으로 넘길 때 강제로 변경해주는 역할을 한다.  UrlRewriteFilter은 서블릿의 매핑보다 세밀한 제어가 가능하기 때문에 원하는 방식으로 URL을 작성할 수가 있다. 일단 모든 스태틱 리소스는 /resource 등에 위치시킨다. 그리고 스프링 DispatcherServlet은 그냥 /app와 같은 서브 폴더에 매핑시킨다. 그런다음 UrlRewriteFilter를 적용한다.

스태틱 리소스인 /resource는 그냥 /resource로 그대로 가도록 한다. 대신 그 외의 /로 시작하는 경로는 앞에 /app를 붙이도록 만들어주는 것이다. 그러면 /resource로 시작하지 않는 모든 URL은 /app가 앞에 붙어서 넘어가므로 /app로 매핑을 해둔 DispatcherServlet처리하고 나머지는 그대로 URL이 유지되므로 디폴트 서블릿이 처리할 수 있는 것이다.

이렇게 해서 깔끔한 URL을 만들어 사용할 수 있긴한데, 제법 복잡한 UrlRewiteFilter를 공부해서 사용해야 한다는 번거로움이 있다. 그래서 책에도 UrlRewriteFilter를 사용하는 방법을 넣을까 하다가 독자들 머리만 더 아플까 해서 그냥 뺐다. 예제도 덜 이쁘지만 /app로 시작하는 URL을 그냥 사용하도록 했고. RESTful URL을 사용하는 ROO에서 사용한 UrlRewriteFilter를 보면 꽤나 복잡하다.

그런데 이런 불만이 나만 있던 것은 아니었나보다. UrlRewriteFilter를 쓰지 않고 스프링 DispatcherServlet을 /에 매핑해서 쓰면 안될까? 스프링에 안되는 게 어딨겠나. 결국 스프링 개발자들은 3.0.4에서 <mvc:default-servlet-handler/>를 추가해서 이 문제를 깔끔하게 해결해버렸다.

 

사용방법은 간단하다.

일단 DispatcherServlet을 그냥 /에 매핑한다. jsp와 같은 특정 확장자를 가진 URL말고는 모두 DispatcherServlet이 다 받는다. 일단 스프링의 기본 등록된 핸들러 매핑 전략을 이용해서 컨트롤러를 매핑해본다. @Controller가 담당하는 URL이라면 그리로 넘어갈거고. 그런데 그러다보면 /js/jquery.js 처럼 컨트롤러에 매핑안되는 URL이 나올 것이다. 이런 나머지 모든 URL은 <mvc:default-servlet-handler/>이 내부적으로 등록해주는 DefaultServletHttpRequestHandler이 담당한다. 이 핸들러(컨트롤러)는 /**로 매핑되어있다. 대신 핸들러 매핑 우선순위가 가장 낮다. 따라서 애노테이션 매핑 등등을 거쳐서 다 실패한 URL만 넘어온다. 그리고 DefaultServletHttpRequestHandler는 이 요청을 자신이 직접 스태틱 리소스를 읽어서 처리하는 것이 아니라, 원래 서버가 제공하는 디폴트 서블릿으로 넘겨버린다. 그러면 서버의 기본 디폴트 서블릿이 동작해서 스태틱리소스를 처리해버리는 것이다. 일단 스프링이 다 받고 스프링이 처리 못하는 건 다시 서버의 디폴트 서블릿으로 넘긴다는 아이디어이다. 멋지지 않은가?

그런데 사실 아이디어는 간단하지만 구현은 쉽지 않다. 디폴트 서블릿을 찾아서 사용하는 건 일반적인 접근방법이 아니다. 서블릿 스펙 2.0 부터는 하나의 서블릿이 다른 서블릿을 직접 찾아서 실행할 수 있는 방법을 막아버렸다. 그래서 ServletContext의 getServlets() 나 getServletNames()와 같은 메소드는 무조건 null을 리턴하도록 스펙에 명시되어있다. 대신 스프링은 URL 대신 이름을 이용해서 RequestDispatcher를 가져오는 getNamedDispatcher() 메소드를 이용한다. 물론 디폴트 서블릿 이름을 알고 있어야 하는데, 그건 스프링 개발자들이 주요 서버의 디폴트 서블릿 이름을 다 조사해놨다. 톰캣, 제티, JBoss, 글래스 피시는 이름이 default, Resin은 resin-file, WebLogic은 FileServlet, WebSphere는 SimpleFileServlet이다. 이 서버들의 디폴트 서블릿 이름은 자동으로 찾아준다. 그 외의 서버라면 디폴트 서블릿 이름을 직접 주면 된다. 보통 서버의 설정 폴더에 보면 디폴트 web.xml이 다 나와있다. 그걸 열어보면 디폴트 서블릿 이름을 알 수 있다. 톰캣이라면 conf 밑의 web.xml에서 찾을 수 있다.

톰캣 6에 보면 디폴트 서블릿이 다음과 같이 등록되어있다.

<servlet>
    <servlet-name>default</servlet-name>
    <servlet-class>org.apache.catalina.servlets.DefaultServlet</servlet-class>
     …
    <load-on-startup>1</load-on-startup>
</servlet>

<servlet-mapping>
    <servlet-name>default</servlet-name>
    <url-pattern>/</url-pattern>
</servlet-mapping>

 

이제 3.0.4를 사용하면 특별한 이유가 아니라면 UrlRewriteFilter를 사용할 필요가 없다. 혹시 스태틱 리소스의 경로를 다이나믹하게(버전 폴더를 바꾼다거나) 바꾸는 필요가 있다면, 그때는 3.0.4에 함께 추가된 <mvc:resource>를 사용하면 된다. 그건 다음 시간에.

참. GAE에서는 이 기능을 사용할 수 없다. 구글이 수정한 GAE의 Jetty 소스를 보면 이 getNamedDispatcher()가 항상 null을 리턴하도록 해놨다. 리소스 접근에 제약을 걸어야 하기 때문에 코드를 통해서 디폴트 서블릿에 접근하는 것을 원천적으로 차단한 것이다.

점점 지겨워지긴 하지만 마무리는 해야 하니까. 처음 이 글을 쓰기 시작했을 때 생각처럼 어떤 시간이었든 책을 써왔던 지난 시간을 한번 정리하고 나서 다 잊어야지. 차마 글로는 옮길 수 없는 유쾌하지 못했던 기억들이 더 많았던 지난 2년여간의 시간들. 어쨌든 정리는 하고 넘어가야 하니까.

 

한국에서 돌아온 뒤 거의 하루 15시간 이상 책을 쓰는 데 매달렸다. 일단 흐름을 타고 나니 1부를 마무리 하는 것은 쉬웠다. 스프링 핵심기술 소개는 6장에서 마무리했지만 거기서 만족할 수 없었다. 단지 스프링이 이렇게 OO와 DI를 비롯한 핵심가치를 자신에게 적용했다는 것을 이해하는데 만족하지 말고 그것이 스프링 사용자 자신의 코드에도 그대로 적용될 수 있기를 바랬다. 그래서 7장을 썼다. 내용은 엉성할지 모르겠지만 7장은 내가 이 책에서 가장 아끼는 장이다. 책을 통해서 내가 가장 하고 싶었던 이야기이기도 하고. 그리고 1부의 마무리로 처음 써놨던, 버리려고 했던 1장을 넣기로 했다. 물론 거의 새로 쓴 것이긴 하지만. 원래 가장 먼저 얘기하려고 했던 도대체 스프링이란 무엇인가라는 질문과 그에 대한 내 생각을 간략하게 정리했다. 1부 끝. 한국을 다녀와서 며칠 지나지 않아 1부를 마쳤다.

그리고 스프링의 기술을 주제별로 설명하는 2부 시작. 2부는 1부보다 훨씬 쉬울 것이라고 생각했다. 그저 스프링의 기술을 쭉 나열해가며 사용방법을 설명하고 평소에 생각해뒀던 선택 기준에 관한 내 의견을 넣으면 될 테니까. IoC/DI를 다루는 10장에서 이미 600페이지가 훌쩍 넘었다. 800페이지가 목표니까 이제 얼마 안남았다고 생각했다. 미리 준비해뒀던 학습 테스트 코드도 많은 도움이 됐다. 그리고 스프링 3.0도 RC버전이 출시되고 최종 릴리스가 눈 앞에 보였다.

이제 끝인가 보다 생각하면서 12월이 시작됐다. 그리고 처음으로 용기를 내서 김희정 부사장님께 먼저 연락을 했다. 늦어도 12월 말까지는 초고를 드릴 수 있을 것 같다고 말씀드렸다. 스프링은 예상과 달리 계속 지연되서 12월 중순에나 나오게 됐고 약속대로 스프링 출시에 맞춰서 책이 나올 수 있을 것 같다고 얘기했다. 물론 스프링이 늦게 나올 것이란 건 충분히 예측했던 일이었으니 다르긴 뭐가 달라. –_-; 죄송하니까 말이라도 그렇게 했어야지.

아내도 11월 말에 학기를 마쳤고 집안일을 전담할 수 있게 됐다. 나는 그저 12월 무더위와 싸우면서 미친듯이 마무리 해나가면 될 것 같았다. 하지만 예상치 못했던 일이 생겼다. 아내가 임신을 한 것이다.

결혼 후 거의 7년만에 평화를 가질 때는 병원의 도움을 받아야 했다. 쉽게 아이가 생기는 체질이 아니라고 했다. 아내는 계속 둘째를 원했다. 혼자는 외로우니까 친구처럼 지낼 형제가 있어야 한다고. 나도 둘째가 있으면 좋겠다고 생각은 했다. 하지만 쉽게 생길 것이라고 기대를 하지는 않았다. 아마 또 병원에 다녀야 하지 않을까 싶었다. 그런데 어느날 갑자기 둘째가 생겼다.

병원에 가보니 5주라고 했다. 한편으로 기뻤지만 다른 한편으로는 걱정이 시작됐다. 평화를 가졌을 때 지독하게 심한 입덧을 했던 것이 기억났다. 입덧이 없거나 가볍게 지나는 사람도 있지만 지독하게 심한 입덧을 경험하는 사람도 있다. 오죽 심하면 출산 때의 고통 때문이 아니라 입덧 때문에 아이를 가지는 것이 두렵다는 얘기를 할 정도다. 아내가 딱 그랬다.

입덧이 서서히 심해지면서 중병을 앓는 사람처럼 누워있어야만 있어야 했다. 물 조차도 마시기 힘들어 했다. 겨우 물 한모금을 마셔놓고 그보다 훨씬 더 많이 토하기도 했다. 평화를 가졌을 때 입덧하면서 자주 먹었던 누릉지를 만들어서 끓여주었지만 그것도 거의 입에 대기 힘들어했다. 평화 때보다 훨씬 빨리 입덧이 시작됐고 훨씬 더 심하게, 오래 진행됐다. 원래 둘째는 입덧이 덜하다는 얘기도 있는데 다 뻥이다. 평화 때처럼 친정에 가있을 수도 없었다. 그렇다고 누군가 와서 도와줄 사람도 없었다. 하루 종일 침대에 누워서 괴로워하다가 물 몇 모금, 묽게 만든 누릉지 조금 먹고 그리고 수시로 토하고. 냄새 때문에 방 밖으로 나오기도 힘들었다. 평화랑 놀아주는 것도 물론 불가능했다. 갑자기 모든 집안 일과 평화를 돌보는 것이 내 책임이 됐다. 겨우 짬을 내서 회사 일에 시간을 쓰고 나면 원고를 쓸 시간이 없었다.

아침에 일어나면 평화를 어린이집에 보낼 준비부터 해야 했다. 평화 점심 도시락을 만들고 간식과 준비물을 챙기고 평화를 깨워서 씻기고 아침을 먹이고 어린이집에 데려다 주고 왔다. 돌아오면 집안 청소와 설거지, 세탁을 하고 아내 상태에 따라서 먹을 음식을 준비해야 했다. 평화를 가졌을 때는 다른 음식에는 전혀 손을 대지 못했지만 누릉지 끓인 것은 겨우 겨우 먹었던 기억이 났다. 한국에서처럼 누릉지를 쉽게 구할 수 없으니 내가 직접 만들어야 했다. 밥을 후라이팬에 얇게 깔고 가장 약한 불에 1-2시간 두면 그럴싸한 누릉지가 만들어진다. 입덧이 심하면 맹물도 제대로 못삼킨다. 억지로 마셨다가 토하면 오히려 수분이 더 빠져나가서 좋지 않다. 그래서 누릉지를 끓어서 먹이려고 노력했다. 그것도 며칠 가지 못했다. 아예 하루 종일 물 몇 모금 말고는 아무것도 먹지 못하기 시작했다.

도저히 안되겠다 싶어서 병원에 가봤다. 의사는 상태가 안좋다고 구토억제제를 처방해줬다. 하지만 약을 먹어도 별 효과가 없었다. 입덧을 시작하고 입덧 시작하고 2주가 지났을때 몸무게가 5킬로나 빠져있었다. 동네 병원에서 검사를 해보더니 상태가 많이 안좋다고, 그대로 있으면 엄마도 태아도 위험하니 얼른 종합병원 응급실로 가라고 했다. 응급실 의사는 입원을 해야 할 수도 있다고 했다. 그나마 응급실에서 구토억제제 주사와 수액 두 팩을 맞고 나니 겨우 혈색이 돌아왔다. 수분이 공급되면서 상태가 호전되니 입원은 하지 않아도 괜찮겠다고 했다. 대신 보통 구토억제제로는 안될 것 같고 말기 암 환자나 수술한 환자들이 먹는 쎈 구토억제제를 먹으라고 했다. 그나마 그 약을 먹으면 잠시라도 역겨운 것이 덜해서 물이라도 조금씩 마실 수 있게 됐다.

평화도 당황했다. 항상 자기랑 놀아주고 안아주던 엄마가 하루 종일 침대에 누워있기만 한데다 엄마 곁에 가지 못하게 내가 자꾸 떼어 놓으니 속상했을 것이다. 나도 종일 집안일을 하거나 틈틈이 원고 몇 줄이라도 써야 했기에 평화랑 놀아줄 시간이 별로 없었다. 낮에는 어린이집에 보내고 돌아오면 좋아하는 비디오를 틀어주고 앉혀놓는 수밖에. 어린이집에 안가는 날은 하루 종일 비디오와 TV만 봤다. TV가 지겨워지면 나에게 와서 놀아달라고 칭얼거리며 손을 잡아 끌었다. 잠시 놀아주다가 괜찮은 것 같아 자리에 돌아와 원고를 좀 더 쓰고 있으면 어느새 다시 달려와서 나를 잡아 끌었다. 낮잠은 물론이고 밤에도 내가 평화를 재워야 했다. 운이 좋으면 같이 누워서 30분만에 잠들기도 하지만 보통 잠이 들려면 1-2시간은 걸렸다. 잠이 든 걸 확인하고 나와서 다시 아내를 돌보거나 집안일을 해야 했다.

집안일이라는게 정말 끝이 없었다. 아내는 본격적으로 구토억제제를 먹기 시작하면서 그나마 속에서 받아주는 시원한 수박이나 오렌지같은 과일 그리고 패들팝아라는 아이스크림을 계속 원했다. 매일 장을 봐와야 했다. 일주일에 3일은 평화를 어린이집에 보냈는데 그런 날은 좀 더 시간을 벌 수 있을 것 같지만 막상 도시락을 준비하고 짐을 챙겨 데려다 주고 데리고 오고 하는 시간을 고려해보면 어린이집에 가지 않는 날에 비해서 겨우 3-4시간 정도 시간을 벌 뿐이다. 아내 먹을 것을 준비하고 집안일을 하다보면 다시 평화를 데려와야 할 시간이 되는 경우도 흔했다.

막막했다. 도저히 책을 쓸 수 있는 시간이 없었다. 그나마 어린이집에 평화를 보내고 아내 필요한 것을 챙겨준 뒤에 잠깐, 그리고 평화를 재운 뒤에 늦은 밤 중에 잠깐. 그게 내가 낼 수 있는 시간의 전부였다. 운이 좋은 날은 4-5시간 어떤 날은 2-3시간씩 밖에 쓸 수 없었다. 고객사의 요구가 들어오면 한밤중에라도 일을 해야 했다. 2010년 계약을 다시 하기 위해서 분석자료를 준비하고 제안서를 쓰는 시간도 필요했다.

12월 말까지 원고를 마무리하겠다고 큰 소리쳐놨는데 12월 한달 내내 그나마 쓰던 장도 마무리를 할 수 없었다. 원래 계획은 12월 말에 초고를 넘기고 1월에는 마음 편하게 휴가를 떠나는 것이었다. 12월부터 1월은 호주의 최대 휴가철이다. 그때가 가장 더운 여름이니까. 하지만 계획은 다 무산됐다. 크리스마스고 연말연시고 그런 것도 없었다. 매일 매일 끝이 보이지 않는 막막함 뿐이었다. 아내는 끝도 없이 계속 되는 심한 입덧으로 매일 힘겨워 했고, 나는 다시 끝이 보이지 않는 책의 마무리에 괴로워 했다.

지금은 이렇게 담담히 글로 적지만 그때는 정말 견디기 힘들만큼 괴로웠다. 내 인생에서 그렇게 힘든 시간은 처음이었다. 도무지 끝날 것 같지 않은 막막함이 들 땐 그 상황을 벗어나는 길은 죽음 뿐이라는 생각도 들었다. 한 자도 못쓰고 종일 뛰어다니다가 걱정 속에서 잠이 들때도 많았다. 고통스러워 하는 아내를 보면서, 뭔가 변한 엄마 아빠 모습에 스트레스를 받는 평화를 보면서도 내가 아무 것도 해줄 수 있는 게 없다는 사실이 괴로웠다. 여유를 가지고 집중하기 힘들었지만 그래도 죽을 힘을 다해서 빨리 마무리 하려고 노력했다. 겨우 겨우 1월 말에 11장을 끝냈다. 처음 생각대로라면 12월에 한 주면 다 썼을 것인데 무려 두달이나 걸렸다. 12월 말에는 초고를 넘긴다고 큰 소리 처놨는데 또 연락이 없으니 김 부사장님은 얼마나 더 실망했을까하는 걱정도 들었다.

그런데 11장을 끝낼 즈음에 내가 큰 착각을 하고 있다는 것을 깨달았다. 약속한 페이지를 맞추기 위해너 나름 재밌고 깊이 있는 내용은 다 빼고 꼭 필요하다고 생각되는 중요한 내용만 고르고 골라서 넣었는데도 11장을 끝냈을 때 이미 페이지가 750이었다. 아직 2부에서 가장 중요하다고 생각하는  MVC/@MVC와 Aspect/AOP, Test 기타 기술 등등이 남았는데 남은 페이지는 겨우 50. 그동안 계속 800을 채우면 끝이다라고 생각하면서 썼는데, 사실은 분량조절을 잘못해왔다는 것을 그제야 깨달았다. 스프링 기술의 선택가능한 옵션을 빠짐없이 소개하고 간단한 선택 가이드를 제공해 주면 되는 2부도 생각보다 쓸게 많다는 것을 그제야 깨달았다.

이미 여러번 800페이지 정도만 쓰겠다고 장담을 했는데 이제와서 분량을 더 늘리겠다고 할 수도 없었다. 그렇다고 이미 써논 것을 다시 편집해서 내용을 줄이자니 그것도 막막했다. 처음부터 끝까지 하나의 코드가 계속 연결되는 1부는 어떻게 건드릴 자신이 없었다. 그래서 일단 그냥 되는데까지 쓰기로 작정했다. 책을 다 쓰고 800페이지를 넘어가는 내용은 빼서 PDF로 공개하든지 그냥 버리든지 할 생각이었다. 아깝지만 그나마 생략해도 어색해보이지는 않는 7장과 8장은 빼버릴까도 생각했다. 적절하게 주제별로 비율을 따져보고 미리 페이지 분배를 꼼꼼하게 하지 않고 써논 것이 잘못이었다.

한숨이 나왔지만 어쩌겠는가 계속 써야지. 그렇다고 MVC/@MVC를 대충 쓸 마음은 전혀 없었다. 그리고 여전히 칭얼대는 평화와 입덧하는 아내를 돌보며 집안일에 매달려야 했지만 마지막이라고 생각하고 죽을 힘을 다해서 12장,13장을 써내려갔다. 그리고 단 20일만에 당시 편집하던 워드 파일로 210페이지(실제 책에서는 270페이쯤 되는 것 같다)에 달하는 MVC/@MVC를 다 써버렸다. 테스트 코드는 이전보다 더 많이 만들었고. 초기에 학습 테스트를 만들 때는 웹 기술의 테스트를 만드는 것이 너무 어려워서 생략을 했다. 그래서 12장을 쓰면서야 겨우 MVC 테스트를 하기 위한 간단한 지원 도구를 만들었다. 그리고 방대한 스프링MVC 내용을 하나씩 다 테스트 해가면서 넣었다. 기존 MVC에 비해 기능은 방대하고 내부 동작 방식도 복잡한 @MVC는 문서가 너무 빈약했다. 레퍼런스나 API문서만 가지고는 보이지 않는 프레임워크 내에서 일어나는 일과 규칙을 다 파악할 수 없었다. 그래서 스프링 @MVC 소스 코드를 모두 분석해야만 했다. 그렇게 분석한 내용을 가지고 가정을 세우고 그것을 2-3중의 테스트로 검증하고난 뒤에야 겨우 그 내용을 추가하는 식으로 써 나갔다. 문서와 소스를 분석하고 테스트를 만들어 검증하는 시간을 빼면 거의 하루에 20페이지씩 쓴 것 같다. 지금 다시 돌아봐도 어떻게 그 여유도 없는 상황에서 그 많은 내용을 다 썼는지 상상이 되지 않는다. 더 이상은 물러날 곳이 없다는 절박함 때문에 초인적인 힘이 났던 것일까.

그리고 14,15,16장을 열흘만에 다 써버렸다.  그렇게 해서 3월 4일 초고를 끝냈다.

마지막 장의 정리 절을 쓰고 나니 1000페이지가 조금 넘었다. 저장 버튼을 누르는 데 눈물이 났다. 지난 2년 넘는 시간동안, 특별히 지난 3개월의 끔찍했던 시간이 떠오르면서 이제야 살았다는 생각이 들었다. 아직 원고를 리뷰하고 교정하는 시간이 남았지만, 그래도 일단 마무리 했다는 것에 안도할 수 있었다. 정말 기분이 좋았다. 그 뒤로 리뷰를 마치고 초고를 넘겼을 때, 출판사 편집을 거쳐서 최종 원고 수정을 마쳤을 때, 책이 인쇄되서 나왔다는 얘기를 들었을 때, 그리고 책을 받았을 때도 기쁘긴 했지만 처음 원고를 마쳤던 그날만큼은 아니었다.

 

출판사에서 특급우편으로 보내준 내 책이 며칠전에야 도착했다. 책을 받아서 가장 먼저 아내에게 건내주었다. 나랑 함께 그 힘든 시기를 보내왔던, 답답하게 오랜시간 책을 쓰고 있는 남편을 보면서 한번도 잔소리도 한 적이 없는 착한 아내. 책을 살펴보라고 건네주고 잠시 방에 들어왔다 다시 나와보니 아내가 울고 있었다. 힘들었던 그때가 생각나서, 그래서 더 감격스러워서 우는 것이라고. 아내도 그때 참 많이 힘들었던 것 같다.

 

지금까지 별 시덥지 않은 얘기와 투덜거림으로 긴 글을 써온 것은 사실 오늘 이 얘기를 적고 싶어서였다. 그때는 이런 글을 쓸 수가 없었다. 책을 마무리 하지 못한 나는 죄인이었다. 맘 편하게 내가 즐기고 싶은 기술을 살펴보는 것도 용납하기 힘들었고, 내가 다 잘못해서 책을 완료하지 못한 주제에 힘든다는 말을 하는 것도 비겁해 보였다. 그래서 힘들어도 힘들다고 내놓고 말을 할 수가 없었다.

하지만 이제는 괜찮겠지. 다 지났으니까. 긴장했던 시간들, 짜증났던 시간들, 가끔 재미와 행복을 느꼈던 시간들, 그리고 견디기 힘들만큼 힘들었던 시간들.. 그 과정을 거쳐야 책이 하나 탄생하는 것인가. 나만 그런거겠지.

2010년 1월 1일. 새해 첫날이었지만 별 다른 것은 없었다. 여전히 괴로워 하며 끙끙 앓는 아내와 바뀐 환경에서 힘들어하던 평화, 그리고 잠시라도 짬이 나면 크리스마스든 새해 첫날이든 상관없이 자리에 앉아서 DI가 어떻고 @Inject가 어떻고 하는 얘기를 써나가야 했던 나. 그냥 달력을 보면서 2010년인가보다, 여전히 책을 끝내지 못한 책로 2010년을 맞았구나 하는 생각을 했다.

그리고 일년에 겨우 한 두 번쓰는 일기를 썼다. 차마 블로그나 공개된 곳에는 쓸 수 없었던 나 혼자만의 이야기를 가끔 적는다. 거기에 2010년 새해 목표를 적었다. 살아남기. 올해목표는 일단 성공한 듯. 그 일부를 여기다 옮겨본다. 이제는 얘기해도 되니까.

—————————–

1 Jan 2010

힘겹게 시작한 2010년. 새해가 시작한다는 느낌도 없다. 아니 느낄 조그마한 여유도 없다. 입덧하는 은성. 거의 폐인이나 다름없이 온종일 누워서 앓기만 한다. 엄마의 변화 때문에 힘겨워하는 평화. 전에 안하던 짓들을 슬슬 한다. 손에 잡히지 않는 일. 시간도 돈도 여유가 없는데 왜 이렇게 몸이 더딜까. 그나마 는 것은 집안 일 실력 뿐.

평화가 말을 듣지 않는 것 때문에 미친 듯이 흥분하는 내 모습을 보면서 아빠로서 기본적인 자격도 없는 것이 아닐까 하는 괴로움.

막막함.

그래도 절망하거나 힘겨워 하지 않고, 그래도 하루 하루 산다. 오늘 못하면 내일 하고, 오늘 조금 잘못했으면 내일 조금 덜 잘못하려고 하고, 그렇게 포기하지 않고 산다. 이제 내 목표는 어떤 멋진 꿈이 아니라, 포기하지 않고 살아남는 것. 이 삶을 살아 내는 것이다. 살아남자.

휴. 힘을 내자.

아자 아자.

내가 잘하는 한가지.

포기하지 않는 것.

지저분하게라도, 서글프게라도, 꿋꿋하게 사는 것.

2010. 시작이다.

© 2017 Toby's Epril Suffusion theme by Sayontan Sinha