[Info]Tags categorized posts and contents patterns..

[AJAX] Ajax Code E xamples.. [Book] About the book.. [CSS] CSS Code E xamples.. [DB] Sql Code E xamples.. [DEV] All development stor...

2016년 5월 4일 수요일

[EP] Deveiw 2012 후기 #2..

출처 : Outsider's Dev Story https://blog.outsider.ne.kr/

node.js를 이용한 단일언어 기반 웹 애플리케이션 개발 - Monoglot web application development with node.js - 채수원(NHN)
PHP 를 할때는 5페이지 만드는데 5시간 정도가 걸렸는데 자바로 넘어왔더니 배워야 할 것들이 엄청나게 많았고 5페이지 만드는게 거의 50시간은 걸리는것 같았다. 더이상 이런 어려움을 겪고 싶지 않았고 팀에서 Node.js로 개발을 하면서 실험한 결과를 공유하기 위한 세션이다. Node.js로 만든 프로젝트는 개발자노트였는데 nForge의 서브프로젝트로 위키와 매뉴얼작성을 위한 웹사이트이고 오픈소스로 제공된다. 이번 프로젝트를 한 의도는 모두가 다 Node.js로 삽질을 하지 말고 먼저 삽질한 경험을 공유해서 다른 사람들은 삽질을 줄일 수 있도록 하고 Node.js를 확산시켜보려는 의도가 있고 Spring 말고 다른 것도 써보고 싶었다. Node.js를 사용한 배경은 NHN의 기술투자적 의미가 있으며 개발자들이 여러 플랫폼을 사용하고 있기 때문에 다양항 픈랫폼을 지원하는 개발환경이 필요했고 대부분 PHP를 배경으로 가지고 있기 때문에 스프링은 잘 몰랐던 것도 한 목했다. Node.js는 자바스크립트이지만 Node.js에 대한  레퍼런스가 많이 없기 때문에 고생할 확률이 높고 개발을 하면서 어떤 방법이 좋은지 판단이 어려워서 계속해서 스타일을 바꿔가면서 하게 됐다.


여러가지 오픈소스 모듈을 사용했는데 Node.js로 프로젝트를 할 때 이 정보들이 도움이 되기를 바란다. 그리고 오픈 소스를 사용할 때는 나중에 해결되겠지 하지 말고 반드시 지금 기능으로 가능한지를 판단해서 선택해야 한다.

  • Express : 웹서버는 익스프레스를 사용했는데 문서가 그리 좋지 않은 데다가 개발자인 TJ가 소위 미친 개발자게 가까워서 개발하면서 API를 자주 바꾸곤 한다. 하지만 대안 별로 없는 편인데 최근에는 로드 존슨도 참여한 Meteor이 주목을 받고 있다.
  • Jade : 뷰템플릿엔진으로 JSP보다는 낫고 익스프레스와 궁합이 좋다. 뷰의 코드량은 줄일 수 있지만 마크업 개발자와 협업이 좀 필요하다.
  • CoffeeScript : 커피스크립트에는 자바스크립트의 베스트 프렉티스가 포함되어 있기 때문에 자바스크립트를 잘 모르니까 커피스크립트를 써보자는 생각으로 적용했다. 코드량이 감소하기 때문에 복잡도도 줄어들고 가독성은 증가하지만 디버깅이 지옥이다. 소스맵이 나오기 까지는 디버깅에서 대안이 없기 때문에 직접 오류난 곳을 찾아야 함다.
  • Mocha : 테스트 프레임웍으로 다양한 리포트와 형식을 지원하고 커피스크립트도 지원하고 있다. 그리고 다른 테스트 라이브러리와 같이 쓸 수 있다는 장점이 있다.
  • Jake : 자바스크립트로 개발하므로 빌드도 자바스크립트로 하고 싶었다.
  • Zombie.js : 웹브라우저 에뮬레이터로 브라우저를 실제로 띄우지 않고도 테스트를 할 수 있다. 클라이언트측 자바스크립트 테스트도 할 수 있다.
  • i18n-node : i18n은 이 모듈이 가장 좋으므로 믿고 딴거 쓰지 말고 이 모듈을 사용해라. 최초 등록시 자동으로 메시지 파일이 생기는 등 아주 쉽게 사용할 수 있다.
  • setp, async : Flow Control을 하는 모듈로 콜백으로 인한 코드의 복잡함을 줄이기 위해서 사용하고 step는 깔끔하지만 기능이 아주 간단하고 Async는 기능이 엄청나게 많기 때문에 잘못하면 오용할 가능성도 있다.
  • 개발도구 : Vi나 Sublime Text2 둘중에 하나를 사용해라. IDE도 좀 있지만 크게 도움은 안되므로 그냥 앞에서 얘기한 두 에디터를 사용하느게 낫다.

프로젝트를 하면서 배운 점들은 콜백으로 인한 오류를 조심해야 한다는 것이다. 단순히 콜백으로 연결된 것은 블럭킹 콜백이고 실제 I/O를 사용해서 호출되는 것이 지연콜백인데 사용해야 하는 것은 지연 콜백이다. 블럭킹 콜백을 사용해서 성능문제가 발생하는 실수를 하지 말아야 하고 비동기로 인한 실행순서를 착각하지 않도록 조심해야 한다. 적용해 보니 처음에는 프론트앤드 개발자가 빨리 배우지만 결국 서버 프로그래밍이기 때문에 나중에는 서버사이드 개발자가 더 많이 나아가는 것 같다. 그리고 개인 생산성은 괜찮은 편이지만 자바스크립트 파일을 여럿이 수정해야 하기 때문에 팀 레벨의 생상선을 그다지 좋지 않다.


 시간대에 어느 세션을 들을까 많이 고민하긴 했는데 그냥 이 세션을 선택했는데 아주 좋았다. 난 역시 발표는 참 어려운데 채수원님은 맛깔나게 발표를 잘하신다. 올해내내 개발자노트를 Node.js로 진행한 실제 경험을 나누어 주는 세션이라 실무 프로젝트를 못해본 나로써도 그 내용이 궁금해서 들었는데 실제 사용해본 경험이기 때문에 설득력도 있고 내용이 아주 알찼다. 나중에 들으니 뒤에 좀더 있었는데 얘기를 못하셨다고 했는데 그게 아쉽기는 하지만 듣는 사람들에게 여러 모로 도움이 되었을 꺼라고 생각한다. Node.js세션은 500명이 들어가는 큰 곳에서 진행되었는데 자리가 없어서 주위에 앉을 정도로 사람이 많아서 아직도 인기가 좋구나 하는걸 새삼 느끼면서 11월에 진행될 Node 컨퍼런스도 괜찮겠다 싶었다. ㅎ

