Docker 관련으로 된 글을 보던중 Vagrant를 알게 되어 Vagrant를 설치하게 되었다. Vagrant에 대한 설치는 도커 Docker 기초 확실히 다지기 이 글을 보고 Vagrant를 설치했다. 그러나 Vagrant를 설치 및 운영하다 보면 이슈사항이 나올것 같아서 개인 기록 및 공유 차원에서 이 글에 기록해두고자 한다. 그래서 이 글은 그런 이슈 사항이 나올때마다 갱신될 예정이다(근데 Vagrant 이건 발음이 어떻게 되나..베이그랜트? 바그랜트?)


1. Windows 10에서 위에 링크되어 있는 글을 보고 설치한뒤 centos/7 으로 VM을 하나 만들어서 vagrant ssh를 실행하니 vagrant@127.0.0.1: Permission denied (publickey,gssapi-keyex,gssapi-with-mic). 에러가 발생하면서 ssh에 대한 접속이 이루어지지 않았다. 이것에 대해 검색해보니 환경변수로 VAGRANT_PREFER_SYSTEM_BIN을 만든 뒤 이 변수에 값을 0을 주면 해결이 된다고 해서 이 방법을 써서 해결이 되었다. VAGRANT_PREFER_SYSTEM_BIN 환경변수가 궁금한 사람은 여기를 클릭해서 보면 된다.


2. 1번의 문제를 해결한뒤 ssh를 접속해보니 vagrant 란 계정을 디폴트로 만든뒤에 이 계정으로 접속이 되었다. 그러나 root 계정이 아니기 때문에 yum 명령을 이용해서 프로그램을 설치할려면 sudo 명령을 같이 써야 한다(ex: sudo yum ...) 이것이 불편해서 root 계정의 패스워드가 무엇인지 찾아봤는데 기본적으로 root 계정의 패스워드는 vagrant로 설정하고 있다. 그러나 현재는 Vagrantfile에서 root 계정의 패스워드를 설정하는 방법을 찾지는 못한 상황이다. VM을 만드는 시점에 root 계정의 패스워드를 지정하면 편할텐데 아직은 모르겠다.


3. Windows 10에서 Vagrant를 설치하니 Vagrant에서 box로 통하는 이미지들(docker로 하다보니 용어가 이미지로 되어버렸다)은 받아지는 위치의 기준이 C:\Users\사용자 계정\.vagrant.d 이 디렉토리 밑에 boxes라는 디렉토리에 받아지고 있었다. .vagrant.d 디렉토리가 Vagrant의 Home 디렉토리 역할을 하고 있다. 이 Home 설정 개념이 Java나 Maven과는 약간 다른데 Java나 Maven의 경우 JAVA_HOME과 MAVEN_HOME은 Java나 Maven이 설치된 디렉토리를 가르켜야 하지만 Vagrant의 경우는 그렇지가 않다(실제로 나는 Vagrant를 D:\Vagrant에 설치했다) 이 이미지를 받는 디렉토리가 C 드라이브에 있을 경우 C 드라이브 용량이 작아지는 상황이 벌어질수 있어서 위치를 바꾸었다. 위치르 바꿀때는 환경변수로 VAGRANT_HOME에 지정하면 된다(나는 VAGRANT_HOME을 D:\Vagrant_VMS\.vagrant.d 로 지정했다) 이때 주의점이 있다. VAGRANT_HOME에 매핑되는 디렉토리를 Vagrant가 설치된 디렉토리(나의 경우로 예를 들면 D:\Vagrant 가 된다)의 하위 디렉토리로 설정하면 안된다. Vagrant를 설치하면 Vagrant가 설치된 디렉토리는 일반 계정으로는 접근이 안되기 때문에 읽거나 쓰지 못하는 상황이 벌어져서 이미지를 다운로드 받지 못하게 된다. 그래서 위에서 언급한 설정으로 Vagrant 설치 디렉토리가 아닌 D:\Vagrant_VMS\.vagrant.d로 지정했다.


트랙백을 확인할 수 있습니다

URL을 배껴둬서 트랙백을 보낼 수 있습니다

다른 카테고리의 글 목록

프로그래밍/기타 카테고리의 포스트 목록을 보여줍니다

집에서 사용하는 도어락 제품인 게이트맨 SR(사용한지 무려 10년은 더 된 제품이다..)에 같이 달려나온 리모콘이 고장나는 바람에 게이트맨을 제조화는 회사인 아이레보에서 나온 통합 리모콘을 구매하였다. 그러나 리모콘을 도어락과 연동하는 과정이 쉽지가 않아서 기록 차원에서 남겨둔다. 구형 도어락과 이 리모콘을 연동할때 애로사항을 겪는 분들이 많을것 같아서 이런 분들이 이 글을 검색했을때 차후에 도움이 되길 바란다.


내가 구매한 리모콘은 아래의 것이다.



사진을 찍을때 책상위에 올려놓고 찍느라 빛이 가려져서 잘 안보일수도 있는데 동그란 버튼 2개중 큰 버튼은 문을 열어주는 열림 버튼이고 작은 버튼은 문을 잠가주는 닫힘 버튼이며 위의 그림에서는 안보이지만 SET이라는 글씨 왼쪽에 조그만 홈 형태로 된 버튼이 있는데 이 버튼이 등록 버튼이다. 이 리모콘은 설명서에도 설명되어 있지만 게이트맨 제품군을 A,B,C 이렇게 3가지의 타입으로 나누어서 지원한다고 되어 있는데 우리집 도어락인 게이트맨 SR의 경우는 A 타입에 속한다. 이 리모콘을 구매할 의사가 있는 사람은 인터넷에서 검색을 해보면 통신팩 이라는 일종의 모듈을 같이 사야 하는걸로 설명되어 있는데 내가 사용중인 게이트맨 SR의 경우는 원래 리모콘 기능이 포함되어 있는 기능이기 때문에 제품안에 통신 기능이 내장되어 있다. 그래서 이 통신팩을 살 필요가 없다(어디까지나 게이트맨 SR에만 해당되는 경우이며 다른 제품을 사용중이라면 카카오 플러스 친구로 게이트맨 고객센터를 등록한뒤 상담하면 구매해야 하는지의 여부를 알 수 있다)


리모콘에 대한 대략적인 설명은 마치고 이제 힘들었던 부분에 대해 설명하겠다. 카카오톡 플러스 친구로 게이트맨 고객센터를 등록한 뒤 상담을 해보니까 리모콘을 산 뒤에 해줘야 할 작업이 있었다. 바로 주파수 변경 이란 작업이다. 리모콘 설명서에서 보면 아래의 내용에 해당하는 것이다.



리모콘의 주파수는 위의 그림에서 B & C Type 제품군과 연결되는 상태인 푸른 램프가 켜지는 상태로 출시가 되어 나온다. 그래서 A Type 제품군을 사용할 경우엔 위의 과정을 거쳐서 적색 램프가 점등이 되게끔 설정을 해주어야 한다. 근데 문제는 설명서대로 해도 이게 되지를 않았다. 이것땜에 고객센터에 직접 전화를 걸어 메뉴얼대로 했는데도 안되었다고 얘길하니 고객센터에서는 내가 사는 지역의 대리점과 연결해주었지만 대리점에서는 이 제품이 자신이 대리점을 차리기도 전에 나온 제품이라 모르겠다며 오히려 본사 제품관리부에 연락을 하라고 했다. 그래서 다시 고객센터에 전화를 걸어 설명을 하니 기사를 보내겠다고 하길래 우리 지역 담당 기사도 다룰수가 없는 제품을 누가 다루겠냐고 하자 다른 지역 기사에게 상황을 설명한뒤 보내겠다고 한다. 그러나 이럴 경우 출장비가 나올수도 있다고 했다. 리모콘이 불량이어서 도어락과 연결이 안되면 출장비를 받지 않지만 그렇지가 않은 경우엔 출장비가 나온다고 한다. 그래서 출장수리는 일단 보류하고 가족 구성원들 의견을 물은뒤에 출장수리 받는 것으로 결정나서 다시 고객센터에 전화를 거니 이번 상담원은 이제껏 나랑 얘기했던 상담원 2명과는 연결하는 방법에 대해 다른 얘기를 해서 그 방법대로 해서 성공을 했다. 썰이 약간 길었는데 이제 그 방법에 대해 설명하겠다.


나는 주파수 변경 작업을 할때 왼손에 리모콘을 들고 오른손에 SET 버튼을 누르기 위한 이쑤시개를 들고서 했다. 그런뒤 리모콘을 들고 있는 왼손 엄지손가락으로 닫힘 버튼을 누르고 이때 동시에 오른손으로 들은 이쑤시개로 SET 버튼을 눌러서 주파수 변경 작업을 했다. 이때 나타나는 반응은 파란색 램프만 계속 깜박깜박하다가 꺼지는 그런 상황이었다. 몇번을 해도 빨간색 램프가 나오지를 않았다. 근데 이번 상담원의 경우 이런 방법으로 설명했다.


  1. 리모콘을 손으로 들지 말고 책상이나 방바닥 같은 평평한 곳에 둔다.
  2. 1번과 같은 상태에서 손으로 잡고 있는 이쑤시개로 리모콘의 SET 버튼 구멍에 넣기만 하고 아직 버튼을 누르지는 않은 상태로 있는다(이쑤시개로 SET 버튼을 언제는 누를수 있게끔 이쑤시개를 구멍에 넣은 상태로 이쑤시개 놓지 말고 손에 붙잡은 상태로 대기)
  3. 2번 상태에서 닫힘 버튼을 누르면서 손으로 붙잡고 있던 이쑤시개를 눌러 SET 버튼을 동시에 누른다.
  4. 파란색 램프가 깜빡이고 있는동안에는 누르고 있는 SET 버튼와 닫힘버튼을 떼지 말고 계속 누르고 있다가 깜빡이는게 꺼지면 그때 SET 버튼과 닫힘버튼에서 손을 뗀다
  5. 다시 2번과 3번 과정을 순서대로 진행한다.


