잊지 않겠습니다.

* 사내 강의용으로 사용한 자료를 Blog에 공유합니다. Spring을 이용한 Web 개발에 대한 전반적인 내용에 대해서 다루고 있습니다.


개발을 할때는 환경이 매우 중요합니다. 개발 환경을 팀의 관점으로 볼때는 단순히 업무 흐름만을 보는것은 너무 옛날 이야기가 되었습니다. 
이제 팀 단위의 개발환경에서 거의 필수가 된 항목들은 다음과 같습니다.

  1. 코드 관리 시스템
  2. 이슈 관리 시스템
  3. 자동 빌드 시스템
  4. Code Review 시스템
  5. 자동 배포 시스템
  6. 로컬 개발 서버(DB 포함) - 개발자 PC
  7. 테스트 서버 (DB 포함)
  8. 배포 서버 (DB 포함)

각 시스템이 유동적으로 연결되어 하나의 프로젝트가 완성이 되는 것이 일반적입니다.
먼저, 간단한 예를 들어보기로 하겠습니다. 여기 신입 개발자 A군과 고참 개발자 B양이 있습니다.



아침

  • B양은 이슈 관리 시스템을 통해서 자신의 팀에 주어진 일들을 확인합니다. 그리고 간단한 팀 미팅을 통해 서로간에 지금 어제 어떤 일을 했고, 오늘 어떤 일을 할 것이며, 오늘 이슈 사항이 무엇이 있는지를 파악합니다. 파악한 후, 추가로 들어온 일이 있는 경우에 그 사항을 이슈 관리시스템이 등록 또는 수정을 하고 담당자를 A군으로 지정합니다.
  • A군은 이슈 관리 시스템을 통해서 자신에게 주어진 일을 확인합니다. 그리고, 이미 분석된 업무이고, 바로 개발을 들어가게 됩니다. 먼저 코드 관리 시스템에서 소스를 최신의 것으로 Update 받습니다. 받은 Source의 History를 파악하고, DB schema의 변경사항이 있는 경우, 자신의 개발 환경에 반영을 하고 코딩을 시작합니다.

점심

  • A군은 코딩을 열심히 해서 일을 마쳤습니다. 그리고, 소스를 코드관리 시스템에 commit을 진행합니다. 소스관리 시스템은 commit된 코드가 있음을 관리자에게 알립니다.
  • B양은 A군의 코딩 내용을 보고 Code Review를 진행합니다. 맘에 안들면 불러다가, 또는 email로 깨고, reject 시킵니다. 그렇게 되면 이 코드는 반영되지 않게 됩니다.
  • A군은 다시 코딩을 합니다. 지적 받은 사항에 대해서 다시 고치고, 다시 commit을 합니다.
  • B양은 A군의 코드를 review하면서 흐뭇해하면서 코드를 accept합니다. accept된 코드는 이제 자동 빌드 시스템으로 넘어갑니다.
  • A군은 자신이 한 일에 대해서 이슈 관리 시스템에 일의 진행정도를 알리고, 완료를 시킵니다. 물론 약간의 문서 작업도 진행합니다.
  • 자동빌드 시스템은 열심히 코드를 build하고, 자동화된 test code를 모두 실행하고 그 결과를 팀원 전체에게 email로 알립니다.
  • test code가 모두 완료되면 build한 결과를 테스트 서버에 배포하게 됩니다. 이제 테스트 서버를 통해서 QC 팀들이 개발팀의 결과를 검증하게 됩니다.
  • QC 팀이 검증을 모두 마쳤습니다. 자동 배포 시스템을 통해서 실제 동작하는 서비스로 배포하게 됩니다. (모두들 수고하셨습니다.)

이러한 과정을 거치게 되는 것이 일반적인 개발회사에서의 흐름입니다. 그리고 계속되는 통합 과정을 통해, 코드의 품질을 계속해서 높이는 과정을 하게 됩니다. 그러나... 불행히도 저희 회사는 저기 위에 있는 시스템중 반도 존재하지 않습니다.

실질적인 예가 아닌, 이제 좀더 본격적인 설명에 들어가보도록 하겠습니다.


코드 관리 시스템

주로 사용되는 것은 svn과 요즘 인기를 끌고 있는 git가 있습니다. 기능으로는 code의 중앙 저장소 및 source 변경에 대한 history 관리 기능과 sync, merge 등이 있습니다. 
저희가 자주 사용하고 있지만, 조금은 다르게 사용하고 있는것 같긴 합니다. 주 기능은 code의 저장입니다. 그렇지만, 그 이상으로 중요하다고 생각되는 기능은 code의 history입니다. 어느날 갑자기 code를 받아보니 변경사항이 있었고, 그 내용이 왜, 그리고 무엇이 변경된것인지 모른다면 예전에 code를 usb같은것으로 카피해서 주는것과 차이가 없습니다. 코드를 변경했으면, 그 code를 왜 변경했는지에 대한 log를 적어주는 것이 이 시스템을 보다 더 잘 쓰는 방법입니다.

이슈 관리 시스템

많은 이슈 관리 시스템이 존재합니다. 대표적인것으로 무료로 배포되는 것들은 trac, jtrac, redmine, bugzillar 등이 있으며 상용은 jira가 있습니다. 상용으로 판매되는 jira는 정말 좋습니다. 개인적으론 구매를 해서 사용해보고 싶은 욕심이 무척 많이 듭니다. ㅠㅠ
코드 관리시스템이 코드에 대한 중앙 집중형 관리라면, 이슈 관리 시스템은 왜 그 코드를 작성하게 되었는지. 우리가 프로젝트를 완료하기 위해서는 앞으로 어떤 일들을 해야지 되는지. 그리고 지금까지 우리가 어떤 일을 해왔는지에 대한 "일, 작업, Issue, Task"에 대한 기록을 남기는 장소입니다. 이슈 관리 시스템은 코드 관리 시스템과 연동되어 코드의 변경사항이 어떤 이슈와 관련이 있는지까지 같이 보여줄 수 있습니다. 또한, 대부분의 코드 관리 시스템은 wiki라는 문서 작업 공간을 같이 제공합니다. wiki를 통해 서로간에 공유할 수 있는 내용을 기록하는 것이 가능합니다.

++ 자동 빌드 시스템, 자동 배포 시스템
이 두개의 시스템은 거의 하나로 만들어져서 사용됩니다. 이를 CI 시스템이라고 하며, hudson, jenkins와 jetBrains 사의 TeamCity가 유명합니다. CI가 CI 가 하는 일은 소스 관리 시스템과 연결되어 commit된 소스가 있는 경우, 자신이 다운받아 build 후, test code를 실행하고 그 결과를 report로 남기는 일을 반복합니다.또한 build 결과 또는 수동으로 실서버 또는 개발서버에 배포를 할 수 있기때문에 신속하고 중지 되지 않는 통합을 가능하게 하고 있습니다.





CI에 대하여 조금 더 알아보도록 하겠습니다.

다음은 CI의 기본 동작 Process를 나타내고 있습니다.

  1. Commit source code : 개발자가 자신의 code 를 commit 한다
  2. Unit Test : CI가 코드에 포함된 Unit Test code를 실행한다.
  3. CPM ( Continiuous Performance Management) : Unit Test 결과를 기록하고, Unit test의 실행 시간등을 기록한다.
  4. License check : 만약 코드 안에 상용 library등이 포함되어 있다면, license에 대한 check를 진행한다.
  5. Build : Build 및 추가 파일 복사 작업 진행
  6. Deploy : Test 환경으로의 배포 ( > QI 영역)









Code Review System

약간은 옵션적인 시스템이지만, 많은 기업들이 채용하고 있는중인 Code Review System입니다. Code Review란 간단히 사용자의 자신의 Code를 코드 관리 시스템에 Commit을 할때, 중간 단계를 하나 거치게 하는 것입니다. 그 중간단계에서 상임 개발자들이 그 코드를 평가하고, 원활히 변경된 경우에 그 코드를 코드 관리 시스템에 반영하는 절차를 추가한 시스템입니다. CodeCollaboration 이 대표적 시스템입니다.

++ 개발자 개발 환경의 구축

개발자들은 자신의 로컬 개발 환경을 반드시 갖춰야지 됩니다. 자신의 PC 자체가 원 운영 시스템과 최대한 동일할 필요가 있습니다. 이와 같은 시스템을 구축하는데 가장 큰 문제가 될 수 있는 것은 다음 방법들이 있습니다.

  1. DB
  2. OS
  3. Servlet Container (Web Server)

먼저, DB는 Project에 따라 다른 DB를 사용하게 되는 경우가 많습니다. 그럼 그 DB를 모두다 설치를 해야지 되는가. 에 대한 의문이 나오게 됩니다. 일단, 자신의 PC 상황이 최대한 좋다면.. 모든 DB가 다 설치가 되어있는 것이 좋겠지요. 그렇지 않다면 mysql이나 hsqldb가 해답이 될 수 있습니다. 그럼 db에 따라 다른 항목들은 어떻게 처리하느냐라는 문제가 남게 됩니다. 이 문제는 다음과 같이 처리합니다.

DB에 종속적인 개발을 하지 않는다. 가 우선 해결책이 될 수 있습니다.

만약에 DB에 종속적인 query를 만들어내고, SP를 실행해야지 되는 경우가 오게되면, local 개발 환경에 그 db를 반드시 설치해야지 됩니다. 개발자가 개발 환경 구축에 시간을 사용하고, 최대한 개발환경과 동일하게 구축하는 것은 당연한 요구사항입니다. 그렇지만, 단순히 crud만을 하는데, 내부에서 trim, substring 등의 함수를 이용해서 날짜 및 숫자를 뽑아내서 db에서 계산을 해서 넘어와야지 된다면, 처음 db 설계가 잘못된 것이 아닌가 생각을 해보는 것이 좋습니다.

다음은 OS입니다. 개발환경이 대체적으로 windows로 구성이 되는 반면에 서버측은 linux환경이 되는 경우가 대다수입니다. 여기서 가장 문제가 되는 것은 파일 구조입니다. 그 이유는 큰 이유는 로그 파일과 각 설정 파일의 절대적 위치때문입니다. 이 부분은 maven이나 ant를 이용한 build system으로 해결하는 것이 좋습니다. local 환경, 테스트 환경, 운영 환경에서 각각 다른 설정파일을 사용하도록 각각의 profile을 이용해서 설정하는 방법으로 해결할 수 있습니다.