Netty Internal - 이희승(Twitter)
Netty네트워크 애플리케이션을 빨리 만들 수 있게 해주는 자바 네트워크 어플리케이션 프레임워크로 비동기이면서 이벤트 기반이다. 즉 성능이 좋고 리소스를 적게 사용한다. 그리고 IO를 추상화한 레이어로 API를 제공하고 있기 때문에 코드의 변경없이 NIO, OIO, AIO를 바꿔가면서 사용할 수 있고 이벤트 모델과 쓰레드 모델을 잘 정의해 놓았기 때문에 개발자가 원하는대로 사용할 수 있다. 유연하게 이벤트를 다루기 위해서 bi-directional chain of responsibility pattern을 사용하고 있고 'Separation fo Concerns'가 잘 되어 있으므로 네트워크에만 Netty를 사용하고 프로토콜과 비즈니스 로직은 따로 사용할 수 있도록 설계되어 있다.

이벤트루프는 윈도우의 이벤트 디스패처나 자바의 스윙 이벤트 루프, Reactor와 비슷하다. 사용자의 I/O 요청은 이벤트 루프 스레드내에서 요청했을 경우에는 즉시 처리하고 이벤트루프 스레드 밖에서 요청했을 경우에는 이벤트 루프의 작업 큐에 넣어서 스케쥴링한다. 외부 자극에 반응해서 파이프라인에 알리고 Netty 4에서는 사용자가 원하는 임의 작업을 실행할 수 있다.

파이프라인은 이벤트루프에서 이벤트를 받아서 핸들러에 전달하는 역할을 한다. BiDiCoR(bi-directional chain of responsibility pattern)는 서블릿필터나 데코레이터 패턴과 유사한데 단방향이 아닌 나가고 들어오는 양방향에 모두 적용된 링크드 리스트라고 할 수 있다. 인바운드 이벤트를 이벤트루프가 발생시킨 이벤트로 소켓 연결이나 데이터수신등이 있으며 사용자가 작성한 inbound event handler가 수신할 수 있도록 해준다. 아웃바운드 이벤트는 인바운드의 반대로 쓰기, 읽기, 일시중단 등 사용자가 요청한 동작에 대한 것이다. 사용자가 작성한 outbound event handler가 다른 핸들러에서 요청한 동작을 가로챌 수 있도록 해주는데 최종적으로는 이벤트 루프에 전달되어 실제 I/O가 수행될 수 있도록 해준다.


이벤트 모델에서 기존에는 이벤트를 자바 객체로 만들어서 이벤트가 생기면 자바객체를 파이프 라인에 전달하는 구조로 되어 있었다. 그리고 이벤트 하나를 전달하는데 총 8개의 이벤트가 발생하는데 이는 논리적으로는 문제가 없이만 효율적인지에 대해 의문을 가지게 되었다. GC의 비용이 크지 않다고 판단했지만 극도의 성능이 필요한 곳에서는 이것도 문제가 된다는 것을 알게 되었고 강시에는 JVM의 GC보다 직접 구현한 버퍼풀이 더 좋을 수 있는지에 대한 의문을 가졌고 보통은 직접 구현한게 더 느리다. 하지만 쓰르풋이 높은 경우에는 직접 구현한 버퍼풀의 성능이 더 좋은 경우가 있으며 JVM은 보안상 이슈로 메모리를 모두 0으로 채우는 등의 문제를 처리하면 더 좋은 버퍼풀을 만들 수 있다고 판단하게 되었다. 그렇다면 이렇게 객체와 버퍼를 생성할 필요가 없어진다.

그리고 이벤트가 올때와 닫을 때 각각 3개의 이벤트가 발생하는데 실제적으로는 openBound는 큰의미를 가지지 않았고 실제 사용자가 관심을 가지는 것은 통신을 할 수 있는 상태냐 아니냐는 것이었기 때문에 여기에 맞춰서 개선하기로 했다. 또한 메시지 이벤트의 경우에는 메시지를 프로퍼티로 가지고 있었기 때문에 항상 메모리에 물려있었고 메시지마가 객체가 생기는 구조였다. 그리고 항상 네티가 버퍼를 사용하므로 어떤 버퍼를 사용할 지를 사용자가 제어할 수 없었고 메시지 이벤트는 무조건 하나의 소켓을 사용하도록 되어 있었는데 상황에 따라 연속적인 메시지는 하나의 소켓으로 계속 보내면 성능을 높일 수 있다. 그래서 새로운 이벤트 모델에서는 이벤트를 메서드 호출로 변경해서 새로운 객체를 만드는 대신 이벤트의 해당 메서드를 호출하도록 하였다. 그리고 메시지 이벤트는 제거하고 버퍼에 데이터를 채운 다음에 flush 이벤트로 메시지 이벤트를 대체하도록 하였다.

네티 4의 쓰레드모델은 3.x 대비 이벤트 모델 개성과 함께 가장 중요하고 긍정적인 변화이다. 쓰레드 모델이 필요한 이유는 프레임워크에서 사용자의 코드가 어느 쓰레드에서 언제 실행될 지 충분하고 정확하게 정의하지 않으면 사용자가 방어적으로 코드를 작성해야 한다.
이렇게 잘 정의된 쓰레드 모델로 SSL이 4.0으로 올라가면서 1400라인에서 900라인으로 줄어들었다. 네티는 추상화를 제공하고 있기 때문에 실제 통신을 하지 않고도 통신을 에뷸레이트할 수 있다. 그리고 네티 자체는 가 전송의 정확한 동작을 확인하기 위해서 모든 전송방법과 프로토콜의 모든 조합을 교차테스트 하고 있다.

네티는 이름은 많이 들어봤지만 실제로 써본 적은 없기 때문에 원래는 이시간대에 다른 세션을 들을 계획이었는데 주위에 네티에 관심갖는 사람도 많은데다가 그리 어렵진 않을꺼라는 얘기도 있었고 이희승님의 발표도 듣고 싶어서 들어갔는데 정말 최고였다. 위에서 설명한대로 Netty 4로 개발하면서 했던 고민들과 변경사항들을 설명해 주셨는데 아주 차분한 말투로 조곤조곤 설명하시는데 진행이 술술되서 귀에 쏙쏙 들어왔다.(한편으로는 이터너티님이 생각나기도...)

설명이 어떻게 했는데 왜 그렇게 생각했고 무슨 문제가 있었고 이렇게 바꿨다 식으로 진행되서 정말 이해하기가 좋았다. 한편으로는 Node.js때문에 이벤트루프나 비동기에 대한 사전 지식이 있었기 때문에 어느정도 이해하면서 들을 수 있었던 것 같다. 내 입장에서는 Netty 정도면 상당한 메인스트림급 오픈소스 프레임웍이라고 생각하고 있었는데 이희승님도 Netty 써본사람 손들어보라면서 반가워 하는걸 보고 다 비슷하구나 하는 생각이 들었다. Netty를 만든 이희승님이 직접 Netty를 설명해 주시는 기회를 놓치지 않아서 아주 다행이다.

Epilogue
컨퍼런스를 많이 다니다 보니 예전에는 공부하러 갔지만 요즘은 공부하러가는거 반, 사람들 보러가는거 반하면서 간다. 컨퍼런스는 한편으로는 축제의 장이라고 생각하는데 국내 컨퍼런스는 쉬는시간이 너무 짧아서 아쉽다. 특히 이번처럼 트랙을 이동하는 컨퍼런스에서는 쉬는시간 10분은 정말 화장실 갔다가 다른 세션으로 이동하기도 벅찬 시간인데 좀 여유있게 진행되었으면 좋겠다. 그래서 자체적으로 2개 세션을 듣고 딱히 관심가는게 없던 한 세션을 그냥 푹~ 쉬었다.(학교 수업들으러 온 건 아니니까...)