이렇게 하니까 5번을 진행할때 빨간색 램프가 한번 깜빡이다가 꺼지는게 보였다. 이렇게 해서 주파수 변경을 할 수 있었다. 이 과정을 진행할때는 도어락과 가까이 있는데서 진행하는게 좋을듯하다. 실제로 이 작업을 진행했던 내 방의 경우 도어락이 있는 출입문의 옆에 있는 방이어서 가까운 위치에 있었다. 주파수를 변경한 뒤에는 리모콘 등록하는 것은 리모콘 설명서에 있는 A 타입에 있는 설명대로 진행해서 리모콘을 등록했다(이 부분도 게이트맨 SR의 경우 차이가 있는데 리모콘 메뉴얼의 경우엔 키등록버튼이라고 되어 있지만 게이트맨 SR의 경우 카드등록 버튼이 그 버튼이었다. 도어락 실내부를 열어보면 건전지를 넣는 곳 아래에 버튼이 2개 있는데 왼쪽 버튼이 리모콘 등록 버튼이라고 생각하면 될 듯 하다)


왜 이리 힘들었나 지금 생각해보면 좀 엉뚱한 결론일수는 있겠지만 손으로 리모콘을 잡고 한것이 리모콘의 주파수 변경에 무언가 영향을 준것이 아닌가 싶다. 적어도 리모콘 설명서에 손으로 리모콘을 잡지 말고 평평한 바닥에 놓고 하라고 했으면 덜 헤맸을텐데 이것땜에 몇시간을 소비하며 하마터면 엉뚱한 출장비까정 물뻔 했다. 나와 같은 사람에게 도움이 되기를 바란다.

트랙백을 확인할 수 있습니다

URL을 배껴둬서 트랙백을 보낼 수 있습니다

다른 카테고리의 글 목록

사는 얘기/나의 흔적 카테고리의 포스트 목록을 보여줍니다

Eclipse에서 Darkest Dark Theme 적용 후 Git과 연동하면서 색상이 보기 흉하게 바뀌는 상황이 있었다. 이유는 Git으로 파일 관리를 하면서 파일의 상태가 바뀌었을때 이에 대해 표현하는 방법때문이었다. 예를 들면 파일을 수정할 경우 Commit이 아직은 안되어 있는 상태이기 때문에 Uncommited 인데 이때 이 자원을 나타내는 background 색상이 흰색으로 설정되어 있어서 전체적으로 검은색 테마에서 배경색을 흰색으로 주다보니 어울리지 않아 보기 싫은 상황이 생겼다. 암튼 Darkest Dark Theme를 사용하면서 배경색이 흰 색으로 되어 있는 부분은 튀어 보이는 상황이 벌어지는 것이 있는데 일단 예를 들은 경우를 수정할려면..(차후에 이런 상황들이 또 일어나면 여기에 정리할 예정이다)


1. Window -> Preference -> General -> Appearance -> Color And Fonts 에 간 뒤 Git 항목의 하위 항목으로 Uncommited Change(Background)를 찾아보면 현재 색상이 흰 색으로 되어 있는데 이것을 검정으로 바꿔준다.


2. Git의 ignored 파일에 속한 파일들도 그 배경색이 흰 색이기 때문에 Window -> Preference -> General -> Appearance -> Color And Fonts 에 간 뒤 Git 항목의 하위 항목으로 Ignored Resource (Background) 를 찾아서 이 색상을 검정으로 바꿔주면 된다.

트랙백을 확인할 수 있습니다

URL을 배껴둬서 트랙백을 보낼 수 있습니다

다른 카테고리의 글 목록

프로그래밍/Java 카테고리의 포스트 목록을 보여줍니다

지난 글에서는 Eclipse와 git을 사용해서 github에 자신이 만든 프로젝트를 올리는 방법에 대해 설명했다. 이번엔 그 반대의 상황으로 github에 올라와 있는 프로젝트를 자신의 eclipse workspace로 받는 방법에 대해 설명하고자 한다. 환경은 지난글과 동일한 환경이어서 환경에 대한 얘기는 생략하기로 하겠다. 대신 지난번 글을 보고 그대로 따라한 상황에서 이번 글을 보고 그대로 따라하고 싶으시다면 지난번 글에 올라간 자신의 프로젝트를 eclipse workspace에서 삭제해주길 바란다. 이번 글로 설명할때 프로젝트가 자신의 workspace에는 없다는 가정에서 할 것이기 때문에 eclipse workspace에 등록되어 있는 github 에 등록된 자신의 프로젝트를 삭제하고 진행하길 바란다(이때 프로젝트에서 마우스 우클릭하여 나오는 context menu에서 Team -> disconnect 를 선택하여 git과의 연동을 끊은 뒤에 프로젝트가 있는 worksppace 디렉토리에서 해당 프로젝트 디렉토리가 삭제가 된 상태로 삭제 처리를 해주어야 한다)


먼저 해야 할 것은 지난 글을 통해 자신의 프로젝트 소스가 올라간 github repository에 가서 주소를 얻어와야 하는 일이다. 아래의 그림은 github repository를 브라우저로 방문했을때 나타나는 화면이다.



여기서 빨간 박스로 표시된 Clone or download 버튼을 클릭하면 위의 그림과 같이 Clone with HTTPS 타이틀을 가진 조그만 레이어 화면이 나오는데 이때 파란색 박스로 표시된 버튼을 클릭하여 현재 방문한 repository의 URL을 복사하여 문서편집기에 일단 붙여넣자. 그리고 탐색기를 통해 eclipse에서 현재 사용중인 workspace 디렉토리로 이동한 뒤 마우스 우클릭하여 나오는 context menu에서 Git Bash Here 메뉴를 선택한다.



여기서 중요한 점은 이 작업을 하게 되는 디렉토리다. 이 부분은 이전 글과는 차이점이 있다. 이전 글에서는 Git Bash 화면을 띄우는 위치가 해당 프로젝트의 디렉토리였다. 그러나 이번글은 프로젝트가 없는 상태에서 github에 등록된 자신의 프로젝트를 받는 것이기 때문에 이전 글과 같이 해당 프로젝트의 디렉토리로 이동할 수 없는 상황이다. 그렇기 때문에 이 작업을 할때는 이 프로젝트를 받게되는 eclipse workspace 디렉토리에서 해야 한다. 이 작업을 하게 되면 해당 디렉토리에서 Git Bash Prompt 화면을 띄워주게 된다. 이 상태에서 다음의 명령어를 입력해주면 된다. 


git clone remote repository 주소


여기서 remote repository 주소 란 것은 아까 위에서 github에서 복사하여 문서편집기에 붙여넣은 주소이다. 아래의 그림은 다음의 명령어를 실행한 화면이다.



위의 화면을 보면 명령어를 remote repository에 접속하여 해당 remote repository에 올라와 있는 것을 받게 된다. 이때 remote repository 이름(여기서는 samplemvn이 된다)으로 디렉토리를 만들어 받게 된다. 현재 이 명령어를 실행할때 eclipse의 workspace 디렉토리에서 실행했기 때문에 workspace 디렉토리에 remote repository 이름으로 디렉토리를 만들어서 받게 된다. 이때 git local repository도 같이 생성해주기 때문에 이전 글에서 했던 git init 명령을 하지 않는다. 아래의 화면은 이 작업을 진행한 뒤의 workspace 디렉토리를 탐색기로 본 화면이다. samplemvn이란 디렉토리가 만들어져있다. 그래서 git으로 할 작업은 모두 다 끝났다.(벌써? 이걸로?)



그러면 이걸로 진정 끝난걸까? 아니다. 끝난것은 git으로 할 작업뿐이다. eclipse의 workspace 디렉토리에 해당 repository를 다운로드 받으면 eclipse에서 바로 사용할 수 있을까? 전혀 아니다. 왜냐면 workspace 디렉토리에 다운로드만 받은것 뿐이지 이것이 eclipse의 프로젝트로 등록되어진 상태는 아니기 때문이다. 그래서 지금부터는 이렇게 다운로드 받은 프로젝트를 eclipse에 등록하는 작업을 진행하고자 한다. 먼저 eclipse에서 다음과 같이 Package Explorer에서 마우스 우클릭하여 나오는 메뉴에서 import 메뉴를 실행한다.



Import 메뉴를 실행하면 아래의 화면과 같이 Import 화면이 나오는데 여기서 Git의 Projects from Git 선택한뒤 Next 버튼을 클릭한다.



Projects from Git을 선택하면 아래와 같은 화면이 나오는 데 여기서 Existing local repository를 선택한 뒤 Next 버튼을 클릭한다.