다음으로 Servlet Container입니다. java는 상용 WAS들은 모두 tomcat의 servlet interface를 따르고 있습니다. 개발환경에서는 큰 부하를 견딜 필요가 없기 때문에 tomcat 또는 glass fish를 설치하는 것이 좋습니다. eclipse에 기본으로 추가 되어있는 tomcat이 가장 좋은 선택입니다. 
.net의 경우에는 윈도우즈를 사용하는 경우에는 IIS가 모두 기본으로 들어가 있기 때문에 별다른 문제가 없습니다.

일단 최대한 개인 PC와 개발 환경은 일치시키는 것이 중요합니다. 그렇지만, 현실적인 다른 문제가 발생하기도 하는데. 다음과 같습니다.


Step 01. 시작입니다. 간편합니다.




Step 02. 뭐. 이정도 즈음이야..

Step 03. 아직까지는 견딜 수 있어.

Step 04. 이제는 버틸수가 없어... 난장판이군요. ㅠㅠ

가상화 서버 또는 개발 서버를 통해서 해결을 하지만, 그 정도까지 큰 프로젝트는 아직까지는 아닙니다. 대부분의 프로젝트는 Step 03 정도면 개발이 가능합니다.


Posted by Y2K
,

Java를 1년정도 공부해보면서 이젠 슬슬 내용을 blog에 공개를 해도 될 것 같아서 공개를 해봅니다. 부끄러운 내용이지만, 도움이 될 수 있으면 좋겠습니다.


웹기술은 standalone에서 부터 시작해서, 2 tier, 3 tier, n-tier로 발전하게 되었고, 그에 따른 기술 역시 계속해서 발전해나간것을 알 수 있습니다.



standalone 시기

HTML이 처음 나오던 시기입니다. HTML에 대한 기술 습득과 http를 서비스할 수 있는 web server에 대한 기술이 발전했습니다. 지금은 누구나 하는 html 수정이 굉장한 기술이 되던 시기였습니다. 오히려 기술적으로는 web server를 직접 개발해서 서비스를 하는 문제가 더욱더 큰 이슈를 가지고 왔습니다. 그리고, 이때 개발의 방향을 바꾼 vm 기술이 나오기 시작했습니다. 전에 이야기한 .net과 java 기술이였지요. 이 두 기술 역시 처음부터 web에 집중한것은 아닙니다. 이때는 주로 사용되는 web server들이 Apache httpd 가 주로 사용이 되었고, static resource를 이용하는 형태가 주가 되었기 때문에 HTML coder 또는 web server 기술, 두 기술만이 발전하고 있었습니다. 
이때 기술의 승자는 apache 재단에서 시작한 httpd와 IIS 입니다. Http daemon 의 약자인 httpd는 아직도 Apache 서버라는 이름으로 불리우고 있습니다. (정식이름은 Apache Httpd 서버입니다.) 또한 MS의 IIS 역시 시장에서 많이 사용되는 Platform으로 사랑받기 시작했지요.


2 tier 시기

Web 기술이 폭발적으로 발전하던 시기입니다. RDBMS의 발전으로 인하여 DB의 내용에 따른 dynamic web이 발전하게 됩니다. 이때, dynamic web기술의 어려움에 의하여 여러 변형 기술들이 계속해서 나오게 됩니다. 큰 기술 흐름만을 보면 다음과 같습니다.


asp의 폭발적인 발전

기존 개발자들의 70%정도를 차지하고 있던 visual basic 개발자들을 모두 web 개발자로 만들어주는 asp와 iis가 처음 선을 보이게 됩니다. 기존 visual basic 개발자들은 모두 db와 db를 표현하는 방식에 대해서 경험이 풍부한 사람들이였습니다. 그 개발자들을 모두 web 개발자로 만드는 엄청난 일을 MS가 성공하게 됩니다. 지금보면 참 문제가 많은 개발방법으로 개발하게 되지만, 그 당시에는 최고의 기술이였으며 누구나 쉽게 웹 프로그래밍에 접근할 수 있다는 점이 가장 큰 장점이였습니다.


php의 발전 및 apache httpd의 확장

기존 standalone 서버에서 주로 사용되던 apache httpd를 이용한 dynamic web programming이 대두되게 됩니다. php는 asp의 장점을 흡수하고, 보다 쉬운 표현 방법을 제시합니다. 그리고 LAMP 라는 환경을 제시하기 때문에, 기업은 새로운 x86 windows server를 구매하는 것이 아닌 공짜로 web을 서비스할 수 있다는 점에서 기존 asp 시장을 잠식하기 시작합니다. 또한 linux 서버환경의 폭발적인 증가로 인하여 서버 환경이 기존 UNIX에서 linux 계열로 발전하게 된 계기가 됩니다.


servlet과 asp .net의 발전

asp와 php로 인하여 dynamic web application이 엄청난 발전을 하게 됩니다. 하지만 asp의 경우 언어의 특성상 큰 약점을 가지고 있습니다. page단위로 동작하게 되기 때문에, 현대적 프로그래밍 기법인 OOP를 사용할 수 없습니다. (방법은 있으나, 매우 괴악한 방법입니다.;) 점점 application이 거대화되어가면서 코드의 재 사용성 및 유지보수의 약점을 극복하기 위해서 asp와 php에 가려서 잠시 존재감을 잊어가고 있던 java와 .net이 다시 등장하게 됩니다.
두 언어는 RDBMS를 표현하는데 있어서 최선(?)의 방식을 가지고 있었으며, vm 기술의 발전으로 memory 및 performance에 강점을 가지고 있었기 때문에, 기존의 web 개발 시장을 빠르게 잠식해나가기 시작합니다.


fat client의 발전

servlet과 asp .net의 발전은 RDBMS와 dynamic web application간의 여러 문제를 모두 해결해준것같지만 사용자들에게 다양한 경험을 보여주기 위한 View를 제공하는데에 있어서, 기존의 HTML만은 한계를 가지고 있었다. 동영상과 같은 멀티미디어를 포함하기에는 기존의 HTML이 지원하지 않는 부분이 너무나 많은 영역들이 있었기 때문입니다. 멀티미디어에 대한 지원을 강화하기 위해서 나온 첫 기술이 micromedia의 Flash입니다. Flash는 멀티미디어의 지원뿐 아니라, 웹을 보다 더 아름답게 만드는데 큰 공헌을 하게 됩니다. Flash의 대성공으로 MS는 asp로 마련했던 서버 시장에 심각한 타격을 입게 됩니다. 그래서 자사의 IIS + .NET Framework 기술로 동작하는 silverlight를 출시하기에 이릅니다. 이러한 멀티 미디어의 지원과 더불어, RDBMS에 특화된 fat client들이 나오게 되는데, PowerBuilder가 바로 그것입니다. 
fat client의 발전은 기존 servlet과 asp .net 기술과 충돌을 일으키게 됩니다. 단순히 servlet과 asp .net을 fat client의 container로 이용하게 되고 모든 로직을 fat client에서 처리하게 되는 개발 방법이 한 때 유행하게 됩니다.


3 tier, n tier 시기

2 tier 가 발전한 형태인 3 tier에서는 다양한 요구사항이 생기게 됩니다. 기존 2 tier system의 확장을 통한 여러 시스템간의 결합으로 최종적으로는 enterprise system으로의 진화, 발전이 바로 그것입니다. 기존 2 tier system에서는 해결할 수 없는 기술적 이슈들이 발생하게 됩니다. 다양한 시스템들이 나오게 되고 그 시스템들간의 상호 통신에 의하여 기존 2 tier 에서 n tier로 계속되는 발전이 이루어지게 됩니다. 이 때는 다음과 같은 기술들이 발전하기 시작합니다.

script language의 발전

기존 .net과 java와는 다른 언어들이 발전하게 됩니다. 기존의 asp와 php가 OOP적 성격을 갖지 못한 단점을 해결하고 OOP적 장점과 개발의 편의성을 극대화한 script language가 발전하게 됩니다. python, ruby가 대두되기 시작하지요. 이 언어들은 기존의 객체 지향적인 특징과 asp, php와 같은 script 적 성격을 모두 갖게 됩니다. 개발의 속도, 변화 가능성에 대한 열린 대응을 토대로 이와 같은 script language가 계속된 발전을 하게 됩니다. 해외의 많은 시스템들이 하부 tier의 경우에는 ruby, python으로 개발된 것을 지금도 자주 볼 수 있습니다.

web service의 개발

n tier system의 발전은 web service가 없었다면 불가능하다고 할 정도로 web service는 n tier system에 깊이 관여되어 있습니다. web service는 기존의 tier를 종적으로나 횡적으로 모두 확장을 시키는 가장 결정적인 역활을 하게 되는 계기가 되었습니다. "Service로서의 Web"에 대한 개념은 수많은 파생적 개념을 만들어 내고, 기술적 발전을 가지고 왔습니다. 기존에는 SOAP을 기반으로 한 web service가 주로 사용되었으나, 지금은 client(javascript) 등에서의 호출 문제로 인하여 xmlrpc의 경량화된 버젼인 REST를 주로 사용하고 있습니다. 이 REST에 대해서는 다시 한번 설명할 기회를 갖도록 하겠습니다.

MVC의 발전

기존의 웹의 개발은 단순히 DB의 결과값을 웹에 표현하는 방식이였습니다. 대부분의 Business Logic은 DB에서 가지고 있고, 그 Business Logic을 web에 표현하는 방식이 대부분이였지요. 이렇게 된 가장 큰 이유중 하나는 웹으로 개발을 한 내용은 웹에 너무나 밀착되어있는 프로그램이라서, Business Logic만의 테스트가 거의 불가능하다는 것에서 시작되었습니다. 그렇지만, n tier system으로 발전해나가면서 tier만의 중복된 Business Logic을 정리 및 관리하는데 있어서 기존 프로그래밍 언어로 하고자 하는 욕구가 계속해서 발전되어 갑니다. SP에 대한 Handling과 계속되는 개발은 필연적인 중복코드와 로직의 누수가 발생하기 마련이니까요.