실망스럽다는 얘기도 많이 들었는데 어차피 컨퍼런스는 세션이 핵심이고 그런 면에서 세션들의 수준은 꽤 괜찮은 편이라고 생각해서 만족하고 있다. 그리고 Deview에서 의도했는지는 모르겠지만 메인스트림이라고 하기는 어려운 루비, 스칼라, 노드를 이런 대형 컨퍼런스에서 들을 수 있어서 상당히 반갑고 이런 기술들의 확산에도 꽤 도움이 되었으리라고 생각한다. 


[EP] Deveiw 2012 후기 #1..

출처 : Outsider's Dev Story https://blog.outsider.ne.kr/

지난 17일(월)에 코엑스 그랜드볼룸에서 NHN이 주최하는 개발자 컨퍼런스 Deview 2012가 개최되었다. 매년 가을은 각 회사들이 컨퍼런스를 열어서 어느 컨퍼런스를 가야할지 고민되는 때이긴 한데(봄이나 여름에 좀 나눠서 하지 ㅠㅠ) 작년에도 참석을 못했고 이번엔 아는 사람들도 많이 발표를 하고 해서 휴가를 내서 참석을 했다. 참고로 작년에는 Deview와 SDEC이 따로 열렸었는데 올해는 SDEC이 Deview로 합춰져서 하나의 트랙을 담당하는 형태처럼 되었다.

Deview 프로그램 시간표

키노트
국내에서 컨퍼런스를 갈 때마다 느끼는 거지만 국내 키노트는 참 들을 게 없다. 해외 컨퍼런스같은 경우는 (키노트에서 제품을 발표하기도 해서이기도 하지만) 각 세션은 챙겨보지 못하더라도 키노트만은 잠을 안자고 챙겨보게 되는데 국내 컨퍼런스는 이렇기는 커녕 이제는 꾀가 생겨서 키노트는 아예 안듣고 세션시간에 맞춰가기도 한다. 그래도 이번에는 혹시나 해서 키노트에 딱 맞춰서 갔다.

키노트는 누가 어떤 내용으로 하는지 사전에 전혀 공지되지 않았는데 키노트는 2개로 나눠서 진행되었다 첫 키노트는 NHN의 센터장님이 "모바일 시대에 필요한 개발자 정신"라는 제목으로 진행을 하셨는데 각 내용을 나눠서 보면 틀린말이 없기는 약간 내용이 이쪽 저쪽으로 왔다갔다 하면서 진행된 내용이라서 다 듣고 나니까 말하고자 하는 핵심을 잘 이해못하겠다. 결론적으로 다 듣고나서도 모바일 시대에 필요한 개발자 정신이 뭔지는 잘 모르겠다. 두번째 키노트는 가천대 IT 부총장이라는 분이 대학교와 기업의 협업에 대해서 발표하셨는데 발표자의 직책과 제목을 보고 고민도 하지 않고 밖으로 나와서 쉬었다. 경험상 협업에 있지 않은 고위직의 발표는 너무 추상화된 뻔한 말밖에는 안하기 때문에 거의 안듣는 편인데 들은 사람들 얘기를 들어보니 안듣기를 잘한듯하다.

사실 키노트는 그냥 전체 참여자를 모아놓은 하나의 세션이 아닌 컨퍼런스의 방향과 내용을 압축해 놓은 핵심세션이어야 하는데 아직도 국내에서 이런 키노트는 단한번도 보지 못했기에 차라리 키노트없이 세션만 하는게 더 낫지 않나 싶기도 하다.

루비는 패셔니스타 - 문추근(KTH)
언젠가부터는 컨퍼런스에 가면 참석자나 발표자나 아는 사람들이 종종 있는 편인데 이 세션의 문추근님도 개인적으로 친분이 있다. 아는 분의 세션을 듣게 되는건 응원(?)의 성격도 약간은 있겠지만 보통은 친분이 있어서 관심사가 비슷하기 때문인다. 루비는 아주 오래전에 약간 공부한 적이 있긴 했지만 거의다 까먹었고 최근에 문추근님이 사내에서 루비에 대한 발표를 해서 듣고 싶었는데 이번에 들을 수 있게 되었다.

문추근님이 세션 시작시 얘기한대로 루비가 만능이고 최고니까 루비 꼭 배워라라고 얘기하기 보다는 루비를 소개하는 세션이었다. 루비는 마츠모토 유키히로(일명 마쯔)가 1995년에 펄보다 강력하고 파이썬 보다 객체지향적인 것을 만들고 싶어서 만들었고 프로그래밍이 재밌게, 개발자들을 행복하게, 머신중심에서 사람중심적으로라는 디자인 의도를 가지고 만들어졌다. 루비의 특징으로는 객체 지향 프로그래밍으로 모든 것이 다 객체로 이루어져 있고 객체가 주인공이 되어 객체.행동의 문법을 가지게 된다. 그리고 유연한 문법을 가지고 있어서 몽키 패치로 중간에 기능을 바꿀 수 있고 메타 프로그래밍으로 코드의 양으로 줄일 수 있으며 Enumerable 모듈을 통한 객체 중심의 반복이 강력하다. 루비의 영향을 받은 것들로는 prototype.js, Underscore.js, Sugar, CoffeeScript 등이 있다.


Rails는 David Heinemeier Hansson(일명 DHH)가 2004년에 만들었으면 특징으로는 Scaffold가 있다. Scaffold로 레일즈 초기에 15분만의 블로그 만들기 영상으로 큰 인기를 끌었으며 데이터베이스 마이그레이션, 모델, 뷰, 컨트롤러, 테스트등을 스캐폴드 명령어만으로 간단하게 만들 수 있다. 일부에서 스캐폴드는 실서비스에서는 사용할 수 없는 사기라고 말하기도 하지만 스캐폴드라는 말 자체가 건물을 지을때 인부들이 일을 편히 할 수 있도록 건물 외부에 세워두운 철골을 의미하고 건물이 완성되면 걷어지는 것이다. 그리고 레일즈는 RESTful 서비스를 만들기 쉽게 되어 있다. 레일즈의 영향을 받은 것들로는 CakePHP, Grails, Play!등이 있다. 레일즈를 활용하는 서비스들은 Twitter, Groupon, Github등이 있다.

하지만 한국에서는 왕따신세이다. 왜 그런가 생각하면 보통 느리다라는 인상이 있고 트위터도 RoR을 걷고 스칼라로 갈아탔다는 얘기들이 있어서인데 이건 하이브리드 앱과 비슷하다고 생각한다. 등가교환의 법칙처럼 얻는게 있으면 잃는게 있는 법인데 모바일의 시대가 되서 수없이 많은 앱이 쏟아지고 있기 때문에 개발속도가 더 중요한 서비스들이 있다고 생각한다. 그리고 배워야 하는 이유는 개발자라면 터미널에서 맘껏 사용할 수 있는 언어가 있어야 된다고 생각한다. 그리고 실용주의 프로그래머 Tip #8에 나왔듯이 "매년 새로운 언어를 배워라."를 생각해 볼 수 있는데 다른 언어는 같은 문제를 다르게 해결하기 때문에 여러모로 도움이 된다. 하지만 새로운 언어를 배울때 기존 언어의 사용법대로 작성하는 것은 의미가 없으므로 각 언어의 스타일대로 하도록 노력해야 한다. 그리고 제가 좋아하는 말이기도 한 루트비히 비트겐슈타인의 "언어의 한계가 곧 자기 세계의 한계다."라는 말로 세션을 끝내셨습니다.