Next 버튼을 클릭하면 아래와 같은 화면이 나오는데 여기서 빨간 박스로 표시된 Add 버튼을 클릭한다.



Add 버튼을 클릭하면 아래의 화면과 같이 나타난다. 여기서 빨간 박스로 표시된 Browse... 버튼을 클릭하면 폴더 선택 화면이 나오는데 여기서 local repository 디렉토리를 선택한다. 그러면 이 디렉토리가 어떤것인가? 이전 글에서는 git init 명령을 실행하면 만들어지는 .git 디렉토리가 local repository 디렉토리였지만 지금 상황에서는 맞지 않는다. 그러면 이 디렉토리는 어디인가? 아까 git clone 명령을 실행해서 받은 디렉토리(remote repository 이름으로 된 디렉토리)의 하위 디렉토리를 보면 .git 디렉토리가 있다. 그 디렉토리를 선택해준다.



.git 디렉토리를 선택하면 아래 화면과 같이 선택한 .git 디렉토리가 화면에 보이게 되는데 이때 아래 화면과 같이 해당 .git 디렉토리를 체크 설정을 해준다. 그러면 화면이 나타날 당시엔 비활성화 되어 있는 Finish 버튼이 활성화가 된다. 아래 화면과 같이 설정해준뒤 Finish 버튼을 클릭한다.



Finish 버튼을 클릭하면 아래의 화면과 같이 선택한 local repository가 추가된다. 이때 아래 화면과 같이 추가된 local repository를 선택한 상태에서 Next 버튼을 클릭한다.



Next 버튼을 클릭하면 아래와 같은 화면이 나오는데 여기서 Import as general project를 선택한뒤 Next 버튼을 클릭한다.



Next 버튼을 클릭하면 이 진행과정의 마지막 단계로 프로젝트 이름을 설정하는 화면이 나온다. 프로젝트 이름을 입력한뒤 Finish 버튼을 클릭하면 eclipse에 프로젝트를 등록하는 모든 과정을 마치게 된다.



여기까지 진행해서 github을 통해 받은 프로젝트를 eclipse에 등록하는 과정을 마쳤다. 그러나 이것으로 끝난 것일까? 아니다. 아직 한가지 작업이 더 남았다. 아래의 화면을 보자.



위의 화면은 방금 등록한 프로젝트의 하위 구조를 열어보인 것이다. 이걸로 작업할 수 있을까? 불가능하진 않지만 불편하다. 이전 글을 통해 우리가 프로젝트를 github에 올렸을 때 Maven 기반의 프로젝트를 올렸는데 위의 화면은 Maven 프로젝트의 형태로 보이지 않는다. 이유는 우리가 이 프로젝트를 일반적인 Java 프로젝트로 등록했기 때문이다. 그래서 이 프로젝트를 Maven 기반 프로젝트로 변환해줘야 한다. 위의 그림과 같이 프로젝트에서 마우스 우클릭하여 나오는 context menu에서 configure 메뉴의 Convert to Maven Project 메뉴를 선택해준다. 그러면 프로젝트 구조를 Maven Project 구조로 바꿔주면서 아래의 화면과 같은 형태로 보여주게 된다.



이상으로 모든 과정이 끝났다. git 관련 명령을 실행하는 것도 이전 글에서와 같이 마찬가지로 Eclipse의 Team 메뉴를 통해 git 관련 명령을 실행할 수 있기 때문에 Git Bash 화면을 띄워서 작업하지 않아도 된다. 


이전 글에서와 마찬가지로 이것도 연동하는 과정에서 로그인 아이디와 비밀번호를 물어보는 상황이 나올수 있다. 이때는 github의 로그인 아이디와 비밀번호를 입력하면 된다.


트랙백을 확인할 수 있습니다

URL을 배껴둬서 트랙백을 보낼 수 있습니다

다른 카테고리의 글 목록

프로그래밍/기타 카테고리의 포스트 목록을 보여줍니다

개인적으로 eclipse와 github을 연동하면서 싫어하는 상황이 하나 있다. 그것은 Eclipse에서 git을 사용하려면 local repository 디렉토리를 설정해야 하는데 연동을 하게 되면 연동된 프로젝트들이 모두 local repository 디렉토리 밑으로 들어가게 되어서 실제로는 프로젝트 소스가 eclipse의 workspace에 존재하지 않게 된다. 이렇게 될 경우 동일한 workspace에 존재하는 여러 프로젝트들 중 git으로 공유되지 않은 프로젝트는 workspace 디렉토리에 있지만 디스크에 2군데에 존재하는 상황이 벌어진다. 이점에 있어서는 IntelliJ 가 잘되어 있는게 IntelliJ의 경우는 local repository를 특정 디렉토리로 설정하는 것이 아니라 공유 대상이 되는 Module 밑에 이 local repository를 두게 된다. 그래서 이클립스와는 달리 같은 소스가 2군데에 존재하는 상황은 벌어지지 않는다(git 연결을 더이상 안할려고 할 경우엔 IntelliJ에서 git과의 연동을 끊고 .git 디렉토리를 지워주면 끝이다) 그래서 이 글에서는 IntelliJ 처럼 Eclipse 프로젝트 디렉토리 밑에 git local repository를 두어서 연동하는 방법에 대해 써보고자 한다. 이 글에서 사용하는 프로그램은 Eclipse는 아니고 Eclipse 기반의 Spring Tool Suite 4(이하 STS로 하겠다)이지만 git을 연동하는것은 Eclipse나 STS 모두 EGit을 사용하기 때문에 같은 방법으로 사용할 수 있다.


이 방법으로 할 경우 먼저 git의 공식 사이트인 git-scm.com에서 자신의 운영체제에 맞는 git client를 설치해야 한다. git client를 설치하는 방법은 다른 블로그들에 이미 많이 소개되어 있어서 여기서는 소개하지는 않고 이것이 설치가 되어 있다는 상태에서 설명을 시작하도록 하겠다. 먼저 git의 remote repository를 만들어준다. github에서 자신이 만든 remote repository를 들어가면 이런 형태의 화면을 보게 될 것이다.



이 화면에서 빨간색 박스가 쳐져 있는 버튼을 클릭해서 현재 자신이 만든 remote repository의 주소를 복사한 뒤에 메모장 같은 문서 편집기에 붙여넣는다. 이 주소는 나중에 쓸 것이라서 일단은 다른데에 보관하는 차원에서 붙여넣어둔 것이다.


그 다음으로 remote repository에 올릴 프로젝트의 디렉토리로 이동한다. 여기서는 설명을 하기 위해 D:\workspaces\samplemvn 디렉토리가 Eclipse의 프로젝트 디렉토리라고 하겠다. 여기서 마우스 우클릭을 하면 다음과 같은 화면이 나타난다(이 프로젝트는 STS에서 Spring Starter Project 메뉴를 이용해서 만든 Maven 기반의 Spring Boot 프로젝트이다. 그래서 .gitignore 파일이 있다)



위에서 언급했던 Git Client를 설치했다면 마우스 우클릭시 다음과 같이 Git GUI Here란 메뉴와 Git Bash Here 란 메뉴가 나타날것이다. 여기서 Git Bash Here 메뉴를 선택한다. 그러면 이 폴더에 위치한 상태로 Git Bash 화면이 나타난다. 이 화면에서 다음의 순서대로 명령어를 입력해서 실행시켜 준다. 그러면 아래의 그림과 같이 나타난다.


  1. git init
  2. git add .
  3. git commit -m "commit message"
  4. git remote add origin 문서편집기에 붙여 두었던 github remote repository 주소
  5. git push origin master

 


위에서 나열한 명령어가 하는 작업에 대해 간략하게 설명하자면 git init이란 명령을 해서 현재 디렉토리를 기준으로 local repository를 만들게 된다. 이 명령을 하면 .git 폴더가 숨김 폴더 형태로 만들어지게 된다. git add . 을 실행해서 local repository에 보관되어 있는 것과 변경점이 있는 파일들을 index에 추가한다. 여기서는 local repository가 금방 만들어져 있는 상태여서 아무것도 없기 때문에 현재 디렉토리에 있는 모든 디렉토리와 파일이 추가된다. 그리고 git commit -m "commit message" 를 통해 index에 추가된 디렉토리와 파일을 commit 하게 된다. 위의 그림을 보면 Init Project 로 commit message를 설정해서 명령을 실행했다. 그리고 git remote origin remote repository 주소를 통해 remote repository를 추가한다. 여기서 사용하는 remote repository 주소는 아까 문서편집기에 붙여넣은 remote repository 주소를 사용한다. 그리고 이렇게 사용되는 remote repository 주소를 origin 이란 이름으로 remote repository에 추가한다. 그리고 마지막으로 git push -u origin master 를 통해 local repository에 있는 디렉토리와 파일들을 remote repository에 추가한다. 이렇게 자신의 프로젝트가 github에 방금 만들어놓은 remote repository에 들어가게 된다. 이 과정을 거친뒤에 github에 들어가면 다음과 같이 자신의 프로젝트 소스들이 들어가 있는 repository를 보게 된다. 



