[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년 4월 15일 금요일

[Talk]잘 나아가고 있는 것인가..

나른한 오후.. 요새 몸이 안좋아서 병원을 다니고 있다.. 비용이 꽤 만만치 않긴 하지만, 그래도 다행히 실손의료보험을 들어놔서 치료 다 받고 나면 신청할 계획이다.. 다른 분들도 건강 잘 챙기시길 특히나 우리처럼 앉아서 노상 일하는 직업은 스트레칭도 해주면서 조금씩의 여유를 느끼는 것이 좋을 듯 하다..

꾸준히 햄의 글을 갖고 오고 있고, 중간 중간 내가 겪었던 것 혹은 정리를 한 번정도는 했어야 하는 것들을 포스팅 하고 있다.. 물론, 난 개발쪽에만 관심이 지대하게 많은 것은 아니기에 다른 내용들도 흥미가 생기는 부분은 종종 올리기도 한다..

또한, 난 햄의 글을 갖고 오면서 완벽하게 이해를 하거나 완벽하게 읽지는 못한다.. 아무래도 내가 공부한게 아니기도 하거니와 내 관심사가 아니지만, 약간의 흥미를 느껴서 가져오는 때도 있기 때문이다..

그렇게 가져오기를 3개월째가 되가고 있다.. 글의 양도 꽤 많아졌고, 구독자도 꽤 많은듯 하고 어쩌다 한 번 댓글이 달리기도 했다.. 근데 문득 오늘 그런 생각이 드는 것이다..

과연.. 잘 하고 있는 것인가..??

애초에 햄처럼 거창하게 할 생각도 없기도 했고, 내 생활패턴이라고 해야될까.. 내 개발 습관이라고 해야될까.. 좀 여러 방면에서 약간의 변화라도 주기 위해서 시작한 것이 이 블로그인데 그런 부분에서 보자면, 상당히 큰 변화가 있었다..

글 적는 것을 거부하지도 않고, 모르던 로직 내지는 문법을 검색을 통해서 해결하고나면, 포스팅하는것도 좋아졌고 나 스스로 뿌듯함도 느끼고 있고, 개발쪽이 아닌 내 취미 혹은 관심사를 나름대로 정리하고 판단도 해보기도 하고 처음보다는 확실히 좋아진거 같긴하다..

그런데.. 햄의 글을 다 갖고 오고나면..?? 그 다음에는 어떻게 방향을 또 잡는게 좋을까.. 햄의 블로그는 지속적으로 관심을 갖고 보긴 하겠지만, 나 스스로도 먼가 관심을 갖고 해야 되는 것이 아닐까 싶다..

하다못해 현 프로젝트에서 쓰이는 문법 혹은 메소드에 대해서 이해가 안가는 것이라면, 찾아서 조금 더 공부도 해보고 세미나 같은 것도 조금 더 능동적으로 찾아서 참석도 해보고 그래야 되는 것일까..

쓸데없는 부담 없이 그런 것 없이 하려고 했던 블로그이긴 한데.. 막상 나중을 생각하니 지금의 방향성이 잘 나아가고 있는 것인가 싶기도 하다..

한 달에 하나의 글을 포스팅하건 두 달에 하나의 글을 포스팅하건 꾸준함을 유지하려고자 한다.. 그게 목표이기도 했고.. 근데 계속 잘하고 있는건가 하는 생각이 드넹.. 아흠..

몸이 아퍼서 그런걸까.. 그냥 차근 차근 한발 한발 나아가면 될수도 있는건데.. 쓸데 없는 생각이 많아진다..

[EP]Google Code Jam 참가 후기..

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

Google Code Jam 2012에 참가했습니다. 구글 코드잼은 구글에서 매년 진행하는 알고리즘 경진대회입니다. 항상 이맘때쯤 진행하는데 알고리즘 문제를 몇개 주고 시간내에 이를 프로그래밍해서 답을 제출하는 방식으로 진행이 되고 그걸로 순위를 매깁니다. 어떤 의미해서는 개발자들의 축제(?)라고 할 수도 있고 이런 식의 알고리즘을 푸는 것은 ACM이나 ICPC 대회라고 부르고 코드잼외에도 수많은 사이트들이 있습니다. 구글처럼 대형기업에서 진행하는 것은 페이스북의 Hacker cup이 있습니다. 구글은 한지 수년된 걸로 알고 있고 Hacker cup은 작년에 처음 한것 같습니다. 국내에서는 올해 처음으로 코드잼 코리아가 진행되었습니다.

code jam 로고
제가 알고리즘은 잘 못 풀기 때문에 아주 얕은 경험이지만 이번에 하다보니 코드잼을 잘 모르는 사람들도 많고 저도 처음에 하면서 불필요한 헛발질들도 좀 한 관계로 내년에 참가해보려는 사람들에게 도움이 좀 될까 하고 코드잼에 대한 내용을 적어봅니다.

코드잼 기준으로 Qualification Round이 예선전입니다. 일정점수(보통 20점) 정도를 넘으면 QR 통과입니다. 통과하면 Online Round 1로 가고 Online Round 1은 총 3번 진행합니다. 3번 중 아무거나 참여해서 1,000명 안에 들면 Online Round 2로 진출합니다. 총 3번 진행하니까 3,000명이 Online Round 2로 진출하는 것입니다. 물론 3개 다 참여해도 됩니다. 문제를 풀면 점수가 나오고 문제를 푼 시간까지 포함해서(제출했는데 틀리면 페널티를 먹는데 시간으로 페날티를 먹습니다.) QR에서는 점수만 보기 때문에 페널티가 의미 없습니다. QR은 보통 24시간정도의 시간이 주어지지만 그 다음부터는 2시간 반내에 풀어야 합니다.(Round 2는 못가봐서 그 뒤부터는 잘 모르겠네요. 거기까지 가신 분이 이 글을 참고하고 있진 않으실 테니...)

코드잼 대회전에 알고 있어야 할 것들
문제는 보통 A, B, C 3개가 나오게 됩니다. 해커컵도 거의 비슷하게 진행이 되는데 어떤 문제인지 설명이 죽~ 나옵니다.(물론 영어로요.) 인풋을 주는데 인풋 파일을 프로그램에 입력하면 아웃풋을 출력하는 형태로 이루어집니다. 그래서 결과는 아웃풋파일을 제출해서 답이 맞는지 안맞는지 검사하게 됩니다.


C-like

4
3 1 5 15 13 11
3 0 8 23 22 21
2 1 1 8 0
6 2 8 29 20 8 18 18 21

인풋은 보통 위와 같은 형식입니다. 맨 위에 4는 문제가 4개라는 의미이고 보통은 한라인에 한문제씩인데 경우에 따라서는 아닌 경우도 있습니다. 각 라인의 값이 무엇을 의미하는지는 문제에 따라 다르지만 위처럼 여러개의 값을 공백으로 분리해서 나타냅니다. 그래서 미리 인풋파일을 읽어서 파싱하는 코드정도는 짜두는게 좋습니다.(이건 어느 문제에서나 해야되는 거니까요.) 저는 보통 첫라인은 읽지 않고 라인별로 리스트로 만들어서 처리하지만 그건 편의에 따라서 하면 됩니다. 문자열을 복사해서 처리할 수 있지만 이건 좀 귀찮으니까 그냥 파일로 읽는게 좋습니다. 알고리즘은 특성상 TDD나 유닛테스트와 함께 짜기 좋기 때문에 테스트 환경을 미리 준비해 두는 것도 좋습니다.


C-like

Case #1: 3
Case #2: 2
Case #3: 1
Case #4: 3

아웃풋은 위와 같은 형태가 됩니다. Case #문제번호: 와 같은 형식에 옆에 답을 적어주게 됩니다. 그래서 인풋에서 각 라인의 문제를 읽었을 때 넘버링을 해서 이처럼 출력할 수 있어야 합니다. 아웃풋도 인풋처럼 출력하는 부분을 미리 작성해 두면 시간을 절약할 수 있어서 좋습니다. 출력을 콘솔에 출력해서 파일에 붙혀넣어도 되고 어차피 파일을 제출해야 하기 때문에 파일쓰기로 만들어도 좋습니다.

코드잼 문제의 인풋 버튼

각 문제는 위처럼 Small input와 Large input 2개가 주어집니다. 위 사진은 이미 끝난 라운드의 문제라 2개 다 버튼이 보이지만 원래는 Small input을 통과해야 Large input에 도전할 수 있습니다. 버튼을 누르면 몇분이내에 아웃풋 파일과 소스를 제출해야 하기 때문에 미리 뭔가 하고 눌러보면 페날티만 물게 됩니다. 해커컵 같은 경우는 작년에 재시도가 없어서 한번만 눌러서 떨어졌던 것 같습니다.

그리고 옆에 점수를 잘봐야 합니다. 보통 20점 넘으면 예선 통과 이런 식인데 한 문제의 small과 Large를 다 풀어도 예선 통과 점수가 안되는 경우도 있습니다. 그럴 경우에는 다른 문제의 Small을 하나 더 풀어야 통과할 수 있기 때문에 점수랑 상황을 잘 보고 해야 합니다. 저는 항상 QR에서 떨어지다가 이번에 처음 QR을 통과했었는데 기분인지는 몰라도 QR에서는 A 문제보다 B 문제가 더 쉬웠던 기분이 들었습니다. 꼭 A가 쉬운건 아닙니다.(보통은 점수가 낮으면 쉽다고 추측할 수 있겠죠.)

코드잼 대회에 참가할때 고려해야 할 것들
몇 번 해보니 가장 크고 처음 부딪히는 난관은 문제의 이해입니다. 한국 구글 코드잼이 아닌 이상 당연히 문제는 영어가 나옵니다. 알고리즘 문제는 구어체가 아니기 때문에 이게 은근히 문제를 이해하기 어렵고 결과적으로 정답을 맞춰야하기 때문에 문제의 조건 중 하나만 잘못 이해하더라도 당연히 정답을 맞출 수 없습니다. 여러번 하다보니 좀 나아졌지만 몇년전에 할 때는 문제도 제대로 이해하지 못했서 메일링이나 채팅에서 여럿이 모여서 문제에 대해서 이야기 나누고는 했습니다.

알고리즘은 현업에서 하는 프로그래밍하고는 좀 다르다고 생각합니다. 프로그래밍이란 것은 문제를 해결하는 과정이라고 하긴 하지만 현업에서 프로그래밍 할 때는 사실 알고리즘 성격의 코딩을 할 일은 별로 없습니다. 웹 어플리케이션에서는 대게 더 비즈니스 로직이 뻔한 수준(?)이고 어떤 어려운 알고리즘을 푸는 일 같은 건 별로 없습니다. 알고리즘을 푸는 것은 평소하던 프로그래밍하고 사고하는 방식이 완전히 다른 느낌입니다. 그래서 이런 쪽으로 연습을 많이 해두는 게 좋습니다.(문제는 알고리즘 사이트들에 수없이 있습니다.)

대회 참가를 시작하고 10분 정도가 지나면 제출자가 나타나기 시작합니다. 코드잼은 왼쪽에 탑스코어러와 제출자 통계가 실시간으로 나옵니다. 저는 아직 문제를 다 읽지도 못했는데 이미 다 풀어서 제출까지 하는 사람들이 등장합니다. 여기서 잠시 멘탈 붕괴가 올 수 있는데 참고 풀어야 됩니다. ㅠㅠ 어차피 그런 수준의 사람과 경쟁할 의도는 아니었으니까요. ㅎ

코드를 짤 때 Large input도 고려해서 코딩해야 합니다. 문제를 보면 input 값의 범위가 나오는데 이 경계값까지 고려해서 작성해야 합니다. 보통 small input은 값의 범위가 좀 적지만 large input은 큰 범위의 값들이 많이 나오게 됩니다. 이런 걸 잘 몰랐을 때는 타입을 Int로 하거나 성능을 고려하지 않고 막 짜다가 Large input을 돌려보다가 오류가 나거나 Stackoverflow가 나서 수정하다가 제출 못하는 일도 있었습니다. input 파일을 다운받기 전에 미리 경계값을 넣어도 잘 돌아가는지 테스트해 보아야 합니다. 보통 small input은 여러번 시도할 수 있지만 large input은 한번의 기회 뿐입니다.(large는 보통 결과도 나중에 나옵니다.)

추가로 피곤한 것 중 하나가 output을 제출했을 틀렸다고는 나오지만 어느게 틀렸는지 나오지 않습니다. 문제를 잘 못 이해한 부분이 있거나 로직에 버그가 있을 경우 어디가 틀린건지 알 수 없습니다. 이건 뭐 방법이 없는것 같습니다. 로직을 보면서 다시 고민해서 버그를 찾거나 문제를 손으로 직접 다 풀어봐서 어느 문제가 왜 잘못되었는지 찾아내는 방법밖에 없는 것 같습니다. 저는 항상 여기서 시간을 다 소비합니다. ㅠㅠ

Epilogue
매년 참가는 하고 있는데 사실 이런 대회는 자신이 가장 익숙한 언어를 사용해서 하는 것이 좋습니다. 하지만 저는 뭐 재미나 연습의 의미도 있고 해서 Scala로 풀었습니다. Scala는 제가 익숙한 언어는 아니고 하다보면 API 문서를 보고 찾아서 하거나 문법을 몰라서 헤매는 경우도 많기는 하지만 Scala를 공부하면서 사실 사용할 일은 많지 않기 때문에 Scala 연습도 겸할 겸해서 참여했습니다. Scala를 연습하는게 한 50%정도라면 알고르짐 푸는 연습하는게 30%정도이고 20%정도는 QR통과를 목표로 하고 있었습니다.(QR통과는 한번도 못해봤기 때문에요.)

그런 면에서는 소기의 성과를 달성했습니다. 몇가지 안되긴 하지만 Scala를 쓰면서 손에도 약간 익었고 Scala IDE나 ScalaTest도 사용해 보았습니다. 알고리즘도 아직 부족하지만 약간씩 늘고 있는 것 같긴 하고요. 그럼에서 Round 1에서 떨어지니까 좀 분했습니다. 문제도 잘못 이해하기도 했고 저는 2시간 반이란 시간이 턱없이 부족하기도 했지만 내년을 위해서는 더 정진해야겠습니다. Code Jam 2012 Statistics - Go, Hero!사이트에 가면 코드잼 참가에 대한 여러가지 언어나 나라별 통계를 볼 수 있습니다. 


My Comment..
음.. 내가 이런 코딩 대회 내지는 알고리즘 문제를 풀지는 미지수다.. 햄의 글을 읽으면서도 당췌 먼소린지 이해가 안되고 있으니.. ㅡ;;ㅡ.. 어찌보면 내 업무도 버거운데 왠 알고리즘 문제풀기 라는 생각도 쪼매 들기도 하고.. 그래도 이런곳이 있다는 것을 알고 있는 것은 좋은 것 같다.. 과거에는 마냥 다 관심이 없었는데 요새는 깊이가 없더라도 좀 새로운 것을 보면 오옹~~~ 이러면서 약간의 흥미를 느끼기도 한다.. ㅎㅎ..


[Book] Node Web Development 한국어판..

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

Node Web Development 한국어판 - 4점
데이비드 헤론 지음
손병대 옮김
에이콘출판

뭐 아시는 분은 아시겠지만 저는 이 책과 같은 주제를 다루고 있는 node.js 프로그래밍의 저자입니다. 이 말을 굳이 하는 것은 제가 이 글을 통해서 제 책을 좀 더 팔아보겠다고 하는 것은 아님에도 완전히 객관적일 수도 없으며 그런 오해를 일으키고 싶지 않기 때문입니다.

저는 사실 이 책의 원서도 구매하고 역서도 구매(출판사에서 받았지만) 했는데 원서는 읽지 않고 역서만 읽었습니다. 작년말이나 올 초까지라면 모르지만 현재는 국내에 Node.js 저서가 2권이나 출간되었기 때문에 이 책이 가지는 중요도(?)나 매리트는 거의 없다고 해도 무방하다고 생각합니다. 저한테도 이 책을 읽어야할 큰 매력은 못 느꼈지만 지금 읽지 않으면 절대 안 읽게 될 것 같고 분량이 많지 않기 때문에 집어들었습니다.

이 책은 Node.js에 대한 입문서를 경향하고 있지만 사실 내용은 꽤 부실합니다. 부실하다기 보다는 난해하다는 표현이 더 맞은지도 모르겠습니다. 원서는 0.4를 기반으로 작성되었지만 그래도 역자분이 0.6 릴리즈에 맞추어서 약간 내용을 보강했기 때문에 0.6에 맞춰서 참고할 수 있습니다. 약간 책을 설렁설렁 읽기는 했지만 책을 쓰면서 Node.js를 상당히 공부했음에도 불구하고 이 책은 좀 난해했고 제가 설명하려고 했던거나 중요하다고 생각하는 부분이 빠져서 그런지 몰라도 과연 이 책을 보고 Node.js를 이해할 수 있을까 하는 의문이 들었습니다.

이 책이 커버하는 부분은 Node.js 설치하고 아주 간략한(제 기분에선 너무 간략한) Node.js의 특징 소개와 함게 npm에 대해서 약간 설명하고 웹 어플리케이션 작성으로 넘어갑니다. Node.js는 바로 코딩할 수 있을만큼 진입 장벽이 낮다고 보기 때문에 이런 부분자체까지 안좋다고 보지는 않지만 저는 예제를 보면서 설명이 좀 헷갈렸습니다. Connect와 Express를 이용해서 같은 예제를 작성하고 후반에는 추가적인 웹어플리케이션과 한께 SQLITE3나 MongoDB를 함께 사용하는 예제가 나옵니다. 줄기만 보면 괜찮아 보이지만 막상 읽으면 예제와 설명이 좀 난해해서 이해하기가 좀 어렵지 않나 생각합니다. 아주 간단한 컨셉에 너무 많은 부분을 한꺼번에 설명하려고 하거나 미리부터 나중에 추가할 예제부분까지 고려되어 있다보니까 오히려 더 예제가 복잡해 지지 않나 생각합니다.

그래도 Connect에 대해서 많이 다루고 있고 Express보다는 직접 웹서버를 컨트롤하는 부분에 대해서 상당부분을 할당하고 있습니다. 그리고 SQLITE3에 대한 부분이 포함되어 있어서 이런 부분에 대해 참고할 내용이 있으시다면 한번 정도는 봐도 나쁠건 없어보입니다. 평점이 낮은 것은 현재 다른 입문서가 나와있어서 매리트가 떨어진 것도 한몫하고 있습니다. 굳이 Node.js를 공부하시려면 저서로 나온 책 중에서 고르시라고 하고 싶네요.(저는 제 책만 보고 다른 저서는 아직 안봤기 때문에 딱히 어느 책이 더 좋다고 말씀드리기는 어렵네요. 제 입장에선 제 책을 보시고 피드백을 주면 감사하긴 하지만요 ㅎㅎㅎ)


My Comment..
내가 노드라는 것 자체를 만져볼지는 미지수지만, 사람일  혹시 모르니.. 내가 공공사업만 할거라고 생각했지.. 금융권쪽에서 일을할거라고 생각지도 못했던 것처럼..

[JAVA]4장 IoC 컨테이너 #10..

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

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

4.10 클래스패스 스캔과 관리된 컨포넌트
이 챕터의 예제 대부분은 스프링 컨테이너에서 각 BeanDefinition를 만드는 설정메타데이터로 XML을 사용한다. 이전 섹션(Section 4.9, “어노테이션기반의 컨테이너 설정”)에서 소스레벨의 어노테이션으로 어떻게 설정 메타데이터를 사용할 수 있는지 보여주었다. 어노테이션을 사용하는 예제에서도 "base" 빈 정의는 명시적으로 XML파일에 정의하고 어노테이션은 의존성 주입에만 사용한다. 이번 섹션은 클래스패스를 스캔해서 후보 컴포넌트들을 암묵적으로 찾아내는 옵션을 설명한다. 후보 컴포넌트들은 필터 크리테리아와 일치한 클래스들이고 컨테이너에 등록된 빈 정의들 중에 대응되는 것들이다. 이는 빈 등록을 수행하는 XML을 사용하지 않아야 하고 대신 컨테이너에 등록된 빈 정를 갖는 클래스들을 선택하기 위해 어노테이션(예를 들면 @Component)이나 AspectJ 타입의 표현식이나 자신만의 커스텀 필터 크리테리아를 사용할 수 있다.



Note
스프링 3.0에서는 Spring JavaConfig project의 많은 기능들이 스프링 프레임워크 핵심의 일부가 되었다. 이는 전통적인 XML파일 대신 자바로 빈을 정의할 수 있도록 한다. 이러한 새로운 기능들을 어떻게 사용하는가에 대한 예제는 @Configuration, @Bean, @Import, @DependsOn 어노테이션을 봐라.

4.10.1 @Component와 스테레오타입(stereotype) 어노테이션
스프링 2.0 이상의 버전에서 @Repository 어노테이션은 어떤 클래스가 그 역할을 충족시켰거나 레파지토리의 stereotype (또는 데이터 접근계층이나 DAO로 알려진)이라는 표시이다. 이 표시의 사용은 Section 14.2.2, “Exception translation”에서 설명했듯이 예외의 자동 변환이다.

스프링 2.5에 도입된 스테레오타입 어노테이션: @Component, @Service, @Controller. @Component 는 스프링이 관리하는 모든 컴포넌트에 대한 제너릭 스테레오타입이다. @Repository, @Service, @Controller는 더 특정한 유즈케이스에 대한 @Component의 특수한 형태이다. 예를 들어 퍼시스턴스, 서비스, 프리젠테이션 계층에서 각각 사용한다. 그러므로 컴포넌트 클래스에 @Component 어노테이션을 붙힐 수도 있지만 대신 @Repository, @Service, @Controller 어노테이션을 붙힘으로써 클래스들이 도구가 처리하는데 더 적합하도록 할 수 있고 관점에 더 연관성이 있게 한 수 있다. 예를 들어 이러한 스테레오타입 어노테이션은 포인트컷에 대한 이상적인 타겟을 만든다. 또한 @Repository, @Service, @Controller는 스프링 프레임웍의 차기 릴리즈버전에서 추가적인 의미가 생길 가능성도 있다. 그래서 서비스계층에 @Component나 @Service 중에서 어느 것을 사용할기 선택해야 한다면 @Service가 명확하게 더 나은 선택이다. 비슷하게 앞에서 얘기했듯이 @Repository는 퍼시스턴스 계층에서 자동 예외변환에 대한 표시로서 이미 지원된다.

4.10.2 자동 클래스 탐지와 빈 정의 등록
스프링은 자동적으로 스테레오타입의 클래스들을 탐지하고 대응되는 ApplicationContext의 BeanDefinition를 등록한다. 예를 들어 다음 두 클래스는 이러한 자동탐지에 알맞는 클래스들이다.


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

@Service
public class SimpleMovieLister {

  private MovieFinder movieFinder;

  @Autowired
  public SimpleMovieLister(MovieFinder movieFinder) {
      this.movieFinder = movieFinder;
  }
}

Java

@Repository
public class JpaMovieFinder implements MovieFinder {
  // 명확하도록 구현은 생략한다
}

이러한 클래스들을 자동탐지하고 대응하는 빈을 등록하려면 XML에 다음 요소를 포함시켜야 한다. base-package 요소는 두 클래스에 대한 공통 부모 팩키지이다. (대신 각 클래스의 부모 팩키지를 포함하는 리스트를 콤마로 분리해서 지정할 수도 있다.)

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

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context-3.0.xsd">

  <context:component-scan base-package="org.example"/>


</beans>


Note
클래스패스 패키지의 스캔은 클래스패스에서 대응되는 디렉토리 진입점이 있어야 한다. Ant로 JAR를 빌드할 때 JAR 태스크의 files-only 스위치가 켜져있지(activate) 않아야 한다.

게다가 component-scan 요소를 사용하면 AutowiredAnnotationBeanPostProcessor와 CommonAnnotationBeanPostProcessor는 둥다 암묵적으로 포함된다. 이는 두 컴포넌트가 자동으로 탐지되고 함께 연결된다. - 모두 XML에서 제공된 어떤 빈 설정 메타데이터는 없다.



Note
annotation-config 속성의 값을 false로 하면 AutowiredAnnotationBeanPostProcessor 와 CommonAnnotationBeanPostProcessor가 등록되지 않게 할 수 있다.

4.10.3 스캐닝을 커스터마이징하기 위한 필요 사용
기본적으로 @Component, @Repository, @Service, @Controller 어노테이션들이나 @Component 어노테이션이 붙은 커스텀 어노테이션이 붙은 클래스들만 후보 컴포넌트로 탐지된다. 하지만 커스텀 필터를 적용해서 간단하게 이러한 행동을 수정하거나 확장할 수 있다. component-scan 요소의 하위요소로 include-filter나 exclude-filter를 추가한다. 각 필터 요소들은 type와 expression속성을 필요로 한다. 다음 표는 필터링 옵션을 설명한다.

Table 4.5. Filter Types

필터 타입표현식 예시설명
annotationorg.example.SomeAnnotation타겟 컴포넌트에서 타입 레벨에서 표현되는 어노테이션
assignableorg.example.SomeClass타겟 컴포넌트들을 할달할 수 있는 (확장(extend)/구현(implement)) 클래스(또는 인터페이스)
aspectjorg.example..*Service+타겟 컴포넌트들과 일치되는 AspectJ 타입 표현식
regexorg\.example\.Default.*타겟 컴포넌트 클래스명과 일치되는 정규 표현식
customorg.example.MyTypeFilterorg.springframework.core.type .TypeFilter 인터페이스를 구현한 커스텀 구현체











다음 예제는 모든 @Repository 어노테이션을 무시하고 "stub" 레파지토리를 대신 사용하는 XML 설정을 보여준다.


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
Xml

<beans>

  <context:component-scan base-package="org.example">
     <context:include-filter type="regex" expression=".*Stub.*Repository"/>
     <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Repository"/>
  </context:component-scan>

</beans>


Note
<component-scan/> 요소의 속성으로 use-default-filters="false"를 지정해서 기본 필터를 사용안하도록 할 수도 있다. 이는 @Component, @Repository, @Service, @Controller 어노테이션이 붙은 클래스들을 자동 탐지하는 기능을 사용하지 않게 한다.

4.10.4 컴포넌트내에서 빈 메타데이터 정의
스프링 컴포넌트들은 컨테이너에 빈 정의 메타데이터를 제공할 수도 있다. 이는 @Configuration 어노테이션이 붙은 클래스들 내에서 빈 메타데이터를 정의하기 위해서 동일한 @Bean 어노테이션을 사용해서 할 수 있다. 다음은 간단한 예제이다.


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

@Component
public class FactoryMethodComponent {

  @Bean @Qualifier("public")
  public TestBean publicInstance() {
    return new TestBean("publicInstance");
  }

  public void doWork() {
    // 컴포넌트 메서드 구현체는 생략한다
  }
}

이 클래스는 내부에 doWork() 메서드가 있는 어플리케이션에 특화된 코드가 있는 스프링 컴포넌트다. 하지만 publicInstance() 메서드를 참조하는 팩토리 메서드가 있는 빈 정의도 제공한다. @Bean 어노테이션은 팩토리 매서드와 @Qualifier 어노테이션을 통한 제한자의 값같은 다른 빈 정의 프로퍼티들을 식별한다. 다른 메서드 레벨의 어노테이션들은 @Scope, @Lazy와 커스텀 qualifier 어노테이션으로 지정할 수 있다. 앞에서 얘기했듯이 추가적인 @Bean 메서드의 자동연결 지원과 함께 필드와 메서드들의 자동연결을 지원한다.

 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
Java

@Component
public class FactoryMethodComponent {

  private static int i;

  @Bean @Qualifier("public")
  public TestBean publicInstance() {
    return new TestBean("publicInstance");
  }

  // 커스텀 qualifier와 메서드 파라미터의 자동연결의 사용

  @Bean
  protected TestBean protectedInstance(@Qualifier("public") TestBean spouse, @Value("#{privateInstance.age}") String country) {
    TestBean tb = new TestBean("protectedInstance", 1);
    tb.setSpouse(tb);
    tb.setCountry(country);
    return tb;
  }

  @Bean @Scope(BeanDefinition.SCOPE_SINGLETON)
  private TestBean privateInstance() {
    return new TestBean("privateInstance", i++);
  }

  @Bean @Scope(value = WebApplicationContext.SCOPE_SESSION, proxyMode = ScopedProxyMode.TARGET_CLASS)
  public TestBean requestScopedInstance() {
    return new TestBean("requestScopedInstance", 3);
  }
}

예제는 String 메서드 파라미터 country를 또 다른 빈 privateInstance의 Age 프로퍼티의 값으로 자동연결한다. 스프링 표현언어(Spring Expression Language)요소는 #{ <expression> } 문법으로 프로퍼티의 값을 정의한다. @Value 어노테이션에 대한 표현식 리졸버는 표현식 문자를 처리할 때 빈 이름을 찾으려머 먼저 설정된다.

스프링 컴포넌트의 @Bean 메서드들은 스프링 @Configuration 클래스 내부의 @Bean 메서드들과는 다르게 수행된다. @Component 클래스들이 메서드와 필드의 호출을 가로채려고 CGLIB로 향상(enhanced)되지 않는다는 것이 차이점이다. CGLIB 프록싱은 @Configuration 클래스의 @Bean 메서드내에서 메서드와 필드를 호출하는 것은 협력객체를 참조하는 빈 메타데이터를 생성한다. 메서드들은 일반적인 자바 시맨틱으로 호출되지 않는다. 반면에 @Component 클래스의 @Bean 메서드내에서 메서드나 필드를 호출하는 것은 표준 자바 시맨틱을 갖는다.

4.10.5 이름으로 자동탐지되는 컴포넌트
스캐닝 과정 중에 컴포넌트를 자동탐지했을 때 그 빈의 이름은 해당 스캐너가 알고 있는 BeanNameGenerator 전략에 의해 생성된다. 기본적으로 name 값이 있는 어떤 스프링 스테레오타입 어노테이션 (@Component, @Repository, @Service, @Controller)도 name 값에 따라서 대응되는 빈 정의에 이름을 제공할 것이다.

어노테이션이 name 값이 없거나 탐지된 다른 컴포넌트(커스텀 필터로 발견된 컴포넌트 같은)가 있다면 기본 빈이름 생성기는 대문자로 쓰지 않고 정규화되지 않은 클래스명을 리턴한다. 예를 들어 다음 두 컴포넌트가 담지되었다면 이름은 myMovieLister와 movieFinderImpl가 될 것이다.


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

@Service("myMovieLister")
public class SimpleMovieLister {
  // ...
}

Java

@Repository
public class MovieFinderImpl implements MovieFinder {
  // ...
}


Note
기본 빈이름 전력에 의존하고 싶지 않다면 커스텀 빈이름 전략을 제공할 수 있다. 일단 BeanNameGenerator 인터페이스를 구현하고 아규먼트가 없는 기본 생성자를 만든다. 그 다음 스캐너를 설정할 때 정규화된 클래스명을 제공해라.


1
2
3
4
5
Xml

<beans>
  <context:component-scan base-package="org.example" name-generator="org.example.MyNameGenerator" />
</beans>

일반적인 규칙에 따라 다른 컴포넌트들을 명시적으로 참조하도록 만들 때마다 어노테이션에 이름을 지정하는 것을 고려해라. 반면 컨테이너가 연결에 대한 책임이 있는 경우에는 자동생성되는 이름으로 충분하다.

4.10.6 자동탐지된 컴포넌트에 범위(scope) 제공하기
보통의 스프링이 관리하는 컴포넌트처럼 자동탐지된 컴포넌트의 기본 범위와 가장 일반적인 범위는 싱글톤이다. 하지만 때로는 다른 범위가 필요하고 스프링 2.5가 제공하는 새로운 @Scope 어노테이션을 제공한다. 어노테이션에 범위의 이름을 지정하면 된다.


1
2
3
4
5
6
7
Java

@Scope("prototype")
@Repository
public class MovieFinderImpl implements MovieFinder {
  // ...
}


Note
어노테이션에 기반한 접근대신에 커스텀 범위 처리 전략을 제공하려면 ScopeMetadataResolver 인터페이스를 구현하고 아규먼트가 없는 기본 생성자를 만들어라. 그 다음 스캐너를 설정할 때 정규화된 클래스명을 제공해라.


1
2
3
4
5
Xml

<beans>
  <context:component-scan base-package="org.example" scope-resolver="org.example.MyScopeResolver" />
</beans>

싱글톤이 아닌 어떤 범위를 사용하면 범위를 가진 객체에 대한 프록시를 생성할 필요가 있을 수 있다. 프록시가 필요한 이유는 Section 4.5.4.5, “의존성에서 범위를 가진 빈”에서 설명했다. 이 목적을 위해 component-scan 요소에서 scoped-proxy 속성을 사용할 수 있다. 이 속성에는 no, interfaces, targetClass 세 가지 값을 지정할 수 있다. 예를 들어 다음 설정은 표준 JDK 다이나믹 프록시의가 될 것이다.

1
2
3
4
5
Xml

<beans>
  <context:component-scan base-package="org.example" scoped-proxy="interfaces" />
</beans>

4.10.7 어노테이션으로 qualifier 메타데이터 제공하기
@Qualifier 어노테이션은 Section 4.9.3, “qualifiers를 사용한 어노테이션 기반 자동연결의 미세조정”에서 이야기 했다. 해당 섹션의 예제들은 자동연결 후보를 처리할 때 세밀한 제어를 제공하는 @Qualifier 어노테이션과 커스텀 qualifier 어노테이션의 사용방법을 보여준다. 이 예제들은 XML 빈 정의에 기반하기 때문에 qualifier 메타데이터는 XML의 bean 요소의 하위요소로 qualifier나 meta 사용하는 후보 빈 정의에서 제공된다. 컴포넌트 자동탐지를 클래스패스에 기반해서 스캔할 때는 후보 클래스의 타입레벨 어노테이션으로 qualifier 메타데이터를 제공한다. 다음 세 가지 예제는 이러한 기법을 보여준다.

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

@Component
@Qualifier("Action")
public class ActionMovieCatalog implements MovieCatalog {
  // ...
}

Java

@Component
@Genre("Action")
public class ActionMovieCatalog implements MovieCatalog {
  // ...
}

Java

@Component
@Offline
public class CachingMovieCatalog implements MovieCatalog {
  // ...
}


Note
어노테이션에 기반한 대부분의 대체방법들 처럼 어노테이션 메타데이터는 클래스 정의 자체와 밀접한 관계가 된다는 것을 기억해야 한다. 반면 XML을 사용하면 해당 메타데이터가 클래스마다가 아니라 인스턴스마다 제공되기 때문에 qualifier 메타데이터에서 다양함을 제공하기 위해 같은 타입의 여러 가지 빈을 사용할 수 있다.

[JAVA]4장 IoC 컨테이너 #9..

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

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

4.9 어노테이션기반의 컨테이너 설정

꺽쇄괄호(< >)를 사용한 선언 대신 컴포넌트를 연결하기 위해 바이트코드 메타데이터에 의존하는 어노테이션 기반의 설정을 XML 설정의 대안으로 제공한다. 개발자들은 빈 연결을 나타내려고 XML을 사용하는 대신 적절한 클래스, 메서드 필스 선언에 어노테이션을 사용해서 선언을 컴포넌트 클래스로 옮겼다. Section 4.8.1.2, “예제 : RequiredAnnotationBeanPostProcessor”에서 말했듯이 BeanPostProcessor에 어노테이션을 사용해서 스프링 IoC 컨테이너를 확장하는 것이 일반적인 목적이다. 예를 들어 스프링 2.0에서 도입한 @Required 어노테이션으로 필수 프로퍼티를 강제할 수 있다. 스프링 2.5는 어노테이션을 스프링의 의존성 주입과 같이 일반적인 접근을 따를 수 있도록 했다. 본질적으로 @Autowired 어노테이션은 Section 4.4.5, “협력객체의 자동연결(Autowiring)”에서 설명한 것과 같은 기능을 제공하지만 더 세세하게 제어할 수 있고 더 넓은 범위에 적용할 수 있다. 스프링 2.5에서는 @PostConstruct, @PreDestroy같은 JSR-250 어노테이션에 대한 지원도 추가했다. 스프링 3.0은 @Inject와 @Named같은 javax.inject 패키지에 포함된 JSR-330 (Java의 의존성 주입) 어노테이션에 대한 지원을 추가했다. 이러한 어노테이션들에 대한 자세한 내용은 관련 섹션에서 확인할 수 있다.



Note
어노테이션 주입은 XML 주입 이전에 수행되므로 두가지 접근에 모두 연결된 프로퍼티들은 XML 설정이 어노테이션 설정을 오버라이드할 것이다.

언제나 그렇듯이 어노테이션들도 개별적인 빈 정의처럼 등록할 수 있지만 XML 기반의 스프링 설정에서 다음의 태그를 포함하면 암묵적으로 등록할 수 있다.(context 네임스페이스가 포함되었다.)



XML을 이용해서 스프링을 설정하는 것보다 어노테이션이 더 나은가?
어노테이션 기반의 설정을 도입하자 이 접근이 XML보다 '나은지'에 대한 의문이 생겼다. 간단한 답을 말하자면 경우에 따라 다르다. 좀 더 길게 얘기하자면 각 접근은 장단점이 있고 보통 개발자가 어떤 전략이 더 적합하다고 결정하는지에 달려있다. 이 접근들이 정의된 방법으로 인해서 어노테이션은 많은 컨텍스트를 제공하고 이는 더 짧고 더 간결한 설정으로 이어진다. 하지만 XML은 소스코드를 건드리거나 다시 컴파일할 필요없이 컴포넌트를 연결하는데 탁원하다. 어떤 개발자들은 소스로 연결하는 것을 선호하는 반면 어떤 개발자들은 어노테이션이 붙은 클래스들은 POJO가 아닐뿐더러 설정이 분산되어서 제어하기 어렵다고 주장한다.

어떤 선택을 하던지 스프링은 두 가지 스타일을 모두 제공하고 두 가지 스타일을 섞어서 사용할 수도 있다. 스프링이 JavaConfig 옵션을 통해 타겟 컴포넌트 소스코드를 건드릴 필요없이 비침입적인 방법으로 어노테이션을 사용할 수 있게 한다는 점은 중요하다. 그리고 도구적인 관점에서 모든 설정 스타일은 SpringSource Tool Suite에서 지원한다.



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

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
     xmlns:context="http://www.springframework.org/schema/context"
     xsi:schemaLocation="http://www.springframework.org/schema/beans
         http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
         http://www.springframework.org/schema/context
         http://www.springframework.org/schema/context/spring-context-3.0.xsd">

   <context:annotation-config/>

</beans>


Note
<context:annotation-config/>는 자신이 정의된 어플리케이션 컨텍스트에서만 어노테이션이 붙은 빈들을 찾는다. 이는 DispatcherServlet에 대한 WebApplicationContext에서 <context:annotation-config/>를 설정했다면 서비스가 아닌 컨트롤러에서만 @Autowired 빈을 확인한다는 의미이다. 더 자세한 정보는 Section 16.2, “The DispatcherServlet”를 참고해라.

4.9.1 @Required
@Required 어노테이션을 다음 예제처럼 빈 프로퍼티 setter 메서드에 적용한다.


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

public class SimpleMovieLister {

  private MovieFinder movieFinder;

  @Required
  public void setMovieFinder(MovieFinder movieFinder) {
    this.movieFinder = movieFinder;
  }

  // ...
}

이 어노테이션이 적용된 빈 프로퍼티는 빈 정의의 명확한 프로퍼티 값이나 자동연결을 통해서 설정시에 반드시 존재해야(populated) 한다는 것은 간단히 나타낸다. 적용된 빈 프로퍼티가 존재하지 않는다면 컨테이너는 예외를 던진다. 이는 최대한 명시적으로 실패를 나타내서 NullPointerException을 피할 수 있도록 한다. 빈 클래스내에 단언문을 두는 것은 여전히 권장한다. 예를 들어 init 메서드에 단언문을 작성한다. 이를 통해 컨테이너 외부의 클래스를 사용하는 경우에도 필수 참조와 값을 강제할 수 있다.

4.9.2 @Autowired
기대하듯이 "전통적인" setter 메서드에 @Autowired 어노테이션을 적용할 수 있다.


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

public class SimpleMovieLister {

  private MovieFinder movieFinder;

  @Autowired
  public void setMovieFinder(MovieFinder movieFinder) {
    this.movieFinder = movieFinder;
  }

  // ...
}


Note
다음 예제처럼 JSR 330의 @Inject 어노테이션을 스프링의 @Autowired 어노테이션 대신 사용할 수 있다. 더 자세한 정보는 here를 참고해라.

임의의 이름을 가진 메서드나 아규먼트가 여러게 있는 메서드에도 이 어노테이션을 적용할 수 있다.


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

public class MovieRecommender {

  private MovieCatalog movieCatalog;

  private CustomerPreferenceDao customerPreferenceDao;

  @Autowired
  public void prepare(MovieCatalog movieCatalog, CustomerPreferenceDao customerPreferenceDao) {
    this.movieCatalog = movieCatalog;
    this.customerPreferenceDao = customerPreferenceDao;
  }

  // ...
}

생성자나 필드에도 @Autowired를 적용할 수 있다.

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

public class MovieRecommender {

  @Autowired
  private MovieCatalog movieCatalog;

  private CustomerPreferenceDao customerPreferenceDao;

  @Autowired
  public MovieRecommender(CustomerPreferenceDao customerPreferenceDao) {
    this.customerPreferenceDao = customerPreferenceDao;
  }

  // ...
}

특정 타입의 배열을 요구하는 필드나 메서드에 어노테이션을 추가함으로써 ApplicationContext에서 해당 타입의 모든 빈을 제공할 수도 있다.

1
2
3
4
5
6
7
8
9
Java

public class MovieRecommender {

  @Autowired
  private MovieCatalog[] movieCatalogs;

  // ...
}

타입있는 컬렉션에도 똑같이 적용된다.

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

public class MovieRecommender {

  private Set<MovieCatalog> movieCatalogs;

  @Autowired
  public void setMovieCatalogs(Set<MovieCatalog> movieCatalogs) {
    this.movieCatalogs = movieCatalogs;
  }

  // ...
}

타입있는 Map도 요구하는 키의 타입이 String이라면 자동연결 할 수 있다. 맵의 값들은 요구하는 타입의 모든 빈을 담을 것이고 키는 대응하는 빈의 이름을 담을 것이다.

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

public class MovieRecommender {

  private Map<String, MovieCatalog> movieCatalogs;

  @Autowired
  public void setMovieCatalogs(Map<String, MovieCatalog> movieCatalogs) {
    this.movieCatalogs = movieCatalogs;
  }

  // ...
}

기본적으로 자동연결은 사용가능한 후보 빈이 없으면 실패한다. 어노테이션이 붙은 메서드, 생성자, 필드가 필수 의존성을 나타내는 것처럼 다루는 것이 기본 동작이다. 다음 예제에서 보여주듯 이 기본 동작을 변경할 수 있다.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
Java
public class SimpleMovieLister {

  private MovieFinder movieFinder;

  @Autowired(required=false)
  public void setMovieFinder(MovieFinder movieFinder) {
    this.movieFinder = movieFinder;
  }

  // ...
}


Note
클래스당 하나의 어노테이션이 붙은 생성자만 필수로 표시할 수 있지만 필수가 아닌 다수의 생성자에도 어노테이션을 붙힐 수 있다. 이 경우에 각각의 생성자는 후보군으로 다루고 스프링은 의존성을 만족할 수 있으면서 제일 많은 수의 아규먼트를 가진 가장 탐욕적인(greediest) 생성자를 사용한다.

@Autowired의 required 속성은 @Required 어노테이션과 상관없이 권장한다. required 속성은 해당 프로퍼티가 자동연결 목적에는 필수가 아니라는 것을 나타내고 자동연결을 할 수 없는 경우에 이 프로퍼티는 무시된다. 반면에 @Required는 컨테이너가 제공하는 어떤 방법으로든 설정되어야 하는 프로퍼티임을 강제한다는 면에서 더 강력하다. 주입되는 값이 없다면 그에 대응하는 예외가 발생한다.

인터페이스들에 대해서도 @Autowired를 사용할 수 있다. 이 인터페이스들은 잘 알려진 해결가능한 의존성들이다: BeanFactory, ApplicationContext, Environment, ResourceLoader, ApplicationEventPublisher, MessageSource. 이러한 인터페이스들과 ConfigurableApplicationContext나 ResourcePatternResolver같이 이 인터페이스들을 상속받은 인터페이스는 특별한 설정을 하지 않고도 자동으로 해결된다.


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

public class MovieRecommender {

  @Autowired
  private ApplicationContext context;

  public MovieRecommender() {
  }

  // ...
}


Note
@Autowired, @Inject, @Resource, @Value 어노테이션들은 스프링의 BeanPostProcessor 구현체가 처리한다. 이는 (무엇이든간에) 자신만의 BeanPostProcessor나 BeanFactoryPostProcessor 타입내에서는 이러한 어노테이션들을 적용할 수 없다는 의미이다. 이러한 타입들은 반드시 명시적으로 XML이나 스프링의 @Bean 메서드를 사용해서 '연결해야' 한다.

4.9.3 qualifiers를 사용한 어노테이션 기반 자동연결의 미세조정
타입으로 자동연결을 하면 다수의 후보들이 생기기 때문에 종종 선택과정에 대해 더 세밀한 제어가 필요하다. 이러한 제어를 하는 한 가지 방법은 스프링의 @Qualifier 어노테이션이다. 각 아규먼트에 특정 빈이 선택되도록 일치하는 타입의 세트를 한정시켜서 qualifier 값들을 지정한 아규먼트들에 연결할 수 있다. 가장 간단한 경우에 이는 평이한 기술적인 값이 될 수 있다.


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
Java

public class MovieRecommender {

  @Autowired
  @Qualifier("main")
  private MovieCatalog movieCatalog;

  // ...
}

생성자 아규먼트나 메서드의 파라미터에 개별적으로 @Qualifier 어노테이션을 지정할 수도 있다.

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

public class MovieRecommender {

  private MovieCatalog movieCatalog;

  private CustomerPreferenceDao customerPreferenceDao;

  @Autowired
  public void prepare(@Qualifier("main") MovieCatalog movieCatalog, CustomerPreferenceDao customerPreferenceDao) {
    this.movieCatalog = movieCatalog;
    this.customerPreferenceDao = customerPreferenceDao;
  }

  // ...
}

일치하는 빈 정의들은 다음과 같다. qualifier값으로 "main"을 가진 빈은 "main"값으로 제한된 생성자 아규먼트와 연결된다.

 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
Xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xmlns:context="http://www.springframework.org/schema/context"
  xsi:schemaLocation="http://www.springframework.org/schema/beans
      http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
      http://www.springframework.org/schema/context
      http://www.springframework.org/schema/context/spring-context-3.0.xsd">

  <context:annotation-config/>

  <bean class="example.SimpleMovieCatalog">
      <qualifier value="main"/>
      <!--  빈이 필요로하는 의존성을 주입한다 -->
  </bean>

  <bean class="example.SimpleMovieCatalog">
      <qualifier value="action"/>
      <!--  빈이 필요로하는 의존성을 주입한다 -->
  </bean>

  <bean id="movieRecommender" class="example.MovieRecommender"/>

</beans>

일치하는 값을 찾지 못했을 때(fallback match)를 대비해서 빈의 이름이 기본 qualifier 값으로 간주된다. 그러므로 중첩된 qualifier 요소대신에 "main"이라는 id로 빈을 정의해도 같은 결과가 나온다. 하지만 이름으로 특정 빈을 참조하는 이 관례를 사용할 수 있더라도 @Autowired는 본질적으로 추가적인 의미있는 qualifier로 타입주도 주입에 대한 것이다. 이 말은 실패했을 때(fallback) 빈 이름을 사용하더라도 qualifier 값은 항상 일치된 타입의 세트내에서 의미로 제한한다는 의미이다. 유일한 빈 id에 대한 참조를 의미론적으로 표현하지 않는다. 앞의 예제처럼 익명 빈 정의의 경우에 자동으로 생성되는빈 id와는 독립적으로 특정 컴포넌트의 특징을 표현하는 "main"나 "EMEA"나 "persistent"가 좋은 qualifier 값이다.

앞에서 얘기했듯이 qualifier는 Set<MovieCatalog> 같은 타입있는 컬렉션에도 적용된다. 이 경우에 선언된 qualifier에 일치하는 모든 빈이 컬렉션으로 주입된다. 이는 qualifier는 유일할 필요가 없다는 의미이다. 더 적절하게 말하면 필터링 크리테리아(criteria)를 간단히 구성한다. 예를 들어 "action" 이라는 같은 qualifier 값을 가진 MovieCatalog 빈을 다수 정의할 수 있다. 이 빈들은 모두 @Qualifier("action") 어노테이션이 붙은 Set<MovieCatalog>로 주입될 것이다.



Tip
이름을 사용하는 어노테이션 주도 주입을 표현하려 한다면 @Qualifier 값으로 빈 이름을 참조하는 것이 기술적으로 가능하더라도 @Autowired을 주로 사용하지 마라. 대신 의미적으로 유일한 이름의 특정 타겟 컴포넌트를 식별하기 위해 정의된 JSR-250 @Resource 어노테이션을 사용해라. 선언된 타입은 일치하는 빈을 찾는 과정과는 관계가 없다.

이러한 의미적 차이점의 특정한 영향처럼 컬렉션이나 맵 타입으로 정의된 빈들은 @Autowired로 주입될 수 없다. 그 이유는 타입 매칭이 이 빈들에는 적용하기가 적절하지 않기 때문이다. 이러한 빈에는 유일한 이름으로 컬렉션이나 맵 타입의 빈을 참조하는 @Resource를 사용해라.

필드, 생성자, 여러 아규먼트를 가진 메서드에 적용하는 @Autowired는 파라미터 레벨에서 qualifier 어노테이션으로 제한할 수 있다. 반면에 @Resource는 필드나 하나의 아규먼트만 가진 프로퍼티 setter 메서드만 지원한다. 그 결과 주입 대상이 생성자나 다수의 아규먼트를 가진 메서드라면 qualifier를 붙여라.

자신만의 커스텀 qualifier 어노테이션을 생성할 수 있다. 간단히 어노테이션을 정의하고 정의내에서 @Qualifier 어노테이션을 제공하라.


1
2
3
4
5
6
7
8
9
Java

@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface Genre {

  String value();
}

그러면 자동연결된 필드나 파라미터에 커스텀 qualifier를 제공할 수 있다.

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

public class MovieRecommender {

  @Autowired
  @Genre("Action")
  private MovieCatalog actionCatalog;

  private MovieCatalog comedyCatalog;

  @Autowired
  public void setComedyCatalog(@Genre("Comedy") MovieCatalog comedyCatalog) {
    this.comedyCatalog = comedyCatalog;
  }

  // ...
}

그 다음 후보 빈 정의에 대한 정보를 제공하라. <bean/> 태그의 하위요소로 <qualifier/> 태그들을 추가할 수 있고 커스텀 qualifier 어노테이션과 일치하는 type과 value를 지정해라. 타입은 정규화된 qualifier 어노테이션과 일치한다. 또는 충돌하는 이름이 존재할 위험이 없다면 편리하게 짧은 클래스명을 사용할 수 있다. 두 가지 접근을 다음 예제에서 보여준다.

 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
Xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xmlns:context="http://www.springframework.org/schema/context"
  xsi:schemaLocation="http://www.springframework.org/schema/beans
      http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
      http://www.springframework.org/schema/context
      http://www.springframework.org/schema/context/spring-context-3.0.xsd">

  <context:annotation-config/>

  <bean class="example.SimpleMovieCatalog">
      <qualifier type="Genre" value="Action"/>
      <!--  빈이 필요로하는 의존성을 주입한다 -->
  </bean>

  <bean class="example.SimpleMovieCatalog">
      <qualifier type="example.Genre" value="Comedy"/>
      <!--  빈이 필요로하는 의존성을 주입한다 -->
  </bean>

  <bean id="movieRecommender" class="example.MovieRecommender"/>

</beans>

Section 4.10, “클래스패스 스캔과 관리된 컨포넌트”에서 XML에서 qualifier 메타데이터를 제공하는 어노테이션 기반 qualifier의 대안을 볼 수 있다. 구체적인 내용은 Section 4.10.7, “어노테이션으로 qualifier 메타데이터 제공하기”를 봐라.

여러 경우에 이는 값이 없이 어노테이션을 사용하는 것이 충분할 것이다. 어노테이션이 상당히 일반적인 목적을 제공할 때 유용할 것이고 여러 다른 타입의 의존성사이에 적용할 수 있다. 예를 들면 인터넷을 이용할 수 없는 경우에 검색된 오프라인 카탈로그를 제공할 것이다. 우선 간단한 어노테이션을 정의한다.


1
2
3
4
5
6
7
8
Java

@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface Offline {

}

그 다음 어노테이션을 자동연결할 필드나 프로퍼티에 추가한다.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
Java

public class MovieRecommender {

  @Autowired
  @Offline
  private MovieCatalog offlineCatalog;

  // ...
}

이제 빈정의는 qualifier type만 필요로 한다.

1
2
3
4
5
6
Xml

<bean class="example.SimpleMovieCatalog">
  <qualifier type="Offline"/>
  <!--  빈이 필요로하는 의존성을 주입한다 -->
</bean>

이름있는 속성을 받는 커스텀 qualifier 어노테이션을 간단한 value 속성에 추가하거나 value 속성 대신 정의할 수도 있다. 다수의 속성 값들이 자동연결하려고 필드나 파라니터에 지정되어 있다면 빈 정의는 자동연결 후보로 여겨지는 속성 값들 모두와 반드시 일치해야 한다. 예를 들어 다음 어노테이션 정의를 살펴보자.

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

@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface MovieQualifier {

  String genre();

  Format format();
}

이 경우에 Format은 enum이다.

1
2
3
4
5
6
Java

public enum Format {

  VHS, DVD, BLURAY
}

동연결되는 필드들은 커스텀 qualifier가 붙어야 하고 genre와 format 두 속성에 대한 값을 포함해야 한다.

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

public class MovieRecommender {

  @Autowired
  @MovieQualifier(format=Format.VHS, genre="Action")
  private MovieCatalog actionVhsCatalog;

  @Autowired
  @MovieQualifier(format=Format.VHS, genre="Comedy")
  private MovieCatalog comedyVhsCatalog;

  @Autowired
  @MovieQualifier(format=Format.DVD, genre="Action")
  private MovieCatalog actionDvdCatalog;

  @Autowired
  @MovieQualifier(format=Format.BLURAY, genre="Comedy")
  private MovieCatalog comedyBluRayCatalog;

  // ...
}

결국 빈 정의는 일치하는 qualifier 값들을 포함해야 한다. 이 예제는 빈의 메타 속성들을 <qualifier/> 하위요소 대신 사용할 수 있다는 것을 보여준다. <qualifier/>와 그 속성들을 사용할 수 있다면 먼저 처리된다. 하지만 다음 예제에서 마지막 두 빈 정의들처럼 그러한 qualifier가 존재하지 않는다면 자동연결 메카니즘은 <meta/> 태그내에서 제공된 값들에 의존할 것이다.

 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
Xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xmlns:context="http://www.springframework.org/schema/context"
  xsi:schemaLocation="http://www.springframework.org/schema/beans
      http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
      http://www.springframework.org/schema/context
      http://www.springframework.org/schema/context/spring-context-3.0.xsd">

  <context:annotation-config/>

  <bean class="example.SimpleMovieCatalog">
      <qualifier type="MovieQualifier">
          <attribute key="format" value="VHS"/>
          <attribute key="genre" value="Action"/>
      </qualifier>
      <!--  빈이 필요로하는 의존성을 주입한다 -->
  </bean>

  <bean class="example.SimpleMovieCatalog">
      <qualifier type="MovieQualifier">
          <attribute key="format" value="VHS"/>
          <attribute key="genre" value="Comedy"/>
      </qualifier>
      <!--  빈이 필요로하는 의존성을 주입한다 -->
  </bean>

  <bean class="example.SimpleMovieCatalog">
      <meta key="format" value="DVD"/>
      <meta key="genre" value="Action"/>
      <!--  빈이 필요로하는 의존성을 주입한다 -->
  </bean>

  <bean class="example.SimpleMovieCatalog">
      <meta key="format" value="BLURAY"/>
      <meta key="genre" value="Comedy"/>
      <!--  빈이 필요로하는 의존성을 주입한다 -->
  </bean>

</beans>

4.9.4 CustomAutowireConfigurer
CustomAutowireConfigurer는 스프링의 @Qualifier 어노테이션이 붙어있지 않더라도 자신만의 커스텀 qualifier 어노테이션 타입을 등록할 수 있도록 해주는 BeanFactoryPostProcessor다.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
Xml

<bean id="customAutowireConfigurer"
     class="org.springframework.beans.factory.annotation.CustomAutowireConfigurer">
  <property name="customQualifierTypes">
      <set>
          <value>example.CustomQualifier</value>
      </set>
  </property>
</bean>

AutowireCandidateResolver의 특정 구현체는 자바 버전에 의존적인 어플리케이션 컨텍스트에 대해 활성화된다. 자바 5 이전의 버전은 qualifier 어노테이션을 지원하지 않으므로 자동연결 후보는 <beans/> 요소에서 사용할 수 있는 default-autowire-candidates 패턴들 뿐만 아니라 각 빈 정의의 autowire-candidate 값으로 단독으로 결정된다. 자바 5 이상의 버전에는 @Qualifier 어노테이션과 CustomAutowireConfigurer으로 등록된 커스텀 어노테이션들이 존재하므로 제 역할을 할 것이다.

자바 버전에 상관없이 다수의 빈들이 자동연결 후보가 되었을 때 "주요(primary)" 후보가 결정되는 방식은 동일하다. 후보 중에 정확하게 primary 속성이 true으로 설정된 빈이 있다면 이를 선택할 것이다.

4.9.5 @Resource
스프링은 필드나 빈 프로퍼티 setter 메서드에 JSR-250 @Resource 어노테이션을 사용한 주입도 지원한다. 이는 Java EE 5나 6에서 일반적인 패턴이다. 예를 들어 JSF 1.2에서 관리되는 빈이나 JAX-WS 2.0 엔드포인트가 있다. 스프링은 스프링이 관리하는 객체들처런 이 패턴을 지원한다.

@Resource에는 name 속성이 있고 기본적으로 스프링이 값을 주입할 빈의 이름으로 인터프린트한다. 다시 말하자면 다음 예제에서 보여주듯 by-name 의미를 따른다.


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

public class SimpleMovieLister {

  private MovieFinder movieFinder;

  @Resource(name="myMovieFinder")
  public void setMovieFinder(MovieFinder movieFinder) {
      this.movieFinder = movieFinder;
  }
}

명시적으로 이름을 지정하지 않았다면 기본 이름은 필드명이나 setter 메서드에서 가져온다. 필드의 경우에는 필드명을 사용하고 setter 메서드의 경우에는 빈 프로퍼티 이름을 사용한다. 그래서 다음 예제는 "movieFinder"라는 이름의 빈을 setter 메서드에 주입할 것이다.

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

public class SimpleMovieLister {

  private MovieFinder movieFinder;

  @Resource
  public void setMovieFinder(MovieFinder movieFinder) {
      this.movieFinder = movieFinder;
  }
}


Note
어노테이션으로 제공한 이름은 CommonAnnotationBeanPostProcessor에 친화적인 ApplicationContext가 빈 이름으로 처리한다. 스프링의 SimpleJndiBeanFactory를 명시적으로 설정했다면 이 이름은 JNDI를 통해서 처리될 수 있다. 하지만 기본 동작에 의존하고 간접적인 수준을 유지하기 위해 스프링의 JNDI 검색 기능을 간단히 사용하기를 권장한다.

명시적으로 name을 지정하지 않은 @Resource 사용의 베타적인 경우에 @Autowired와 유사하게 @Resource는 지정한 이름있는 빈 대신 일치하는 주요 타입을 찾고 잘 알려진 해결할 수 있는 의존성을 해결한다. 잘 알려진 해결할 수 있는 의존성은 BeanFactory, ApplicationContext, ResourceLoader, ApplicationEventPublisher, MessageSource 인터페이스들이다.

그러므로 다음 예제에서 customerPreferenceDao 필드는 customerPreferenceDao라는 이름의 빈을 먼저 찾고 못 찾으면 CustomerPreferenceDao 타입과 일치하는 주요 타입을 찾는다. "context" 필드는 알고 있는 해결할 수 있는 의존성 타입인 ApplicationContext에 기반해서 주입된다.


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

public class MovieRecommender {

  @Resource
  private CustomerPreferenceDao customerPreferenceDao;

  @Resource
  private ApplicationContext context;

  public MovieRecommender() {
  }

  // ...
}

4.9.6 @PostConstruct와 @PreDestroy
CommonAnnotationBeanPostProcessor는 @Resource 어노테이션 뿐만 아니라 JSR-250 라이프사이클 어노테이션도 인식한다. 스프링 2.5에서 도입된 이러한 어노테이션들에 대한 지원은 초기화 콜백와 파괴 콜백에서 설명한 또다른 대안을 제공할 것이다. 스프링 ApplicationContext에 등록된 CommonAnnotationBeanPostProcessor가 있을 때 이러한 어노테이션 중 하나가 붙은 메서드는 대응되는 스프링 라이프사이클 메서드나 명시적으로 선언된 콜백 메서드와 동시에 호출된다. 다음 예제에서 캐시는 초기화에 따라 먼저 채워지고 파괴할 때 비워진다.

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

public class CachingMovieLister {

  @PostConstruct
  public void populateMovieCache() {
    // 초기화에 따라 movie cache가 채워진다...
  }

  @PreDestroy
  public void clearMovieCache() {
    // 파괴할 때 movie cache가 비워진다...
  }
}


Note
여러가지 라이프사이클 메카니즘을 조합한 효과에 대해서 자세히 알고 싶다면 Section 4.6.1.4, “라이프사이클 메카니즘 조합하기”를 봐라.