정말 오랜만에 루비 세션이었다. 사실 RoR을 신기술이라고 할 수는 없기 때문에 여기서 문법을 설명할 것인지 어떤 수준으로 설명할 것인지 고민이 많으셨을꺼라는 생각이 드는데 그런 면에서 발표는 괜찮았다고 생각한다. 너무 오랜만에 듣는 루비 세션이라 왠지 반갑;;; 문추근님이 사내에서 루비발표를 여러번 해서 한 3시간정도로 준비된 발표를 축약해서 발표하신 걸로 알고 있는데 시간계산을 다 하셨다고 했는데 안타깝게 한 15분 정도나 일찍 끝났다. 그 시간에 좀더 많은 내용이 다뤄졌어도 좋았겠지만 개인적으로 발표한 주요개념들은 나도 좋아하는 얘기들이라 재미나게 들었다.

Apache Kafka: Inside LinkedIn's distributed publish/subscribe messaging system - Richard Park (LinkedIn)
Apache Kafka는 분산 Pub/Sub 메시징 시스템으로 Scala로 만들어졌다. Kafka이전에는 메시징에 ActiveMQ를 쓰고 로깅에는 Splunk등을 사용했었는데 서비스의 성장에 맞춰서 확장하기가 어려웠다. 그래서 링크드인은 Robust하고 확장할 수 있고 지연시간이 적으면서 신뢰할 수 있는 시스템이 필요해졌다. 기존에 Flume이나 Scribe등이 있었지만 효율적으로 데이터를 보관할 수 있고 온라인/오프라인 모두에서 데이터를 소비하고 pull/push가 되는 Kafka를 새로 만들기로 결졍했다.


Producer가 앞에서 로드밸런싱의 역할을 해서 이벤트 메시지나 로그를 전송하고 Blocker가 메인 스토리지 서버의 역할을 하게 된다. Consumer가 데이터를 땡겨오게 되고 ZooKeeper가 노드를 모니터링하고 있다. 프로토콜은 Avro를 사용하고 있는데 Avro는 다이나믹 타입이고 별도의 코드를 생성하지 않으면서 간단하고 JSON이기 때문이다. 그리고 데이터를 실뢰할 수 있도록 넣은 데이터가 반드시 나왔는지를 감시해서 놓치거나 지연되는 데이터를 감시하기 위헤서 프로듀서, 블록커, 하둡의 이벤트 카운트를 세서 문제를 찾는다. 카프카를 모니터링하는데 카프카를 사용하고 있으면 현재 아파치 인큐베이팅상태이고 0.7.1버전이다.

이 날 들은 유일한 영어 세션인데 동시통역이 제공되었지만 듣지 않았다. 동시통역을 듣다 보면 영어도 잘 안들리면서 동시통역도 헷갈리고 해서 영어공부도 할겸 그냥 직접 들었기 때문에 내용을 다 파악하진 못했다. 얼기설기 대충 듣기는 하는데 아직 들으면서 정리해 적기까지는 힘들다. ㅋ 그냥 자세한건 다 파악하기 어렵구 Kafka가 이런거구나... 정도만 이해한 것 같다. 당분간 내가 쓸일은 없겠다 싶은..

Scala, 미지와의 조우 - 이동욱(SK Planet)
스칼라를 40분동안 어떻게 설명할 지를 고민했는데 결국 주입식밖에는 없겠다는 생각이 들었다. Scala는 Scalable Language의 머릿글자를 따서 이름이 지어졌고 마틴 오더스키가 만들었는데 이 사람은 자바의 제너릭을 만든 사람이기도 하다. 구글 트랜드에 검색해봤을 때 Groovy나 Clojure보다 인기가 좋고 트위터가 현재 많이 사용하고 있다. 스칼라는 하이브리드 언어로 Eiffel, Haskell, Erlang, Smalltalk, ML, Java등의 영향을 받았고 함수형 프로그래밍이면서 강한 타입시스템을 가지고 있고 JVM상에서 동작한다.



개인적으로 친분이 있는 이동욱님의 세션이었다. 2년전 KSUG 세미나에서 발표한 Scala 세션을 듣고 계속해서 스터디를 같이 해놨기 때문에 내용을 배우려고 한다기 보다는 응원차 참석했다. 그래서 딱히 정리하진 않았다. Scala에 기본적인 사용법에 대한 안내라서... 내가 현실을 망각했는지 이런 큰 세미나에서 다른 선택권 대신 Scala를 선택했으면 기본적인 지식은 있을 때니 기초문법 하지말라고 꼬셨었는데 그 말을 듣지 않으시고 기초부터 진행하셨는데 나이스한 선택이셨다 ㅡㅡ;; 나도 못 알아들으면 어쩌나 했는데 그래도 알아들어서 안심은 했지만 쉽게 잘 설명했다고 생각했는데 어렵다는 반응이 온걸로 봐서는 내말대로 하셨으면 아주 큰일날 뻔 했다. 요즘 Scala의 분위기가 조금씩 올라오는데 국내에서도 이런 큰 컨퍼런스에서 Scala를 볼 수 있게 되서 아주 기쁘다.


[Book] 디자이너가 아닌 사람들을 위한 디자인북..

출처 : Outsider's Dev Story https://blog.outsider.ne.kr/

디자이너가 아닌 사람들을 위한 디자인북 - 6점
로빈 윌리엄스 지음
윤재웅 옮김
고려원북스

일단 책에 대한 얘기를 잠시하면 어딘가에서 누가 괜찮다고 하는 얘기를 듣고 6개월정도 전에 샀는데 오늘 리뷰를 쓸려고 들어가보니 그사이에 절판이 되고 몇일 전에 개정판이 나왔다. (읽지도 않았는데 개정판이 나온것 보다야 낫지만 ㅠㅠ)

평가를 하자면 나쁘지도 않지만 나한테는 뭐 크게 좋지도 않았다. 이 책의 첫장에는 상당히 인상적인 이야기로 시작하고 있다. 조슈아 나무 이야기라는 건데 식물도감에서 죠슈아나무라는 특이한 모양의 나무가 소개되어 있어서 이런 나무가 우리 동네에는 없을꺼라고 생각했는데 동네에 나가보니 곳곳에 죠슈아 나무가 있었고 삼십년동안 살면서도 기억에 없었는데 이름을 알고 구별할 수 있게 되자 알아볼 수 있게 되었다는 이야기이다. 실제로 살다보면 이러한 일은 상당히 많이 있다.