이때, ruby를 기반으로 한 ruby on rails가 발표됩니다. rails framework라고도 불리우는 이 framework는 기존의 web 개발 방법을 완전히 바꿔놓게 됩니다. 사용자에게 보여지는 영역인 View, Http response/request를 처리하는 Controller 마지막으로 Domain의 Business Logic을 처리하는 Model로 완벽한 영역을 분리할 수 있음을 ruby on rails는 보여주게 됩니다. 이러한 장점은 각 영역을 개발자들이 테스트를 해볼 수 있고, 영역에 대한 전문화를 분리시킬 수 있기 때문에 단숨에 웹 개발의 주도적 방법으로 대두되었습니다. java에서는 struct2가, .net에서는 asp .net mvc가 기존 ruby on rails의 사상을 반영한 MVC web framework입니다.

개발자는 지금 MVC를 중요하게 봐야지 됩니다. MVC 개발 방법은 웹뿐 아니라, 모든 application에 적용되어 있는 상황이고 각 영역에 대한 테스트를 통해서 자신의 코드의 완벽성을 스스로 검증할 수 있는 기회를 가지게 되었습니다. 에러에 대한 명확한 정의가 가능하게 되었으며, 에러가 발생했을때의 장애 처리와 같은 새로운 프로세스 정립 역시 MVC의 확립으로 가능하게 되었습니다. 이 부분은 지금까지는 개발자가 아닌, 기획이나 의사결정자들의 손에 있던 부분이였지만, 지금은 개발자들이 제시하는 방법을 선택하는 방향으로 전환이 된 상태입니다.

mobile 기기의 발전 및 확산

기존 n tier system의 발전은 획적 확장에 해당된다면 mobile 기기의 발전 및 확산은 종적 확장에 해당됩니다.기존까지 있던 web application의 대상은 모두 PC의 browser를 대상으로 하고 있었습니다. PC를 기반으로 하고 있기 때문에 PC의 특정 browser만을 target으로 하고 개발이 가능해졌지요. 그렇지만, iPhone을 시작으로 한 mobile 기기의 발전은 이러한 생각을 모두 바꾸어놓게됩니다. 누구나 가지고 다니는 mobile 기기는 언제 어디에서나 접근이 가능한 특징을 가지고 있습니다. 이는 기존보다 많은 접속을 만들어 내고, 모든 device에서 동일하게 보여야지 된다는 문제를 가지고 오게 됩니다. 그리고, mobile device의 빈약한 시스템 자원과 기존 windows system과 다른 OS 환경으로 인하여 HTML의 표준화에 대한 요구 사항이 높아지게 됩니다.

fat client의 쇠퇴와 HTML5의 대두

기존 flash와 silverlight와 같은 fat client가 mobile device에서 정상적으로 동작하지 않는 문제가 발생하게 됩니다. 이는 기존 PC에서도 windows-IE 환경 이외에서도 계속 지적되던 문제였지만, 본격적인 문제로서 국내에서는 대두되기 시작한 것은 바로 mobile 기기의 확산때문입니다. 기존 HTML에서 지원되지 않던 multimedia에 대한 지원을 비롯하여 animation과 websocket등의 표준화로 인하여 기존 flash와 silverlight가 설 자리가 없어지기 시작합니다. 지금 fat client는 flash만을 제외하고 거의 사장되어가는 분위기입니다. Flash의 경우에도 HTML5의 확산 전까지 잠시의 대체제로서의 의미 이외에는 퇴색해가는 것이 현실입니다. 기존 Flex 개발자들이 설 자리가 많이 없어지고 있지요.

Big data와 Cloud 의 대두

끊임없이 이야기가 나오고 있는 Big data와 Cloud는 mobile 기기의 발전 및 확산과 기존 web system의 오랜 발전으로 인하여 나온 기술이라고 할 수 있습니다. 기존의 RDBMS에서는 처리를 할 수 없을 정도의 데이터가 이제는 수집이 된 상태이고, 이 데이터들을 어떻게 분석을 해야지 될지. 이 데이터들을 어떻게 활용을 해야지 될지. 그리고 많은 mobile 기기에서 동시 다발적으로 들어오는 데이터를 어떻게 해야지 될지에 대한 물음에서 Big Data와 Cloud를 활용하는 방법을 찾아보는것이 방법일것 같습니다. 이러한 Big Data를 저장하기 위한 방법으로 cassandra, mongoDB, 등이 있으며 Big Data를 처리하기 위한 방법으로 Hadoop이 대두되게 됩니다. 또한 Cloud 시장은 아직 춘추전국의 시대와 같이 복잡한 상황이며, 시장 1위인 amazon의 EC2에 MS의 Azure, Google의 Google Cloud Platform등이 경쟁을 하고 있습니다.

Open source의 대두

기존에는 특정 회사의 특정 Solution 위주의 시스템에서부터 Open Source를 조합한 Open Platform이 대두됩니다. stacktrace, github 등의 사이트에서 여러 개발자들이 참여한 open source들은 어머어마한 양과 회사들이 만든 Solution보다 더 뛰어난 성능을 자랑하며 모든 시장을 휩쓸고 있습니다.

이러한 기술적 흐름속에 개발자가 익혀야지 될 기술은 어마어마하게 많은 것이 사실입니다. 그렇지만, 지금(2013)년을 기준으로 우리가 해야지 되는 기술들은 조금 목표가 정해질 수 있습니다. 어찌보면 지금까지 서론을 이야기해왔다면, 이제는 어떤 기술을 익혀야지 되고, 어떤 기술을 중심으로 다른 기술들을 곁다리로 붙여서 발전해나가야지 되는지에 대한 이정표가 될 수 있겠지요.


웹 개발자가 익혀야지 될 기술들 - 2013년 기준

.net보다는 java

대한민국에서 개발자로 먹고 살기 위해서는 .net은 이미 밀렸습니다. 행안부 기준의 정부표준 프레임워크가 java로 발표가 되고, 정부 표준 프레임워크로 모든 SI 사업이 진행되어가고 있는 현 상황에서 더이상 .NET을 한다는 것은 이제는 개발자로서의 자신을 스스로 학대하는 일 그 이상, 그 이하도 안되게 되었습니다. 일단 java에 집중하는 것이 맞습니다. 그리고 세계적으로도 java의 놀라운 발전은 이제 JVM의 성능이 Ch2.의 성능까지 따라왔다는 이야기가 나올정도로 최적화 및 향상이 되었습니다. 더이상 느리다는 이야기가 나오지를 않는 추세지요.

spring

정부표준프레임워크의 핵심입니다. 정부표준프레임워크는 spring으로 구성이 되어 있고, spring으로 돌아갑니다. 일단 spring을 잘 할 줄 알면, 정부 표준 프레임워크의 반이상은 해결하고 들어간다고 보면 됩니다. spring에 대한 소개에서 다시 이야기를 하겠지만, spring 자체가 java의 표준화에 영향을 주고 있고, java의 표준 자체가 spring이 되어가고 있는 현실입니다. 이 추세로 간다면 spring은 하나의 open source framework가 아닌 java의 일부분이 될 수 있을 것 같습니다.

DB 기술

dao(data access object) 기술은 오래되었지만, 그 기술을 표현하는 방식은 계속해서 바뀌어왔습니다. DB의 특정 데이터들을 보이기 위한 VO로 접근하는 방식과 DB역시 객체화 하여 객체적으로 접근을 하는 ORM 방식으로 크게 나뉘고 있습니다. 현장에서는 국내는 VO 방식을 더 많이 사용하고 있으나, 세계적으로는 ORM 방식이 압도적으로 많이 사용하고 있습니다. 두가지 모두 알아야지 됩니다.

Controller 기술

spring의 세부 기술중 하나입니다. spring에 대해서 잘 안다면...에 포함되는 기술 영역이긴 하지만, HTTP가 어떻게 활용되어가는지에 대한 이해가 필요합니다. 이는 Servlet Container(ex:tomcat)에 대한 이해와도 같이 연결이 됩니다. web이 돌아가는 시스템에 대한 이해라고 할 수 있습니다.

Modeling 기술

Modeling은 소위 말하는 '업무분석' 과정입니다. 업무를 분석하고 분석한 업무를 programming language로 구현 가능한 형태로 추상화를 시키는 과정을 의미합니다. 개발자의 생각의 방향은 학교에서 자주 들은 Divide & conquer 입니다. 작게 나누고, 하나하나 해결해나가고. 그 해결한 조그마한 것들을 다시 붙이는 작업들이 필요합니다.

개발 방법론

소프트웨어 공학은 매우 변화가 심한 학문입니다. 건축학에서 많은 개념들을 차용해왔으나, 지금은 건축학과는 많이 다르다는 것을 인지하고 접근하고 있습니다. 요즘 S/W 개발 방법론의 추세는 agile 개발 방법론입니다. agile에 대해서는 차후에 보다 더 심도있게 짚어보도록 하겠습니다.

패턴 (Pattern)

개발 방법론과 매우 유사한 분야입니다. 개발을 할때, 코드를 최적화 하는 패턴들이 존재합니다. 패턴을 익히는 것보다는 패턴을 이용해서 개발자들끼리의 의사소통이 가능해져야지 됩니다. 개발자들끼리 사용하는 약어가 되는 경우가 꽤나 많습니다. 그리고 책을 통해서 학습을 할때도 유용합니다.

단위 테스트 (Unit Test)

개발자는 자신이 만든 모든 s/w를 테스트할 수 있어야지 되고, 그 테스트한 결과로서 자신의 s/w의 품질을 증명할 수 있어야지 됩니다. "내가 하면 되었는데.", "어제는 되었는데." 식의 이야기는 곤란합니다. in/out이 결정나면 그 in/out에 대한 명확한 테스트를 하고 그 테스트 결과를 보여줄 수 있어야지 됩니다. 또한 s/w의 규모가 크면 클수록 그 코드를 검증할 수 있어야지 됩니다. 이제는 테스트를 돌리기 위해서 개발을 한다. 라는 말이 나올 정도로 테스트는 일반화된 기술입니다. 자동화된 테스트를 구성하는 능력은 반드시 필요합니다.