지금까지 한 과정은 github과의 연동을 전체 과정중 절반까지 한 것이다. 우리가 github 연동할 때 위에서 사용했던 Git Bash 화면으로만 할 것이면 전부 다 한것이지만 그렇게 할 경우엔 git 명령을 일일이 프롬프트 화면에서 명령어 입력하는 형태로 진행해야 하기 때문에 불편하다. Git Bash  화면으로 하는 것은 연동에 대한 초기화 작업만 진행하는 것이고 그 다음부터 commit, push, pull 등의 git 관련 명령을 실행하는 것은 eclipse에서 하는 것이 편하다. 실제 현재 상황을 설명하자면 git 과의 연동은 Git Bash 화면에서는 모두 끝이 났지만 eclipse에서는 아무런 연동이 되어 있지 않은 상황이다. 그래서 지금부터 하는 설명은 eclipse에서 git 명령을 할 수 있게 연동하는 과정을 설명하겠다. eclipse에서 Git Bash 화면을 통해 github과 연동되어 있는 프로젝트에서 마우스 우클릭을 하면 아래와 같은 context menu가 나오는데 이 메뉴에서 아래 그림과 같이 Team -> Share Project... 를 선택한다.



Share Project... 메뉴를 선택하면 아래의 그림과 같이 나타난다. 흔히 eclipse와 git 연동 관련 글을 보게 되면 이 과정에서 나오는 화면과는 다른 모습이 보일 것이다. 왜냐면 기존 글은 git을 연동하지 않은 상태에서 메뉴를 선택했기 때문에 local repository를 만드는 과정을 거치지만 우리는 이미 Git Bash 화면을 통해 local repository를 만들어놓은 상태이기 때문에 local repository 디렉토리(여기서는 .git 디렉토리)를 자동으로 인지하게 되어서 아래의 그림과 같이 나타난다. 아래와 같이 화면이 나온 상태에서 Finish 버튼을 클릭하면 Eclipse에서 Git을 연동한 과정이 모두 끝나게 된다.



이렇게 모든 과정을 진행한뒤 다시 eclipse 프로젝트에서 마우스 우클릭을 해서 Team 메뉴를 들어가보면 아래의 그림과 같이 Git 관련 명령을 사용할 수 있는 메뉴들을 볼 수 있게 된다. 이후부터 git 관련 작업은 Git Bash 화면이 아닌 eclipse의 Team 메뉴를 통해서 작업하면 된다.



이렇게 자신이 만든 프로젝트를 github과 같은 remote repository에 올리는 방법에 대해 설명했다. 지금까지 설명한 내용을 살펴보면 eclipse에서 local repository를 만든것이 아니라 Git Bash 화면을 통해 현재 프로젝트 디렉토리 하위에 만들었기 때문에 프로젝트 소스가 workspace에 존재하지 않는 그런 상황이 발생되지 않는다. 이 글에서는 자신이 만든 프로젝트를 remote repository에 올리는 방법에 대해 설명했고 다음 글에서는 remote repostory에 올라와 있는 소스를 자신의 eclipse workspace 디렉토리에 가져오는 방법에 대해 설명하겠다.


이 글에서는 언급하지 않았지만 연동하는 과정에서 로그인 아이디와 비밀번호를 물어보는 상황이 나오게 된다. 이때는 github의 로그인 아이디와 비밀번호를 입력하면 된다.


트랙백을 확인할 수 있습니다

URL을 배껴둬서 트랙백을 보낼 수 있습니다

다른 카테고리의 글 목록

프로그래밍/기타 카테고리의 포스트 목록을 보여줍니다

이번 글은 이 연재의 마지막으로 해당 템플릿에서의 예외처리에 대해 설명하도록 하겠다. 우리가 만든 Spring Application에서는 이런저런 이유로 예외가 발생할 수 있다. 비즈니스적인 로직으로 인해 발생하는 예외도 있고 Runtime 예외도 있다. 예외가 발생할 경우 try-catch를 이용해서 발생시점에서 해당 예외에 대한 처리를 할 수 있지만 비즈니스적으로 이러한 예외처리를 해야 하는게 아니라면 발생된 예외는 호출한 곳으로 계속 던져지게 된다. 단계별로 예외가 계속 던져지게 되면 최종적으로는 Controller 까지 도달하게 되며 Controller에서도 예외 처리를 안하게 되면 Controller 에서는 다시 예외를 던져서 Spring에서 정해져있는 ExceptionResolver 객체에서 이를 처리하게 된다. ExceptionResolver 객체에는 처리해야 할 예외 클래스와 이 예외를 보여줘야 할 View를 정해주기 때문에 해당 예외에 대한 출력이 가능해진다. 그러나 여기서는 ExceptionResolver 객체를 별도로 만들어 등록하지 않고 @ControllerAdvice 와 @ExceptionHandler 를 이용해서 예외를 처리하게 된다. 다음은 이러한 예외를 처리하는 GlobalExceptionHandler 클래스이다.


@ControllerAdvice
public class GlobalExceptionHandler {

	@ExceptionHandler(value={DataAccessException.class})
	public XplatformView processDataAccessException(DataAccessException ex){
		XplatformView xplatformView = new XplatformView("30", ex.getMessage());
		return xplatformView;
	}

	@ExceptionHandler(value={Exception.class})
	public ModelAndView processException(Exception ex){
		ModelAndView modelAndView = new ModelAndView();
		modelAndView.addObject("ErrorCode", "156");
		modelAndView.addObject("ErrorMsg", ex.getMessage());
		modelAndView.setViewName("errorView");
		return modelAndView;
	}
}


코드를 보면 클래스 레벨에 @ControllerAdvice 어노테이션을 붙이고 메소드 레벨에 @ExceptionHandler 어노테이션을 붙이고 있다. @ExceptionHandler 어노테이션에 처리해야 할 예외를 정의하게 되는데 예외를 하나의 메소드에서 여러개를 처리해야 할 경우 배열 형태로 정의해서 설정할 수 있다(코드에서도 보면 예외 클래스를 1개만 설정하고 있어도 표현하는 방법은 배열 형태의 {}를 사용해서 설정하고 있다) 세부 코드를 보면 예외를 설정해서 View로 표현하는 방법이 2가지의 방법으로 구현되고 있다. 하나는 이전 글에서 설명했던 글인 XplatformView 클래스 객체를 생성해서 이를 return 하는 방법이 있고, 다른 하나는 우리가 늘 익숙했던 코드인 ModelAndView 클래스 객체를 생성해서 여기에 ErrorCode와 ErrorMsg 란 이름으로 값을 설정한 뒤 이 객체를 return 하고 있다. 결과만 얘기하면 둘다 동일한 형태의 결과를 보여준다. 예전 글에서 XplatformView 클래스 코드에 대해 설명할 때 다음의 내용을 설명한 것이 있다.


if(!model.containsKey("ErrorCode")) {
	variableList.add("ErrorCode", ERROR_CODE_VALUE);
}

if(!model.containsKey("ErrorMsg")) {
	variableList.add("ErrorMsg", ERROR_MSG_VALUE);
}


XplatformView 코드를 보면 ERROR_CODE_VALUE와 ERROR_MSG_VALUE 멤버변수가 있고 이를 설정하는 생성자도 있다. GlobalExceptionHandler 클래스의 processDataAccessException 메소드는 XplatformView 생성자를 통해 이 멤버변수를 설정했고 위의 코드를 실행하는 시점에 model에 ErrorCode와 ErrorMsg 가 없을 경우 model에 ErrorCode와 ErrorMsg를 각각 key로 주어서 이 멤버변수 값이 들어가게 된다. 이러한 방법으로 processDataAccessException 메소드가 해당 예외에 대한 코드와 메시지를 설정하게 된다. 그러나 processDataAccessException 메소드는 model에 ErrorCode와 ErrorMsg를 설정한 상태에서 XplatformView에 전달되기 때문에 위의 코드가 실행이 되지 않는다. 만약 XPlatformView 생성자에서 에러코드와 에러메시지를 설정하고 model에도 ErrorCode와 ErrorMsg가 설정된 상태로 XplatformView에 전달되면 model에 있는 것으로 전달이 된다. 여기서는 예시로 DB 관련 예외인 DataAccessException 과 Exception 2개만 예로 들었지만 예외와 그 처리 방법에 따라 메소드를 늘려가며 작업하면 된다.