하지만 나에겐 이 이야기가 가장 인상적인 내용이었다. 이 책은 디자인의 원리를 배치, 정렬, 반복, 대비 4가지로 설명하고 있고 그에대한 상세한 내용을 예시를 들어서 설명하고 있다. 뒷부분은 색상과 폰트에대한 얘기로 폰트부분에 대해서 꽤 많은 내용을 참고하고 있다. 전에도 디자인원리는 좀 찾아본적이 있기 때문인지 크게 인상적이지는 않았지만 디자인에 대한 기초가 아예 없다면 설명은 쉽게 해놨기 때문에 볼만할 것 같다. 제목대로 디자이너가 아닌 사람들을 대상으로 하고 있기 때문에 쉽게 설명하고 있는데 나한텐 좀 너무 쉬웠던 것 같다. 중요한 내용이지만 어느정도는 알기에 새로 배우거나 하는 느낌은 없었고 이런거 안다고 디자인이 되는게 아니라.. ㅠㅠ

디자인 책의 번역서에서 아쉬운 부분은 보통 예시가 영문을 기준으로 되어 있다는 것이다. 어설프게 예시를 갈아치우는것 보다는 낫지만 한글과 영문은 차이가 크기때문에 아쉬운 편인데 이 책에는 한글에 대한 설명이 많이 나와있다. 원서에 그렇게 되어 있지는 않을것 같은데 따로 설명이 없기 때문에 역자가 추가한것인지 어떤지는 모르지만 영문부분설명 뒤에 한글폰트를 쓸때 어떤 특징들이 있는지를 꽤 자세히 설명하고 있다. 이부분은 상당히 도움이 되는 부분이라고 생각한다. 그리고 이 책의 대부분의 예제는 명함, 광고 같은 인쇄물에 집중하고 있다. 기본적인 원리야 동일하겠지만 웹이나 어플리케이션만 주로 만지는 나한테는 인쇄물에 집중되어 있는건 좀 아쉬운 부분이다.(물론 이는 내가 책에대한 사전조사가 부족했기 때문이겠지만.)



[JAVA] 7장 Spring 표현 언어 (SpEL) #2..

출처 : Outsider's Dev Story https://blog.outsider.ne.kr/

이 문서는 개인적인 목적이나 배포하기 위해서 복사할 수 있다. 출력물이든 디지털 문서든 각 복사본에 어떤 비용도 청구할 수 없고 모든 복사본에는 이 카피라이트 문구가 있어야 한다.

7.5 언어 레퍼런스
7.5.1 리터럴 표현식
지 원하는 리터럴 표현식의 타입은 문자열, 날짜, 숫자값(int, real, hex), 불리언, null이다. 문자열은 따옴표로 구분된다. 문자열내에 따옴표를 사용하려면 2개의 따옴표를 사용해라. 다음 예제에서 리터럴의 사용방법을 간단히 보여준다. 보통은 이 예제처럼 단독적으로 쓰이지 않고 더 복잡한 표현식의 일부로써 사용한다. 예를 들면 논리적인 비교연산의 한쪽부분에 리터럴을 사용한다.


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
Java

ExpressionParser parser = new SpelExpressionParser();

// "Hello World"로 평가된다
String helloWorld = (String) parser.parseExpression("'Hello World'").getValue(); 

double avogadrosNumber  = (Double) parser.parseExpression("6.0221415E+23").getValue();  

// 2147483647로 평가된다
int maxValue = (Integer) parser.parseExpression("0x7FFFFFFF").getValue();  

boolean trueValue = (Boolean) parser.parseExpression("true").getValue();

Object nullValue = parser.parseExpression("null").getValue();

숫자는 음수기호, 지수표시, 소수점을 지원한다. 기본적으로 실제 숫자는 Double.parseDouble()로 파싱한다.

7.5.2 프로퍼티, 배열, 리스트, 맵, 인덱서
프 로퍼티 참조를 탐색하는 것은 쉬운데 그냥 중첩된 프로퍼티 값을 가리키는 마침표를 사용해라. Inventor 클래스의 pupin과 tesla 인스턴스에는 예제에 사용한 클래스들 섹션에 나온 데이터들이 있다. 다음의 표현식을 사용해서 Tesla가 태어난 해솨 Pupin이 태어난 도시를 탐색한다.


1
2
3
4
5
6
Java

// 1856으로 평가된다.
int year = (Integer) parser.parseExpression("Birthdate.Year + 1900").getValue(context); 

String city = (String) parser.parseExpression("placeOfBirth.City").getValue(context);

프로퍼티명의 첫글자는 대소문자를 구별하지 않는다. 배열과 리스트의 내용은 대괄호를 사용해서 획득한다.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
Java

ExpressionParser parser = new SpelExpressionParser();

// 발명품 배열
StandardEvaluationContext teslaContext = new StandardEvaluationContext(tesla);

// "Induction motor"로 평가된다.
String invention = parser.parseExpression("inventions[3]").getValue(teslaContext, String.class); 

// 회원 리스트
StandardEvaluationContext societyContext = new StandardEvaluationContext(ieee);

// "Nikola Tesla"로 평가된다.
String name = parser.parseExpression("Members[0].Name").getValue(societyContext, String.class);

// 리스트와 배열 탐색
// "Wireless communication"로 평가된다.
String invention = parser.parseExpression("Members[0].Inventions[6]").getValue(societyContext, String.class);

대괄호로 키값 리터럴을 지정해서 맵의 내용을 획득한다. 아래 예제의 경우 Officers 맵의 키가 문자열이므로 문자열 리터럴을 지정할 수 있다.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
Java

// Officer의 딕션어리
Inventor pupin = parser.parseExpression("Officers['president']").getValue(societyContext, Inventor.class);

// "Idvor"로 평가된다
String city = parser.parseExpression("Officers['president'].PlaceOfBirth.City").getValue(societyContext, String.class);

// 값을 설정한다
parser.parseExpression("Officers['advisors'][0].PlaceOfBirth.Country").setValue(societyContext, "Croatia");

7.5.3 인라인 리스트
리스트는 {} 표시법을 사용한 표현식으로 직접 나타낼 수 있다.

1
2
3
4
5
6
Java

// 4개의 숫자를 담고 있는 자바 리스트로 평가된다
List numbers = (List) parser.parseExpression("{1,2,3,4}").getValue(context); 

List listOfLists = (List) parser.parseExpression("{{'a','b'},{'x','y'}}").getValue(context); 

단독으로 {}를 사용하면 비어있는 리스트를 의미한다. 성능때문에 리스트 자체가 고정된 리터럴로 구성되어 있다면 각 평가마다 새로운 리스트를 만드는 것이 아니라 표현식을 나타내는 변하지 않는 리스트를 생성한다.

7.5.4 배열 생성
배열은 자바문법과 유사하게 만들 수 있고 선택적으로 생성시에 존재해야 하는 배열을 갖는 initializer를 제공할 수 있다.


1
2
3
4
5
6
7
8
9
Java

int[] numbers1 = (int[]) parser.parseExpression("new int[4]").getValue(context); 

// initializer가진 배열
int[] numbers2 = (int[]) parser.parseExpression("new int[]{1,2,3}").getValue(context); 