Posted by Y2K
,

CheckStyle Rule 정의 목록

Java 2013. 5. 13. 14:50

사내의 코드 품질 평가를 위해서 static analysis 방법을 조사하면서 항목에 대해서 list up을 할 필요성이 발견되어 조사한 내용들을 공유합니다. 


항목

원인

회피방법

사용여부

비고

JavadocPackage

모든 method, class에는 help 존재해야지 된다.

시간상 힘들고, 관리되지 않는 주석은 더욱 혼란을 가지고 온다. method 이름 규칙으로 대신하기로 한다.

X

NewlineAtEndOfFile

java code 가장 마지막 줄은 빈공백열로 마쳐져야지 된다.

마지막 line에는 항시 빈공백을 넣는다.

O

Translation

Properties file 이용한 경우, 국가별 번역이 모두 존재해야지 된다.

국가별 번역 파일을 따로 만들거나 default 문자열만을 이용한다.

O

FileLength

java file length 2000 line 넘지 않도록 작성한다.

2000 line 넘어가는 경우, 설계상의 문제가 있기 때문에 class 재정의한다.

O

FileTabCharacter

java file 내부에 tab 문자열이 있으면 안된다.

tab 모두 space 치환해서 사용하도록 한다.

O

RegexpSingleline

1 line에는 한개의 method만이 존재해야지 된다.

1 line 대한 설정을 명확하게 해서 사용하도록 한다.

O

ConstantName, LocalFinalVariableName, LocalVariableName, MemberName, MethodName, PackageName

상수, class, method, parameter, package 대한 naming 규칙이 틀릴 경우 발생한다.

naming 규칙에 맞는 명명법을 사용한다.

O

AvoidStarImport

package안에 있는 모든 객체들을 import할때 발생한다.

package안에서 사용되는 객체만을 import 한다.

O

테스트 코드 작성시에는 예외로 한다.

UnusedImports

package안에 사용하지 않는 객체를 import하면 발생한다.

package안에 사용되지 않는 객체들은 import 하지 않는다.

O

테스트 코드 작성시에는 예외로 한다.

LineLength

Line 길이가 80자가 넘는 경우 발생한다.

Line 길이를 120자로 수정해서 사용한다.

O

80 -> 120

MethodLength

Method 길이는 150자가 넘는 경우 발생한다.

Method 길이를 150 이내로 사용한다.

O

ParameterNumber

Method parameter 7개가 넘지 않도록 한다.

Method안의 input parameter 갯수를 제한한다.

O

ModifierOrder

Method 앞에 붙는 order 다음과 같은 순서를 갖는다. (public, abstract, static, final, transient, volatile, synchronized, native, strictfp)

순서를 따르도록 한다.

O

AvoidNestedBlocks

내부 {} 사용하지 않는다. - switch 구문 제외

내부 {} 사용하지 않는다.

O

EmptyBlock

{}안에 아무런 구문이 없는 경우에 발생한다.

{}안에 구문이 없는 경우, 제거한다.

O

LeftCurly

'{' interface method 구현문 끝에 넣어준다.

이집트 표기법을 사용하도록 한다.

O

NeedBraces

code안의 {} 반드시 짝이 맞아야지 된다.

Compile error 발생하지 않도록 만들어준다.

O

RightCurly

'}' 뒤에는 반드시 CRLF만이 존재해야지 된다.

이집트 표기법을 사용하도록 한다.

O

AvoidInlineConditionals

1 line에서 if 문을 이용해서 처리하지 않는다.

condition 제거하도록 한다.

X

1 line에서 가독성이 높은 경우가 존재한다.

EmptyStatement

for loop문에서 무한 loop 발생시킬 있는 empty statement 존재한다.

반드시 for loop 경우에는 statement 존재한다. loop 안에서 조건이 걸리는 경우, while문을 사용하도록 한다.

O

EqualsHashCode

equals(), hashCode() 어느하나 override 경우, 둘다 재정의 되어야지 된다.

반드시 method 쌍으로 재정의 하도록 한다.

O

IllegalInstantiation

boolean, String 같이 java 기본 type 재정의하는 경우 발생

java 기본 type 그대로 사용하도록 한다.

O

InnerAssignment

if문이나 toString() 같은 내부에서 변수에 값을 할당한다.

값의 할당은 따로 line 잡아서 사용하도록 한다.

O

MagicNumber

상수값을 사용하는 경우 발생한다.

상수값을 static final 이름을 지정해서 사용한다.

O

MissingSwitchDefault

switch 문에 default case 없는 경우에 발생한다.

switch문은 반드시 default case 넣어서 작성한다.

O

RedundantThrows

try-catch 시에 throws 순서로 인하여 실행될 없는 catch문이 존재한다.

catch 만들때, exception 상속 상태를 확인하고 구성하도록 한다.

O

SimplifyBooleanExpression

if문내에서 1 line으로 return한다.

if 문안에서 return 하지 않고, return값에 대한 명명을 정확히 한다.

O

SimplifyBooleanReturn

if문의 결과를 그대로 return 한다.

if문의 로직 자체를 return 값으로 변경한다.

O

DesignForExtension

객체는 확장 가능하도록 되어야지 되고, public문은 반드시 final 재정의 되는 것을 막아줘야지 된다.

spring 사용하는 경우 Proxy aspectJ 의해서 재정의 되는 method 다음 규칙에서 에러를 발생시킬 있기 때문에 사용하지 않는다.

X

FinalClass

final class 생성자가 private 되어있는 경우 발생한다.

final class 경우에는 특별한 경우를 제외하고 사용하지 않는다.

O

spring 이용하는 경우에는 특히 사용할 필요가 없는 구성이다.

HideUtilityClassConstructor

public static method만이 존재하는 class 생성자가 protected, private 되어 있다.

UtilityClass 경우에는 모두 public modifier 이용한다.

O

InterfaceIsType

interface type만이 존재하고, method 존재하지 않는다.

type descript interface 사용하지 않는다.

O

VisibilityModifier

getter/setter 사용하지 않고, 내부 변수에 접근 가능하다.

getter/setter 이용해서 property 처리를 하도록 한다.

O

ArrayTypeStyle

java style input array parameter 이용한다. (java style : main(String[] args), C style : main(String args[])

java style 사용하도록 한다.

O

FinalParameters

input parameter 내부에서 참조만 하는 경우, final 선언한다.

모든 input parameter final 사용하는 것을 기본 원칙으로 갖는다.

O

coding style 밀접한 연관이 있다.

TodoComment

"TODO: " 정확히 사용하지 않는 경우에 발생한다. (대소문자, 공백위치)

TODO 정확히 사용한다.

O

UpperEll

'L', '1', 'I', 'i' 명확히 구분할 있도록 method 이름과 객체이름을 짓는다.

명명규칙에 따라 이름을 작성하도록 한다.

O

Posted by Y2K
,

Spring 3.1에서 강화된 @Configuration을 사용한 설정에서 재미있는 @EnableXX annotation을 이용한 @EnableOrm을 만들어보고자한다. 

먼저, 요구사항

1. 기본적으로 BoneCP를 이용
2. packagesToScan 을 통해서 entity가 위치한 package를 지정해줄 수 있어야지 된다.
3. Hibernate와 Hibernate를 이용한 JPA를 모두 지원하는 형태로 구성한다.
4. Ehcache를 사용할 수 있어야지 된다. 

기본적으로 구성되는 pom.xml의 구성은 다음과 같습니다. 

        <dependency>
            <groupId>com.jolbox</groupId>
            <artifactId>bonecp</artifactId>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-orm</artifactId>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-tx</artifactId>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-core</artifactId>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-validator</artifactId>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-entitymanager</artifactId>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>com.mysema.querydsl</groupId>
            <artifactId>querydsl-core</artifactId>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>com.mysema.querydsl</groupId>
            <artifactId>querydsl-apt</artifactId>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>com.mysema.querydsl</groupId>
            <artifactId>querydsl-jpa</artifactId>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>com.mysema.querydsl</groupId>
            <artifactId>querydsl-sql</artifactId>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-classic</artifactId>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-core</artifactId>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>jcl-over-slf4j</artifactId>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>log4j-over-slf4j</artifactId>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>provided</scope>
        </dependency>

먼저, @EnableOrm interface는 다음과 같이 정의 될 수 있습니다. 

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(OrmConfigurationSelector.class)
public @interface EnableOrm {
    String[] packagesToScan() default {};

    boolean enableCache() default false;

    boolean showSql() default false;

    OrmType ormType() default OrmType.Hibernate;
}

public enum OrmType {
    Hibernate, Jpa;
}

packageToScan과 enableCache, 그리고 console 창에 sql query문을 출력해야지 되는 경우를 기본적으로 고려해줄 수 있습니다.  그리고 OrmType을 통해서 기본 Hibernate를 이용한 Orm과 Jpa를 이용하는 두가지를 모두 사용할 수 있도록 합니다.  OrmType에 따라 각각 Load되는 Configuration이 바뀌어야지 되기 때문에 Import class는 ImportSelector를 구현한 객체여야지 됩니니다. 

ImportSelector를 구현한 객체는 다음과 같이 구성됩니다. 

public class OrmConfigurationSelector implements ImportSelector {
    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        Map<String, Object> metadata = importingClassMetadata.getAnnotationAttributes(EnableOrm.class.getName());
        OrmType ormType = (OrmType) metadata.get("ormType");
        if (ormType == OrmType.Hibernate) {
            return new String[] { HibernateConfiguration.class.getName() };
        } else {
            return new String[] { JpaConfiguration.class.getName() };
        }
    }
}

그리고, Hibernate를 이용할 때와 JPA를 이용할 때의 공통 코드가 존재하는 abstract 객체를 하나 만들어주는 것이 좋을 것 같습니다. 기본적으로 Hibernate JPA를 사용할 예정이기 때문에 공통적으로 DataSource와 Hibernate Property는 완벽하게 중복되는 코드가 만들어질테니까요. 
공통 Configuration객체인 OrmConfiguration객체의 기능은 다음과 같습니다.

1. BoneCP datasource 제공
2. Hibernate Property의 제공
3. enableCache, packateToScan, showSql 등 protected 변수의 제공