지금까지 Spring과 Xplatform을 연동한 템플릿에 대한 설명을 진행했다. 궁금한 내용이 있으면 댓글로 달아주길 바란다. 그리고 이것은 모든 상황에 대응할 수 있는 템플릿은 아니다. 이 템플릿은 Xplatform에서 제공하는 샘플 예제로 만들어놓은 템플릿이기 때문에 아주 기본적인 상황에서는 대응할 수 있지만 그거보다 확장된 상황에서는 대응할 수가 없다(내가 받은 Xplatform 예제엔 파일 업로드 기능이 없어서 이에 대한 내용을 템플릿에 구현하지를 못했다. 지인에세 물어본바로는 Xplatform도 Java단에서 MultipartRequest 로 받는다고 하기 때문에 XplatformArgumentResolver에서 MultipartRequest로 받아서 진행하면 될 것으로 생각한다) 그러니 적용시엔 커스터마이징 할 것을 생각하고 진행하길 바란다.


 1. Xplatform과 Spring Framework 연동 템플릿으로 보는 HandlerMethodArgumentResolver와 ViewResolver (1) - 개요

 2. Xplatform과 Spring Framework 연동 템플릿으로 보는 HandlerMethodArgumentResolver와 ViewResolver (2) - 사전지식

 3. Xplatform과 Spring Framework 연동 템플릿으로 보는 HandlerMethodArgumentResolver와 ViewResolver (3) - HttpServletRequestWrapper

 4. Xplatform과 Spring Framework 연동 템플릿으로 보는 HandlerMethodArgumentResolver와 ViewResolver (4) - Spring Controller에서 하려는 것

 5. Xplatform과 Spring Framework 연동 템플릿으로 보는 HandlerMethodArgumentResolver와 ViewResolver (5) - HandlerMethodArgumentResolver 분석(1)

 6. Xplatform과 Spring Framework 연동 템플릿으로 보는 HandlerMethodArgumentResolver와 ViewResolver (6) - HandlerMethodArgumentResolver 분석(2)

 7. Xplatform과 Spring Framework 연동 템플릿으로 보는 HandlerMethodArgumentResolver와 ViewResolver (7) - HandlerMethodArgumentResolver 분석(3)

 8. Xplatform과 Spring Framework 연동 템플릿으로 보는 HandlerMethodArgumentResolver와 ViewResolver (8) - HandlerMethodArgumentResolver 분석(4)

 9. Xplatform과 Spring Framework 연동 템플릿으로 보는 HandlerMethodArgumentResolver와 ViewResolver (9) - XplatformView 분석

 10. Xplatform과 Spring Framework 연동 템플릿으로 보는 HandlerMethodArgumentResolver와 ViewResolver (10) - 예외처리


트랙백을 확인할 수 있습니다

URL을 배껴둬서 트랙백을 보낼 수 있습니다

다른 카테고리의 글 목록

프로그래밍/Spring 카테고리의 포스트 목록을 보여줍니다

이전 글까지는 Xplatform에서 넘어온 DataSetList과 VariableList를 Controller의 메소드 파라미터에서 자바 객체로 변환해주는 HandlerArgumentResolver 인터페이스 구현 클래스에 대해 설명했다. 지금까지의 내용이 Xplatform에서 넘어온 데이터들을 Spring에서 사용하기 위해 적절하게 데이터를 변환한 작업이었고 지금부터 설명한 내용은 Spring에서 나온 데이터를 Xplatform에서 사용가능한 데이터로 변환하는 작업에 대해 설명하고자 한다. 이해를 돕기 위해 설명하자면 자바에서 제공하는 List 객체로 나온 결과물을 Xplatform에서 제공하는 DataSet 클래스 객체로 변환하는 것이다. 이 부분에서도 또한 Java의 Reflection 기능을 사용해서 변환하게 된다. 이전 글들에서 예기했던 DataSet 클래스 객체를 Java의 Collection 인터페이스 구현 객체로 변환할때 했던 설명들의 역방향이라고 생각하면 된다. 결과물이 List<Map<String, Object>> 객체일 경우 List 객체에 들어있는 Map 객체 갯수만큼 loop를 돌면서 Map 객체 하나하나를 DataSet의 레코드로 변환하는 것이다. Map의 key를 레코드의 컬럼 이름으로 설정해서 레코드를 만들게 된다. Map 객체가 아닌 POJO 스타일 Java 클래스 객체일 경우 클래스의 멤버변수를 레코드의 컬럼 이름으로 설정해서 해당 멤버변수의 값을 레코드의 컬럼값으로 넣게 된다. 이러한 개념을 알고 구체적인 내용을 보도록 하자.


먼저 현재의 상황에서는 Spring에서 Contoller를 통해 결과물을 출력할때 2가지의 클래스가 필요하다. 


  1. View 인터페이스를 구현한 클래스
  2. 1번의 클래스 객체를 생성하는 ViewResolver 인터페이스를 구현한 클래스


모든 상황이 반드시 이 2가지 클래스가 필요한 것은 아니다. Rest 방식의 경우는 HttpServletRequest의 Header 값을 통해 넘어오는 파라미터의 타입을 알아내어 그에 맞는 HttpMessageConverter를 이용해서 파라미터를 Java 클래스 객체로 변환하거나 또는 Java 클래스 객체를 해당 타입의 값으로 변환하기도 한다. 그래서 위에서 언급할때 현재의 상황 이란 단어를 사용했다. Xplatform의 경우는 XML로 전송하기 때문에 XML 관련 HttpMessageConverter를 이용해 객체를 만들거나 또는 객체를 XML로 변환할 수 있겠지만 이미 투비소프트에서 자신들이 정의한 DataSetList나 VariableList 등의 클래스로 변환을 해서 return 해주거나 또는 그 반대의 기능을 수행하는 PllatformRequest와 PlatformResponse 클래스를 제공하고 있기 때문에 굳이 HttpMessageConverter를 구현한 클래스를 별도로 만들 필요가 없다. 물론 구현에 대해 말리지는 않겠으나 투비소프트에서 정의한 XML에 대한 분석이 필요하기 때문에 이러한 쓸데없는 노가다를 피할려고 PlatformRequest나 PlatformResponse를 사용했다


Spring에서는 View인터페이스와 ViewResolver 인터페이스를 구현한 여러 클래스들을 제공하고 있다. 이러한 클래스는 추상클래스와 그렇지 않은 클래스들이 혼재되어 있는데 이 클래스들중에서 본인 용도에 맞는 클래스가 있으면 그걸 사용하면 되고 그런 클래스가 없을 경우 제공되는 추상클래스에서 상속받아 이를 구현하거나 추상클래스 상속받아서 해결될 문제가 아니면 해당 인터페이스를 구현한 클래스를 직접 만들어서 사용하면 된다. 여기서는 후자의 방법으로 진행했다(어떠한 클래스들이 있는지 궁금한 사람은 Spring API 문서의 이 두 인터페이스 관련 내용을 보면 알 수 있다.)


그러면 먼저 ViewResolver 인터페이스를 구현한 클래스 코드를 보도록 하자


public class XplatformViewResolver implements ViewResolver, Ordered {

	private int order = Ordered.LOWEST_PRECEDENCE;

	@Override
	public View resolveViewName(String viewName, Locale locale) throws Exception {
		// TODO Auto-generated method stub
		XplatformView xplatformView = new XplatformView();
		return xplatformView;
	}

	@Override
	public int getOrder() {
		// TODO Auto-generated method stub
		return order;
	}

	public void setOrder(int order) {
		this.order = order;
	}

}


이 클래스 소스를 보면 2개의 인터페이스를 구현하고 있는데 하나는 위에서 말한 ViewResolver 인터페이스이고 또 하나는 Ordered 인터페이스이다. Ordered 인터페이스를 구현한 이유는 좀 이따가 설명하기로 하고 ViewResolver 인터페이스에 대해 설명하도록 하겠다. ViewResolver 인터페이스는 1개의 메소드를 제공하는데 View 인터페이스를 구현한 클래스 객체를 return 해주는 resolveViewName 메소드이다. 이 메소드가 하는 일은 단순하다. view 이름과 locale 정보를 받아서 데이터를 보여주는 View 객체를 return 해주면 된다. 즉 우리가 클라이언트에 전달할 데이터를 담은 View 객체를 여기서 만들어서 return 해주면 된다. 그래서 코드도 단순하다. View 인터페이스를 구현한 클래스인 XplatformView 클래스 객체를 만들어서 return 해주기만 하면 된다. 그리고 Ordered 인터페이스를 구현한 이유는 ViewResolver 인터페이스를 구현한 클래스가 여러개일 경우 우선순위를 설정해야 하는데 이것을 하기 위해 Ordered 인터페이스를 구현했다. Spring에서는 ViewResolver를 여러개 설정할 수 있다. 우리가 표현할 데이터를 여러 다양한 클라이언트를 통해 표현해야 하기 때문에 해당 클라이언트에 따른 ViewResolver를 구현하므로 여러개를 설정할 수 있게 된다. 이때 어떤것을 가장 먼저 적용해야 하는지 우선순위를 정할 수 있다. 이를 위해서 Ordered 인터페이스를 구현했다. 이 인터페이스에서 제공하는 메소드는 getOrder 메소드 하나뿐이 없다. getOrder 메소드에서 설정된 우선순위 값을 return 해주면 된다.


ViewResolver는 View를 만드는 단순한 작업만 하기 때문에 따로 설명할 내용은 없다. Java 객체를 Xplatform에서 사용하도록 만드는 핵심 기능은 View에 있다. 그럼 위에서 보여준 클래스 코드인 XplatformViewResovler 클래스가 만들어주는 View인 XplatformView 클래스를 살펴보도록 하자. 대강의 윤곽은 다음과 같다.


public class XplatformView implements View {

	/**
	 * Xplatform의 작업결과가 성공적이었을때의 ErrorCode 값을 설정한다
	 */
	private final String ERROR_CODE_VALUE;

	/**
	 * Xplatform의 작업결과가 성공적이었을때의 ErrorMsg 값을 설정한다
	 */
	private final String ERROR_MSG_VALUE;

	public XplatformView() {
		this.ERROR_CODE_VALUE = "0";
		this.ERROR_MSG_VALUE = "";
	}

	public XplatformView(String errorCodeValue, String errorMsgValue) {
		this.ERROR_CODE_VALUE = errorCodeValue;
		this.ERROR_MSG_VALUE = errorMsgValue;
	}