// 다차원 배열
int[][] numbers3 = (int[][]) parser.parseExpression("new int[4][5]").getValue(context);

현재는 다차원 배열을 생성할 때 initializer를 제공할 수 없다.

7.5.5 메서드
메서드는 전형적인 자바 프로그래밍 문법을 사용해서 호출한다. 리터럴에서 메서드를 호출할 수도 있다. 가변인자(Varargs)도 지원한다.


1
2
3
4
5
6
7
Java

// 문자열 리터럴, "bc"로 평가된다
String c = parser.parseExpression("'abc'.substring(2, 3)").getValue(String.class);

// true로 평가된다.
boolean isMember = parser.parseExpression("isMember('Mihajlo Pupin')").getValue(societyContext, Boolean.class);

7.5.6 연산자(Operators)
7.5.6.1 관계 연산자
표준 연산자 표기법을 사용해서 같다, 같지 않다, 작다, 작거나 같다, 크다, 크거나 같다 등의 관계 연산자를 지원한다.


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
Java

// true로 평가된다
boolean trueValue = parser.parseExpression("2 == 2").getValue(Boolean.class);

// false로 평가된다
boolean falseValue = parser.parseExpression("2 < -5.0").getValue(Boolean.class);

// true로 평가된다
boolean trueValue = parser.parseExpression("'black' < 'block'").getValue(Boolean.class);

표준 관계 연산자뿐만 아니라 SpEL은 'matches' 연산자에 기반한 정규표현식과 'instanceof'를 지원한다.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
Java

// false로 평가된다
boolean falseValue = parser.parseExpression("'xyz' instanceof T(int)").getValue(Boolean.class);

// true로 평가된다
boolean trueValue = parser.parseExpression("'5.00' matches '^-?\\d+(\\.\\d{2})?$'").getValue(Boolean.class);

// false로 평가된다
boolean falseValue = parser.parseExpression("'5.0067' matches '^-?\\d+(\\.\\d{2})?$'").getValue(Boolean.class);

각 심볼릭 연산자는 순수하게 연문자로 지정할 수도 있다. 표현식을 내장하는 문서형식에서(예를 들면 XML 문서) 특별한 의미를 가지는 기호를 사용할 때 발생할 수 있는 문제를 피하기 위해서 사용한다. 문자표현은 다음과 같다. lt ('<'), gt ('>'), le ('<='), ge ('>='), eq ('=='), ne ('!='), div ('/'), mod ('%'), not ('!'). 대소문자는 구별하지 않는다.

7.5.6.2 논리 연산자
and, or, not 같은 논리 연산자를 지원한다. 사용방법은 다음 예제에 나온다.


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
Java

// -- AND --

// false로 평가된다
boolean falseValue = parser.parseExpression("true and false").getValue(Boolean.class);

// true로 평가된다
String expression =  "isMember('Nikola Tesla') and isMember('Mihajlo Pupin')";
boolean trueValue = parser.parseExpression(expression).getValue(societyContext, Boolean.class);

// -- OR --

// true로 평가된다
boolean trueValue = parser.parseExpression("true or false").getValue(Boolean.class);

// true로 평가된다
String expression =  "isMember('Nikola Tesla') or isMember('Albert Einstien')";
boolean trueValue = parser.parseExpression(expression).getValue(societyContext, Boolean.class);

// -- NOT --

// false로 평가된다
boolean falseValue = parser.parseExpression("!true").getValue(Boolean.class);

// -- AND and NOT --
String expression =  "isMember('Nikola Tesla') and !isMember('Mihajlo Pupin')";
boolean falseValue = parser.parseExpression(expression).getValue(societyContext, Boolean.class);

7.5.6.3 수식 연산자
더 하기 연산자를 숫자, 문자열 날짜에서 사용할 수 있다. 빼기는 숫자와 날짜에서 사용할 수 있다. 곱하기와 나누기는 숫자에서만 사용할 수 있다. 다른 수식 연산자로 계수(%)와 지수(^)를 지원한다. 표준 연산자를 우선적으로 처리한다. 이러한 연산자는 다음 예제에서 보여준다.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
Java

// 더하기
int two = parser.parseExpression("1 + 1").getValue(Integer.class); // 2

String testString = parser.parseExpression("'test' + ' ' + 'string'").getValue(String.class);  // 'test string'

// 빼기
int four =  parser.parseExpression("1 - -3").getValue(Integer.class); // 4

double d = parser.parseExpression("1000.00 - 1e4").getValue(Double.class); // -9000

// 곱하기
int six =  parser.parseExpression("-2 * -3").getValue(Integer.class); // 6

double twentyFour = parser.parseExpression("2.0 * 3e0 * 4").getValue(Double.class); // 24.0

// 나누기
int minusTwo =  parser.parseExpression("6 / -3").getValue(Integer.class); // -2

double one = parser.parseExpression("8.0 / 4e0 / 2").getValue(Double.class); // 1.0

// 계수(Modulus)
int three =  parser.parseExpression("7 % 4").getValue(Integer.class); // 3

int one = parser.parseExpression("8 / 5 % 2").getValue(Integer.class); // 1

// 연산자 우선순위
int minusTwentyOne = parser.parseExpression("1+2-3*8").getValue(Integer.class); // -21

7.5.7 할당
할당 연산자를 사용해서 프로퍼티를 설정한다. 이는 보통 setValue 호출내에서 이뤄지지만 getValue호출내에서도 이뤄질 수 있다.

1
2
3
4
5
6
7
8
9
Java

Inventor inventor = new Inventor();        
StandardEvaluationContext inventorContext = new StandardEvaluationContext(inventor);

parser.parseExpression("Name").setValue(inventorContext, "Alexander Seovic2");

// 대신에
String aleks = parser.parseExpression("Name = 'Alexandar Seovic'").getValue(inventorContext, String.class);

7.5.8 타입
특 수한 'T' 연산자를 java.lang.Class ('type')의 인스턴스를 지정하는 데 사용할 수 있다. 정적 메서드도 이 연산자를 사용해서 호출한다. StandardEvaluationContext는 타입을 찾으려고 TypeLocator를 사용하고 StandardTypeLocator(교체할 수 있다)는 java.lang 패키지로 만들어진다. 즉, java.lang 내에서 타입을 참조하는 T()는 정규화될 필요는 없지만 다른 모든 타입참조는 정규화되어야 한다.

1
2
3
4
5
6
7
8
9
Java

Class dateClass = parser.parseExpression("T(java.util.Date)").getValue(Class.class);

Class stringClass = parser.parseExpression("T(String)").getValue(Class.class);