OrmConfiguration 객체는 다음과 같이 구성될 수 있습니다. 

public abstract class OrmConfiguration implements ImportAware {
    public static final String HIBERNATE_DIALECT = "hibernate.dialect";
    public static final String CONNECT_USERNAME = "connect.username";
    public static final String CONNECT_PASSWORD = "connect.password";
    public static final String CONNECT_DRIVER = "connect.driver";
    public static final String CONNECT_URL = "connect.url";

    public static final String HIBERNATE_SHOW_SQL = "hibernate.show_sql";
    public static final String ORG_HIBERNATE_CACHE_EHCACHE_EH_CACHE_REGION_FACTORY = "org.hibernate.cache.ehcache.EhCacheRegionFactory";
    public static final String HIBERNATE_CACHE_USE_QUERY_CACHE = "hibernate.cache.use_query_cache";
    public static final String HIBERNATE_CACHE_USE_SECOND_LEVEL_CACHE = "hibernate.cache.use_second_level_cache";
    public static final String HIBERNATE_CACHE_REGION_FACTORY_CLASS = "hibernate.cache.region.factory_class";

    @Autowired
    protected Environment env;

    protected boolean showSql;
    protected boolean enableCache;
    protected String[] packagesToScan;

    @Bean
    public DataSource dataSource() {
        BoneCPDataSource dataSource = new BoneCPDataSource();
        dataSource.setUsername(env.getProperty(CONNECT_USERNAME));
        dataSource.setPassword(env.getProperty(CONNECT_PASSWORD));
        dataSource.setDriverClass(env.getProperty(CONNECT_DRIVER));
        dataSource.setJdbcUrl(env.getProperty(CONNECT_URL));
        dataSource.setMaxConnectionsPerPartition(20);
        dataSource.setMinConnectionsPerPartition(3);
        return dataSource;
    }

    @Bean
    public HibernateExceptionTranslator hibernateExceptionTranslator() {
        return new HibernateExceptionTranslator();
    }

    @Bean
    public abstract PlatformTransactionManager transactionManager();

    protected Properties getHibernateProperties() {
        Properties properties = new Properties();
        properties.put(HIBERNATE_DIALECT, env.getProperty(HIBERNATE_DIALECT));
        if (enableCache) {
            properties.put(HIBERNATE_CACHE_REGION_FACTORY_CLASS, ORG_HIBERNATE_CACHE_EHCACHE_EH_CACHE_REGION_FACTORY);
            properties.put(HIBERNATE_CACHE_USE_SECOND_LEVEL_CACHE, true);
            properties.put(HIBERNATE_CACHE_USE_QUERY_CACHE, true);
        }
        return properties;
    }

    @Override
    public void setImportMetadata(AnnotationMetadata importMetadata) {
        if (env.getProperty(HIBERNATE_DIALECT) == null || env.getProperty(CONNECT_USERNAME) == null
                || env.getProperty(CONNECT_PASSWORD) == null || env.getProperty(CONNECT_DRIVER) == null
                || env.getProperty(CONNECT_URL) == null) {
            throw new IllegalArgumentException("properties is not completed! check properties (hibernate.dialect, "
                    + "connec.username, connect.password, connect.driver, connect.url)");
        }
        Map<String, Object> metaData = importMetadata.getAnnotationAttributes(EnableOrm.class.getName());
        enableCache = (boolean) metaData.get("enableCache");
        packagesToScan = (String[]) metaData.get("packagesToScan");
        showSql = (boolean) metaData.get("showSql");
    }
}

기본적인 Property들은 모두 Properties 파일에 정의되지 않으면 에러가 발생하도록 객체들을 구성하였습니다. 이제 HibernateConfiguration을 한번 구성해보도록 하겠습니다. 

@Configuration
@EnableTransactionManagement
public class HibernateConfiguration extends OrmConfiguration implements HibernateConfigurer {
    private static final String HIBERNATE_SHOW_SQL = "hibernate.show_sql";

    @Bean
    public LocalSessionFactoryBean sessionFactory() {
        LocalSessionFactoryBean sessionFactory = new LocalSessionFactoryBean();
        sessionFactory.setDataSource(dataSource());
        setLocalSessionFactoryBean(sessionFactory);
        return sessionFactory;
    }

    @Override
    @Bean
    public PlatformTransactionManager transactionManager() {
        HibernateTransactionManager transactionManager = new HibernateTransactionManager();
        transactionManager.setDataSource(dataSource());
        transactionManager.setSessionFactory(sessionFactory().getObject());
        return transactionManager;
    }

    @Override
    public void setLocalSessionFactoryBean(LocalSessionFactoryBean localSessionFactoryBean) {
        Properties properties = getHibernateProperties();
        if (showSql) {
            properties.put(HIBERNATE_SHOW_SQL, "true");
        }
        localSessionFactoryBean.setHibernateProperties(properties);
        localSessionFactoryBean.setPackagesToScan(packagesToScan);
    }
}


기본적으로 항시 사용되는 SessionFactory와 그에 대한 설정부분을 Load 시켜주고, PlatformTransactionManager를 return 시켜주는 매우 단순한 @Configuration class입니다. 

이제 JpaConfiguration입니다. 
@Configuration
@EnableTransactionManagementpublic class JpaConfiguration extends OrmConfiguration implements JpaConfigurer {
    @Override
    public void setEntityManagerFactoryBeanProperties(LocalContainerEntityManagerFactoryBean entityManagerFactoryBean) {
        entityManagerFactoryBean.setPackagesToScan(packagesToScan);
    }

    @Bean
    public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
        LocalContainerEntityManagerFactoryBean entityManagerFactory = new LocalContainerEntityManagerFactoryBean();
        setEntityManagerFactoryBeanProperties(entityManagerFactory);
        entityManagerFactory.setDataSource(dataSource());
        entityManagerFactory.setJpaVendorAdapter(hibernateJpaVendorAdapter());
        entityManagerFactory.setJpaProperties(getHibernateProperties());
        return entityManagerFactory;
    }

    @Bean
    public HibernateJpaVendorAdapter hibernateJpaVendorAdapter() {
        HibernateJpaVendorAdapter hibernateJpaVendorAdapter = new HibernateJpaVendorAdapter();
        hibernateJpaVendorAdapter.setShowSql(showSql);
        return hibernateJpaVendorAdapter;
    }

    @Bean
    public PersistenceExceptionTranslationPostProcessor exceptionTranslation() {
        return new PersistenceExceptionTranslationPostProcessor();
    }

    @Override
    @Bean
    public PlatformTransactionManager transactionManager() {
        JpaTransactionManager transactionManager = new JpaTransactionManager();
        transactionManager.setDataSource(dataSource());
        transactionManager.setEntityManagerFactory(entityManagerFactory().getObject());
        return transactionManager;
    }
}


둘의 코드는 거의 완전히 동일합니다. Hibernate를 이용할 것인가, 아니면 Jpa를 이용할 것인가에 대한 기본적인 차이만이 존재합니다. 
테스트 코드 구성은 다음과 같습니다. 

@Configuration
@EnableOrm(ormType = OrmType.Hibernate, enableCache = true, packagesToScan = "com.xyzlast.domain.configs", showSql = true)
@PropertySource(value = "classpath:spring.properties")
@ComponentScan("com.xyzlast.domain.repositories")
public class TestHibernateConfiguration {
    @Bean
    public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
        PropertySourcesPlaceholderConfigurer configHolder = new PropertySourcesPlaceholderConfigurer();
        Properties properties = new Properties();
        properties.setProperty("org.jboss.logging.provier", "slf4j");
        configHolder.setProperties(properties);
        return configHolder;
    }
}

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = TestHibernateConfiguration.class)
public class HibernateConfigurationTest {

    @Autowired
    private ApplicationContext applicationContext;

    @Before
    public void setUp() {
        assertThat(applicationContext, is(not(nullValue())));
    }

    @Test
    public void dataSource() {
        DataSource dataSource = applicationContext.getBean(DataSource.class);
        assertThat(dataSource, is(not(nullValue())));
    }

    @Test
    public void transactionManager() {
        PlatformTransactionManager transactionManager = applicationContext.getBean(PlatformTransactionManager.class);
        assertThat(transactionManager, is(not(nullValue())));
    }

    @Test
    public void sessionFactory() {
        SessionFactory sessionFactory = applicationContext.getBean(SessionFactory.class);
        assertThat(sessionFactory, is(not(nullValue())));
    }
}

이제 Hibernate와 JPA에 따른 각각의 configuration을 따로 구성하지 않아도 되는 멋진 코드가 만들어졌습니다.
회사에서 다른 팀원들이 사용할 수 있도록 jar를 만들어서 사내 nexus 서버에 올려야지 되겠습니다. ㅋㅋ




Posted by Y2K
,
<mvc:annotation-driven>
이 설정될 경우, 여기에 따른 여러가지 설정이 많이 이루어지게 된다. 
먼저 Spring 문서에 따르면, 

 1. Support for Spring 3′s Type ConversionService in addition to JavaBeans PropertyEditors during Data Binding. A ConversionService instance produced by the org.springframework.format.support.FormattingConversionServiceFactoryBean is used by default. This can be overriden by setting the conversion-service attribute. 
2. Support for formatting Number fields using the @NumberFormat annotation 
3. Support for formatting Date, Calendar, Long, and Joda Time fields using the @DateTimeFormat annotation, if Joda Time is present on the classpath. 
4. Support for validating @Controller inputs with @Valid, if a JSR-303 Provider is present on the classpath. The validation system can be explicitly configured by setting the validator attribute. 
5. Support for reading and writing XML, if JAXB is present on the classpath. 
6. Support for reading and writing JSON, if Jackson is present on the classpath. 

와 같은 일들이 벌어지게 된다. 

여기서 주의할 점은 1번 항목. ConversionService가 default로 설정이 된다는것이다. ConversionService가 이미 설정이 되어 있기 때문에 Spring source에서 보면 다음과 같은 항목에 의하여 에러가 발생하게 된다.
        public void setConversionService( ConversionService conversionService ) {
               Assert .state ( this. conversionService == null, "DataBinder is already initialized with ConversionService");
               this .conversionService = conversionService ;
               if ( this .bindingResult != null && conversionService != null ) {
                      this .bindingResult . initConversion( conversionService );
               }
        }