	@Override
	public String getContentType() {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response)
			throws Exception {
		// TODO Auto-generated method stub
		String contentType = request.getHeader("Content-Type").startsWith("text/xml;") ? XplatformConstants.CONTENT_TYPE_XML
				: request.getHeader("Content-Type");

    	if(contentType == XplatformConstants.CONTENT_TYPE_XML) {
    		...
    	} else if(contentType == XplatformConstants.CONTENT_TYPE_CSV) {

    	}
	}

	...

}


위에서 보여주는 코드는 XplatformView 클래스의 전체 코드는 아니다. 설명에 필요한 부분만 남긴 것이며 설명할때마다 관련 코드는 그때그때 추가로 보여주도록 하겠다. 일단 이 클래스 코드를 보면 멤버변수로 ERROR_CODE_VALUE와 ERROR_MSG_VALUE가 있다. 이것은 Spring에서 Xplatform 관련 작업을 하면서 문제가 발생하여 예외가 발생했을 경우 이를 처리하기 위한 변수이다. ERROR_CODE_VALUE에는 에러 Code를 설정하고 ERROR_MSG_VALUE는 에러 메시지를 설정하게 된다. 이 변수들에 대한 설정은 생성자에서 하고 있다. 그러나 예외가 발생한 일이 없어서 작업 자체가 성공적으로 수행이 되면 파라미터가 없는 생성자를 이용해서 View를 생성함으로써 관련 코드와 메시지를 설정하고 있다. 이것에 대한 내용은 나중에 예외 관련 처리 글에 대한 설명에서 좀더 자세히 다루겠다. 지금은 이 변수들의 의미만 알아두고 넘어가자.


View 인터페이스의 핵심 메소드는 render 메소드이다. render 메소드에서는 3개의 파라미터를 받게 되는데 첫번째는 Controller의 메소드에서 설정한 Model 객체이고 두번째는 Controller의 메소드가 처리하고 있는 HttpServletRequest 객체이며, 세번째는 Controller의 메소드를 통해 출력하게 되는 HttpServletResponse 객체가 넘어오게 된다. 


처음으로 하는 작업은 해당 요청에 대한 content type을 request의 header에서 읽어오게 된다. content type을 읽어오는 이유는 content type에 맞춰서 출력하기 위함이다. 특별한 작업을 거치지 않는 한에는 content type은 text/xml; charset=UTF-8 로 넘어온다. 그래서 읽어온 header 값이 text/xml; 로 시작하면 XML 로 출력하는 것을 의미하는 뜻에서 Xplatform의 PlatformType 인터페이스를 상속받은 XplatformConstants 인터페이스의 상수인 CONTENT_TYPE_XML을 설정했다. 이 CONTENT_TYPE_XML은 PlatformType 인터페이스에 정의되어 있는 상수이다.


사실 이 View는 원래 목적에서 100% 구현된 것은 아니다. github을 통해 이 클래스의 render 메소드를 보면 contentType 변수가 XplatformConstants 인터페이스의 CONTENT_TYPE_CSV 일 경우에 대한 처리부분은 비어있다. 내가 Xplatform 전문 프로그래머가 아니어서 이 부분에 대한 지식이 약한데, Xplatform 전문 프로그래머인 지인의 말에 의하면 Xplatform은 출력할때 XML 포맷으로 출력하거나 또는 CSV 스타일로 출력할 수 있다고 한다. 근데 내가 가지고 있는 Xplatform 샘플 프로젝트는 CSV 형태로의 출력 기능이 구현되어 있지 않아 이 부분에 대한 구현을 할 수 없었다. 이 부분에 대해서는 투비소프트에서 이를 처리하는 jsp 소스를 받아 이를 구현해야 할 것임을 미리 밝혀둔다. 그래서 request의 content type이 xml이 아닌 경우 이 content type 값을 그대로 설정하도록 했다. (아마 추측에는 그냥 response stream에 CSV 형태의 문자열을 그대로 내려주면 될 것 같다는 추측은 해본다)


그러면 이제 XML인 경우 어떻게 처리하는지 알아보자. XML일 경우 다음의 코드를 실행하게 된다.


if(contentType == XplatformConstants.CONTENT_TYPE_XML) {
	VariableList variableList = new VariableList();
	DataSetList dataSetList = new DataSetList();
	HttpPlatformResponse httpPlatformResponse = new HttpPlatformResponse(response, XplatformConstants.CONTENT_TYPE_XML);

	if(model != null) {

		for(Entry<String, ?> entry : model.entrySet()) {
			String key = entry.getKey();
			Object object = entry.getValue();
			if(object instanceof Collection) {
				@SuppressWarnings("unchecked")
				DataSet dataSet = makeDataSet(key, (Collection<Object>)object);
				dataSetList.add(dataSet);
			} else {
				Variable variable = null;
				if(object instanceof Integer) {
					variable = new Variable(key, PlatformDataType.INT, (Integer)object);
				} else if(object instanceof Long) {
					variable = new Variable(key, PlatformDataType.LONG, (Long)object);
				} else if(object instanceof Float) {
					variable = new Variable(key, PlatformDataType.FLOAT, (Float)object);
				} else if(object instanceof Double) {
					variable = new Variable(key, PlatformDataType.DOUBLE, (Double)object);
				} else if(object instanceof Date) {
					variable = new Variable(key, PlatformDataType.DATE, (Date)object);
				} else if(object instanceof String){
					variable = new Variable(key, PlatformDataType.STRING, (String)object);
				} else if(object instanceof Variable) {
					variable = (Variable)object;
				} else {
					// model에 들어있는 클래스 객체중에 DataSet으로 변환할 수 없는 클래스 객체가 들어있는것은 bypass 하게끔 한다

					if(skipDataSet(object)) {
						continue;
					}

					// 객체의 멤버변수들 값을 읽어서 한 행짜리 데이터셋으로 return 하는 방법을 고민해보자
					DataSet dataSet = makeDataSet(key, object);
					dataSetList.add(dataSet);
				}
				if(variable != null) {
					variableList.add(variable);
				}
			}
		}

		// XplatformView를 만든다는 것은 그 이전단계까지는 예외없이 진행되었다는 뜻이기 때문에 Xplatform에서 읽어들일변수인 ErrorCode 와 ErrorMsg 변수에 작업이 성공했다는 내용을 설정한다
		// Controller에서 ErrorCode와 ErrorMsg를 설정한 것이 없으면 XplatformView에서 설정하도록 한다
		if(!model.containsKey("ErrorCode")) {
			variableList.add("ErrorCode", ERROR_CODE_VALUE);
		}

		if(!model.containsKey("ErrorMsg")) {
			variableList.add("ErrorMsg", ERROR_MSG_VALUE);
		}
	}

	PlatformData platformData = new PlatformData();
	platformData.setVariableList(variableList);
	platformData.setDataSetList(dataSetList);
	httpPlatformResponse.setData(platformData);
	httpPlatformResponse.sendData();

}


여기에서도 Java Reflection을 통해 Java에서 제공되는 클래스 객체를 Xplatform에서 제공되는 클래스 객체로 바꾸게 된다. 처음의 세 줄은 Xplatform 클라이언트에서 받게될 VariableList 객체와 DataSetList 객체를 생성하고 HttpServletResponse 객체를 넘겨서 Xplatform에서 제공하는 HttpPlatformResponse 객체를 생성하게 된다. 이때 XML로 출력할 것임을 설정하는 작업으로 XplatformConstants.CONTENT_TYPE_XML 상수를 같이 넘겨준다.  이렇게 출력과 관련된 기초작업을 마치면 본격적으로 model 안에 있는 데이터들을 그 성격에 따라 Variable 객체로 만들어서 VariableList 객체에 넣거나 또는 DataSet 객체로 만들어 DataSetList 객체에 넣으면 된다.


넘겨받은 model은 Map<String, ?> 구조이기 때문에 이에 대한 Entry객체가 들어있는 Set 객체를 이용해 반복적으로 Entry 객체 단위의 작업을 진행할 수 있다. Entry 객체를 통해 key와 value를 얻어오면 value로 얻어온 값이 어떤 타입인지을 알아내야 한다. 예전에 ArgumentResolver 에 대한 설명을 했을 당시 DataSet 클래스 객체는 Collection 인터페이스를 상속받은 인터페이스(List, Set)를 구현한 클래스 객체로 변환할 수 있었다. 마찬가지다. 지금은 이것의 반대방향으로 변환한다고 보면 된다. 즉 Collection 인터페이스 계열 객체이면 이를 DataSet 클래스 객체로 변환하는 것이다. 그래서 Collection 인터페이스 계열 객체이면(if(object instanceof Collection)) makeDataSet 메소드에 key와 해당 value를 넘겨서 DataSet 객체를 받은 뒤 이 DataSet을 위에서 만든 DataSetList 객체에 넣게 된다. Collection 인터페이스 계열 객체가 아니면 Java에서 제공하는 Type(ex:int, long, float, double 등)의 객체이거나 또는 사용자가 만든 VO 클래스 객체일 것이다. 그래서 Java에서 제공하는 Type의 객체일 경우 이를 Variable 객체로 변환하여 VaribableList 객체에 넣게 된다. Variable 객체로 만들때 지정하게 된 Variable 이름은 파라미터로 전달된 model 의 key 를 사용하게 된다. 변환해야 할 클래스 객체가 VO 클래스면 1개의 레코드를 가진 DataSet 클래스 객체로 변환하게 된다. model 파라미터에서 객체를 DataSet으로 변환할때는 model에서 사용했던 key를 DataSet 이름으로 사용하게 된다. 이 key는 나중에 Xplatform 코드에서 다음과 같이 사용하면 된다. 예를 들어 ds_output 이란 key로 Controller의 메소드에서 model에 결과를 저장했으면 Xplatform의 에서 ds_output 으로 DataSet 이름을 주면 된다(엄밀하게 말하면 ds_result=ds_output 이런 식으로 설정하게 된다. ds_output 이란 이름의 DataSet 을 서버에서 받아 Xplatform에 있는 ds_result DataSet에 전달한다는 의미로 해석하면 되겠다) 이 변환과정에서 사용되는 메소드로 makeDataSet 메소드를 별도로 만들어서 사용하고 있는데 이 부분에 대한 설명은 생략하도록 하겠다. 예전에 ArgumentResolver 메소드에서 사용했던 XplatformReflectionUtils 클래스의 메소드같이 Java Reflection을 이용해서 객체를 변환한다. 대신 ArgumentResolver 에서는 DataSet을 Java 객체로 변환했지만 이번엔 그 반대방향으로의 변환을 하게 된다. ArgumentResolver 글에서 설명한 내용을 잘 이해했으면 여기서 사용한 메소드를 이해하는데 큰 무리가 없으리라 본다