boolean
 trueValue = parser.parseExpression("T(java.math.RoundingMode).CEILING 
< T(java.math.RoundingMode).FLOOR").getValue(Boolean.class);

7.5.9 생성자
생성자는 새로운 연산자를 사용해서 호출할 수 있다. 프리미티브 타입과 String 외에는(int, float등이 사용될 수 있는) 모두 정규화된 클래스명을 사용해야 한다.

1
2
3
4
5
6
7
8
Java

Inventor einstein = p.parseExpression("new org.spring.samples.spel.inventor.Inventor('Albert Einstein', 'German')")
                                                                   .getValue(Inventor.class);

//리스트의 add 메서드내에서 새로운 inventor 인스턴스를 생성한다
p.parseExpression("Members.add(new org.spring.samples.spel.inventor.Inventor('Albert Einstein', 'German'))")
                                                                   .getValue(societyContext);

7.5.10 변수
#변수명 문법을 사용해서 표현식내에서 변수를 참조할 수 있다. StandardEvaluationContext에서 setVariable 메서드를 사용해서 변수를 설정한다.


1
2
3
4
5
6
7
8
9
Java

Inventor tesla = new Inventor("Nikola Tesla", "Serbian");
StandardEvaluationContext context = new StandardEvaluationContext(tesla);
context.setVariable("newName", "Mike Tesla");

parser.parseExpression("Name = #newName").getValue(context);

System.out.println(tesla.getName()) // "Mike Tesla"

7.5.10.1 #this와 #root 변수
#this 변수는 항상 정의되어 있고 현재 평가객체를(정규화되지 않은 참조를 처리하는 것에 대비해서) 참조한다. #root변수도 항상 정의되어 있고 루트 컨텍스트 객체를 참조한다. #this가 평가되는 표현식 컴포넌트에 따라 다양하지만 #root는 항상 루트를 참조한다.


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
Java

// create an array of integers
List<Integer> primes = new ArrayList<Integer>();
primes.addAll(Arrays.asList(2,3,5,7,11,13,17));

// 파서를 생성하고 'primes' 변수를 정수 배열로 설정한다
ExpressionParser parser = new SpelExpressionParser();
StandardEvaluationContext context = new StandardEvaluationContext();
context.setVariable("primes",primes);

// 리스트에서 10보다 큰 모든 소수(?{...} 선택을 사용)
// [11, 13, 17]로 평가된다
List<Integer>
 primesGreaterThanTen = (List<Integer>) 
parser.parseExpression("#primes.?[#this>10]").getValue(context);

7.5.11 함수
표현식 문자열내에서 호출할 수 있는 사용자 정의 함수를 등록해서 SpEL을 확장할 수 있다. 사용자 정의 함수는 메서드를 사용해서 StandardEvaluationContext에 등록한다.

Java

public void registerFunction(String name, Method m)

자바 메서드에 대한 참조는 함수의 구현체를 제공한다. 예를 들어 다음에서 문자열을 뒤집는 유틸리티 메서드를 보여준다.


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
Java

public abstract class StringUtils {

  public static String reverseString(String input) {
    StringBuilder backwards = new StringBuilder();
    for (int i = 0; i < input.length(); i++) 
      backwards.append(input.charAt(input.length() - 1 - i));
    }
    return backwards.toString();
  }
}


이 메서드를 평가 컨텍스트에 등록하고 표현식 문자열내에서 사용할 수 있다.


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
Java

ExpressionParser parser = new SpelExpressionParser();
StandardEvaluationContext context = new StandardEvaluationContext();

context.registerFunction("reverseString", 
                         StringUtils.class.getDeclaredMethod("reverseString", 
                                                             new Class[] { String.class }));

String helloWorldReversed = 
          parser.parseExpression("#reverseString('hello')").getValue(context, String.class);

7.5.12 빈(Bean) 참조
평가 컨텍스트가 빈 리졸버로 설정되었다면 (@) 기호를 사용해서 표현식에서 빈을 검색하는 것이 가능하다.


1
2
3
4
5
6
7
8
Java

ExpressionParser parser = new SpelExpressionParser();
StandardEvaluationContext context = new StandardEvaluationContext();
context.setBeanResolver(new MyBeanResolver());

// 평가하는 동안 MyBeanResolver에서 resolve(context,"foo")를 호출할 것이다
Object bean = parser.parseExpression("@foo").getValue(context);

7.5.13 3항 연산자 (If-Then-Else)
표현식에서 if-then-else 조건을 위해 3항 연산자를 사용할 수 있다. 다음은 간단한 예제다.


1
2
3
Java

String falseString = parser.parseExpression("false ? 'trueExp' : 'falseExp'").getValue(String.class);

이 경우에 false 불리언값으로 'falseExp' 문자열값을 리턴한다. 다음은 좀 더 실제적인 예제다.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
Java

parser.parseExpression("Name").setValue(societyContext, "IEEE");
societyContext.setVariable("queryName", "Nikola Tesla");

expression = "isMember(#queryName)? #queryName + ' is a member of the ' " + 
             "+ Name + ' Society' : #queryName + ' is not a member of the ' + Name + ' Society'";

String queryResultString = 
                    parser.parseExpression(expression).getValue(societyContext, String.class);
// queryResultString = "Nikola Tesla is a member of the IEEE Society"

3항 연산자의 더 간단한 문법은 다은 섹션의 엘비스 연산자를 봐라.

7.5.14 엘비스(Elvis) 연산자
엘비스 연산자는 3항 연산자 문법의 단축형으로 Groovy 언어에서 사용된다. 보통 3항 연산자에서는 변수를 두번 반복해야 한다. 예를 들면 다음과 같다.

Java

String name = "Elvis Presley";
String displayName = name != null ? name : "Unknown";

대신에 엘비스의 헤어스타일과 유사한 엘비스 연산자를 사용할 수 있다.


1
2
3
4
5
6
7
Java

ExpressionParser parser = new SpelExpressionParser();

String name = parser.parseExpression("null?:'Unknown'").getValue(String.class);

System.out.println(name);  // 'Unknown'

좀 더 복잡한 예제를 보자.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
Java

ExpressionParser parser = new SpelExpressionParser();

Inventor tesla = new Inventor("Nikola Tesla", "Serbian");
StandardEvaluationContext context = new StandardEvaluationContext(tesla);

String name = parser.parseExpression("Name?:'Elvis Presley'").getValue(context, String.class);

System.out.println(name); // Mike Tesla

tesla.setName(null);

name = parser.parseExpression("Name?:'Elvis Presley'").getValue(context, String.class);

System.out.println(name); // Elvis Presley

7.5.15 안전한 탐색(Navigation) 연산자
안 전한 탐색 연산자는 NullPointerException를 피하기 위해 사용하고 Groovy 언어에서 가져왔다. 보통 객체를 참조할 때 메서드에 접근하거나 객체의 프로퍼티에 접근할 때 null이 아닌지 확인해야 한다. 이 작업을 하지 않기 위해 안전한 탐색 연산자는 예외를 던지는 대신에 null을 반환한다.


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
Java

ExpressionParser parser = new SpelExpressionParser();

Inventor tesla = new Inventor("Nikola Tesla", "Serbian");
tesla.setPlaceOfBirth(new PlaceOfBirth("Smiljan"));

StandardEvaluationContext context = new StandardEvaluationContext(tesla);

String city = parser.parseExpression("PlaceOfBirth?.City").getValue(context, String.class);
System.out.println(city); // Smiljan

tesla.setPlaceOfBirth(null);

city = parser.parseExpression("PlaceOfBirth?.City").getValue(context, String.class);

System.out.println(city); // null - NullPointerException을 던지지 않는다!!!