따라서, Converter 를 이용한 @InitBinder는 사용할 수 없게 되고 에러를 발생시키게 된다. 이 경우에, @InitBinder를 이용하기 위해서는 PropertyEditorSupport를 상속받아 등록시키는 방법을 사용할 수 밖에 없게 된다. 을 통해서 설정되는 것을 xml로 표현하면 다음과 같다.
    
        
    
     
    
    

    
        
            
                
                
            
        
        
            
                
                
                    
                
                
                
                





            
        
    

    
        
        
            
                
            
        
    
위에서 주석처리 되어 있는 항목들은 해당 외부 라이브러리들이 존재하면 사용되게 된다.



Posted by Y2K
,

긁어온 내용. 출처는 > http://www.gurubee.net/pages/viewpage.action?pageId=26739591&

1. Apache HTTP 서버의 이해

1.1 개요

  • Apache HTTP 서버는 아파치 소프트웨어 재단(ASF:Apache Software Foundation)에서 개발하여 배포하고 있는 무료/오픈소스 웹 서버이다.
  • 아파치 HTTP 서버는 전세계 웹 서버 시장 점유율의 50% 이상을 차지하고 있으며 , 리눅스, 유닉스, BSD, 윈도우즈 등 다양한 플랫폼에서 사용이 가능하다.
  • 아파치 HTTP 서버는 빠르고 효율적이며, 이식성이 좋고 안정적이며, 기능이 다양하고 확장성이 좋다.

1.2 Apache HTTP 서버 왜 필요한가?

  • 보안기능 (SSL, Proxy, ACL, Directory 접근제한등..)
  • 성능적인 측면(리소스 분산처리, Cache(Expires), HTTP 표준설정(ETag등), MPM(Multi-Processing Module), KeepAlive등..)
  • 가상호스트 기능(하나의 서버에 여러 도메인 운영, 서버호스팅등..)
  • 운영적인 측면 (ErrorDocument, Access Log등..)
  • 부가적인 기능 (여러가지 유용한 Apache Module등..)

1.3 설치

2. Apache HTTP 서버의 주요 설정

2.1 가상호스트 (VirtualHost)

  • VirtualHost(가상호스트)란 하나의 웹 서버에서 여러 개의 도메인 주소를 운영 및 관리 할 수 있는 기능이다.
  • Apache HTTP Server에서는 이름기반 가상호스트와 IP 기반 가상호스트 기능을 제공한다.
이름기반 가상호스트(Name-based VirtualHost) 설정
  • 하나의 IP 주소에 여러 개의 호스 도메인 주소 사용이 가능하다.
  • httpd-vhost.conf의 VirtualHost를 설정하여 이름기반의 가상 호스트를 사용 할 수 있다.
  • httpd.conf 파일에서 httpd-vhosts.conf 파일의 주석을 해제해야 한다.
Name-based VirtualHost 예제
Listen 80
NameVirtualHost *:80

<VirtualHost *:80>
  ServerName www.oracleclub.com
  ServerAlias oracleclub.com
  DocumentRoot C:\workspace\project\oracleclub
</VirtualHost>

<VirtualHost *:80>
  ServerName wiki.oracleclub.com
  DocumentRoot C:\workspace\project\wiki
</VirtualHost>
가상호스트 설정 실습
  • $APACHE_HOME/conf/extra/httpd-vhost.conf 파일을 열어 NameVirtualHost와 VirtualHost를 아래와 같이 설정한다.
  • ServerName은 test.apache.org로 입력하고, DocumentRoot는 아파치 $APACHE_HOME/htdocs로 설정한다.
httpd-vhost.conf
NameVirtualHost *:80

<VirtualHost *:80>
    ServerName test.apache.org
    DocumentRoot C:\dev\Apache2.2\htdocs    # Apache htdocs 디렉토리 
</VirtualHost>
  • 아래와 같이 C:\Windows\System32\drivers\etc\hosts 파일에 호스트 설정을 추가 한다.
  • hosts.zip 파일을 받아 바로가기를 만들어 사용하면 편하다.
C:WindowsSystem32driversetchosts
# 127.0.0.1  localhost
127.0.0.1  test.apache.org

2.2 Files, Directory, Location 섹션

Files (파일시스템 관점)
  • 특정 파일에 대한 접근제한을 설정한다.
  • 아래는 위치에 상관없이 private.html 파일에 대한 접근을 제한하는 예이다.
    <Files private.html>
    Order allow,deny
    Deny from all
    </Files>
  • 아래는 "/home/user/webapps" 경로아래에 있는 private.html 파일에 대한 접근을 제한하는 예이다.
    <Directory /home/user/webapps>
    <Files private.html>
    Order allow,deny
    Deny from all
    </Files>
    </Directory>
Directory (파일시스템 관점)
  • 운영체제 입장에서 디스크를 보는 관점이다. 운영체제 디렉토리에 대한 접근제한을 설정한다.
  • 아래 예제를 보면서 Allow,Deny에 대해 이해를 해보자
    • Order 절 순서대로 Allow와 Deny를 실행한다
    • 아래 예는 Allow를 먼저 수행하고, Deny를 수행한다.
    • 즉 모두 허용하고, 127.0.0, 192.168.123 두 개의 아이피 대역에 대해서 접근 제한을 설정한다 .
      # 특정 IP 대역의 IP 차단
      <Directory /usr/local/apache/htdocs>
          Order Allow,Deny
          Deny from 127.0.0 192.168.123
          Allow from all
      </Directory>
  • 위 예제를 이해하였다면, 아래 예제는 쉽게 이해할 수 있을 것이다.
    • Deny를 먼저 수행하고 Allow를 수행한다.
    • 127.0.0, 192.168.123 두 개의 아이피 대역만 접근이 가능할 것이다.
      # 특정 IP 대역의 IP 허용
      <Directory /usr/local/apache/htdocs>
          Order Deny,Allow
          Allow from 127.0.0 192.168.123
          Deny from all
      </Directory>
  • 아래는 Allow, Deny 설정을 잘못한 예제이다.
    • 어떻게 되겠는가?
    • 먼저 Allow를 하고, Deny from all을 하기 때문에 모두 차단하겠다라는 의미를 가진다.
      <Directory /usr/local/apache/htdocs>
         Order Allow,Deny
         Deny from all
         Allow from 192.168.123.1
      </Directory>
Location (웹 경로 관점)
  • 웹서버가 제공하는 경로를 클라이언트가 보는 사이트의 관점이다
  • 아래는 /private 경로에 대해서 접근을 제한하는 예이다.
    • www.oracleclub.com/private 문자열로 시작하는 요청이 해당된다.
      <Location /private>
      Order Allow,Deny
      Deny from all
      </Location>
FilesMatch, DirectoryMatch, LocationMatch
  • 정규표현식을 사용할 수 있다.
  • 아래는 이미지 파일에 대한 접근을 제한하는 예이다.
    <FilesMatch \.(?i:gif|jpe?g|png)$>
    Order allow,deny
    Deny from all
    </FilesMatch>
  • 아래는 WEB-INF, META-INF 디렉토리에 접근을 금지하는 예이다.
    <DirectoryMatch "(^|/)META-INF($|/)">
      Order deny,allow
      deny from all
    </DirectoryMatch>
    
    <DirectoryMatch "(^|/)WEB-INF($|/)">
      Order deny,allow
      deny from all
    </DirectoryMatch>
Directory 설정 실습
  • 위의 Directory Allow, Deny 설정 예제를 직접 실습해 보자
  • 아래와 같이 특정 IP의 접근을 막는 Directory 옵션을 추가해 보자 (아래는 로컬 IP 주소의 접근을 막는 예제이다.)
  • Apache restart 후 http://test.apache.org 접속시 "Forbidden" 에러 메시지가 나오는지 확인 해 보자
    httpd-vhost.conf
    <VirtualHost *:80>
        ServerName test.apache.org
        DocumentRoot C:\dev\Apache2.2\htdocs    
        
        <Directory C:\dev\Apache2.2\htdocs>
            Order Allow,Deny
            Deny from 192.168  127.0.0
            Allow from all
        </Directory>    
    </VirtualHost>

2.3 ErrorDocument

ErrorDocument란
  • Apache HTTP Server에서는 ErrorDocument 지시자를 사용해 특정 에러발생시 특정 페이지로 redirect 할 수 있다.
  • ErrorDocumen를 활용하여 다양한 HTTP Status Code에 대해서 설정을 해놓으면 사용자에게 좀 더 편리하고 친절하게 메시지를 보여 줄 수 있다.
  • httpd.conf 파일에서 ErrorDocument 지시자를 설정하면 된다.
  • 아래와 같이 세가지 방법으로 설정 할 수 있다.
    ErrorDocument 설정 예제
    # 1. plain text 설정 방법
    # 500 메세지를 "The server made a boo boo." 로 변경해 준다.
    ErrorDocument 500 "The server made a boo boo."
    
    # 2. local redirects 방법
    # 404 발생시 missing.html의 내용을 보여준다.
    ErrorDocument 404 /missing.html
    
    # 3. external redirects 설정 방법
    # 404 발생시 외부 페이지로 redirect 한다.
    ErrorDocument 404 http://www.example.com/subscription_info.html
ErrorDocument 설정 실습
  • Directory 에서 실습한 접근권한 설정예제에서 403 접근 권한 오류가 발생 했을 때 Apache HTTP Server의 기본 메시지인 "Forbidden" 에러 메시지 대신 특정 HTML 이 나오게 실습 해보자
  • AccessDeny.html 파일을 "$APACHE_HOME/htdocs" 디렉토리에 저장한다.
  • 아래와 같이 ErrorDocument를 설정 한다.
    httpd-vhost.conf
    <VirtualHost *:80>
        ServerName test.apache.org
        DocumentRoot C:\dev\Apache2.2\htdocs    
    
        ErrorDocument 403 /AccessDeny.html   
        
        <Directory C:\dev\Apache2.2\htdocs>
            Order Allow,Deny
            Deny from 192.168.123 127.0.0
            Allow from all
        </Directory>    
    </VirtualHost>