다른 메소드에 대한 설명은 생략하지만 VO 클래스 객체를 DataSet으로 변환할때 사용된 메소드인 skipDataSet 메소드에 대한 설명은 하고자 한다. model에서 Xplatform의 Variable 객체나 DataSet 객체로의 변환과는 무관한 클래스 객체들이 있다. 이러한 클래스가 있는지는 개발하는 과정에서 발생되는 예외를 보면 알게된다. 그래서 이런 상황일 경우 debug 모드로 프로젝트를 실행해서 파라미터도 넘겨받은 map에 어떤 클래스 객체들이 있는지 살펴봐서 해당 클래스를 알아내면 된다. 이런 클래스 객체인지 확인하기 위해 해당 객체를 파라미터로 넘겨서 변환에서 제외되어야 할 클래스이면 true, 그렇지 않으면 false를 return 하도록 했다. 지금은 이 skipDataSet 메소드에서 제외해야 할 클래스로 Spring에서 제공되는 BeanPropertyBindingResult 클래스와 RequestContext 클래스를 설정했으나 이것은 이 템플릿 상황에서 이 2개의 클래스가 불필요해서 넣은것이지 어떤 상황에서든 이 2개만 하면 되는건 아니다. 이러한 클래스는 비즈니스 로직 상황에서도 인위적으로 변환작업에 예외를 둘 수도 있다. 이것은 자신이 직접 작업해보면서 불필요한 클래스 객체가 발견되면 이 메소드에 해당 클래스를 추가해서 작업해주면 된다. 


이렇게 변환한 객체들을 VaribaleList 객체와 DataSetList 객체에 해당 변수들과 DataSet 들을 넣는 작업을 마치면 마지막으로 작업 결과 코드와 작업 결과 메시지를 넣어야 한다. 아래 코드를 보자


if(!model.containsKey("ErrorCode")) {
	variableList.add("ErrorCode", ERROR_CODE_VALUE);
}

if(!model.containsKey("ErrorMsg")) {
	variableList.add("ErrorMsg", ERROR_MSG_VALUE);
}


이 코드는 작업 결과 코드와 작업 결과 메시지를 넣는 코드이다. 파라미터로 받은 model에 ErrorCode란 key가 없을 경우(if문을 보면 조건에 !가 있다) VariableList 객체에 ErrorCode란 이름으로 XplatformView 클래스의 멤버변수로 설정한 ERROR_CODE_VALUE 값을 넣게 된다. 마찬가지로 model에 ErrorMsg란 key가 없을 경우 VariableList 객체에 ErrorMsg란 이름으로 XplatformView 클래스의 멤버변수로 설정한 ERROR_MSG_VALUE 값을 넣게 된다. 그러면 만약 model에 ErrorCode나 ErrorMsg란 key가 이미 있었다면 어떻게 됐었을까? 그랬다면 이 if문을 거치기 이전에 VariableList 객체에 Variable 객체를 만들어 넣는 과정에서 이미 해당 객체를 만들어서 VariableList 클래스 객체에 넣어 놓는 작업을 진행했을것이다. 여기서 작업결과 코드와 작업결과 메시지 넘겨줄때 반드시 ErrorCode와 ErrorMsg란 이름으로 넘겨줘야 한다. 예전 글에서도 한번 언급했지만 Xplatform에서 서버를 통해 작업결과와 메시지를 받을때는 ErrorCode 와 ErrorMsg란 이름으로 받아야하는 일종의 개발 규약이 있어서 그렇다.


지금까지 Xplatform Client로 출력하기 위해 사용되는 클래스인 XplatformViewResolver와 XplatformView 클래스에 대해 설명했다. 다음에는 여기서 잠깐 언급하게 되었던 ErrorCode와 ErrorMsg를 설정하게 되는 과정인 예외처리 부분에 대해 설명하도록 하겠다.


 1. Xplatform과 Spring Framework 연동 템플릿으로 보는 HandlerMethodArgumentResolver와 ViewResolver (1) - 개요

 2. Xplatform과 Spring Framework 연동 템플릿으로 보는 HandlerMethodArgumentResolver와 ViewResolver (2) - 사전지식

 3. Xplatform과 Spring Framework 연동 템플릿으로 보는 HandlerMethodArgumentResolver와 ViewResolver (3) - HttpServletRequestWrapper

 4. Xplatform과 Spring Framework 연동 템플릿으로 보는 HandlerMethodArgumentResolver와 ViewResolver (4) - Spring Controller에서 하려는 것

 5. Xplatform과 Spring Framework 연동 템플릿으로 보는 HandlerMethodArgumentResolver와 ViewResolver (5) - HandlerMethodArgumentResolver 분석(1)

 6. Xplatform과 Spring Framework 연동 템플릿으로 보는 HandlerMethodArgumentResolver와 ViewResolver (6) - HandlerMethodArgumentResolver 분석(2)

 7. Xplatform과 Spring Framework 연동 템플릿으로 보는 HandlerMethodArgumentResolver와 ViewResolver (7) - HandlerMethodArgumentResolver 분석(3)

 8. Xplatform과 Spring Framework 연동 템플릿으로 보는 HandlerMethodArgumentResolver와 ViewResolver (8) - HandlerMethodArgumentResolver 분석(4)

 9. Xplatform과 Spring Framework 연동 템플릿으로 보는 HandlerMethodArgumentResolver와 ViewResolver (9) - XplatformView 분석

 10. Xplatform과 Spring Framework 연동 템플릿으로 보는 HandlerMethodArgumentResolver와 ViewResolver (10) - 예외처리


트랙백을 확인할 수 있습니다

URL을 배껴둬서 트랙백을 보낼 수 있습니다

다른 카테고리의 글 목록

프로그래밍/Spring 카테고리의 포스트 목록을 보여줍니다

저번 글에서는 HandlerMethodArgumentResolver 인터페이스를 구현한 클래스인 XplatformArgumentResovler 클래스에서 @RequestDataSet 어노테이션이 붙었을 경우에 대한 처리를 설명했다. 이제 3개의 어노테이션 중 마지막인 @RequestVariable 어노테이션에 대한 설명을 하도록 하겠다. 먼저 이 부분에 대한 전체적인 골격에 대한 설명을 하도록 하겠다


else if(annotationClass.equals(RequestVariable.class)) {	// @RequestVariable 어노테이션에 대한 처리
	RequestVariable requestVariable = (RequestVariable)annotation;
	String variableName = requestVariable.name();
	Variable variable = variableList.get(variableName);
	if(StringUtils.hasText(variableName)) {					// 특정 변수 이름이 있기 때문에 해당 이름에 대한 값을 낸다
		...
	} else {										// 특정 변수 이름이 없으면 List, Set, Map 또는 VO로 매핑하는 것이기 때문에 오히려 이런 경우 자바의 데이터타입과는 매핑을 할 수 없다
		List<String> keyList = variableList.keyList();

		if(Collection.class.isAssignableFrom(type)) {
			...
		} else if(Map.class.isAssignableFrom(type)) {
			...
		} else {
			// 객체로 변환하는 과정에서 예외가 발생하면 지원하지 않는 타입이기 때문에 그에 대한 처리를 한다
			try {

				Object obj = type.newInstance();
				...
				result = obj;
			} catch(Exception e) {
				result = WebArgumentResolver.UNRESOLVED;
			}
		}
	}
}


이 시점에서 Xplatform의 Variable과 VariableList에 대한 구조를 다시 한번 상기시킬 필요가 있어서 한번 상기시키도록 하겠다. Xplatform에서 DataSet이 아닌 1개 이상의 key=value 형태의 값을 넘길때 VariableList 객체로 받게 된다. 이때 VariableList에서 key를 통해 Variable 객체로 변환된 value를 저장하고 있게 된다. 어찌보면 VariableList는 개념적인것만 놓고 보면 Java의 Map과 같은 구조이다. 이러한 VariableList 객체에 있는 값들을 Controller의 메소드에서 Java 클래스 객체로 받아야 하는데 이때 사용하는 것이 @RequestVariable 어노테이션이 된다.


