[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월 13일 금요일

[EP] 제9회 개발자를 위한 ‘共感(공감)’ 세미나 후기..

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

지난 20일(토)에 강남 교보타워 23층 대회의실에서 열린 공감세미나에 갔다왔다. 공감세미나는 OKJSP, JBoss 유저그룹, KSUG가 공동주최하는 세미나로 최초에는 어도비의 주관으로 이루어졌지만 그뒤부터는 세개 커뮤니티 연합으로 정기적으로 열리고 있다. 이런 저런 사정으로 공감세미나에 참석하는 건 꽤 오랜만인듯 하다.

공감세미나
일정표를 봤을 때 이번 공감세미나는 여러가지 기술 트랜드 보다는 자바 자체에 초점을 맞춘 분위기였다.

자바 탄생 이야기 - 김영수
이 세션은 제목대로 자바가 만들어진 과정에 대한 이야기로 자바 10주년을 기념해서 2005년에 나온 Hello World(s) -- From Code to Culture: A 10 Year Celebration of Java Technology라는 책의 내용을 정리해서 발표한 세션이었다. 1991년 제임스 고슬링은 변화의 시기에 맞추어서 무언가 해보기 위해서 Green 팀을 구성해서(사무실의 문이 녹색이었다고 한다.) 네트워크 기반의 무언가를 만들려고 했다. 그리고 사무실 밖에 있는 오크나무의 이름을 따서 OAK라는 프로토타이핑 프로젝트를 시작해서 여러 다바이스를 핸들링하려고 했는데 고슬링의 아이디어를 위한 언어가 없어서 만들기로 한다.(NO LANGUAGE FOR HIS IDEA THEN MAKE IT.)


1992년 팀의 멤버는 13명이 되고 회사의 디자이너인 Joe Palrang이 만든 캐릭터를 가져와서 Duke를 자바의 캐릭터로 만들었다. 이런 것으로 보아 단순이 코드만 짠 것이 아니라 캐릭터까지 도입해서 과감하게 진행을 했다. 고슬링이 만든 언어를 보고 C++의 빌 조이가 "C++과 비슷한 언어 아니냐"고 하자 고슬링은 "C++를 많이는 사용하지만 좋아하는 사람은 별로 없다(almost nobody liked it)라고 대답했다고 한다. OAK의 릴리즈 준비를 하는 과정에서 OAK가 이미 등록된 이름이라 여러 후보를 검토하다가 Java라는 이름으로 결정을 하고 1995년 5월 23일 SunWorld Conference에서 공개를 하고 썬의 적극적인 지원아래 이후 여러 컨퍼런스 등을 통해 홍보를 시작하고 그 뒤 부터는 커니터들이 참여하면서 퍼지기 시작한다.

약간은 지루한 세션이었다고 생각한다. 저 책을 보지 않아서 정확한 내용은 모르고 탄생과정이 스펙타클하지 않았는지는 모르지만 그래도 흥미로운 요소들이 많았으리라고 생각하는데 세션은 좀 지루했다. 자바의 탄생 과정을 탄탄하게 엮어서 설명했다기 보다는 책의 내용은 단편적으로 짚으면서 진행하다보니 듣는 입장에서는 진행과정이 잘 안이어지고 좀 지루하게 느껴지지 않았나 싶다. 그래도 탄생과정에 대해서는 잘 몰랐던 사실들을 알게 되어서 좀 나았다.

Java: The Good Parts - 박성철
이번 세션은 더그의 JavaScript: The Good Parts의 제목을 본따서 지은 제목이었고 자바의 특징을 하나씩 살펴보면서 이야기 해보자고 하셨다. 세션은 semtlnori님이 블로그에 프로그래밍 언어들을 한문장으로 소개한 글을 시작했는데 여기서는 각 언어의 정의를 정리해놨지만 마지막에 자바는 한문장으로 정리된 글을 찾을 수 없었다고 나와있다. 여기에 대한 답변을 하기 전에 자바의 특징을 살펴보았는데 자바 백서를 보면 앞에는 언어에 대한 이야기이고 뒤에는 JVM에 대한 이야기인데 설계당시 둘을 따로 구분하지는 않았겠지만 지금은 구분하므로 둘을 나눠서 설명하셨다.

언어로서의 자바를 먼저 살펴보면 자바는 단순함과 친숙함을 목표로 C/C++에서 WTF적인 요소를 뺀 것이라고 할 수 있다. 자바는 경고하고 안전하다고 할 수 있는데 여기서 견고성이란 정적(static) 타입의 언어에서 오는 장점이고 안정성은 엄격한(strong) 타입에서 오는 장점이다. (정적타입과 엄격한 타입은 약간 다르지만 보통은 같이 얘기한다.) 정적 타이핑에 대한 반대의견들이 있는데 보통 정적타이핑은 버그를 탐지하기에는 불충분하기 때문에 단위테스트를 작성해야 하고 단위테스트를 작성하고 나면 정적 타입확인 과징이라는 것이다. 거기에 정작 타이핑은 유효한 프로그램을 오류로 분류하기도 하므로 오히려 해롭다고 할 수 있다. 이에 대해서 해외에서 논문으로 연구한 내용이 있는데 파인썬 오픈소스 라이브러리 중 코드양은 적으면서 테스트가 잘 작성된 것 프로젝트들을 골라서 하스켈로 변환한뒤 컴파일하자 오류가 발생했다는 연구 결과가 있다. 정적 타입의 또다른 장점은 도구지원이다. IDE를 통해서 자동완성/자동제안을 지원하면 쉽게 리팩토링할 수 있으며 보일러 플레이트 코드를 자동 생성하거나 정적 코드분석을 할 수 있다.

자바의 또 하나의 특징은 객체중심의 프로그래밍이라는 것이다. OOP는 시뮬라67에서부터 시작되었는데 코드를 레고블럭처럼 생각하고 구조에 신경쓰기 위해서 만든 것이고 실제로는 스몰토크부터 시작되었다고 할 수 있는데 스몰토크는 객체지향을 메시징으로 보고 각 부분을 생물의 세포로 보고 있다. 알란 케이는 OOP에 대해서 정의해 달라는 말에서 메시징이 핵심이라고 했는데 자바에서는 그게 인터페이스이므로 인터페이스를 안쓰면 시뮤라67 스타일이 된다고 할 수 있다. 패키지는 디렉토리, 이름영역, 모듈이라고 할 수 있는데 응집의 단위이다. 자바의 기본 가시성은 package-private나 package-protected라고 보통 부르는데 이름이 없어서 잊혀진 존재이다. package-private는 public < protected < package-private < private정도의 가시성을 갖는데 계층관계에서는 보이지 않지만 패키지 사이에서는 보이는 가시성이다. 자바가 기본 가시성으로 package-private를 선택한 것을 보면 패키지를 단순한 그룹으로 보지 않았다는 것을 알 수 있다.


그 외에는 자바 1.1에서 추가된 내부클래스(inner class)가 있는데 LISP에서 온 개념으로 (잘못된 내용입니다.) 클래스=파일이라는 원칙으로 스스로 위배하면서 도입한 기능이다. 그리고 열거형(Enum)은 C++에서 일부러 가져오지 않았던 특징인데 자바의 열거형은 클래스이면서 타입세이프 하기 때문에 자기 부정을 한 것은 아니고 설계패턴 중 상태 패턴(state pattern)을 적용할 수 있다. 지네릭(generics) 는 C++에서 유보해떤 특징으로 알고리즘이 처리할 타입을 사용시에 지정하는 것으로 코드재사용 기술이라고 할 수 있다.

두번째는 JVM에 대한 이야기인데 자바는 파이썬하고 똑같이 컴파일한 뒤 VM위에서 동작하는 인터프리터 언어라고 하셨다. 자바는 클래스로더로 동적로딩, 동적 바인딩을 위해서 인터프리터를 선택했고 LISP에서 가비지 컬렉터의 개념을 가져와서 도입했다. 그리고 코드의 메타데이터를 다루는 기술인 리플렉션을 통해서 다른 프로그램을 생성하거나 조작하는 프로그램을 작성하는 메타프로그래밍을 할 수 있다. 메타프로그래밍으로는 선행처리, 컴파일러 확장(Lombok 처럼), 스프링의 Di + AOP등이 있다. 그리고 JVM은 다언어 프로그래밍(Polyglot)인데 이제 JVM은 자바만의 VM이 아닌 상황이 되었고 바이트코드라면 원천을 따지지 않고 실행한다.

자바에 대한 기초가 많이 없는 나로써는 자바에 특징에 대한 여러가지 관점을 들을 수 있어서 어느정도 흥미로왔지만 자바의 기초에 대해서 깊게 들어간 것은 아니었기 때문에 어떤 면에서는 너무 기초적이거나 추상적일 수도 있었다고 생각한다. 흥미롭다면 흥미롭지만 사람들의 반응을 보면 또 너무 뻔하다면 뻔한 얘기일 수도 있었겠다는 생각이 든다. 또 한편으로는 추상적/개념적인 얘기가 아니라 더그의 JavaScript: The Good Parts처럼 실제 코드를 이용한 좋은 점을 설명했으면 깊이도 있고 좋지 않았을까 싶기도 한다.

챔피언이 사랑한 언어, Java - 양수열
1996년에 처음 자바를 접했는데 당시에는 Java라고 쓰고 Innovation이라고 읽었다. 정적인 웹페이지만 보다가 웹에서 듀크가 춤추는 것을 처음 보았을 때는 깜짝 놀랐고 그 당시에는 새롭고 혁신적인 언어였다. 그 당시 자바는 새롬게 성장하는 기술로 웹과 함께한 언어였기 때문에 혁신을 좋아하는 젊은 엔지니어들이 많이 관심을 가졌고 좋은 취지에 공감해서 자바 개발자 커뮤니티를 만들기도 했다.  당시의 키워드로는 Distibuted Object, OOP, Garbage collector, Enterpirse, SOA, CBD등이었다.


하지만 이제는 2012년이고 이제는 Java라고 쓰고 Platform이라고 읽는다. 오늘날의 자바는 성숙한 기술이면서 언어에서 플램폼으로 바뀌었고 구식이면서 오래된 엔지니어들이 있는 기술이게 되었다. 그럼 우리는 어디로 가야하는가? 아마 WAS는 영원할 것이다. 과거 메인프레임의 시대가 끝난뒤에도 메인프레임을 유지하는 곳이 많았기 때문에 IBM은 노령의 엔지니어를 메인프레임 유지보수를 큰 비용을 주고 고용하고 있는데 WAS도 비슷하게 계속 유지될 것이다. 자바 플랫폼위에 새로운 언어를 사용할 수도 있고 요즘은 데이터 분석기술이 많이 중요해 지고 있다고 생각하기 때문에 이쪽으로 스킬업을 해도 도움이 될 것이라고 본다. 이제는 완전히 새로운 새상이 왔고 개발자가 창조가가 되었으면 글로벌 시장이 열렸고 혼자서도 회사를 할 수 있는 세상이 되었다. 개발자들이 이 과도기를 잘 헤쳐나가서 좋은 세상을 만들기를 기대한다.

그냥 무난한 세션이었다. 한편으로는 과거에는 자바가 혁신이었다는 말에 어쩌면 당연한 말이었지만 새삼 깨닫게 되었다. 양수열님이 오랫동안 자바를 사용하면서 생각했던 내용들을 그냥 에세이처럼 들려준 세션으로 그냥 웃으면서 가볍게 이야기 듣듯이 들은 세션이었다. 그런 목적도 아니었겠지만 특별히 큰 인사이트를 주거나 하는 세션은 아니었다.

자바 기반 시스템의 개발 및 운영에 도움이 되는 툴들 - 이상민
제목 그대로 여러가지 도구에 대해서 설명한 세션이었다. 우리나라에서는 이클립스만 거의 쓰지만 해외 컨퍼런스를 가면 넷빈즈를 상당히 많이 쓰고 있고 사용 IDE인 IntelliJ도 상당히 좋다. 보통은 JDK나 JRE만 설치해서 쓰지만 JDK에는 상당히 좋은 도구들이 많이 들어있다.

  • jps : jps는 java process 목록을 제공하는 도구로 보통은 이름만 나오기 때문에 톰캣을 여러개 띄운 경우 구분할 수 없는데 이때는 jps -v를 사용하면 실행옵션을 함께 볼 수 있다. ps -ef | grep java를 하는 것보다는 jps가 훨씬 간단하고 jstat과 다른 점은 자신의 계정으로 실행한 것만 보인다는 것이다.
  • jstat : jvm의 상황을 모니터링하는 도구로 운영서버에 절대 영향을 안주기 때문에 리얼서버에서 사용해도 문제가 없다. 유용한 옵션으로는 gc상태를 모니터링하는 -gcutil과 jvm의 메모리 점유 상황을 모니터링하는 -gccapacity가 있다.
  • javap : 자바 클래스 파일을 분석해 준다. javap className을 실행하면 해당 클래스에 선언된 변수와 메서드 정보등을 출력해 주고 javap -c className을 실행하면 해당 클래스의 opcode(JVM을 위한 중간코드)를 출력해준다.
  • jstack : 쓰레드 덤프(thread dump)를 발생시켜서 덤프 발생시점에 어떤 쓰레드가 어떤 작업을 수행하는 지 제공하는 덤프파일을 생성한다. 하지만 jstack는 경우에 따라 프로그램에 Hang이 발생할 수 있으므로 사용하지 않는 것이 좋고 쓰레드 덤프는 kill -3 pid를 사용할 것을 권장한다.(-3 옵션을 빼먹거나 -9를 사용하지 않도록 주의해야 한다.)
  • jmap : 힙덤프(heap dump)를 발생시켜서 덤프발생시점에 어떤 객체가 어떤 값을 갖고 있는지를 저장한다. 운영시에 jmap을 실행하면 자바 프로세스가 멈추므로 꼭 필요한 경우(메모리 릭 같은)에만 사용해야하고 jmap -dump:format=b, file=filename pid같은 옵션을 사용한다.
  • jhat : 힙덤프를 분석해준다.
  • jconsole, jvisualvm : 뒤에서 얘기할 비쥬얼 vm이 낫다.

테스트 도구에는 여러가지가 있는데 유닛테스트에는 JUnit이 대표적이고 UI 테스트에는 Selenium이나 NHN의 Guitar가 있다. 인수 테스트도구로는 Fitness와 NTAF가 있고 성능테스트 도구에는 Load Runner만 있는게 아니고 상용툴로는 Microfocus사의 qa load나 무료툴인 grinder, jmeter등이 있다. 로드러너 아니면 안되는줄 아는 사람이 많은데 참고로 로드러너는 엄청 비싸다.


프로파일링 도구는 프로그램의 성능, 메모리 사용량, 코드 커퍼리지 등을 확인할 수 있는 도구인데 성능저하를 많이 발생시키기 때문에 운영서버에서는 사용하면 안되고 개발자PC나 개발서버에서만 사용해야 한다. 크게 상용툴과 무료툴로 나뉘지만 상용툴을 쓰기를 권장하고 자바 프로그래머라면 IDE처럼 항상 옆에 끼고 있어야 하는 도구이고 대부분의 상용도구들은 IDE와 연계할 수 있고 라인단위로 걸린시간과 메모리까지 파악할 수 있다. 프로파일링 도구로는 microfocus사의 devpartner for java, quest사의 jprobe, ej-technologies의 jprofiler, yourkit 등이 있다.

모니터링 도구로는 국산제품이 많은 APM에는 Jennifer, webtune, pharos가 있다. 그 외에 JMX(Java Management Extension)를 사용할 때는 무료툴로도 충분한데 visual vm과 jconsole이 있다. visual vm은 JRE에 포함된 jvisualvm과 별도로 설치하는 visual vm으로 나뉘는데 버전업이 가능하고 사용이 편한 visual vm이 더 낫다.

트러블슈팅 도구를 보면 쓰레드덤프 분석도구에는 대표적으로 TDA가 있는데 어플리케이션의 성능이나 기능상에 문제가 있을 경우에 사용하면 된다. 힙덤프 분석도구에는 IBM Heap analyzer나 MAT이 있고 Tracing 도구에는 Btrace나 byteman이 있다. CI도구는 지속/반복적으로 빌드를 수행해주는 도구로 Hudson과 Jenkins가 있는데 Hudson의 핵심개발자가 나와서 만든게 Jenkins이므로 그냥 Jenkins를 사용하면 된다. 그외에 크루즈 컨트롤이 있다. 프론트앤드 도구에는 웹페이지를 최적화해주는 도구인 야후의 yslow가 있지만 요즘은 브라우저에 빌드인된 도구도 충분히 좋다. 발표자료는 이상민님이 블로그에 올려주셨다.

이번 공감세미나에서 거의 유일하게 기술세션이라고 할 수 있다. 뭐 워낙 발표도 잘하시고 농담도 잘 하시니 즐겁게 들었다. 상당수의 도구는 알고 있는 도구이기는 했지만 한방에 분야별로 정리를 해주었기 때문에 도구를 선택할 때 꽤 도움이 될듯하다. 특히 도구들에 대해서 잘 몰랐다면 이상민님이 좋은 도구를 다 골라주었기 때문에 아주 유용했을 것 같다. 나로써는 다른 부분보다는 JDK에 포함된 도구들을 설명해 준것이 크게 도움이 되었다. 여기 꽤 많은게 있다는 것을 알고 있지만 익숙하지 않아서 자주 쓰진 않는데 여러가지 상황별로 JDK도구들을 사용해야 할때 도움이 될 것 같다.

자바 개발 안티 스타일 - 스프링을 이용한 레거시 코드 중심으로 - 윤여진

이 세션은 요약하지 않는다. 머 이리저리 깊게 얘기하고 싶지도 않다. 특정인을 머라하는 것 같아 신경쓰이긴 하지만 머 그냥 느낀대로 딱 까놓고 얘기하면 발표 참 쉽게 하신다 싶은 생각이 들었다. 제목과는 전혀 다르게 "토비의 스프링 3"책에 나온 초난감 DAO를 리팩토링하는 과정을 그래도 PT로 만들어서 설명해 준 세션이라서 솔직히 난 그냥 딴짓을 했다. 그냥 "토비의 스프링 3"책을 그룹스터디 한다면 초난감 DAO 장을 정리해서 보여준 발표정도였다.

Epilogue
최근에 메일링등에서 너무 최신기술만 다루지 말고 자바 자체에 대해서도 관심을 가지자라는 말을 들었었는데 그때는 꽤 공감했었지만 막상 자바를 주제로 한 세미나를 듣자 만족감이 그리 높지는 못했다. 뭐랄까 세미나의 의도가 정확히 어땠는지는 알 수 없지만 자바에 집중하다보니 세미나가 전체적으로 추상적인 내용이나 개념적인 내용이 상당후가 되어버린것 같다. 개인적으로는 기본적인 내용(쉽다라는 의미는 아니다.)을 중심으로 기술세션들을 기대했는데 대부분이 추상적인 얘기를 하는 세션들이 되다보니 듣는 입장에서는 만족도가 좀 떨어진게 아닌가 쉽다. 그렇다고 뭐 괜히 갔네.. 이런건 아니다. ㅎ



[JAVA] 9장 Spring AOP API #2..

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

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

9.5 AOP 프록시 생성에 ProxyFactoryBean 사용하기
비 즈니스 객체에 스프링 IoC 컨테이너(ApplicationContext나 BeanFactory)를 사용한다면(꼭 사용해야 한다!) 스프링의 AOP FactoryBean중의 하나를 사용하고자 할 것이다. (팩토리빈은 간접계층(layer of indirection)을 도입해서 다른 타입의 객체들을 생성할 수 있도록 한다는 점을 기억해라.)



Note
스프링 2.0 AOP 지원도 팩토리빈을 사용한다.

스 프링에서 AOP 프록시를 생성하는 기본적인 방법은 org.springframework.aop.framework.ProxyFactoryBean를 사용하는 것이다. 이 방법으로 적용할 포인트컷과 어드바이스와 이들의 순서를 완전히 제어할 수 있다. 하지만 이러한 제어가 필요하지 않다면 적절한 더 간단한 옵션이 있다.

9.5.1 기초
다른 스프링 FactoryBean 구현체처럼 ProxyFactoryBean은 간접 수준(level of indirection)을 도입한다. foo라는 이름으로 ProxyFactoryBean를 정의했다면 foo를 참조하는 객체들이 보는 것은 ProxyFactoryBean 인스턴스 자체가 아니라 getObject() 메서드의 ProxyFactoryBean 구현체가 생성한 객체다. 이 메서드는 대상 객체를 감싸는 AOP 프록시를 생성할 것이다.

AOP 프록시를 생성하는데 ProxyFactoryBean을 사용하거나 다른 IoC와 관련된 클래스를 사용할 때 IoC가 어드바이스와 포인트컷도 관리할 수 있다는 것이 가장 큰 장점이다. 다른 AOP 프레임워크와 함께 구성하는 여러운 접근을 가능하게 하는 것은 강력한 기능이다. 예를 들어 의존성 주입이 제공하는 플러거블(pluggability) 기능에서 얻은 이점으로 어드바이스 자체가 어플리케이션 객체를 참조할 것이다.(모든 AOP 프레임워크에서 사용가능해야 하는 대상객체이외에)

9.5.2 JavaBean 프로퍼티
스프링이 제공하는 대부분의 FactoryBean 구현체와 마찬가지로 ProxyFactoryBean 클래스 자체가 JavaBean이다. 이 클래스의 프로퍼티들은 다음을 할 수 있다.

  • 프록시할 대상을 지정한다.
  • CGLIB을 사용할 지를 지정한다.(아래의 내용과 Section 9.5.3, “JDK기반과 CGLIB기반의 프록시”를 참고해라.)

몇몇 핵심 프로퍼티는 org.springframework.aop.framework.ProxyConfig에서 상속받는다. (스프링에서 모든 AOP 프록시 팩토리들의 수퍼클래스다.) 이 핵심 프로퍼티들은 다음과 같다.

  • proxyTargetClass: 대상 클래스의 인터페이스가 아니라 대상 클래스가 프록시되었다면 true이다. 이 프로퍼티 값이 true이라면 CGLIB 프록시가 생성될 것이다.(하지만 Section 9.5.3, “JDK기반과 CGLIB기반의 프록시”도 참고해라.)
  • optimize: CGLIB으로 생성된 프록시에 적극적인 최적화를 적용할 것인지를 제어한다. 적절한 AOP 프록시가 어떻게 최적화를 다루는 지를 완전히 이해하지 못한채로 이 설정을 그냥 사용하지는 말아야 한다. 이 설정은 현재 CGLIB 프록시에만 사용되고 JDK 동적 프록시에서는 아무런 영향이 없다.
  • frozen: 프록시설정이 frozen되면 더이상 설정을 변경할 수 없다. 이 설정은 사소한 최적화와 프록시가 생성된 후에 콜러(caller)가 프록시(Advised 인터페이스를 통해서)를 조작할 수 없게 하는 경우에 유용한다. 이 프로퍼티의 기본값은 false이므로 부가적인 어드바이스를 추가하는 것같은 변경작업이 가능하다.
  • exposeProxy: 대상객체가 현재의 프록시에 접근할 수 있도록 현재 프록시를 ThreadLocal에 노출해야 하는지를 결정한다. 대상객체가 프록시를 획득해야 하고 exposeProxy 프로퍼티가 true로 설정되었다면 대상객체는 AopContext.currentProxy() 메서드를 사용할 수 있다.
ProxyFactoryBean에 지정하는 다른 프로퍼티들은 다음과 같다.
  • proxyInterfaces: 문자열 인터페이스 이름의 배열. 이 프로퍼티가 제공되지 않는다면 대상 클래스에 대한 CGLIB 프록시가 사용될 것이다.(하지만 Section 9.5.3, “JDK기반과 CGLIB기반의 프록시”도 참고해라.)
  • interceptorNames: 적용할 Advisor나 인터셉터, 다른 어드바이스 이름의 문자열 배열. 순서가 중요하므로 처음 나온 것이 먼저 적용된다. 즉, 리스트의 첫 인터셉터가 가장 먼저 호출을 가로챌 수 있다.
    이름들은 조상 팩토리의 빈 이름을 포함해서 현재 팩토리의 빈 이름들이다. 여기서 빈 참조를 없는데 그렇게 할 경우 어드바이스의 싱글톤 설정을 무시하는 ProxyFactoryBean가 되기 때문이다.
    인 터셉터 이름을 별표(*)로도 추가할 수 있다. 이는 어플리케이션에서 별표 앞부분으로 시작하는 이름을 가진 모든 어드바이저 빈을 적용하게 된다. 이 기능을 사용하는 예제를 Section 9.5.6, “'전역' 어드바이저 사용하기”에서 볼 수 있다.
  • singleton: getObject() 메서드를 얼마나 많이 호출했는가에 상관없이 팩토리가 하나의 객체를 반환해야 하는지를 결정한다. 다수의 FactoryBean 구현체가 이러한 메서드를 제공한다. 기본값은 true다. 상태를 가진(stateful) 어드바이스를 사용하고자 한다면(예를 들면 상태를 가진 믹스인) singleton 값을 false로 하고 프로토타입 어드바이스를 사용해라.

9.5.3 JDK기반과 CGLIB기반의 프록시
이번 섹션에서는 특정 대상객체(프록시 되어야 하는 객체)에 대해서 JDK 기반의 프록시와 CGLIB 기반의 프록시 중 어느 프록시를 생성할 지 ProxyFactoryBean가 어떻게 선택하는지에 대한 완전한 문서를 제공한다.



Note
JDK 기반과 CGLIB기반 프록시를 생성하는 것과 관련된 ProxyFactoryBean의 동작은 스프링 버전 1.2.x와 2.0에서 달라졌다. ProxyFactoryBean는 이제 TransactionProxyFactoryBean 클래스등처럼 자동탐지되는 인터페이스과 관련된 의미와 유사하다.

프록시될 대상 객체의 클래스(이후에는 대상 클래스라고 부른다.)가 어떤 인터페이스도 구현하지 않았다면 CGLIB 기반의 프록시가 생성될 것이다. 이는 JDK 프록시가 인터페이스 기반이라서 인터페이스가 없다는 것은 JDK 프록시를 적용할 수 없다는 의미이므로 가장 쉬운 시나리오다. 다른 시나리오는 대상 객체에 연결하고(plug) interceptorNames 프로퍼티로 인터셉터 리스트를 지정하는 것이다. ProxyFactoryBean의 proxyTargetClass 프로퍼티가 false로 설정되었을 때도 CGLIB 기반 프록시가 생성될 것이다. (분명히 이는 말이 안되지만 이것이 중복을 가장 줄여주로 혼란이 적기 때문에 빈 정의에서 제거하는 것이 가장 좋다.)

대상 클래스가 하나이상의 인터페이스를 구현했다면 생성되는 프록시의 타입은 ProxyFactoryBean 설정에 달려있다.

ProxyFactoryBean 의 proxyTargetClass 프로퍼티가 true이면 CGLIB기반 프록시가 생성될 것이다. 이는 논리적이고 최소놀람의 원칙(principle of least surprise)을 유지한다. ProxyFactoryBean의 proxyInterfaces 프로퍼티가 하나이상의 정규화된 인터페이스 이름으로 설정되고 proxyTargetClass가 true로 설정되었을 때 조차도 CGLIB 기반의 프록시가 적용될 것이다.

ProxyFactoryBean의 proxyInterfaces 프로퍼티가 하나 이상의 정규화된 인터페이스 이름으로 지정되면 JDK기반 프록시가 생성될 것이다. 생성된 프록시는 proxyInterfaces 프로퍼티에 지정된 모든 인터페이스를 구현할 것이다. 대상 클래스가 proxyInterfaces에 지정된 것보다 훨씬 더 많은 인터페이스를 구현했다면 문제는 없지만 이러한 부가적인 인터페이스들은 반환되는 프록시는 구현하지 않을 것이다.

ProxyFactoryBean 의 proxyInterfaces 프로퍼티를 설정하지 않았지만 대상 클래스가 하나 이상의 인터페이스를 구현했다면 ProxyFactoryBean는 대상 클래스가 실제로 최소 하나의 인터페이스를 구현했는지를 자동탐지해서 JDK기반 프록시를 생성할 것이다. 실제로 프록시되는 인터페이스는 대상 클래스가 구현한 모든 인터페이스이다. 이는 사실상 대상 클래스가 구현하는 각각의 인터페이스 리스트를 proxyInterfaces 프로퍼티에 제공한 것과 같다. 하지만 이는 현지히 적게 동작하고 오타가 날 확률이 적다.

9.5.4 프록시 인터페이스
동작하는 ProxyFactoryBean의 간단한 예제를 보자. 이 예제는 다음을 포함하고 있다.

  • 프록시될 대상 빈. 이 빈은 아래 예제에서 "personTarget" 빈 정의이다.
  • 어드바이스를 제공하는데 사용한 어드바이저와 인터셉터
  • 대상 객체(personTarget 빈)을 지정하는 AOP 프록시 빈 정의와 프록시하는 인터페이스와 적용할 어드바이스.

 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

<bean id="personTarget" class="com.mycompany.PersonImpl">
  <property name="name" value="Tony"/>
  <property name="age" value="51"/>
</bean>

<bean id="myAdvisor" class="com.mycompany.MyAdvisor">
  <property name="someProperty" value="Custom string property value"/>
</bean>

<bean id="debugInterceptor" class="org.springframework.aop.interceptor.DebugInterceptor">
</bean>

<bean id="person" 
  class="org.springframework.aop.framework.ProxyFactoryBean">
  <property name="proxyInterfaces" value="com.mycompany.Person"/>

  <property name="target" ref="personTarget"/>
  <property name="interceptorNames">
    <list>
      <value>myAdvisor</value>
      <value>debugInterceptor</value>
    </list>
  </property>
</bean>
interceptorNames 프로퍼티는 문자열의 리스트를 받는다. 이 리스트는 현재 팩토리의 인터셉터와 어드바이저의 빈 이름들이다. 어드바이저, 인터셉터, before advice, after returning advice, throws advice 객체를 사용할 수 있다. 어드바이저의 순서는 중요하다.


Note
아 마도 왜 리스트가 빈 참조를 가지지 않는지 궁금할 것이다. 그 이유는 ProxyFactoryBean의 싱글톤 프로퍼티가 false로 설정되어있드면 개별적인 프록시 인스턴스를 리턴할 수 있어야 하기 때문이다. 어드바이저 중에 그 프로토타입이 있다면 개별적인 인스턴스는 리턴되어야 하므로 팩토리에서 프로토타입의 인스턴스를 반드시 획득할 수 있어야 한다. 레퍼런스를 가지고 있다면 이를 만족시키지 못한다.

위의 "person" 빈 정의를 다음과 같이 Person 구현체 대신에 사용할 수 있다.

Java

Person person = (Person) factory.getBean("person");

같은 IoC 컨텍스트내의 다른 빈들은 보통의 자바 객체처럼 강타입의 의존성을 빈에 나타낼 수 있다.

Xml

<bean id="personUser" class="com.mycompany.PersonUser">
  <property name="person"><ref local="person"/></property>
</bean>

이 예제에서 PersonUser 클래스는 Person 타입의 프로퍼티를 노출할 것이다. 이에 관한한 AOP 프록시를 "실제" person 구현체대신에 투명하게 사용할 수 있다. 하지만 AOP 프록시의 클래스는 동적 프록시 클래스가 될 것이다. 이 클래스는 Advised 인터페이스(아래에서 설명한다)로 캐스팅 될 수 있다.

다음과 같은 익명 내부 빈(inner bean)을 사용해서 대상객체와 프록시사이의 구별을 감출 수 있다. ProxyFactoryBean 정의만이 다르다. 완전함을 위해서 어드바이스만이 포함된다.


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

<bean id="myAdvisor" class="com.mycompany.MyAdvisor">
  <property name="someProperty" value="Custom string property value"/>
</bean>

<bean id="debugInterceptor" class="org.springframework.aop.interceptor.DebugInterceptor"/>

<bean id="person" class="org.springframework.aop.framework.ProxyFactoryBean">
  <property name="proxyInterfaces" value="com.mycompany.Person"/>
  <!-- 대상에 대한 로컬 참조말고 내부 빈을 사용해라 -->
  <property name="target">
    <bean class="com.mycompany.PersonImpl">
      <property name="name" value="Tony"/>
      <property name="age" value="51"/>
    </bean>
  </property>
  <property name="interceptorNames">
    <list>
      <value>myAdvisor</value>
      <value>debugInterceptor</value>
    </list>
  </property>
</bean>

여기에는 Person 타입의 객체가 딱 하나만 존재한다는 장점이 있다. 어플리케이션 컨텍스트의 사용자가 어드바이즈되지 않은 객체에 대한 참조를 획득하는 것을 막거나 스프링 IoC의 자동연결(autowiring)의 애매모호함을 피해야할 필요가 있을 경우 유용하다. ProxyFactoryBean 정의가 독립적인데서 오는 확실한 장점도 있다. 하지만 팩토리에서 어드바이즈되지 않은 대상을 획득할 수 있는 것이 실제로 장점이 될 수 있는 경우가 많이 있다. 예를 들면 몇몇 테스트 시나리오가 이러한 경우이다.

9.5.5 프록시 클래스
하나 이상의 인터페이스가 아니라 클래스를 프록시해야 한다면?

위 의 예제를 생각해 보자. 이 예제에는 Person 인터페이스가 없다. 그래서 어떤 비즈니스 인터페이스도 구현하지 않은 Person 클래스를 어드바이즈해야 한다. 이 경우에 스프링이 동적 프록시 대신 CGLIB 프록시를 사용하도록 설정할 수 있다. 위의 ProxyFactoryBean에서 proxyTargetClass 프로퍼티를 true로 설정해라. 클래스보다는 인터페이스를 사용하는 것이 좋지만 인터페이스를 구현하지 않은 클래스를 어드바이스할 수 있는 능력은 레가시코드를 사용해야 하는 경우에 유용할 수 있다. (보통 스프링은 규범적이지 않다. 스프링은 좋은 사례를 적용하기 쉽게 만들면서 특정 접근을 강제하지 않는다.)

인터페이스를 가진 경우에도 필요하다면 모든 경우에 CGLIB을 사용하도록 강제할 수 있다.

CGLIB 프록시는 런타임시에 대상 클래스의 하위클래스를 생성해서 동작한다. 스프링은 원래 대상에 메서드 호출을 위임하려고 생성한 이 하위클래스를 설정한다. 하위클래스는 어드바이스에서 위빙을 하는 데코레이터 패턴을 구현하는데 사용한다.

CGLIB 프록시는 보통 사용자에게 투명해야 한다. 하지만 고려해야할 이슈가 몇가지 있다.

  • Final 메서드는 오버라이드할 수 없는 것처럼 어드바이즈할 수도 없다.
  • 클래스패스에 CGLIB 2 바이너리가 필요할 것이다. 동적 프록시는 JDK에서 사용할 수 있다.
CGLIB 프록시와 동적 프록시 사이에는 약간의 성능차이가 있다. 스프링 1.0에서는 동적 프록시가 약간 더 빠르다. 하지만 차후에는 달라질 것이다. 이 경우에 성능은 결정적인 고려사항은 아니어야 한다.

9.5.6 '전역' 어드바이저 사용하기
인터셉터 이름에 별표를 추가하면 별표앞의 부분과 일치하는 빈 이름을 가진 모든 어드바이저를 어드바이저 체인에 추가할 것이다. 이는 '전역' 어드바이저의 표준 세트에 추가해야하는 경우 쓸모가 있을 수 있다.


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

<bean id="proxy" class="org.springframework.aop.framework.ProxyFactoryBean">
  <property name="target" ref="service"/>
  <property name="interceptorNames">
    <list>
      <value>global*</value>
    </list>
  </property>
</bean>

<bean id="global_debug" class="org.springframework.aop.interceptor.DebugInterceptor"/>
<bean id="global_performance" class="org.springframework.aop.interceptor.PerformanceMonitorInterceptor"/>

9.6 간결한 프록시 정의
특히 트랜잭션 프록시를 정의하는 경우에 유사한 프록시 정의를 많이 사용할 것이다. 내부 빈 정의와 함께 부모 빈 정의와 자식 빈 정의를 사용하면 프록시 정의를 더 깨끗하고 간결하게 만들 수 있다.

먼저 부모, 템플릿, 빈 정의를 프록시에 대해서 생성한다.


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

<bean id="txProxyTemplate" abstract="true"
        class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
  <property name="transactionManager" ref="transactionManager"/>
  <property name="transactionAttributes">
    <props>
      <prop key="*">PROPAGATION_REQUIRED</prop>
    </props>
  </property>
</bean>

이는 스스로 인스턴스화 되지 않을 것이므로 사실 불완전하다. 대상은 어떻게든 고유하게 사용되지는 않을 것이므로 그 다음 생성되어야 하는 각 프록시는 내부 빈 정의로 프록시의 대상을 감싸는 자식 빈 정의이다.

1
2
3
4
5
6
7
8
Xml

<bean id="myService" parent="txProxyTemplate">
  <property name="target">
    <bean class="org.springframework.samples.MyServiceImpl">
    </bean>
  </property>
</bean>

물론 이번 경우처럼 부모 템플릿의 프로퍼티인 트랜잭션 전파(transaction propagation) 설정을 오버라이드할 수 있다.

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

<bean id="mySpecialService" parent="txProxyTemplate">
  <property name="target">
    <bean class="org.springframework.samples.MySpecialServiceImpl">
    </bean>
  </property>
  <property name="transactionAttributes">
    <props>
      <prop key="get*">PROPAGATION_REQUIRED,readOnly</prop>
      <prop key="find*">PROPAGATION_REQUIRED,readOnly</prop>
      <prop key="load*">PROPAGATION_REQUIRED,readOnly</prop>
      <prop key="store*">PROPAGATION_REQUIRED</prop>
    </props>
  </property>
</bean>

위 의 예제에서 앞에서 설명했던 abstract 속성을 사용해서 부모 빈 정의를 명시적으로 abstract로 표시했다. 그러므로 이 빈 정의는 실제로는 인스턴스화 되지 않는다. 어플리케이션 컨텍스트(하지만 단순한 빈 팩토리는 아니다)는 기본적으로 미리 인스턴스화되어서 모두 싱글톤이다. 그러므로 탬플릿으로만 사용하려는 (부모) 빈 정의가 있다면(그리고 이 정의는 클래스를 지정한다.) abstract 속성을 true로 설정해야하고 그렇지 않으면 어플리케이션 컨텍스트가 실제로 이 빈을 미리 인스턴스화 하려고 시도한다는 점은 중요하다.(최소한 싱글톤 빈에 대해서는)

9.7 ProxyFactory로 AOP 프록시를 프로그래밍적으로 생성하기
스프링에서 AOP 프록시를 프로그래밍적으로 생성하는 것은 쉽다. 이는 스프링 IoC에 대한 의존성없이 스프링 AOP를 사용할 수 있게 한다.

다음의 리스트는 하나의 인터셉터와 하나의 어드바이저를 가진 대상 객체에 대한 프록시 생성을 보여준다. 대상객체가 구현한 인터페이스는 자동적으로 프록시될 것이다.


1
2
3
4
5
6
Java

ProxyFactory factory = new ProxyFactory(myBusinessInterfaceImpl);
factory.addAdvice(myMethodInterceptor);
factory.addAdvisor(myAdvisor);
MyBusinessInterface tb = (MyBusinessInterface) factory.getProxy();

첫 단계는 org.springframework.aop.framework.ProxyFactory 타입의 객체를 생성하는 것이다. 위의 예제처럼 대상객체와 함께 이를 생성하거나 대체 생성자(alternate constructor)에서 프록시되는 인터페이스를 지정할 수 있다.

어드바이스(어드바이스의 특수한 종류인 인터셉터와 함께)와 어드바이저를 추가할 수 있고 ProxyFactory가 살아있는 동안 이들을 조작할 수 있다. IntroductionInterceptionAroundAdvisor를 추가했다면 프록시가 부가적인 인터페이스를 구현하도록 할 수 있다.

ProxyFactory(AdvisedSupport에서 상속받은)에는 before advice와 throws advice같은 다른 어드바이스 타입을 추가하도록 하는 편리한 메서드도 존재한다. AdvisedSupport는 ProxyFactory와 ProxyFactoryBean의 수퍼클래스이다.



Tip
AOP 프록시 생성과 IoC 프레임워크를 통합하는 것은 대부분의 어플리케이션에서 좋은 사용사례이다. 일반적으로 AOP를 가진 자바코드에서 설정을 분리해내는 것을 권장한다.

9.8 어드바이즈된 객체 조작하기
AOP 프록시를 생성했더라도 org.springframework.aop.framework.Advised 인터페이스를 사용해서 프록시를 조작할 수 있다. 다른 인터페이스가 이 인터페이스를 구현했더라도 모든 AOP프록시는 이 인터페이스로 캐스트할 수 있다. 이 인터페이스는 다음 메서드를 포함하고 있다.


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

Advisor[] getAdvisors();

void addAdvice(Advice advice) throws AopConfigException;

void addAdvice(int pos, Advice advice) 
      throws AopConfigException;

void addAdvisor(Advisor advisor) throws AopConfigException;

void addAdvisor(int pos, Advisor advisor) throws AopConfigException;

int indexOf(Advisor advisor);

boolean removeAdvisor(Advisor advisor) throws AopConfigException;

void removeAdvisor(int index) throws AopConfigException;

boolean replaceAdvisor(Advisor a, Advisor b) throws AopConfigException;

boolean isFrozen();

getAdvisors() 메서드는 팩토리에 추가된 모든 어드바이저, 인터셉터, 다른 어드바이스 타입에 대해서 Advisor를 반환할 것이다. Advisor를 추가했다면 이 위치에서 반환된 어드바이저는 추가한 객체가 될 것이다. 인터셉터나 다른 어드바이스 타입을 추가했다면 스프링은 항상 true를 반환하는 포인트컷을 가진 어드바이저에서 이를 감쌀것이다. 그러므로 MethodInterceptor를 추가했다면 이 위치에서 반환된 어드바이저는 MethodInterceptor와 모든 클래스와 메서드와 일치하는 포인트컷을 반환하는 DefaultPointcutAdvisor가 될 것이다.

addAdvisor() 메서드는 어떤 Advisor라도 추가하는데 사용할 수 있다. 보통은 포인트컷과 어드바이스를 가진 어드바이저는 어떤 어드바이스나 포인트컷(하지만 인트로덕션과는 아니다)과도 사용할 수 있는 일반적인 DefaultPointcutAdvisor가 될 것이다.

기본적으로 일단 프록시가 생성된 후에도 어드바이저나 인터셉터를 추가하거나 제거할 수 있다. 유일한 제약사항은 팩토리에 존재하는 프록시가 인터페이스 변경을 보지 못하는 것처럼 인트로덕션 어드바이저를 추가하거나 제거할 수 없다. (이 문제를 피하기 위해서 팩토리에서 새로운 프록시를 획득할 수 있다.)

다음은 AOP 프록시를 Advised 인터페이스로 캐스팅하고 그 어드바이스를 검사하고 조작하는 간단한 예제이다.


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

Advised advised = (Advised) myObject;
Advisor[] advisors = advised.getAdvisors();
int oldAdvisorCount = advisors.length;
System.out.println(oldAdvisorCount + " advisors");

// 포인트컷없이 인터셉터처럼 어드바이스를 추가해라
// 프록시된 모든 메서드를 매칭할 것이다
// 인터셉터, before advice, after returning advice, throws advice에 대해 사용할 수 있다
advised.addAdvice(new DebugInterceptor());

// 포인트컷을 사용해서 선택적으로 어드바이스를 추가한다
advised.addAdvisor(new DefaultPointcutAdvisor(mySpecialPointcut, myAdvice));

assertEquals("Added two advisors", oldAdvisorCount + 2, advised.getAdvisors().length);




Note
분 명히 정당한 사용이라고 하더라도 프로덕션단계에서 비즈니스 객체의 어드바이스를 수정하는 것이 합당한가에 대해서 의문이 있을 수 있다. 하지만 개발단계에서는 아주 유용할 수 있다. 예를 들면 테스트의 경우이다. 테스트하려는 메서드 호출내부에서 얻은 인터셉터나 다른 어드바이스의 형식으로 테스트 코드를 추가할 수 있다는 것이 아주 유용하다는 것을 종종 발견한다. (예를 들어 어드바이스가 해당 메서드에 대해 생성된 트랜잭션안으로 들어갈 수 있다. 또는 트랜잭션을 롤백하기 이전에 데이터베이스가 제대로 갱신되었는지 검사하는 SQL을 실행할 수 있다.)

어떻게 프록시를 생성했는가에 따라 frozen 플래그를 보통 설정할 수 있다. frozen 플래그를 설정한 경우 Advised isFrozen() 메서드는 항상 true를 반환할 것이다. 추가나 제거로 어드바이스를 수정하려는 어떤 시도도 AopConfigException가 발생할 것이다. 어드바이즈된 객체의 상태를 고정시키는 이 기능은 몇몇 경우에 유용하다. 예를 들면 보안 인터셉터를 제거하는 코드의 호출을 차단할 수 있다. 알려진 런타입 어드바이스 수정이 필요하지 하지 않은 경우 적극적인 최적화를 위해서 스프링 1.1에서도 사용할 수 있다.

9.9 "autoproxy" 기능 사용하기
지금까지 ProxyFactoryBean나 유사한 팩토리빈을 사용해서 명시적으로 프록시를 생성하는 방법을 살펴보았다.

스 프링도 선택한 빈 정의를 자동으로 프록시할 수 있는 "autoproxy" 빈정의를 사용할 수 있게 한다. 이 기능은 컨테이너 로딩처럼 모든 빈 정의를 수정할 수 있는 스프링의 "bean post processor" 인프라에 기반을 두고 있다.

이 모델에서 자동 프록시 인프라스트럭처를 설정하려고 XML 빈 정의 파일에 몇가지 특수한 빈 정의를 설정했다. 이는 자동프록시에 알맞는 대상을 선언할 수 있게 한다. ProxyFactoryBean를 사용할 필요가 없다.

이것을 하는 두가지 방법이 있다.

  • 현재 컨텍스트에서 특정 빈을 참조하는 autoproxy creator의 사용.
  • 별도로 생각할 수 있는 자동프록시 생성의 특수한 경우. 자동프록시 생성은 소스수준의 메타데이터 속성으로 만들어진다.

9.9.1 자동프록시 빈 정의
org.springframework.aop.framework.autoproxy 패키지는 다음의 표준 자동프록시 생성자(creator)를 제공한다.

9.9.1.1 BeanNameAutoProxyCreator
BeanNameAutoProxyCreator 클래스는 리터럴 값이나 와일드카드와 일치하는 이름을 가진 빈에 대한 AOP 프록시를 자동으로 생성하는 BeanPostProcessor이다.


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
Xml

<bean class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
  <property name="beanNames" value="jdk*,onlyJdk"/>
  <property name="interceptorNames">
    <list>
      <value>myInterceptor</value>
    </list>
  </property>
</bean>

ProxyFactoryBean 가 그렇듯이 프로토타입 어드바이저에 올바른 행위를 하도록 하는 인터셉터의 리스트 대신 interceptorNames 프로퍼티가 있다. 지명된 "interceptors"는 어드바이저나 어떤 어드바이스 타입이라도 될 수 있다.

보통 자동 프록시가 그렇듯이 BeanNameAutoProxyCreator를 사용하는 핵심관점은 최소한의 설정으로 여러 객체에 하나의 설정을 일관성있게 적용하려는 것이다. 이는 여러 객체에 선언적 트랜잭선을 적용할 때 선호하는 선택이다.

위의 예제에서 "jdkMyBean"와 "onlyJdk"처럼 이름이 일치한 빈 정의는 대상클래스의 평범한 빈 정의(plain old bean definition)이다. BeanNameAutoProxyCreator가 자동적으로 AOP 프록시를 생성할 것이다. 같은 어드바이스가 매칭된 모든 빈에 적용될 것이다. 어드바이저를 사용했다면(위의 예제에서 인터셉터 대신에) 포인트컷은 다른 빈에 다르게 적용될 것이다.

9.9.1.2 DefaultAdvisorAutoProxyCreator
DefaultAdvisorAutoProxyCreator 는 더 일반적이고 아주 강력한 자동 프록시 생성자이다. 이 생성자는 자동프록시 어드바이저의 빈 정의에서 특정 빈 이름을 포함하지 않고도 현재 컨텍스트에서 적합한 어드바이저를 마법처럼(automagically) 적용할 것이다. 이는 일관성있는 설정과 BeanNameAutoProxyCreator같은 중복제거의 이점과 같은 이점을 제공한다.

이 메카니즘을 사용하는 것은 다음을 포함한다.

  • DefaultAdvisorAutoProxyCreator 빈 정의를 지정함.
  • 같 은 컨텍스트 혹은 관계된 컨텍스트에 다수의 Advisor를 지정함. 이들은 인터셉터나 다른 어드바이스가 아닌 반드시 Advisor여야 한다. 후보 빈 정의에 대한 각 어드바이스의 자격을 검사하기 위해 평가할 포인트컷이 있어야 하기 때문에 이 어드바이저는 필수적이다.
DefaultAdvisorAutoProxyCreator는 각 비즈니스 객체(예제의 "businessObject1"와 "businessObject2"같은)에 어떤 어브다이스를 (존재한다면) 적용해야 하는지 알기 위해 각 어드바이저에 있는 포인트컷을 자동적으로 평가할 것이다.

이는 자동으로 각 비즈니스 객체에 다수의 어드바이저를 적용할 수 있다는 말이다. 어느 어드바이저의 포인트컷도 비즈니스 객체의 매서드와 일치하는 것이 없다면 객체는 프록시되지 않을 것이다. 새로운 비즈니스 객체에 빈 정의를 추가했을 때 필요하다면 자동으로 프록시 될 것이다.

보통 자동프록시는 호출자(caller)나 의존성이 어드바이즈되지 않은 객체를 획득할 수 없게 하는 장점이 있다. 이 ApplicationContext에서 getBean("businessObject1")을 호출하면 대상 비즈니스 객체가 아니라 AOP 프록시를 반환할 것이다.(앞에서 나온 "내부 빈(inner bean)"도 이 장점을 제공한다.)


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

<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"/>

<bean class="org.springframework.transaction.interceptor.TransactionAttributeSourceAdvisor">
  <property name="transactionInterceptor" ref="transactionInterceptor"/>
</bean>

<bean id="customAdvisor" class="com.mycompany.MyAdvisor"/>

<bean id="businessObject1" class="com.mycompany.BusinessObject1">
  <!-- Properties omitted -->
</bean>

<bean id="businessObject2" class="com.mycompany.BusinessObject2"/>

많은 비즈니스 객체에 같은 어드바이스를 일관성있게 적용하려는 경우 DefaultAdvisorAutoProxyCreator가 아주 유용하다. 일단 인프라스트럭처 정의가 제 위치에 있다면 구체적인 프록시 설정을 포함하지 않고도 새로운 비즈니스 객체를 쉽게 추가할 수 있다. 부가적인 관점도 설정을 최소한으로 변경하면서 아주 쉽게 추가할 수 있다.(예를 들면 추적하거나 성능을 모니터링하느 관점)

DefaultAdvisorAutoProxyCreator는 필터링과 정렬을 지원한다. (같은 팩토리의 AdvisorAutoProxyCreator의 특정 어드바이저들만 평가하고 여러 용도로 사용하고 다르게 설정하기 위해 작명 관례를 사용해서) 이슈가 있는 경우에 정렬을 제대로 하기 위해 어드바이저가 org.springframework.core.Ordered를 구현할 수 있다. 위의 예제에서 사용한 TransactionAttributeSourceAdvisor에는 설정할 수 있는 order값이 있다. 기본 설정은 정렬하지 않는 것이다.

9.9.1.3 AbstractAdvisorAutoProxyCreator
DefaultAdvisorAutoProxyCreator 의 수퍼클래스다. 어드바이저 정의가 프레임워크의 동작을 충분하게 커스터마이징하지 못할 때 이 DefaultAdvisorAutoProxyCreator 클래스의 하위클래스를 만들어서 자신만의 오토프록시 생성자(creator)를 만들 수 있다.

9.9.2 메타데이터 주도 오토프록시 사용하기
오 토프록시의 특히 중요한 타입은 메타데이터에 주도된다. 이는 .NET ServicedComponents과 유사한 프로그래밍 모델을 만든다. EJB에서 처럼 XML 배포 디스크립터를 사용하는 대신에 소스수준의 속성으로 트랜잭션 관리와 다른 엔터프라이즈 서비스에 대한 설정을 유지한다.

이 경우에 메타데이터 속성을 이해하는 어드바이저와 결합해서 DefaultAdvisorAutoProxyCreator를 사용한다. 메타데이터를 오토프록시 생성 클래스 자체가 가지는 대신 후보 어드바이저들의 포인트컷 부분이 메타데이터를 가진다.

이는 실제로 DefaultAdvisorAutoProxyCreator의 특수한 경우이지만 독립적으로 고려할 만 하다.(메타데이터 친화적인 코드는 AOP 프레임워크 자체가 아니라 어드바이저들의 포인트컷이 담고 있다.)

JPetStore 샘플 어플리케이션의 /attributes 디렉토리는 속성주도 오토프록시의 사용방법을 보여준다. 이 경우에는 TransactionProxyFactoryBean를 사용할 필요가 없다. 메타데이터 친화적인 포인트컷을 사용하기 때문에 비즈니스 객체에서 transactional 속성을 정의하는 것만으로도 충분하다. /WEB-INF/declarativeServices.xml의 빈 정의는 다음의 코드를 포함하고 있다. 이는 일반적이고 JPetStore 외부에서도 사용할 수 있다.


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

<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"/>

<bean class="org.springframework.transaction.interceptor.TransactionAttributeSourceAdvisor">
  <property name="transactionInterceptor" ref="transactionInterceptor"/>
</bean>

<bean id="transactionInterceptor"
    class="org.springframework.transaction.interceptor.TransactionInterceptor">
  <property name="transactionManager" ref="transactionManager"/>
  <property name="transactionAttributeSource">
    <bean class="org.springframework.transaction.interceptor.AttributesTransactionAttributeSource">
      <property name="attributes" ref="attributes"/>
    </bean>
  </property>
</bean>

<bean id="attributes" class="org.springframework.metadata.commons.CommonsAttributes"/>

DefaultAdvisorAutoProxyCreator 빈 정의(이름은 중요하지 않으므로 생략할 수도 있다)는 현재 어플리케이션 컨텍스트에서 적절한 포인트컷을 모두 선택할 것이다. 이 경우에 TransactionAttributeSourceAdvisor 타입의 "transactionAdvisor" 빈 정의를 transaction 속성을 가진 클래스나 메서드에 적용할 것이다. TransactionAttributeSourceAdvisor는 생성자 의존성을 통해서 TransactionInterceptor에 의존한다. 예제는 자동연결(autowiring)로 이 문제를 해결한다. AttributesTransactionAttributeSource는 org.springframework.metadata.Attributes 인터페이스의 구현체에 의존한다. 이 부분에서 "attributes" 빈은 속성 정보를 얻기 위해 Jakarta Commons Attributes API를 사용해서 이를 만족시킨다. (어플리케이션 코드는 반드시 Commons Attributes 컴파일 작업을 사용해서 컴파일되어야 한다.)

JPetStore 샘플 어플리케이션의 /annotation 디렉토리에는 JDK 1.5+ 어노테이션을 사용한 오토프록시와 유사한 예제가 있다. 다음 설정은 스프링의 Transactional 어노테이션을 자동으로 탐지할 수 있게 해서 해당 어노테이션이 붙은 빈을 암묵적으로 프록시하게 된다.


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

<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"/>

<bean class="org.springframework.transaction.interceptor.TransactionAttributeSourceAdvisor">
  <property name="transactionInterceptor" ref="transactionInterceptor"/>
</bean>

<bean id="transactionInterceptor"
    class="org.springframework.transaction.interceptor.TransactionInterceptor">
  <property name="transactionManager" ref="transactionManager"/>
  <property name="transactionAttributeSource">
    <bean class="org.springframework.transaction.annotation.AnnotationTransactionAttributeSource"/>
  </property>
</bean>

여기서 정의한 TransactionInterceptor는 PlatformTransactionManager 정의에 의존하고 있다. PlatformTransactionManager는 어플리케이션의 트랜잭션 요구사항(일반적으로는 이 예제처럼 JTA이고 그 외 Hibernate, JDO, JDBC이다.)에 지정될 것이므로 이 일반적인 파일에는 포함되어 있지 않다.(가능하기는 하지만)

1
2
3
4
Xml

<bean id="transactionManager" 
    class="org.springframework.transaction.jta.JtaTransactionManager"/>




Tip
선 언적인 트랜잭션 관리만 필요한 경우 이러한 일반적인 XML 정의를 사용하면 스프링이 transaction 속성이 있는 모든 클래스와 메서드를 자동으로 프록시할 것이다. AOP로 직접 작업할 필요가 없고 프로그래밍 모델은 .NET ServicedComponents의 프로그래밍 모델과 유사하다.

이 메카니즘은 확장할 수 있다. 커스텀 속성에 기반해서 오토프록시를 할 수 있다. 이렇게 하려면 다음의 과정이 필요한다.

  • 커스텀 속성을 정의해라.
  • 클래스나 메서드에 커스텀 속성이 있어서 발생하는 포임트컷을 포함해서 필수 어드바이스를 가진 Advisor를 지정해라. 커스텀 속성을 선택하는 정적 포인트컷을 구현한 이미 존재하는 어드바이스를 사용할 수도 있다.
이 러한 어드바이저들이 각각의 어드바이즈된 클래스에 유일하도록 할 수 있다.(예를 들면 믹스인) 이 어드바이저들은 싱글톤 대신 프로토타입 빈 정의로 정의되어야 한다. 예를 들어 앞에서 나왔던 스프링 테스트 슈트의 LockMixin 인트로덕션 인터셉터를 여기서 보여주듯 믹스인할 속성주도 포인트컷과 결합하는데 사용할 수 있다. JavaBean 프로퍼티로 설정한 일반적인 DefaultPointcutAdvisor를 사용한다.

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

<bean id="lockMixin" class="org.springframework.aop.LockMixin"
    scope="prototype"/>

<bean id="lockableAdvisor" class="org.springframework.aop.support.DefaultPointcutAdvisor"
    scope="prototype">
  <property name="pointcut" ref="myAttributeAwarePointcut"/>
  <property name="advice" ref="lockMixin"/>
</bean>

<bean id="anyBean" class="anyclass" ...

속성을 이해하는 포인트컷이 anyBean이나 다른 빈 정의의 어떤 메서드와 일치한다면 믹스인이 적용될 것이다. lockMixin와 lockableAdvisor는 둘다 프로토타입이다. myAttributeAwarePointcut 포인트컷은 어드바이즈된 개별 객체가 상태를 유지하지 않기 때문에 싱글톤 정의가 될 수 있다.

9.10 TargetSources 사용하기
스 프링은 org.springframework.aop.TargetSource에 나타난 TargetSource의 개념을 제공한다. 이 인터페이스는 조인포인트를 구현한 "대상 객체"를 반환하는 역할을 한다. TargetSource 구현체는 AOP 프록시가 메서드 호출을 다룰 때마다 대상 인스턴스를 요청한다.

스프링 AOP를 사용하는 개발자들이 TargetSource를 직접 다룰 필요는 보통 없지만 풀링, 핫 스왓, 다른 세련된 대상등을 지원하는 강력한 기능을 제공한다. 예를 들어 풀링 TargetSource는 인스턴스를 관리하는 풀(pool)을 사용해서 호출시마다 다른 대상 인스턴스를 반환할 수 있다.

TargetSource를 지정하지 않으면 지역(local) 객체를 감싸는 기본 구현체를 사용한다. 각 호출시마다(기대하는 대로) 같은 객체를 반환한다.

스프링이 제공하는 표준 대상 소스(target source)를 살펴보고 어떻게 사용할 수 있는지 알아보자.



Tip
커스텀 대상 소스를 사용하는 경우 대상은 보통 싱글톤 빈 정의가 아니라 프로토타입 빈 정의가 되어야 할 것이다. 이는 요청했을 때 스프링이 새로운 대상 인스턴스를 생성할 수 있게 한다.

9.10.1 핫 스왑이 가능한 대상 소스
호출자(caller)가 AOP 프록시의 대상에 대한 참조를 유지하는 동안 AOP 프록시의 대상을 교체할 수 있도록 org.springframework.aop.target.HotSwappableTargetSource가 존재한다.

대상 소스의 대상을 변경한 효과는 즉각적으로 일어난다. HotSwappableTargetSource는 쓰레드세이프하다.

다음과 같이 HotSwappableTargetSource의 swap() 메서드로 대상을 변경할 수 있다.


1
2
3
4
Java

HotSwappableTargetSource swapper = (HotSwappableTargetSource) beanFactory.getBean("swapper");
Object oldTarget = swapper.swap(newTarget);

필요한 XML 정의는 다음과 같다.

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

<bean id="initialTarget" class="mycompany.OldTarget"/>

<bean id="swapper" class="org.springframework.aop.target.HotSwappableTargetSource">
  <constructor-arg ref="initialTarget"/>
</bean>

<bean id="swappable" class="org.springframework.aop.framework.ProxyFactoryBean">
  <property name="targetSource" ref="swapper"/>
</bean>

위의 swap() 호출은 스왑가능한 빈의 대상을 변경한다. 이 빈에 대한 참조를 가진 클라이언트들은 변경을 알아차리지 못하지만 즉시 새로운 대상을 만날 것이다.

이 예제에서는 어떤 어드바이스도 추가하지 않았지만(TargetSource를 사용하는데 어드바이스를 추가해야 할 필요는 없다.) 당연히 모든 TargetSource는 임의의 어드바이스와 결합해서 사용할 수 있다.

9.10.2 대상 소스 풀링
대상 소스의 풀링을 사용하면 상태가 없는 세션 EJB와 유사한 프로그래밍 모델을 제공한다. 이 프로그래밍 모델은 동일한 인스턴스의 풀을 유지하고 메서드 호출로 풀에서 객체를 가져온다.

스프링 풀링과 SLSB 풀링사이의 결정적인 차이점은 스프링 풀링은 어떤 POJO라도 적용할 수 있다는 것이다. 보통 스프링이 하듯이 이는 비 침략적인 방법을 적용할 수 있다.

스 프링은 상당히 능률적인 풀링 구현체를 제공하는 Jakarta Commons Pool 1.3을 지원한다. 이 기능을 사용하려면 어플리케이션의 클래스패스에 commons-pool Jar가 필요하다. 다른 풀링 API를 지원하기 위해서 org.springframework.aop.target.AbstractPoolingTargetSource의 하위클래스를 만드는 것도 가능하다.

다음은 설정 예시이다.


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

<bean id="businessObjectTarget" class="com.mycompany.MyBusinessObject" 
    scope="prototype">
  ... properties omitted
</bean>

<bean id="poolTargetSource" class="org.springframework.aop.target.CommonsPoolTargetSource">
  <property name="targetBeanName" value="businessObjectTarget"/>
  <property name="maxSize" value="25"/>
</bean>

<bean id="businessObject" class="org.springframework.aop.framework.ProxyFactoryBean">
  <property name="targetSource" ref="poolTargetSource"/>
  <property name="interceptorNames" value="myInterceptor"/>
</bean>

대상 객체(이 예제에서는 "businessObjectTarget")는 반드시 프로토타입이어야 한다. 이는 PoolingTargetSource 구현체가 필요에 따라 풀의 크기를 조절하기 위해서 대상의 새로운 인스턴스를 생성할 수 있도록 한다. AbstractPoolingTargetSource의 프로퍼티에 대한 정보는 AbstractPoolingTargetSource javadoc과 구현된 하위클래스를 참고해라. 프로피티 중 "maxSize"가 가장 기본적인 프로퍼티이고 항상 존재한다는 것을 보장한다.

이 경우에 "myInterceptor"는 같은 IoC 컨텍스트에서 정의되어야 하는 인터셉터의 이름이다. 하지만 풀링을 사용하려고 인터셉터를 지정할 필요는 없다. 다른 어드바이스는 필요없고 풀링만을 사용하고자 한다면 interceptorNames 프로퍼티를 아예 설정하지 말아라.

풀에 있는 어떤 객체라도 org.springframework.aop.target.PoolingConfig 인터페이스로 캐스팅할 수 있도록 스프링을 설정할 수 있다. 이 인터페이스는 인트로덕션을 통해서 설정과 현재 풀의 크기에 대한 정보를 노출한다. 다음과 같이 어드바이저를 정의해야 한다.


1
2
3
4
5
6
Xml

<bean id="poolConfigAdvisor" class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">
  <property name="targetObject" ref="poolTargetSource"/>
  <property name="targetMethod" value="getPoolingConfigMixin"/>
</bean>

AbstractPoolingTargetSource 클래스의 편리한 메서드를 호출해서 즉, MethodInvokingFactoryBean를 사용해서 이 어드바이저를 얻어올 수 있다. 이 어드바이저의 이름은(여기서는 "poolConfigAdvisor") 풀에 있는 객체를 노출하는 ProxyFactoryBean의 인터셉터 이름 목록에 있어야 한다.

캐스팅은 다음과 같을 것이다.


1
2
3
4
Java

PoolingConfig conf = (PoolingConfig) beanFactory.getBean("businessObject");
System.out.println("Max pool size is " + conf.getMaxSize());




Note
보 통 상태가 없는 서비스 객체들을 풀링할 필요는 없다. 상태를 갖지 않는 대부분의 객체들은 자연히 쓰레드세이프하기 때문에 풀링하는 것이 기본적인 선택이어야 한다고 생각하지 않고 리소스가 캐싱되는 경우 인스턴스 풀링은 문제의 소지가 있다.

자동프록시를 사용해서 더 간단한 풀링을 사용할 수 있다. 자동프록시 생성자(autoproxy creator)가 사용하는 TargetSources를 설정할 수 있다.

9.10.3 프로토타입 대상 소스
"prototype" 대상소스를 설정하는 것은 TargetSource를 풀링하는 것과 비슷하다. 이 경우에 메서드 호출시마다 대상의 새로운 인스턴스를 생성할 것이다. 최신(modern) JVM에서는 새로운 객체를 생성하는 비용이 크지 않지만 새로운 객체를 연결하는 비용은(객체의 IoC 의존성을 만족시키는) 아마 좀 더 비쌀 것이다. 그러므로 타당한 이유가 없다면 이 접근을 사용하지 말아야 한다.

프로토타입 대상 소스를 설정하기 위해서 위에 나온 poolTargetSource 정의를 다음과 같이 수정할 수 있다.(명확함을 위해서 이름도 변경했다.)


1
2
3
4
5
Xml

<bean id="prototypeTargetSource" class="org.springframework.aop.target.PrototypeTargetSource">
  <property name="targetBeanName" ref="businessObjectTarget"/>
</bean>

여기에는 대상 빈의 이름을 위한 딱 하나의 프로퍼티만 존재한다. 일관성 있는 작명을 보장하기 위해서 TargetSource 구현체는 상속을 사용한다. 대상 소스를 풀링하는 것처럼 대상 빈은 프로토타입 빈 정의여야 한다.

9.10.4 ThreadLocal 대상 소스
요 청이 들어올때마다 객체를 생성해야할 때(쓰레드마다) ThreadLocal 대상 소스가 유용하다. ThreadLocal의 개념은 쓰레드에 투명하게 리소스를 저장하는 JDK 범위의 기능을 제공한다. ThreadLocalTargetSource을 설정하는 방법은 다른 타입의 대상소스에서 설명한 것과 거의 같다.


1
2
3
4
5
Xml

<bean id="threadlocalTargetSource" class="org.springframework.aop.target.ThreadLocalTargetSource">
  <property name="targetBeanName" value="businessObjectTarget"/>
</bean>




Note
멀티 쓰레드 환경이나 멀티-클래스로더 환경에서 ThreadLocal을 잘못 사용할 때 심각한 이슈가 있다.(잠재적으로 메모리 누수가 있을 수 있다.) 항상 다른 클래스에 쓰레드로컬을 감싸고 ThreadLocal 자체를 직접 사용하는 않는 것을 고려해야 한다. (물론 랩퍼(wrapper) 클래스는 제외하고) 그리고 쓰레드에 리소스로컬을 제대로 설정하고 설정해제해야 한다.(설정해제는 ThreadLocal.set(null)를 호출하는 것을 말한다) 설정해제를 사지 않으면 문제의 소지가 있는 동작이 일어날 수 있으므로 설정해제는 항상 해주어야 한다. 스프링의 ThreadLocal 지원은 개발자를 위해서 이것을 해주고 달리 코드를 적절하게 다루지 않도로 ThreadLocal을 사용하는 것은 항상 생각해야한다.

9.11 새로운 Advice 타입 정의하기
스 프링 AOP는 확장할 수 있도록 설계되었다. 내부적으로 인터셉션 구현체 전략(interception implementation strategy)을 사용하고 있기 때문에 interception around advice, before advice, throws advice, after returning advice에 추가로 임의의 어드바이스 타입을 지원하는 것이 가능하다.

org.springframework.aop.framework.adapter 패키지는 코어 프레임워크를 수정하지 않고 새로운 커스텀 어드바이스 타입을 추가할 수 있도록 하는 SPI 패키지이다. 커스텀 Advice 타입의 유일한 제약사항은 org.aopalliance.aop.Advice 태그 인터페이스를 반드시 구현해야 한다는 것 뿐이다.

더 자세한 정보는 org.springframework.aop.framework.adapter 패키지의 javadoc을 참고해라.

9.12 추가적인 리소스
스프링 AOP의 더 많은 예제를 보려면 스프링 샘플 어플리케이션을 참고해라.

  • JPetStore의 기본 설정은 선언적인 트랜잭션 관리를 위한 TransactionProxyFactoryBean의 사용방법을 설명한다.
  • JPetStore의 /attributes 디렉토리는 속성주도 선언적인 트랜잭션 관리의 사용방법을 설명한다.