IE ErrorDocument 설정 기준
  • error page 의 사이즈가 아래 기준보다 작으면 IE 메세지를 보여준다.(브라우저가 알아서 에러 페이지가 성의 없다고 판단함)
CodeDescriptionFile Size
400Bad Request512 bytes
403Forbidden256 bytes
404Not Found512 bytes
500Internal Server Error512 bytes

참고자료


Apache HTTP 서버와 Tomcat서버의 연동

1. Apache와 Tomcat을 연동하는 이유

  • Tomcat 서버는 본연의 임무인 서블릿 컨테이너의 역할만 하고, Apache HTTP Server는 웹서버의 역할을 하도록 각각의 기능을 분리하기 위해 연동을 할 수 있다.
  • Apache HTTP Server에서 제공하는 편리한 기능을 사용하기 위해서 연동을 할수 있다.
  • 대규모 사용자가 사용하는 시스템을 구축할 때 웹 서버인 아파치와 연동을 하면 부하 분산의 효과를 가질 수 있다. mod_jk의 Load Balancing과 FailOver 기능을 사용하여 안정적으로 운영 할 수 있다.

2. Apache와 Tomcat 연동하기

  • 아래 내용은 윈도우 OS를 기준으로 설명하였다.
2.1 mod_jk 다운로드 및 설정
httpd.conf
# jk_module 추가
LoadModule jk_module modules/mod_jk.so
2.2 workers.properties 파일 설정
  • apache와 tomcat를 연동하기위해서는 workers.properties 파일을 설정해야 한다.
  • $APACHE_HOME/conf/workers.properties 파일을 아래 예제와 같이 생성한다.
  • workers.properties 파일은 일반적으로 httpd.conf 파일과 같은 디렉토리에 위치하게 설정한다.
workers.properties
worker.list=sample

# 톰캣 server.xml 파일 AJP/1.3 Connector의 Port를 입력한다.
worker.sample.port=8009

# 톰탯 server 호스트
worker.sample.host=localhost

# 아파치 + 톰캣 통신 프로토콜
worker.sample.type=ajp13
  • ※참고 Tomcat Worker
    • 톰캣 워커는(Tomcat worker) 웹서버로부터의 서블릿 요청을 톰캣 프로세스(Worker)에게 전달하여 요청을 처리하는 톰캣 인스턴스이다.
    • 대부분 하나의 worker를 사용하나, load 밸런싱이나 site 파티셔닝을 위해 여러개의 worker를 사용 할 수 있다.
    • 워커 타입에는 ajp12, ajp13, jni, lb 등이 있다.
2.3 workers.properties 경로 지정
  • httpd.conf 파일에 workers.properties 파일 경로를 지정한다.
httpd.conf
# workers.properties 파일 추가
JkWorkersFile conf/workers.properties
2.4 VirtualHost 설정 변경
  • $APACHE_HOME/conf/vhosts/extra/httpd-vhost.conf 파일의 VirtualHost의 DocumentRoot를 Tomcat 디렉토리로 변경하자
  • JkMount 설정을 추가하자
httpd-vhost.conf 파일 설정
NameVirtualHost *:80

<VirtualHost *:80>
    ServerName test.apache.org
    DocumentRoot C:\dev\apache-tomcat-6.0.32webapps\ROOT 
 
 # URL중 jsp로 오는 Request만 Tomcat에서 처리 함
 # sample은 workers.properties에서 등록한 worker이름