이 @RequestVariable 어노테이션을 이용해서 데이터를 가져올때 Xplatform에서 데이터를 전송하기 위해 사용했던 key를 이용해서 데이터를 가져오는 경우와 key를 이용하지 않고 전송된 모든 데이터를 가져오는 경우로 사용 방법이 나뉘게 된다. 이런 구조인것을 알고 이제 이러한 구조를 어떤 구조의 데이터 객체로 변환할 것인지를 요약하자면 다음과 같다.


  1. Collection 인터페이스를 상속받은 인터페이스(List, Set) 또는 이를 구현한 클래스 객체
  2. Map 인터페이스 또는 이를 구현한 클래스 객체
  3. VO 형태의 클래스 객체


Collection 인터페이스와 같이 key가 없는 구조에서는 value 값을 Java의 Object 객체로 변환해서 넣게 된다. Map의 경우는 key가 있기 때문에 key를 사용해서 value를 Object 객체로 변환해서 넣게 된다. VO 형태의 클래스 경우엔 key와 같은 이름의 멤버변수에 value를 멤버변수의 해당 타입으로 변환해서 멤버변수에 값을 넣게 된다. 이러한 변환 개념을 이해해두고 구체적인 코드 설명에 들어가도록 하겠다. 먼저 List, Set 인터페이스의 경우에 대해 보도록 하겠다.


Collection collectionResult = null;
if(type.isInterface()) {
	if(type == List.class) {
		collectionResult = new ArrayList<Object>();
	} else if(type == Set.class) {
		collectionResult = new HashSet<Object>();
	}
} else {
	collectionResult = (Collection)type.newInstance();
}

for(String key : keyList) {
	collectionResult.add(variableList.getObject(key));
}

result = collectionResult;


먼저 Collection 인터페이스를 구현한 클래스 객체를 저장하기 위해 Collection 인터페이스 변수를 정한뒤 Controller 메소드의 파라미터에 설정한 타입이 인터페이스일 경우 List 인터페이스면 ArrayList 객체를, Set 인터페이스이면 HashSet 객체를 Collection 인터페이스 변수에 설정한다. 만약 인터페이스가 아닐 경우엔 파라미터에 설정한 타입이 클래스이기 때문에 newInstance 메소드를 이용해서 해당 클래스 객체를 생성한다. 그리고 VariableList 객체에 있는 key들을 이용해서 값을 꺼내와 Collection 인터페이스 변수에 설정된 객체에 넣고 그 객체를 최종 return 할 객체로 설정한다. 설명이 좀 복잡할 수도 있는데 요약하자면 VariableList 객체에서 key를 이용해 VariableList에 있는 값들을 꺼내와서 Collection 인터페이스 구현 객체에 값들을 저장하게 되는 것이다. 이 경우 주의할 것이 있는데 List일 경우엔 중복을 허용하기 때문에 key가 다르더라도 값이 같은 상황에서는 중복되어서 들어가게 된다. 그러나 Set일 경우엔 중복을 허용하지 않기 때문에 key가 다르더라도 값이 같은 상황에서는 중복을 허용하지 않고 1개만 들어가지게 된다.


그러면 Map 인터페이스 또는 이를 구현한 객체에 넣을 경우를 보도록 하자


Map<String, Object> mapResult = null;
if(type.isInterface()) {
	mapResult = new HashMap<String, Object>();
} else {
	mapResult = (Map<String, Object>)type.newInstance();
}

for(String key : keyList) {
	mapResult.put(key, variableList.getObject(key));
}

result = mapResult;


Map 인터페이스도 위에서 다루었던 Collection 인터페이스의 경우와 같은 방법이다. Controller 메소드 파라미터에 Map 인터페이스로 선언했을 경우엔 HashMap 객체를, 그렇지 않은 경우는 사용자가 지정한 클래스 객체를 생성한다. 그리고 Map은 key를 가지고 있기 때문에 VariableList 객체의 key를 Map의 key로 삼아서 VaraibleList에 저장되어 있는 값을 Object 타입으로 넣게 된다.


마지막으로 Collection, Map 인터페이스도 아닌 VO 클래스 객체의 경우를 보자


try {

	Object obj = type.newInstance();

	for(String key : keyList) {
		Field keyField = ReflectionUtils.findField(type, key);
		if(keyField == null) continue;
		ReflectionUtils.makeAccessible(keyField);

		Class<?> keyFieldType = keyField.getType();

		if(keyFieldType == int.class) {
			keyField.setInt(obj, variableList.getInt(key));
		} else if(keyFieldType == Integer.class) {
			keyField.set(obj, new Integer(variableList.getInt(key)));
		} else if(keyFieldType == long.class) {
			keyField.setLong(obj, variableList.getLong(key));
		} else if(keyFieldType == Long.class) {
			keyField.set(obj, new Long(variableList.getLong(key)));
		} else if(keyFieldType == float.class) {
			keyField.setFloat(obj, variableList.getFloat(key));
		} else if(keyFieldType == Float.class) {
			keyField.set(obj, new Float(variableList.getFloat(key)));
		} else if(keyFieldType == double.class) {
			keyField.setDouble(obj, variableList.getDouble(key));
		} else if(keyFieldType == Double.class) {
			keyField.set(obj, new Double(variableList.getFloat(key)));
		} else if(keyFieldType == Date.class) {
			keyField.set(obj, variableList.getDateTime(key));
		} else if(keyFieldType == String.class) {
			keyField.set(obj, variableList.getString(key));
		} else {
			keyField.set(obj, variableList.getObject(key));
		}
	}

	result = obj;
} catch(Exception e) {
	result = WebArgumentResolver.UNRESOLVED;
}


VO 클래스의 경우엔 VariableList의 key와 같은 이름을 가진 멤버변수를 찾아 여기에 해당 key에 대한 값을 넣는 것이다. 그러나 이 경우도 해당 key에 대한 멤버변수가 발견되지 않았을 경우 그냥 bypass 한뒤 다음 key에 대한 작업을 하도록 했다. 멤버변수를 찾으면 해당 멤버변수에 대한 타입을 알아낸뒤 그 타입에 맞춰서 VariableList에 저장되어 있는 값을 꺼내와 설정하게 된다. 기본적으로 Java에서 제공하는 타입으로 했고 만약 지정된 타입이 아닐 경우 Object 객체 그대로 넣도록 했다. 이러한 과정을 진행하면서 예외가 발생했을 경우 처리할 수 없다는 의미로 WebArgumentResolver.UNRESOLVED를 return 하게끔 설정했다. 


지금까지 Xplatform에서 넘어온 DataSet 및 VariableList를 Controller의 메소드 파라미터에서 정의한 타입으로 받아오기 위해 만든 HandlerArgumentResolver 인터페이스 구현에 대해 설명했다. Java Reflection 등 좀 복잡한 내용이 있지만 궁금한 부분은 디버그 모드로 돌려보면서 해당 변수안에 어떤 값이 들어가는지 추적해보면 이해하는데 큰 문제는 없을 것이다. 다음 글부터는 서버쪽 작업이 마쳐지면 이제 Xplatform 으로 데이터를 전송해야 하는데 이때 사용되는 ViewResolver 인터페이스 구현 클래스인 XplatformViewResolver 클래스와 이 클래스에서 사용하는 클래스인 XplatformView 클래스에 대해 알아보도록 하겠다.


 1. Xplatform과 Spring Framework 연동 템플릿으로 보는 HandlerMethodArgumentResolver와 ViewResolver (1) - 개요

 2. Xplatform과 Spring Framework 연동 템플릿으로 보는 HandlerMethodArgumentResolver와 ViewResolver (2) - 사전지식

 3. Xplatform과 Spring Framework 연동 템플릿으로 보는 HandlerMethodArgumentResolver와 ViewResolver (3) - HttpServletRequestWrapper

 4. Xplatform과 Spring Framework 연동 템플릿으로 보는 HandlerMethodArgumentResolver와 ViewResolver (4) - Spring Controller에서 하려는 것

 5. Xplatform과 Spring Framework 연동 템플릿으로 보는 HandlerMethodArgumentResolver와 ViewResolver (5) - HandlerMethodArgumentResolver 분석(1)

 6. Xplatform과 Spring Framework 연동 템플릿으로 보는 HandlerMethodArgumentResolver와 ViewResolver (6) - HandlerMethodArgumentResolver 분석(2)

 7. Xplatform과 Spring Framework 연동 템플릿으로 보는 HandlerMethodArgumentResolver와 ViewResolver (7) - HandlerMethodArgumentResolver 분석(3)

 8. Xplatform과 Spring Framework 연동 템플릿으로 보는 HandlerMethodArgumentResolver와 ViewResolver (8) - HandlerMethodArgumentResolver 분석(4)

 9. Xplatform과 Spring Framework 연동 템플릿으로 보는 HandlerMethodArgumentResolver와 ViewResolver (9) - XplatformView 분석

 10. Xplatform과 Spring Framework 연동 템플릿으로 보는 HandlerMethodArgumentResolver와 ViewResolver (10) - 예외처리


트랙백을 확인할 수 있습니다

URL을 배껴둬서 트랙백을 보낼 수 있습니다

다른 카테고리의 글 목록

프로그래밍/Spring 카테고리의 포스트 목록을 보여줍니다