Note
엘비스 연산자를 표현식의 기본값을 설정하는데 사용할 수 있다. 예를 들어 @Value 표현식에서 다음과 같이 사용한다.

Java

@Value("#{systemProperties['pop3.port'] ?: 25}")

이는 시스템 프로퍼티 pop3.port가 정의되어 있다면 pop3.port를 주입하고 정의되어 있지 않다면 25를 주입한다.

7.5.16 컬렉션 선택기능(Selection)
선택기능(Selection)은 소스 컨텍션에서 언트리를 선택해서 다른 컬렉션으로 변환하는 강력한 표현식 언어의 기능이다.

선 택기능은 ?[selectionExpression]의 문법을 사용한다. 선택기능은 컬렉션을 필터링해서 원래 요소의 서브셋을 가진 새로운 컬렉션을 반환한다. 예를 들어 선택기능으로 세르비아(Serbian) 발명가의 리스트를 쉽게 얻을 수 있다.


1
2
3
4
Java

List<Inventor> list = (List<Inventor>) 
      parser.parseExpression("Members.?[Nationality == 'Serbian']").getValue(societyContext);

선 택기능은 리스트와 맵에서 도무 사용할 수 있다. 리스트의 경우 선택 크리테리아는 개별 리스트요소에 대해 평가되고 맵에서 선택 크리테리아는 맵의 각 엔트리에 대해 평가된다. (Map.Entry 자바 타입의 객체들) 맵 엔트리는 선택기능에서 사용하기 위해 프로퍼티로 접근할 수 있는 키와 값을 가진다.

다음 표현식은 원래의 맵에서 27보다 작은 값을 가진 엔트리로 이루어진 새로운 맵을 반환한다.


1
2
3
Java

Map newMap = parser.parseExpression("map.?[value<27]").getValue();

게다가 선택된 모든 요소에서 첫번째나 마지막 값을 획득하는 것도 가능한다. 선택기능의 ^[...] 문법을 사용해서 매칭된 첫 엔트리를 획득하고 $[...]로 마지막 엔티르를 획득한다.

7.5.17 컬렉션 투영(Projection)
투 영기능은 컬렉션에 하위 표현식을 평가해서 새로운 컬렉션을 반환한다. 투영 문법은 ![projectionExpression]이다. 예제로 이해하는 것이 가장 쉬운데 발명가들의 리스트를 가지고 있지만 발명가들이 테어난 도시의 리스트를 원한다고 가정해보자. 발명가 리스트에서 모든 인트리에 대해 'placeOfBirth.city'를 효율적으로 평가하기를 원한다. 다음과 같이 투영을 사용한다.


1
2
3
4
Java

// [ 'Smiljan', 'Idvor' ]를 반환한다.
List placesOfBirth = (List)parser.parseExpression("Members.![placeOfBirth.city]");

맵에서도 투영기능을 사용할 수 있고 맵의 경우 투영 표현식은 맵의 각 엔트리마다(자바 Map.Entry로 표현되는) 평가된다. 맵의 투영결과는 맵의 각 엔트리에 대한 투영 표현식의 평가결과로 이루어진 리스트이다.

7.5.18 표현식 템플릿
표현식 템플릿으로 하나 이상의 평가 블럭을 가진 리터럴 문자를 섞을 수 있다. 각 평가 블럭은 사용자가 정의할 수 있는 접두사와 접미사로 구분되고 일반적으로는 구분자로 #{ }를 사용한다. 다음 예제를 보자.


1
2
3
4
5
6
7
Java

String randomPhrase = 
   parser.parseExpression("random number is #{T(java.lang.Math).random()}", 
                          new TemplateParserContext()).getValue(String.class);

// "random number is 0.7038186818312008"로 평가된다

#{ } 구분자내의 표현식을 평가한 결과(이 예제에서는 random() 메서드를 호출한 결과이다.)와 'random number is ' 리터럴 문자를 연결해서 문자열을 평가한다. parseExpression() 메서드의 두번째 아규먼트는 ParserContext의 타입이다. ParserContext 인터페이스는 표현식 템플릿 기능을 지원하려고 표현식을 어떻게 파싱하는 지에 영향을 주려고 사용한다. TemplateParserContext의 정의는 다음에 나와있다.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
Java

public class TemplateParserContext implements ParserContext {

  public String getExpressionPrefix() {
    return "#{";
  }

  public String getExpressionSuffix() {
    return "}";
  }
  
  public boolean isTemplate() {
    return true;
  }
}

7.6 예제에 사용한 클래스들


  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
Inventor.java
Java

package org.spring.samples.spel.inventor;

import java.util.Date;
import java.util.GregorianCalendar;

public class Inventor {

  private String name;
  private String nationality;
  private String[] inventions;
  private Date birthdate;
  private PlaceOfBirth placeOfBirth;
  
  
  public Inventor(String name, String nationality)
  {
    GregorianCalendar c= new GregorianCalendar();
    this.name = name;
    this.nationality = nationality;
    this.birthdate = c.getTime();
  }
  public Inventor(String name, Date birthdate, String nationality) {
    this.name = name;
    this.nationality = nationality;
    this.birthdate = birthdate;
  }
  
  public Inventor() {
  }

  public String getName() {
    return name;
  }
  public void setName(String name) {
    this.name = name;
  }
  public String getNationality() {
    return nationality;
  }
  public void setNationality(String nationality) {
    this.nationality = nationality;
  }
  public Date getBirthdate() {
    return birthdate;
  }
  public void setBirthdate(Date birthdate) {
    this.birthdate = birthdate;
  }
  public PlaceOfBirth getPlaceOfBirth() {
    return placeOfBirth;
  }
  public void setPlaceOfBirth(PlaceOfBirth placeOfBirth) {
    this.placeOfBirth = placeOfBirth;
  }
  public void setInventions(String[] inventions) {
    this.inventions = inventions;
  }
  public String[] getInventions() {
    return inventions;
  }       
}

PlaceOfBirth.java
Java

package org.spring.samples.spel.inventor;

public class PlaceOfBirth {

  private String city;
  private String country;

  public PlaceOfBirth(String city) {
    this.city=city;
  }
  public PlaceOfBirth(String city, String country)
  {
    this(city);
    this.country = country;
  }
  public String getCity() {
    return city;
  }
  public void setCity(String s) {
    this.city = s;
  }
  public String getCountry() {
    return country;
  }
  public void setCountry(String country) {
    this.country = country;
  }
}

Society.java
Java

package org.spring.samples.spel.inventor;

import java.util.*;

public class Society {

  private String name;

  public static String Advisors = "advisors";
  public static String President = "president";

  private List<Inventor> members = new ArrayList<Inventor>();
  private Map officers = new HashMap();

  public List getMembers() {
    return members;
  }

  public Map getOfficers() {
    return officers;
  }

  public String getName() {
    return name;
  }

  public void setName(String name) {
    this.name = name;
  }

  public boolean isMember(String name)
  {
    boolean found = false;
    for (Inventor inventor : members) {
      if (inventor.getName().equals(name))
      {
        found = true;
        break;
      }
    }        
    return found;
  }    
}