JkMount  /*.jsp  sample   

# servlet 예제 실행을 위해서 추가
JkMount  /examples/* sample
</VirtualHost>
2.5 Tomcat의 server.xml 수정
  • <Context> 태그의 docBase 디렉토리를 Apache HTTP Server 설정과 동일하게 Tomcat 서버의 webapps/ROOT 디렉토리를 절대경로로 지정하자.
원하는 디렉토리를 Document Root로 사용
<Host name="localhost"  appBase="webapps"
           unpackWARs="true" autoDeploy="true"
           xmlValidation="false" xmlNamespaceAware="false">
 <Context path="" docBase="C:\dev\apache-tomcat-6.0.32\webapps\ROOT" reloadable="true"/>
 ..
</Host>

3. Apache HTTP Server와 Tomcat의 연동 테스트

Posted by Y2K
,

Filter가 적용이 되어 있는 경우, Filter에 적용되는 bean의 설정을 위해서 XmlWebApplicaionContext를 이용해서 Context객체들을 따로 로딩해줘야지 된다. 


순서는 다음과 같다. 


  1. ServletContext의 생성
  2. XmlWebApplicationContext의 생성 : 이때, Filter에 사용되는 Bean의 설정들은 모두 /WEB-INF/web.xml에 위치하고 있어야지 된다.
  3. XmlWebApplicationContext에 SevletContext를 주입하고, refresh를 시켜준다.
  4. 마지막으로, ServletContext에 속성을 추가한 후, MockFilterConfig, MockFilterChain을 이용해서 각각 Filter가 적용되는 Controller들을 테스트 한다.


    @Test

    public void get() throws Exception {

    ServletContext sc = new MockServletContext();

    XmlWebApplicationContext wctx = new XmlWebApplicationContext();    

    wctx.setServletContext(sc);            

        wctx.refresh();

        sc.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, wctx);

        assertTrue(wctx.containsBean("sessionFactory"));

        

        

    MockHttpServletRequest request = new MockHttpServletRequest("GET","/rest/person/get/" + person.getId().toString());

    MockHttpServletResponse response = new MockHttpServletResponse();

        MockFilterConfig filterConfig = new MockFilterConfig(sc);

        MockFilterChain filterChain = new MockFilterChain();

        

        OpenSessionInViewFilter filter = new OpenSessionInViewFilter();

        filter.init(filterConfig);

        filter.doFilter(request, response, filterChain);

        

        

    Object handler = handlerMapping.getHandler(request).getHandler();

    ModelAndView modelAndView = handlerAdapter.handle(request, response, handler);    

    System.out.println(response.getContentAsString());    

    assertNull(modelAndView);

    }



Posted by Y2K
,

Eclipse 단축키 모음

Java 2012. 11. 24. 14:26

ctrl + s: 저장 및 컴파일
ctrl + i: 소스 깔끔 정리(인덴트 중심의 자동구문정리)
ctrl + space : 어휘의 자동완성(Content Assistance)

ctrl + E : 열린파일 옮겨다니기

ctrl + shift + E : 열린파일 띄우기

ctrl + M : 에디터화면 넓게
ctrl + 1 : Quick Fix(Rename에 주로 사용)
ctrl + shift + M : 캐럿이 위치한 대상에 필요한 특정클래스 import
ctrl + shift + O : 소스에 필요한 패키지의 자동 임포트
ctrl + /: 한줄 또는 선택영역 주석처리/제거
ctrl + Q : 마지막 편집위치로 가기
ctrl + L : 특정줄번호로 가기
ctrl + D : 한줄삭제
ctrl + H : Find 및 Replace
ctrl + K : 다음찾기(또는, 찾고자 하는 문자열을 블럭으로 설정한 후 키를 누른다.)
ctrl + shift + K : 이전찾기(또는, 찾고자 하는 문자열을 블럭으로 설정한 후 역으로 찾고자 하는 문자열을 찾아감.)
alt + shift + j : 설정해 둔 기본주석 달기
Ctrl + 객체클릭(혹은 F3) : 클래스나 메소드 혹은 멤버를 정의한 곳으로 이동(Open Declaration)


ctrl + shift + f : 소스 깔끔 정리
ctrl + 2 + R : Rename(리팩토링)
ctrl + shift + / : 선택영역 block comment 설정
ctrl + shift + \ : 선택영역 block comment 제거
alt + shift + up: Enclosing Element 선택(괄호의 열고 닫기 쌍 확인에 유용함)
ctrl + O : Outline창열기

ctrl + T : 상속구조 보기, 한번더 누르면 수퍼타입/서브타입 구조가 토글된다

Alt + ->, Alt + <-: 이후, 이전
해당프로젝트에서 alt + enter : Project 속성
sysout > Ctrl + Space: System.out.println();
try > Ctrl + Space : 기본 try-catch문 완성
for > Ctrl + Space : 기본 for문 완성
템플릿을 수정,추가: Preferences > java > editor > Templates

블럭 씌운상태에서 alt + shift + z : try/catch, do/while, if, for, runnable.... 등 블럭씌우기


ctrl + N : 새로운 파일 및 프로젝트 생성
ctrl + shift + s : 열려진 모든파일 저장 및 컴파일
alt + / : Word Completion
alt + shift + R : Rename
ctrl + shift + G : 특정 메써드나 필드를 참조하고 있는 곳을 찾는다.
ctrl + shift + B : 현재커서위치에 Break point설정/해제
ctrl + alt + R
ctrl + f11 : 실행
f11 : 디버깅 시작

f4 : 상속구조 클래스 보기(메소드, 멤버)
f5 : step into
f6 : step over
f8 : 디버깅 계속
ctrl + . : 다음오류부분으로 가기
ctrl + , : 이전오류부분으로 가기
f12 : 에디터로 커서이동
ALT + UP,DOWN : 현재 줄 위치 이동
Ctrl + j : 검색할 단어를 입력하면서 실시간으로 검색
Ctrl + Shift + j : 검색할 단어를 입력하면서 실시간으로 거꾸로 검색
F4 : 클래스명을 선택하고 누르면 해당 클래스의 Hierarchy 를 볼 수 있다.
ctrl + alt + up/down : 한줄 duplicate
alt + shift + 방향 : 선택
ctrl + shift + g : 객체(변수)가 참조 되는 곳을 찾아 준다

alt + shift + m : 코드 중복 해결(중복부분을 블록선택한 다음 단축키를 누르면 이부분을 별도의 메서드로 뽑아내줌)

ctrl + alt + h : 메서드 호출구조 보기

Posted by Y2K
,

Spring Security는 다음과 같은 순서로 동작하게 된다. 


  1. <security:http> 영역안의 인증정보를 얻게 된다. 
  2. 인증이되지 않은 경우, form-login login-page 항목의 url로 이동하게 된다. 
  3. 인증은 기본적으로 {wepapp}/j_spring_security_check에서 처리되게 되며, 페이지의 in/out값은 j_username, j_password가 된다. 
  4. j_spring_security_check는 등록된 authentication-manager의 authentication-provider에 username만을 넘긴다. 
  5. username을 이용, authentication-provider는 username, password, authentication-role을 설정해서 사용자 정보를 넘겨준다.
  6. Spring security에서 j_spring_security_check 에서 반환된 사용자 정보를 이용. password가 일치하는지 확인
  7. password가 일치되는 경우, authentication-success-handler-ref 에 정의된 Handler를 이용해서, page의 이동이나 json 응답을 준다.
  8. password가 일치되지 않는 경우, authentication-failure-handler-ref 에 정의된 Handler를 이용해서, page의 이동이나 json 응답을 준다.





하나 중요한 상황이. web.xml에 반드시 securityFilter와 ContextLoaderListener가 선언이 되어야지 된다. 

spring web mvc에서 각 controller들이 url을 등록하지만, 각 servlet의 applicationContext를 모두 포함하는 parent의 개념에서 spring security가 접근이 된다고 생각해야지 된다. 


구성한 web.xml 파일의 내용은 다음과 같다

<?xml version="1.0" encoding="UTF-8"?>

<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"

xsi:schemaLocation="http://java.sun.com/xml/ns/javaee 

                             http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"

id="WebApp_ID" version="3.0">


<display-name>board</display-name>

<welcome-file-list>

<welcome-file>index.jsp</welcome-file>

</welcome-file-list>


<servlet>

<servlet-name>spring</servlet-name>

<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>

<load-on-startup>1</load-on-startup>

</servlet>


<servlet-mapping>

<servlet-name>spring</servlet-name>

<url-pattern>/</url-pattern>

</servlet-mapping>


<listener>

<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>

</listener>


<filter>

<filter-name>encodingFilter</filter-name>

<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>

<init-param>

<param-name>encoding</param-name>

<param-value>UTF-8</param-value>

</init-param>

</filter>


<filter-mapping>

<filter-name>encodingFilter</filter-name>

<url-pattern>/*</url-pattern>

</filter-mapping>


<filter>

<filter-name>springSecurityFilterChain</filter-name>

<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>

</filter>


<filter-mapping>

<filter-name>springSecurityFilterChain</filter-name>

<url-pattern>/*</url-pattern>

</filter-mapping>

</web-app>



applicationContext.xml 파일의 내용은 다음과 같다.

<beans:beans xmlns="http://www.springframework.org/schema/security"

xmlns:beans="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xsi:schemaLocation="http://www.springframework.org/schema/beans 

                    http://www.springframework.org/schema/beans/spring-beans-3.0.xsd

                    http://www.springframework.org/schema/security 

                    http://www.springframework.org/schema/security/spring-security-3.1.xsd">



<beans:bean id="userService" class="com.xyzlast.services.UserServiceImpl" />

<beans:bean id="loginProcessHandler" class="com.xyzlast.handlers.LogInProcessHandler" />

<beans:bean id="encoder"

class="org.springframework.security.crypto.password.StandardPasswordEncoder" />

<http use-expressions="true">

<intercept-url pattern="/account/index" access="permitAll" />

<intercept-url pattern="/**" access="isAuthenticated()" />

<intercept-url pattern="/welcome" access="isAuthenticated()" />

<form-login login-page="/account/index"

authentication-success-handler-ref="loginProcessHandler"

authentication-failure-handler-ref="loginProcessHandler" />

</http>


<authentication-manager>

<authentication-provider user-service-ref="userService">

<password-encoder ref="encoder" />     

</authentication-provider>

</authentication-manager>

</beans:beans>   



Posted by Y2K
,

이제 구성된 서비스를 중심으로 REST API 서버를 구축해보도록 한다. 


먼저, 구축할 REST API는 다음과 같다. 


  • rest/person/{id} : (GET) , Person 데이터를 얻어온다.
  • rest/person/list : (GET) 전체 사용자 데이터를 얻어온다.
  • rest/person/edit : (POST) 사용자 데이터를 수정한다.
  • rest/person/delete/{id} : (DELETE) 사용자를 삭제한다.

1. pom.xml 설정


REST API를 구성하기위해서, 객체의 JSON serialization을 담당하는 jar가 필요하다. 주로 사용되는 jackson을 추가하도록한다.


<dependency>

<groupId>org.codehaus.jackson</groupId>

<artifactId>jackson-mapper-asl</artifactId>

<version>1.9.9</version>

</dependency>


주의사항 : jackson-core-asl은 jackson의 core만을 가지고 있다. jackson-mapper-asl을 추가해야지, spring과 연결되는 REST view가 구현된다. 


2. web.xml에 다음과 같은 설정을 추가한다.


<servlet>

<servlet-name>spring</servlet-name>

<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>

<init-param>

<param-name>contextConfigLocation</param-name>

<param-value>/WEB-INF/spring-servlet.xml</param-value>

</init-param>

<load-on-startup>1</load-on-startup>

</servlet>

<servlet-mapping>

<servlet-name>spring</servlet-name>

<url-pattern>/</url-pattern>

</servlet-mapping>


servlet이름을 spring으로 하고, spring bean 설정 file이 위치한 path를 설정한다. 이때, servlet-class는 spring web framework의 Front Controller Class인 DispatcherServlet이 된다.



3. spring-servlet.xml을 작성한다.


controller들을 등록하기 위해서, 다음 설정을 추가한다.  아래 설정은 Spring MVC의 @Controller annotation을 구성한 모든 class들을 Spring bean에 등록하고, URL에 mapping하는 작업을 한다.


<context:component-scan base-package="com.xyzlast.controllers"></context:component-scan>

<mvc:annotation-driven />



4. PersonAPIController를 작성한다. 


@Controller annotation을 이용해서 Controller로 등록을 하고, @RequestMapping을 이용해서 URL을 구성한다.


@Controller

public class PersonAPIController {

@Resource(name="personService")

private IPersonService personService;

@RequestMapping(value = "rest/person/{id}")

@ResponseBody 

public Person get(@PathVariable Integer id) {

Person person = personService.get(id);

return person;

}

@RequestMapping(value="rest/person/list")

@ResponseBody

public List<Person> list() {

return personService.getAll();

}

@RequestMapping(value="rest/person/add", method=RequestMethod.PUT)

@ResponseBody

public Person add(@RequestParam(value="firstName", required=true) String firstName,

@RequestParam(value="lastName", required=true) String lastName, 

@RequestParam(value="money", required=true) Double money) {

return personService.add(firstName, lastName, money);

}

@RequestMapping(value="rest/person/edit", method=RequestMethod.POST)

@ResponseBody

public Person edit(@RequestParam(value="id", required=true) Integer id, 

          @RequestParam(value="firstName", required=true) String firstName,

          @RequestParam(value="lastName", required=true) String lastName,

          @RequestParam(value="money", required=true) Double money) {

return personService.edit(id, firstName, lastName, money);

}

@RequestMapping(value="rest/person/delete/{id}", method = RequestMethod.DELETE)

@ResponseBody

public Person delete(@PathVariable Integer id) {

Person person = personService.get(id);

if(person != null) {

personService.delete(id);

}

return person;

}

}




5. Controller Test code를 작성한다.

Test code는 MockHttpServletRequest와 MockHttpServletResponse를 이용해서 구성하면 된다. 각 URL에서의 request/response 모두를 측정가능하다. 


먼저, servlet-api를 pom.xml에 추가한다. tomcat과 같은 servlet container가 항시 로드하는 jar지만, 지금은 JUnit test runner에서 돌리기 위한 것임으로 maven에 추가를 해준다. 테스트에서만 사용할 것이기 때문에 scope를 test로 주는것이 매우 중요하다.


<dependency>

<groupId>org.apache.tomcat</groupId>

<artifactId>servlet-api</artifactId>

<version>6.0.35</version>

</dependency>


그리고 아래와 같이 테스트 코드를 작성한다.

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("/spring-context.xml")
public class PersonAPIControllerTest {
@Resource
private IPersonService personService;
    @Autowired
    private RequestMappingHandlerAdapter handlerAdapter;

    @Autowired
    private RequestMappingHandlerMapping handlerMapping;
    
    private Person person;
    
    @Before
    public void setup() {
    person = personService.add("ykyoon", "lastName", 100.00);
    }
    
    @Test
    public void get() throws Exception {
    MockHttpServletRequest request = new MockHttpServletRequest("GET","/rest/person/" + person.getId().toString());
    MockHttpServletResponse response = new MockHttpServletResponse();

    Object handler = handlerMapping.getHandler(request).getHandler();
    ModelAndView modelAndView = handlerAdapter.handle(request, response, handler);    
    System.out.println(response.getContentAsString());    
    assertNull(modelAndView);
    }
    
    @Test
    public void edit() throws Exception {
    MockHttpServletRequest request = new MockHttpServletRequest("POST", "/rest/person/edit");
    request.addParameter("id", person.getId().toString());
    String editedFirstName = "EDITED_FIRSTNAME";
    request.addParameter("firstName", editedFirstName);
    request.addParameter("lastName", person.getLastName());
    request.addParameter("money", person.getMoney().toString());
   
    MockHttpServletResponse response = new MockHttpServletResponse();
   
    Object handler = handlerMapping.getHandler(request).getHandler();
    ModelAndView modelAndView = handlerAdapter.handle(request, response, handler);    
    System.out.println(response.getContentAsString());    
    assertNull(modelAndView);
   
    Person editedPerson = personService.get(person.getId());
    assertThat(editedPerson.getFirstName(), is(editedFirstName));
    }
    
    @Test
    public void delete() throws Exception {
    MockHttpServletRequest request = new MockHttpServletRequest("DELETE", "/rest/person/delete/" + person.getId().toString());
    MockHttpServletResponse response = new MockHttpServletResponse();

    Object handler = handlerMapping.getHandler(request).getHandler();
    ModelAndView modelAndView = handlerAdapter.handle(request, response, handler);    
    System.out.println(response.getContentAsString());    
    assertThat(response.getStatus(), is(200));
    assertNull(modelAndView);

    Person deletedPerson = personService.get(person.getId());
    assertNull(deletedPerson);
    }
}






Posted by Y2K
,