'00.Flex,Flash,ActionScript'에 해당되는 글 131건
- 2015.03.17 http://flex.apache.org/tourdeflex/index.html
- 2014.12.01 [펌] crossdomain.xml 정책변경에 따른 변경사항.
- 2014.08.21 40+ Beautiful Themes/Skins For Adobe Flex Apps
- 2014.08.20 [펌] Flash Player 의 가비지 컬렉션(GC) 동작 방식에 대해
- 2014.08.20 [펌] Flex Builder에서 디버깅&프로파일링 하기
- 2014.08.20 [펌] Flex Builder에서 디버깅&프로파일링 하기 -1부-
- 2014.08.20 [FLEX] Memory leak 문제
- 2014.08.06 actionsript 데이터 유형 설명
- 2014.07.30 Introducción a Apache FlexJS
- 2014.07.25 [펌] flex StringUtil.substitute 사용하기
- 2014.06.25 flex charts link
- 2014.06.20 The Blogagic Flex Explorer Collection
- 2014.06.19 [펌] [Flex3] Error : mx.managers.IDragManager(으)로 변환할 수 없습니다 ...
- 2014.06.17 [펌] Flash 애플리케이션의 속도와 메모리 개선 : Object Pool의 활용
- 2014.06.13 [펌] [Flex]TextInput 글자제한 ( restrict )
- 2014.06.12 [펌] DataGrid CheckBox 스크롤때 없어지는문제 해결 [출처] DataGrid CheckBox 스크롤때 없어지는문제 해결|작성자 neodijkstra
- 2014.06.12 [펌] 데이터그리드 스크롤 버그
- 2014.06.12 [펌] Flex DataGrid ItemRenderer 스크롤 버그
- 2014.06.10 [펌] FLEX 마우스 커서 모양 바꾸기 (시스템)
- 2014.06.10 [펌] flex 컴포넌트 크기 변경하기
- 2013.12.03 flex 843 보안정책서버 예제
- 2013.11.06 flex 에 jsp 페이지 삽입 iframe
- 2013.11.04 [펌] flex에서 font 사용하기 2
- 2013.11.01 [펌] Flex 줄바꿈 처리 2
- 2013.10.31 flex textarea 개행문자
- 2013.10.01 flex 개행문자 제거
- 2013.09.26 flex add Dynamic children to an array or arraycollection 1
- 2013.09.12 [펌] 세션 가져오는 법
- 2013.09.12 [펌] Flex Log log4j 처럼 사용하기
- 2013.09.12 [펌] spring+flex4+mybatis
출처 : http://v1.jowrney.com/xe/index.php?mid=sas&page=1&category=3863&document_srl=8807&sort_index=readed_count&order_type=desc
이슈제기
플래시의 MovieClip을 Bitmap 데이터로 서버에 전송후 PHP의 GD라이브러리를 이용해 이미지 생성 후,
다시 플래시로 돌려받아 파일 저장 대화상자를 띄우는 모듈(이하 플래시 이미지 저장 모듈)이 정상 작동하지 않았다.
이슈분석
일반적으로 플래시가 있는 서버를 A(http://www.a.com)이라고 하고,
파일을 생성하는 서버 B(http://file.a.com)하면,
Sandbox보안은 crossdomain정책에 따라 운용되게 된다.
그 동안 정상적으로 작동되던 기능이 작동되지 않아 분석해 보니,
아래와 같았다.
1. 파일서버에 이미지 생성은 정상적으로 되는 것으로 보아, 비트맵 데이터 전달의 문제는 없다.
2. swf가 위치한 서버와 위치가 다른 파일서버의 파일의 FP의 접근을 허용하지 않는 이유는 보다 강력해진,
crossdomain.xml 정책으로 기인한다는 사실 발견.
3. crossdomain정책 실시 이후에도 특정브라우저에서 여전히 문제 남아 있었고, 그에 대한 해결방법으로
파일저장 대화상자 오픈 실패시, 다운로드 버튼 표시로 사용자 수동방식으로 다운로드 유도.
이슈핵심
FP9.0.124.0 (현재 FP10) 업데이트 악의적인 HTTP 헤더에 대한 보안 취약점을 해결하기 위해서
크로스-도메인 정책이 변경되었다. 다른 도메인 상의 SWF 파일로 부터 HTTP 헤더의 전송을 허용할 지
여부를 크로스-도메인 정책 파일에서 설정할 수 있다.
또한 html에 포함하는 임베디드 태그의 파라미터 중 allowScriptAccess의 값에서도 설정을 하여야 한다.
FP10에서는 접근을 시도하려는 파일이 있는 서버에도, 접근을 허용하는 서버에도 모두 crossdomain이 필요하다.
이러한 crossdomain을 master policy라고도 하는데, 이 파일 외에 다른 파일타입이나 파일명으로 된
정책파일의 사용을 허용할지 말지를 설정한다. 기존에는 기본값이 all로 되어 있어서 제한없이 사용할 수 있었으나,
FP10에서는 master-only로 변경, 기본적으로는 마스터 정책 파일만 사용할 수 있게 바뀌었다.
이러한 변경은 악의적인 사용자가 게시판 글쓰기나 파일업로드 등의 방법으로 크로스 도메인 설정의 내용을
가지는 파일을 만들수 있고, 이를 이용해 loadPolicyFile() 메소드를 이용하여 로드하는 경우, 실제로는 권한이
없는 사이트에서도 데이터를 가져갈 수 있는 문제를 야기 시키기에 서버 관리자가 마스터 설정 파일을 가지고,
이런 악의적인 적근을 제한하기 위해 추가된 기능이다.
이슈해결
1. 서비스 서버(http://www.a.com) 의 root의 기존 crossdomain.xml 내용변경.
[ 기존설정 ]
1.
<?xml version=
"1.0"
?>
2.
<cross-domain-policy>
3.
<allow-access-from domain=
"*"
/>
4.
</cross-domain-policy>
[ 변경 설정 ]
1.
<?xml version=
"1.0"
?>
2.
<!DOCTYPE cross-domain-policy SYSTEM
"http://www.adobe.com/xml/dtds/cross-domain-policy.dtd"
>
3.
<cross-domain-policy>
4.
<allow-access-from domain=
"*"
/>
5.
<allow-http-request-headers-from domain=
"*"
headers=
"*"
/>
6.
</cross-domain-policy>
2. 파일 생성 서버(http://file.a.com) 의 root의 새로운 crossdomain.xml 내용변경.
[ 기존설정 ]
crossdomain.xml 파일 없었음.
[ 변경설정 ]
서비스 파일 서버의 crossdomain.xml 동일하게 생성.
3. SWF 임베디드 코드 페이지 수정요.
[ 기존설정 ]
1.
<object classid=
"clsid:d27cdb6e-ae6d-11cf-96b8-444553540000"
codebase=
"http://fpdownload.adobe.com/pub/shockwave/cabs/flash/swflash.cab#version=8,0,0,0"
width=
"550"
height=
"400"
align=
"middle"
>
2.
<param name=
"movie"
value=
"http://www.a.com/ex.swf"
>
3.
<param name=
"allowScriptAccess"
value=
"sameDomain"
>
4.
<embed type=
"application/x-shockwave-flash"
pluginspage=
"http://www.adobe.com/go/getflashplayer"
width=
"550"
height=
"400"
align=
"middle"
src=
"http://www.a.com/ex.swf"
allowScriptAccess=
"sameDomain"
></embed>
5.
</object>
[ 변경설정 ]
1.
<
object
classid
=
"clsid:d27cdb6e-ae6d-11cf-96b8-444553540000"
codebase
=
"http://fpdownload.adobe.com/pub/shockwave/cabs/flash/swflash.cab#version=8,0,0,0"
width
=
"550"
height
=
"400"
align
=
"middle"
>
2.
<
param
name
=
"movie"
value
=
"http://www.a.com/ex.swf"
>
3.
<
param
name
=
"allowScriptAccess"
value
=
"always"
>
4.
<
embed
type
=
"application/x-shockwave-flash"
pluginspage
=
"http://www.adobe.com/go/getflashplayer"
width
=
"550"
height
=
"400"
align
=
"middle"
src
=
"http://www.a.com/ex.swf"
allowScriptAccess
=
"always"
></
embed
>
5.
</
object
>
관련 자료
Cross-domain policy file specification
http://www.adobe.com/devnet/articles/crossdomain_policy_file_spec.html#site-control-permitted-cross-domain-policies
Links from SWF files in HTML page no longer function(Flash Player 9)http://kb.adobe.com/selfservice/viewContent.do?externalId=50c1cf38&sliceId=2
Lime green, grey and white skins with smooth curves and glassy surfaces.
A theme based on the brushed metal and glossy look of the Apple Aqua styling of OS X.
Arcade by Brandon Snyder
Elements of this theme take on a look of older arcade games.
Black and electric blue give this theme a futuristic feel.
A dark black and grey skin with simple lines that can be used in Flash Catalyst and Flex 4.
Blend by Richard Williams
A theme of black and red that uses transparency and highlights to create definition of components.
Blue Tan by Oluwaseun Ladeinde
Subtle gradients of a muted blue with various highlights throughout.
Brauwny by Oluwaseun Ladeinde
Subtle gradients of a golden-brown with various highlights throughout.
A contrasting, bold, and lively skin. Colors include yellow, greys and light blue. Patterns and medium highlights compliment the darker colors.
Classic Orange by Bhavin Padhiyar
A theme of yellow-orange and grey with components of a glossy feel.
Darkroom is a Flex theme inspired by Adobe Lightroom. The theme is applied to a Flex application that mimics the actual layout and functionality of Adobe Lightroom.
Demo | Download Source Code | Download Art
Bold colors of green and pink sit on a dark background and intermingle with patterns of typographic forms.
The Edding Black theme makes your Flex application look as though it was drawn roughly on grid paper using a marker.
Feeling Love by Pei Peng
Red and white theme with subtle gradients.
Flexrays Grey by Rashmi Nagaraju
Grey theme with a rough cut-out look.
Granite by Dan White
Grey theme with square corners and the ability to create and export your own custom CSS.
iCandy by Phil Chung
A candy coating cascades across every component with hidden sugary elements.
The iTunes 7 theme has the look of Apple’s iTunes 7 interface. Many of the Flex components are accounted for.
This is a dark grey theme inspired by the interface of Photoshop Express. You also can read the tutorials Using FlexBuilder 3 and Flash CS3 to Build Your Skin in Flex to learn how to build this theme by yourselves.
The Minty theme is great for giving your Flex application a Mint-chocolate exterior. The look was created entirely using CSS.
Obsidian is a dark theme with semi-transparent features of black and electric-blue.
Demo | Download Source Code | Download Art
The theme was entirely drawn in Flash Catalyst using the drawing tools. This theme is inspired by Adobe Scope Skins & the kingnare theme with a dash of orange to it!
This theme is inspired by the regular plastic ware that you would see everyday. It is a sober, light theme with a dash of boldness added by the sunshine-orange roll over scheme.
Professional Yellow by Bhavin Padhiyar
Black and yellow theme with subtle detailing.
A rough and grungy look with bold colors of green and orange on black, mixed with patterns.
The Shadow theme is a dark and somewhat matte theme. The look was created entirely using CSS.
Simplicity Gray by Vasile Ermicioi
White and gray theme with subtle gradients and grey outlines.
Something Like Mac Flex 4 theme by Harish Sivaramakrishnan
The aim was to build a Flex 4 theme that looks like the Mac Aqua theme exclusively using Adobe Flash Catalyst drawing tools *only*. The theme isn’t an exact replica of the mac look, it is an extrapolation of sorts. This is work in progress.
Summer Sky by Erick Ghaumez
Subtle tones of grey and light blue with various amounts of surface modeling.
Sun Night by Erick Ghaumez
A dark theme with highlights of light grey and yellow.
Undefined Skin One by Undefined.es
Undefined SkinOne is a mixture of greys and details of color. Subtle highlights and details are around every corner.
Electric-blue, grey and black theme with detailes container backgrounds.
This theme was inspired by Windows Vista and a cool blue color palette.
Demo | Download Source Code | Download Art
Vista Remix by Jerry Don
A theme based on a remix of the styling in Windows Vista.
Windows Classic by Sven-Olav Paavel
This theme is based on the look of a Windows classic interface.
The WMP 11 theme was inspired by Windows Media Player 11 interface. There are multiple skins for a few components, like ComboBox.
Demo | Download Source Code | Download Art
Xan by Lars Hacking
Grey, black and red colors with a glossy finish.
나는 최근에 [Flex / AIR / ActionScript3] Event 청취자와 메모리 관리 에 대한 글을 썼다.
그러나 놀랍게도 "불꽃남자"님께서 다음과 같이 지적해주셨다.
Flex의 메모리 문제에 대한 마지막 방법이
weakly reference로 모든 객체를 다루는것이지만...
무수한 테스트에 의해도....
결국 메모리는 털어지지 않습니다.
일반적인 OOP에서 메모리를 sweep하는 프로세스는
스레드가 담당을 하게 되고...그 스레드만의 stop the world를
통해 다른 실행중인 스레드가 영향을 받지 않게 해야지만,
AS3의 GC에는 그러한 구조가 없습니다.
weakly reference가 잘 동작하지 않는다고 Adobe측에 아주 강력하게
항의를 했지만, 돌아오는 답은 원래 그런거다...라는...
어이가 없죠?ㅋㅋㅋ
Flex Product Manager에게도 강력히 요청했으나.
Flex 3에서도 변한건 없습니다.
Large Application에서 GC를 정확히 할려면,
편법으로 밖에 할 수 없겠죠...참고로..Flex SDK안의 많은 오브젝트에 addEventListner가 구현되어있지만,
removeEventListner가 없는 코드가 더 많다는..ㅡ,ㅡ;;
이부분 역시 어도비에 따졌지만,
돌아오는 답은...원래 그런거다..그냥 써라..
역시 어이가 없죠?ㅋㅋㅋ
자기들이 가이드라인해준 방식을 SDK안에서 조차 적용이 안되어있는걸
어떻게 설명해야하나요?ㅡ,ㅡ;;아..그리고 위의 코드에서
용호님이 테스트 하셔서 좋은 정보 올려주세요.
objects.destroy();
objects = null;
을 호출하게 되면, 이론상으로 잘 돌아가야는데...
원래 OOP의 CG구조가 저렇게 destroy()를 시키고, null을 대입하면
바로 GC가 일어나는건 아닙니다.
하지만 최 우선순위를 두고 메모리에서 없애야 하는데,
Flash Player에서 그 타이밍이 언제인지는 아무도 모릅니다.
다만,
어도비에서 주장하는 바는 웹브라우저가 가용할 수 있는메모리, 혹은
Flash Player가 가용할 수 있는 메모리의 임계치까지 다 찼을때만
GC가 발생한다고 합니다.
이게 말이나 되는 일입니까?ㅎㅎ
그럼 destroy() 메소드는 왜 만들었으며, 객체에 null을 대입하는
코드는 무슨 소용이겠습니까?
좋은글 올려주셨는데, 우리가 생각하는데로 Flex가 동작하지 않기에
몇마디 달아봤습니다.ㅎㅎㅎ
수고하세요 지돌스타님~~~^^
그리고 위에서 보여주신 정보는 상당히 좋은 정보입니다.
보통 자바개발자라면 저러한 부분을 생각하지만,
그렇지 않은 분들도 많거든요.
외국에서도 weakly reference하게 개발하자고 많은 분들이 주장하시고 있고,
말씀하신 remove관련된 내용은 분명히 어플리케이션의 부하를 줄여주는
좋은 방법입니다.
메모리 문제 때문에 악받은 Flex 챔피언들 몇명있죠..ㅋㅋ
동호대표, 진욱대표...그리고 저..ㅋㅋㅋ
어도비 수석 엔지니어의 말로는 Flash Player 10이 나오면
해결 가능할것 같다...같다...라고 했습니다..ㅎㅎ
결국 무슨말이고하니....
이런식으로는 가비지 컬렉터(GC)가 바로 동작하지 않는다는 것이다.
그래서 실제로 그러한가 테스트 해보았다.
그랬더니... 정말 그러한 것이 아닌가?
수백개의 자식 컴포넌트를 추가한뒤 참조까지 삭제후 부모에서 떼어냈는데도... System.totalMemory 값은 거의 불변이었다. 재미있는 것은 다시 수백개의 자식을 추가하는 동안 갑자기 System.totalMemory가 증가만 하는것이 아니라 뚝 떨어지는 시기가 있다. 즉, 프로그래머는 메모리 관리는 내 마음대로 할 수 없다는 것을 의미한다. Flash Player가 알아서 어느정도 GC 대상이 만들어졌을 경우 삭제한다는 것이다.
한가지 중요한 점은 [Flex / AIR / ActionScript3] Event 청취자와 메모리 관리 글에서 쓴 것처럼 최소작업은 해야 그나마 Flash Player의 GC가 동작할 수 있다는 것이다. 그래서 쓸데없는 글은 아니였구나 생각했다.
더 재미있는 것이 있다.
바로 System.gc() 함수이다.
이 함수는 GC를 강제적으로 실행해준다. 그...그런데....
gc () method public static function gc():void
Forces the garbage collection process.
For the Flash Player debugger version and AIR applications only. In an AIR application, the
gc()
method is only enabled in content running in the AIR Debug Launcher (ADL) or, in an installed applcation, in content in the application security sandbox.Player Version: Flash Player 9 Update 3 or the AIR Debug Launcher (ADL).
Flash Player debugger와 AIR Applications 에서만 동작한단다....
즉, 배포용 Flash/Flex 프로그램에서는 동작하지 않는다는 말이다.
왜 이렇게 만들었지???? ㅡㅡ;;;;;
테스트 소스를 받아봐서 실행해보길 바란다(Flex 3기반)
이 소스는 http://wooyaggo.tistory.com/search/system.gc 에서 제공한 소스를 참고했다.
만약 Flex나 Flash 프로그래머라면 Flash Player가 debug버전으로 설치되어 있을 것이다. 그 상태에서는 System.gc()가 잘 동작하므로 GC가 매우 잘 동작하는 것처럼 보일것이다. 하지만
http://wooyaggo.tistory.com/112
에서 Flash Player Uninstaller를 이용해 debug용 Flash Player를 삭제한 후 보통 Flash Player 를 설치후 방금 받은 소스를 실행해보자.
그럼 System.gc()는 제대로 동작안되고 GC가 동작되는 시기가 Flash Player에서 임의로 정해진다.
결론적으로 이러한 환경에서는
되도록이면 객체를 최소한 만들고
모듈화를 최대한 활용하며
재활용할 수 있는 것은 재활용하면서 사용하는 것이
메모리관리에 도움된다.
아~~~ 다 좋은데.... 왜 메모리가 이렇게 골치아프게 만드는 것이냐.... ㅡㅡ;;
잘못된 내용이나 추가할 사항이 있다면 언제든지 지적해주세요.
이러한 사항은 함께 공유해야 한다고 생각하거든요.
읽을거리 : AIR/Flex: Memory optimisation
글쓴이 : 지돌스타(http://blog.jidolstar.com/319)
<목차>
1. 프로파일링이란?
2. 가비지 콜렉션
2-1. 레퍼런스 카운팅
2-2. 마크앤스윕
3. Flex Profiling perspective
3-1. 메모리 프로파일링
3-1-1. Profile 뷰
3-1-2. Memory Usage 뷰
3-1-3. Live Objects 뷰
3-1-4. Memory Snapshot 뷰
3-1-5. Object References 뷰
3-1-6. Loitering Objects 뷰
3-1-7. Allocation Trace 뷰
3-1-8. Object Statistics 뷰
3-2. 퍼포먼스 프로파일링
3-2-1. Performance Profile 뷰
3-2-2. Method Statistics 뷰
4. 맺음말
1. 프로파일링 이란?
애플리케이션을 개발하고 실행하게 되면 알 수 없는 원인에 의해 느려지거나 심지어 멈추는 경우가 종종 발생한다. 디버깅만으로는 이런 알 수 없는 원인을 찾아내기란 쉽지 않다. 이런 경우 애플리케이션의 성능측정을 통해 어느 부분에서 어떤 문제가 발생하는지를 찾아낼 수 있다. 성능측정을 전문적인 용어로 프로파일링 이라고 한다.
프로파일링은 디버깅과 더불어 소프트웨어 품질 향상에 도움을 준다.
우리는 Flex Builder3 Professional(이하 플렉스빌더) 에서 제공하는 프로파일링 기능을 이용할 것이다.
(Flex Builder Standard 버전은 프로파일링 기능을 제공하지 않는다.)
플렉스빌더에서 프로파일링을 하기전 플래시 플레이어의 가비지 콜렉션에 대한 이해가 필요하다.
따라서 먼저 가비지 콜렉션에 대해 알아보겠다.
2. 가비지 콜렉션
가비지 콜렉션이란, 플래시 플레이어의 숨겨진 프로세스인 "가비지 콜렉터"에 의해서 참조 되지 않는 객체들을 메모리에서 해제 하는 것을 말한다. 참조 되지 않는 객체가 있다고 해서
가비지 콜렉터는 객체가 더 이상 참조되지 않는지 확인하기 위해 레퍼런스 카운팅과 마크앤스윕 이라는 두 가지 절차를 따른 후 가비지 콜렉션을 수행한다. 이 두 가지 절차를 이해하는 것은 플렉스 프로파일링 기능을 이용하는데 도움을 줄 것이고 메모리를 효율적으로 이용하는 애플리케이션을 작성하도록 도움을 줄 것이다.
그럼 가비지 콜렉션을 위한 두 가지 절차에 대해 알아보자.
2-1. 레퍼런스 카운팅
레퍼런스 카운팅은 ActionScript 1.0 때 부터 사용되어 왔으며, 객체가 가비지 콜렉션의 대상인지 확인하는 가장 쉽고 빠른 방법이다. 어떤 객체를 생성하고
다음 예제를 보면 쉽게 이해할 수 있을 것이다.
위 예제에서 생성된 Object는 레퍼런스 카운트가 0이기 때문에 가비지 콜렉션의 대상이 된다.
하지만 레퍼런스 카운팅 만으로는 상호참조에 대한 문제를 해결할 수 없다.
상호참조가 무엇인지 다음 예제를 통해 살펴보자.
위 예제에서 생성된 두개의 Object는 obj.foo와 obj2.foo 에서 상호참조 되고 있다.
obj와 obj2에 null을 할당하더라도 이 두개 Object의 레퍼런스 카운트는 결코 0이 될 수 없다.
이런 상황은 매우 빈번하게 일어나며 더욱 복잡해질 수 있다. 플래시 플레이어7 까지는 XML 객체에서 노드들의 상호참조 때문에 이슈가 되기도 했는데, 다행히 플래시 플레이어8에서 마크앤스윕 이라는 새로운 방법이 등장해 이 문제를 해결하고 있다.
2-2. 마크앤스윕
플래시 플레이어(버전 8이상) 는 루트객체(Application 자체 또는 메인MXML) 부터 시작해서 루트객체가 참조하는 객체들과 그 객체들(참조 당하는 객체들)이 참조하는 객체들을 마킹한다. 여기서 마킹의 의미는 객체가 가비지 콜렉션의 대상이 되지 않게 하기 위해 점 찍어두는 것이다. 이 처럼 플래시 플레이어는 애플리케이션의 모든 객체트리를 돌며 참조되고 있는 객체들을 마킹한다. 마킹된 객체들은 레퍼런스 카운트가 무조건 1이상이기 때문에 결코 가비지 컬렉션의 대상이 되지 않는다. 반면 마킹되지 않은 객체들은 가비지 컬렉션의 대상이 된다. 마킹되지 않은 객체는 레퍼런스 카운트가 0일거라고 생각할 수 있지만 그렇지 않다. 플래시 플레이어는 오직 루트객체 까지 연결된 객체만 마킹하므로, 루트객체 까지 연결되지 않은 상호참조 객체들은 마킹되지 않는다. 따라서 상호참조 객체들은 레퍼런스 카운트가 1이상이라도 루트객체에 연결되어 있지 않으면 가비지 콜렉션의 대상이 된다.
다음 그림을 통해 마킹된 객체와 마킹되지 않은 객체들을 파악해보자.
(위 그림은 어도비에서 제공하는 가비지 콜렉터 시뮬레이션이다.http://www.adobe.com/devnet/flashplayer/articles/garbage_collection/example2.html 에서 직접 시뮬레이션 해볼 수 있다.)
위 그림에서 초록색은 루트객체에 연결된 마킹된 객체들이고 빨간색은 상호참조로 인해 레퍼런스 카운트가 각각 1 이지만 루트객체에 연결되지 않았기 때문에 가비지 콜렉션이 수행되면 빨간색 객체들은 메모리에서 제거될 것이다.
지금까지 플래시 플레이어에서 메모리가 어떻게 관리되는지 가비지 콜렉션이 언제 수행되는지 그리고 가비지 콜렉션이 수행될 때 거치는 두가지 과정에 대해 알아봤다. 이러한 밑 바탕이 되는 지식과 함께 본격적으로 플렉스 프로파일링을 시작해 보겠다.
3. Flex Profiling perspective
Flex Profiling perspective 는 플렉스빌더에서 제공하는 프로파일링 관련 도구들을 모아놓은 작업 환경이다.
이 작업 환경에서 실시간으로 애플리케이션이 사용하는 메모리를 비롯해 생성된 객체들의 수를 확인할 수 있는 메모리 프로파일링과, 함수 수행 시간을 확인할 수 있는 퍼포먼스 프로파일링을 할 수 있다.
3-1. 메모리 프로파일링
메모리 프로파일링은 일반적으로 애플리케이션의 메모리가 계속 증가 할 때 이용한다.
메모리가 계속 증가하는 이유는 더 이상 사용되지 않는 객체들이 어디선가 참조되고 있어서 가비지 콜렉터가 제거하지 못하기 때문이다. 이러한 현상을 보다 전문적인 용어로 메모리 누수(memory leak) 라고 부른다.
메모리 프로파일링은 메모리 누수를 찾을수 있을뿐만 아니라, 어디서 어떤 객체가 생성되고 있으며 객체들이 얼마 만큼의 메모리를 차지하고 있는지 알 수 있다. 또한 특정한 두개의 서로 다른 시점의 메모리 상태를 저장하고 이를 비교분석 할 수도 있다.
먼저 정상적인 애플리케이션을 통해 플렉스빌더의 프로파일링 도구를 살펴보자.
아래의 코드를 프로파일링 모드로 실행한다. (디버그 실행버튼 바로 오른쪽 버튼)
프로파일링 실행버튼을 누르면 다음과 같은 창이 나타난다.
위 창은 프로파일링 옵션을 묻는 창이다. 메모리 프로파일링의 경우 두가지 옵션이 더 존재하는데 Watch live memory data는 실시간으로 현재 활동중인 객체와 메모리 크기를 감지하고 지금까지 생성되었던 객체와 메모리의 크기를 알 수 있는 옵션이며, Generate object allocation stack traces는 특정한 두 시점 사이에서 호출된 메소드와 그 메소드에서 사용했던 메모리 크기를 알 수 있는 옵션이다. 모든 체크박스를 선택하게 될 경우 프로파일링 데이터 수집을 그 만큼 더 많이 하게 되기 때문에 애플리케이션의 속도가 느려진다. 지금은 위 그림처럼 메모리 프로파링일 항목만(하위 두개의 옵션 포함) 체크하고 Resume 버튼으로 프로파일링을 시작해보자.
프로파일링이 시작되면 플렉스빌더는 프로파일링 모드로 전환되고 다음 그림처럼 보일 것이다.
①번 영역은 프로파일링 목록과 프로파일링 실행을 제어할 수 있는 [Profile 뷰] 이다.
②번 영역은 현재 메모리 상황을 그래프로 표현하는 [Memory Usage 뷰] 이다.
③번 영역은 실시간으로 메모리에 존재하는 객체들을 볼 수 있는 [Live Objects 뷰] 이다.
그럼 먼저 프로파일링을 시작했을 때 기본적으로 보여지는 위 세가지 뷰에 대해 자세히 알아보겠다.
3-1-1. Profile 뷰
프로파일링이 시작된 이후에, 프로파일링을 일시중지 하고 다시 시작 하는 등 실행을 제어 하거나, 메모리와 퍼포먼스 분석을 위한 스냅샷 저장을 할 때 [Profile 뷰] 이용한다. [Profile 뷰] 는 프로파일링 실행을 제어할 수 있는 컨트롤 부분과 프로파일링이 진행중 이거나 이미 중단된 애플리케이션과 스냅샷의 목록이 나타나는 부분이 있다. 애플리케이션 목록 중 하나를 선택하고 수행하길 원하는 버튼을 클릭함으로써 프로파일링을 제어하게 된다. 그럼 실행을 제어하는 컨트롤 부분의 버튼들이 어떤 기능을 하는지 알아 보도록 하겠다.
Resume
일시 정지된 애플리케이션과 함께 프로파일링을 다시 계속한다.
Suspend
애플리케이션과 함께 프로파일링을 일시 정지한다.
Terminate
프로파일링을 종료 한다. 애플리케이션(브라우저)는 종료되지 않는다.
Run Garbage Collector
강제적으로 가비지 콜렉션 수행한다. 이 버튼을 누르면 [Memory Usage 뷰] 에서 가비지 콜렉션이 수행된 것을 알 수 있다.
Take Memory Snapshot
현재 실행중인 애플리케이션의 메모리 상황을 저장하고, [Profile 뷰]의 선택된 애플리케이션 하위로 메모리 스냅샷 항목이 추가된다. 이 버튼을 누르면 가장 먼저 가비지 콜렉션을 수행한 뒤 메모리 상황을 저장한다. 가비지 콜렉션이 먼저 수행되는 이유는 가비지 객체들이 메모리 상황을 분석하는데 있어서 잘못된 정보로 활용되기 때문이다. 메모리 스냅샷은 1개 이상 저장할 수 있고, 저장된 스냅샷 목록중 하나를 더블클릭 했을 때 열리는 [Memory Snapshot 뷰] 를 이용해 그 당시 메모리 상황을 분석할 수 있다. 또한 저장된 스냅샷 목록 중 두개의 스냅샷을 한 쌍으로하여 앞으로 나오게 될 두 시점 사이의 비교 기능을 이용할 수 있다. 자세한 내용은
3-1-4. Memory Snapshot 뷰, 3-1-6. Loitering Objects 뷰, 3-1-7. Allocation Trace 뷰 이 세 파트에 걸쳐서 다루겠다.
Find Loitering Objects
이 버튼은 메모리 스냅샷 목록 중 두개를 선택 했을 때 작동 된다. 이 버튼은 선택된 두 메모리 스냅샷을 비교하여 두 시점 사이에서 생성된 객체들을 확인할 수 있는 [Loitering Objects 뷰]를 연다. 자세한 내용은
3-1-6. Loitering Objects 뷰 파트에서 다루겠다.
View Allocation Trace
이 버튼은 프로파일링을 시작할 대 실행 옵션을 묻는 창에서 Generate object allocation stack traces 항목을 체크하고, 메모리 스냅샷 목록 중 두개를 선택 했을 때 작동 된다. 이 버튼은 선택된 두 메모리 스냅샷을 비교하여 두 시점 사이에서 수행된 메소드들을 확인할 수 있는 [Allocation Trace 뷰]를 연다. 자세한 내용 3-1-7. Allocation Trace 뷰 파트에서 다루겠다.
Reset Performance Data
현재까지 플렉스빌더가 수집한 퍼포먼스 데이터를 초기화 한다. 플렉스빌더는 이 버튼이 눌린 시점으로 하여 퍼포먼스 데이터를 새로 수집하게 된다.
Capture Performance Profile
프로파일링이 시작된 이후 또는 퍼포먼스 데이터가 초기화된 시점부터 현재까지 수집한 퍼포먼스 데이터를 저장하고, [Profile 뷰]의 선택된 애플리케이션 하위로 퍼포먼스 스냅샷 항목을 추가한다. 추가된 퍼포먼스 스냅샷 항목을 더블클릭 하면 성능에 관련된 정보를 보여주는 [Performance Profile 뷰]가 열린다. 자세한 내용은 3-2. 퍼포먼스 프로파일링 파트에서 다루겠다.
Delete
선택된 스냅샷 항목 또는 프로파일링이 종료되고 선택된 애플리케이션을 목록에서 제거한다.
3-1-2. Memory Usage 뷰
만약 여러분의 애플리케이션에서 메모리가 정상적인지 확인하고 싶다면 가장 먼저 [Memory Usage 뷰]를 확인하라. [Memory Usage 뷰]를 통해 플래시 플레이어의 메모리 상황을 한눈에 파악할 수 있기 때문에 메모리 누수가 발생하는지 가장 쉽고 빠르게 알 수 있는 도구이다.
위 그림에서 빨간색 라인은 애플리케이션이 시작된 이후로 가장 많은 메모리를 사용했던 양을 나타내고, 파란색 라인은 현재 사용중인 메모리 양을 나타낸다. 위 그래프는 매우 정상적인 흐름의 톱니 패턴을 띄고 있다. 이 톱니 패턴은 앞서 설명 했듯이 애플리케이션이 플래시 플레이어가 OS로 부터 빌려온 메모리 보다 더 많은 메모리를 요구할 때 가비지 콜렉션이 수행된 흔적이다.
3-1-3. Live Objects 뷰
애플리케이션에 존재하는 객체들을 실시간으로 확인하고 싶을 때 [Live Objects 뷰]를 이용한다. 이 뷰는 프로파일링이 진행되는 동안 애플리케이션에서 실시간으로 생성되는 객체와 그 객체가 얼마나 많은 메모리를 사용하는지 보여준다. 또한, 그것들에 대한 누적된 수치도 함께 보여준다.
위 그림에서 알 수 있듯이 [Live Objects 뷰]에는 6개의 컬럼이 존재한다. 이 중 Class, Package 컬럼은 궂이 설명하지 않아도 어떤 컬럼인지 알 수 있을 것이다. 그 두개를 제외한 나머지 컬럼들에 대해 알아보자.
프로파일링이 시작된 이후로 생성된 객체의 누적 합계이다.
Instances
현재 메모리에 존재하는 객체의 수이다.
Cumulative Memory
프로파일링이 시작된 이후로 생성된 객체가 사용했던 메모리 양의 누적 합계이다.
Memory
현재 메모리에 존재하는 객체가 사용중인 메모리 양이다.
위 그림에서 처럼 Package 컬럼 값이 없거나 그 외에 생소한 값을 가진 항목들은 우리가 만든 애플리케이션 단계의 정보가 아니기 때문에 그다지 유용한 정보가 되지 못한다. 지금 우리에게 필요한 정보는 예제 코드에서 1초마다 100개씩 생성되고 있는 DataGrid 객체에 대한 정보일 것이다. 하지만 기본적으로 적용된 필터 때문에 우리에게 필요한 DataGrid 객체에 대한 정보를 볼 수 없기 때문에 필터를 수정해야 한다.
먼저 위 그림의 우측 상단의 빨간색 마크된 부분의 세번째 아이콘을 누르면 Filters 창이 나타나는데, Inclusion filters와 Exclustion filters를 다음과 같이 변경하자. 그러면 필터의 포함 항목과 제외 항목을 통해 적절하게 필터링된 객체들이 보여질 것이다.
DataGrid 클래스의 패키지인 mx.controls.* 패키지를 포함 시켰다면 다음과 같이 [Live Objects 뷰]에 DataGrid 하나만 나타날 것이다.
위 그림에서 처럼 DataGrid 객체가 지금까지 어마어마하게 생성 되었으며, 현재 메모리에서 존재하는 객체의 수를 비롯해 사용중인 메모리 양도 쉽게 알 수 있다. 이처럼 [Live Objects 뷰]를 통해 실시간으로 생성되거나 소멸되는 객체들 뿐만 아니라, 누적된 수치까지 확인함으로써 애플리케이션의 전반적인 메모리 상황을 알 수 있고, 이러한 정보는 여러분의 애플리케이션을 깊게 이해하는데 도움이 될 것이다.
※코드 변경하기
지금까지 프로파일링이 시작되었을 때 구성되는 기본적인 뷰들에 대해 알아봤다. 이제 예제 코드를 변경한 후 [Memory Usage 뷰]를 통해 메모리 누수를 확인할 것이다. 그리고 앞으로 나오게 될 [Memory Snapshot 뷰] 와 [Loitering Objects 뷰] 그리고 [Allocation Trace 뷰]를 이용해 어디에서 메모리 누수가 발생하는지 확인할 것이다. 변경된 코드는 다음과 같다.
코드를 변경했다면 프로파일링을 시작하고 [Memory Usage 뷰]를 관찰해 보자. 다음 그림처럼 메모리가 계속 상승하는 그래프를 볼 수 있을 것이다. 이렇게 메모리가 계속 상승하는 이유는 가비지 콜렉션이 수행될 때 메모리에서 제거 되어야할 객체들이 어디선가 계속 참조되고 있기 때문이다.
변경된 예제코드에서 직접 메모리 누수 부분을 찾을 수 있지만 우리는 그 사실을 모른다고 가정하고 메모리 프로파일링을 통해 어디에서 메모리 누수가 발생하는데 찾아볼 것이다.
3-1-4. Memory Snapshot 뷰
여러분이 과거 특정한 시점에 저장했던 메모리 상황을 알고 싶을 때 [Memory Snapshot 뷰]를 이용한다. 이 뷰는 특정한 시점의 메모리 상황을 보여 주는 것이기 때문에 [Live Objects 뷰] 처럼 메모리 상황을 업데이트 하지 않고, 오직 해당 시점의 메모리 상황만 보여주는 정적인 뷰이다. [Profile 뷰]에서 저장된 메모리 스냅샷 항목을 더블클릭 하면 이 뷰를 열 수 있다.
이제 위 예제코드의 문제점을 찾아보자. [Memory Usage 뷰] 에서 메모리 누수를 포착 했다면 가장 먼저 [Live Objects 뷰]를 통해서 어떤 객체가 메모리에서 계속 증가하는지 발견해야 한다. 여러분은 이미 [Live Objects 뷰]에 대한 사용법을 알고 있고, 직접 확인해보면 DataGrid 객체가 계속 증가하는 것을 확인할 수 있을 것이다. 확인 했다면 Take Memory Snapshot 버튼으로 메모리 스냅샷을 만들고, 추가된 메모리 스냅샷을 더블클릭 하여 [Memory Snapshot 뷰]를 열어보자. [Memory Snapshot 뷰]를 열었다면 다음 그림처럼 DataGrid 객체만 나오도록 앞서 배운 필터를 설정해보자.
[Memory Snapshot 뷰]에서 제공하는 컬럼들은 앞서 배운 [Live Objects 뷰]에 이미 있는 것들이다. 우리는 현재까지 [Memory Usage 뷰]를 통해 메모리 누수를 발견하고 [Live Objects 뷰]를 통해 DataGrid 가 계속 생성되는 것을 확인했다. 이제는 그 문제의 DataGrid 객체가 과연 어디에서 생성되는지 알아야할 때 이다. 위 그림에서 알 수 있듯이 [Memory Snapshot 뷰] 자체만으로는 DataGrid가 어디서 생성되는지 알 수가 없다. 하지만 이 뷰에서 특정 로우를 더블클릭 하거나, 특정 로우를 선택한 상태에서 우측상단 빨간색 박스 부분의 첫번째 아이콘을 누르면 객체가 어디에서 생성되었는지 알려주는 [Object References 뷰]를 열 수 있다. 자세한 내용은 바로 다음 3-1-5. Object References 뷰 파트에서 다룬다.
3-1-5. Object References 뷰
메모리 프로파일링을 하는 동안 객체가 생성된 위치를 확인 하고자 할 때 [Object References 뷰]를 이용한다. 이 뷰는 객체를 생성시킨 메소드를 보여주며 해당 메소드의 소스코드 위치까지 알려준다.
우리는 이 뷰를 [Memory Snapshot]뷰를 통해 연다는 것을 바로 이전 파트에서 배웠다. 이제 문제의 DataGrid 객체가 어디에서 생성되는지 확인하기 위해 이 뷰를 열어보자.
이 뷰는 객체의 리스트를 보여주는 [Instance] 영역과 해당 객체가 생성된 위치를 보여주는 [Allocation Trace] 영역으로 나뉘어져 있다. 위 그림에서 알 수 있듯이 좌측 [Instance] 영역에서 무수히 많이 생성된 DataGrid 객체를 선택하면 우측 [Allocation Trace]에서 그 객체를 생성한 메소드와 소스 위치까지 파악할 수 있다. 이러한 정보를 이용해 잘못된 애플리케이션을 즉시 수정할 수 있을 것이다.
3-1-6. Loitering Objects 뷰
과거 특정한 시점에 메모리 스냅샷 두개를 저장했고 그 두 시점 사이에서 생성 되었던 객체들을 확인하고 싶을 때 [Loitering Objects 뷰]를 이용한다. 이 뷰는 두 시점 사이에서 생성된 객체와, 그 객체가 사용했던 메모리의 총 합계를 보여준다.
이제 이 뷰를 활용하기 위해 프로파일링을 다시 시작하고, 시작 하자마자 메모리 스냅샷을 저장하고 약 10초뒤 다시 한번 메모리 스냅샷을 저장 해보자. 그리고 [Profile 뷰]에서 두 개의 메모리 스냅샷을 선택하고 Find Loitering Objects 버튼을 누르면 다음 그림처럼 [Loitering Objects 뷰]가 나타날 것이다.
위 그림에서 짧은 시간에 DataGrid 객체가 900개나 생성되고 전체 메모리에서 62%에 해당하는 메모리를 사용하고 있음을 알 수 있다. 이 처럼 이 뷰는 메모리 누수를 발견 하는데 유용하며 [Memory Sanapshot 뷰]와 마찬가지로 특정 객체를 더블클릭 하거나 우측 상단의 빨간색으로 마킹된 아이콘을 누르면 해당 객체가 생성된 위치를 보여주는 [Object References 뷰]를 열 수 있다.
3-1-7. Allocation Trace 뷰
[Allocation Trace 뷰]는 바로 이전 파트에서 배웠던 [Loitering Objects 뷰]와 비슷한데 특정한 두 시점 사이의 메모리 상황을 분석할 때, 호출된 메소드에 대한 정보를 알고 싶을 때 이용한다. 앞서 말했듯이 [Loitering Objects 뷰]가 생성 되었던 객체를 보여줬다면, 이 뷰는 호출된 메소드와 그 메소드가 호출되는 동안 생성한 객체의 수를 비롯하여, 사용했던 메모리 양을 보여준다.
이제 [Profile 뷰]에서 3-1-6 Loitering Object 뷰 파트에서 저장했던 두 개의 메모리 스냅샷을 선택하고, View Allocation Trace 버튼을 누르면 다음 그림처럼 [Allocation Trace 뷰]가 나타날 것이다.
위 그림과 같이 [Allocation Trace 뷰]에는 6개의 컬럼이 존재한다. 이 중 Method, Package 컬럼은 궂이 설명하지 않아도 어떤 컬럼인지 알 수 있을 것이다. 그 두개를 제외한 나머지 컬럼들에 대해 알아보자.
두 시점 사이에서 이 메소드에 의해서 호출된 메소드와 이 메소드 자체에서 생성된 객체의 수이다.
Self Instances
두 시점 사이에서 이 메소드 자체에서 생성된 객체의 수이다.
Cumulative Memory
두 시점 사이에서 이 메소드에 의해서 호출된 메소드와 이 메소드 자체에서 생성된 객체가 사용한 메모리 양이다.
Self Memory
두 시점 사이에서 이 메소드 자체에서 생성된 객체가 사용한 메모리 양이다.
과거 특정한 두 시점 사이에서 호출 되었던 메소드에 대한 메모리 통계정보를 알고 싶을 때 [Object Statistics 뷰]를 이용한다. 이 뷰는 해당 메소드가 수행 되는 동안 생성한 객체가 무엇인지, 그 객체가 얼마나 많은 메모리를 사용했는지 보여준다.
이 뷰는 바로 이전 파트에서 [Allocation Trace 뷰]를 통해 연다는 것을 배웠다. 이제 특정 메소드의 통계 정보를 보기 위해 이 뷰를 열어보자.
위 그림처럼
3-2. 퍼포먼스 프로파일링
퍼포먼스 프로파일링은 애플리케이션에서 응답이 느린 메소드를 찾아내거나, 성능이 향상될 수 있는 메소드를 찾아 내고자 할 때 이용한다. 퍼포먼스 프로파일링은 이 두가지 타입의 메소드를 최적화 하기 위한 리팩토링 정보를 제공하여 애플리케이션의 전체적인 성능을 향상 시킬 수 있다.
우리는 잘못된 예제코드를 이용하여 수행시간 가장 긴 메소드와 빈번히 호출되는 메소드를 찾아낼 것이다. 예제코드는 다음과 같다.
위 예제코드를 다 적었다면 프로파일링을 시작해보자. 이번에는 퍼포먼스 프로파일링만 하기 때문에 실행 옵션을 묻는 창에서 가장 아래 있는 Enable performance profiling 항목에만 체크를 한다. 그리고 나서 애플리케이션이 시작되면 화면에 보이는 버튼을 10회 누른다. 플렉스빌더는 프로파일링이 시작된 이후로 성능 데이터를 수집 하는데, 성능 데이터는 메소드 호출시간과 횟수이다. 따라서 여러분이 버튼을 10회 눌렀다면 버튼에 의해 실행된 메소드 정보가 수집 되었을 것이다. 이제 플렉스빌더로 돌아가서 [Profile 뷰]에 있는
퍼포먼스 데이터가 저장되면 위 그림처럼 [Profile 뷰]에 목록으로 추가된다. 또한 하나 이상의 퍼포먼스 데이터를 저장할 수 있으며, 플렉스빌더가 수집한 퍼포먼스 데이터를 초기화 하고 싶다면
3-2-1. Performance Profile 뷰
[Performance Profile 뷰]는 퍼포먼스 프로파일링을 하는 동안 사용하는 가장 중요한 뷰 이다. 이 뷰는 메소드 호출에 관련된 통계를 보여주며, 이러한 정보는 애플리케이션의 병목현상을 개선하거나 수행속도를 높이는 정보로 이용될 수 있다.
[Profile 뷰]에서 저장된 퍼포먼스 데이터 항목을 더블클릭 하면 다음 그림처럼 [Performance Profile 뷰]가 열릴 것이다.
위 그림과 같이 [Performance Profile 뷰]에는 6개의 컬럼이 존재한다. 이 중 Method, Package 컬럼은 궂이 설명하지 않아도 어떤 컬럼인지 알 수 있을 것이다. 그 두개를 제외한 나머지 컬럼들에 대해 알아보자.
퍼포먼스 데이터가 저장된 시점까지 메소드가 호출된 횟수이다.
Cumulative Time
이 메소드에 의해서 호출된 메소드들의 총 수행 시간이다.
Self Time
이 메소드 자체의 수행 시간이다.
AVG. Cumulative Time
이 메소드에 의해서 호출된 메소드들의 평균 수행 시간이다.
AVG. Self Time
이 메소드 자체의 평균 수행 시간이다.
또한 이 뷰에서 특정 메소드 항목을 더블클릭 하거나 위 그림의 우측상단 빨간색 마크된 아이콘을 누르면 해당 메소드의 좀 더 진보된 정보를 볼 수 있는 [Method Statistics 뷰]를 열 수 있다. 다음 파트에서 [Method Statistics 뷰]에 대해 자세히 알아 보겠다.
퍼포먼스 프로파일링 하는동안 호출된 메소드에 대한 성능 통계정보를 알고 싶을 때 [Method Statistics 뷰]를 이용한다. 이 뷰는
이제
<목차>
1. 디버깅이란?
2. 디버깅의 종류
2-1. Trace()
2-2. <mx:TraceTarget />
2-3. Flex Debugging perspective
2-3-1. Debug view
2-3-2. Variables view
2-3-3. Expressions view
2-3-4. Breakpoints view
2-4.
1. 디버깅이란?
프로그래머가 의도하지 않은 소프트웨어의 오작동을 버그라고 한다.
디버깅은 이러한 버그를 찾아내고 수정하는 것을 말하며,
소프트웨어의 품질을 향상 시키기위해 반드시 거쳐야 할 과정이다.
이 글에서 여러분은 Flex Builder에서 효과적으로 디버깅 하는 방법을 배울 것이며,
더 나아가 개발 습관에도 좋은 영향을 줄 것이다.
2. 디버깅의 종류
Flex에서 할 수 있는 디버깅은 크게 3가지가 있다.
- <mx:TraceTarget />
- Flex Debugging perspective
그럼으로써 프로그램이 의도한대로 동작하는지 알게 될 것이다.
그리고 이 중에서 Trace()와 <mx:TraceTarget /> 태그는 디버깅 보다는 로깅에 가깝다.
하지만 "특정한 시점에 객체가 가지고 있는 값" 을 확인하는 용도로써 훌륭한 디버깅 도구가 될 수 있다.
그럼 이제부터 이 3가지 종류에 대해 알아보도록 하겠다.
2-1. Trace()
프로그램의 특정 기능이 수행되는 동안 객체들이 의도한 값을 가지고 있는지 확인하고 싶을때
이 함수를 이용한다. 이 함수는 객체의 값을 콘솔에 출력해주는 역할을 한다.
프로그램의 흐름속에 Trace() 함수를 이용해 값을 확인 함으로써 프로그램이 정상적으로 작동 되는지
확인할 수 있을 것이다.
간단한 예제를 통해 사용법을 알아 보겠다.
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"
creationComplete="creationComp()">
<mx:Script>
<![CDATA[
import mx.utils.ObjectUtil;
private function creationComp():void {
var obj:Object = new Object();
obj.a = "a";
obj.b = "b";
trace(ObjectUtil.toString(obj));
}
]]>
</mx:Script>
</mx:Application>
위 코드를 디버그 모드로 실행(F11 키 또는 벌레모양 아이콘
a = "a"
b = "b"
위 예제에서 사용한 ObjectUtil 클래스는 Trace() 함수와 함께 사용하면 매우 유용하다.
ObjectUtil.toString() 메서드는 객체가 가지고 있는 프로퍼티와 값을 출력결과 처럼 포맷해서 리턴해준다.
Flex 애플리케이션을 개발 하는데 있어 어려운 부분 중 하나는 서버와 통신하는 경우이다.
어떤 원인에 의해 서버로부터 기대했던 데이터를 받지 못할 경우 원인을 찾아내기가 힘들다.
이런 경우 <mx:TraceTarget /> 태그를 이용하면 원인을 쉽게 파악할 수 있다.
이 태그는 서버와 통신을 할 때 통신상태 및 전송되는 데이터를 콘솔에 출력해주는 역할을 한다.
단지 태그를 MXML 문서에 추가하기만 하면 콘솔에서 그 내용을 확인할 수 있다.
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml">
<mx:TraceTarget />
<mx:HTTPService id="http"
url="http://www.naver.com"
resultFormat="text" />
<mx:Button label="click me"
click="http.send()" />
</mx:Application>
mx.messaging.Channel 'direct_http_channel' channel endpoint set to http:
mx.messaging.Producer '1D8D6911-2BBB-B851-5B2B-A2BA93C8F10A' producer sending message '54738616-39F6-E6DA-940C-A2BA98DFACB2'
mx.messaging.Channel 'direct_http_channel' channel sending message:
(mx.messaging.messages::HTTPRequestMessage)#0
body = (Object)#1
clientId = (null)
contentType = "application/x-www-form-urlencoded"
destination = "DefaultHTTP"
headers = (Object)#2
httpHeaders = (Object)#3
messageId = "54738616-39F6-E6DA-940C-A2BA98DFACB2"
method = "GET"
recordHeaders = false
timestamp = 0
timeToLive = 0
url = "http://www.naver.com"
mx.messaging.Producer '1D8D6911-2BBB-B851-5B2B-A2BA93C8F10A' producer connected.
mx.messaging.Producer '1D8D6911-2BBB-B851-5B2B-A2BA93C8F10A' producer acknowledge of '54738616-39F6-E6DA-940C-A2BA98DFACB2'.
mx.rpc.http.HTTPService Decoding HTTPService response
mx.rpc.http.HTTPService Processing HTTPService response message:
(mx.messaging.messages::AcknowledgeMessage)#0
body = "결과 값 생략"
clientId = "DirectHTTPChannel0"
correlationId = "54738616-39F6-E6DA-940C-A2BA98DFACB2"
destination = ""
headers = (Object)#1
DSStatusCode = 200
messageId = "EC0ED47D-ABD2-8671-79ED-A2BA9950521B"
timestamp = 0
timeToLive = 0
위 결과에서 알 수 있듯이 전송상태 및 전송 데이터("결과 값 생략" 부분)를 쉽게 확인할 수 있다.
또한 이 태그는 디버깅 보다는 로깅과 매우 밀접한 연관이 있다.
하지만 이 글에서는 오직 디버깅의 용도로만 사용한 것이며 로깅에 관한 부분은 다루지 않는다.
2-3. Flex Debugging perspective
Flex Builder(Eclipse Platform, 이하 빌더) 에서 지원하는 디버깅으로써 이 글의 핵심이라고 할 수 있다.
개발자가 원하는 "특정한 시점"에 객체들의 상세한 값을 확인하고자 할 때 이 기능을 이용한다.
빌더에서 지원하는 디버깅 기능을 이용하면 애플리케이션을 일시 중지하고 중지된 상태의 시점에서
객체들의 상태를 확인할 수 있을 뿐만 아니라 한줄한줄 코드 실행을 제어할 수 있다.
그럼 실습을 통해 자세하게 알아보겠다.
먼저 소스뷰에서 특정 라인번호를 더블클릭 하면 다음 그림처럼 빨간색 박스친 부분의 점이 표시될 것이다.
그 점이 바로 "특정한 시점" 이며 애플리케이션이 중지되고 객체의 상태를 확인할 수 있는 지점이다.
이제 애플리케이션을 디버그 모드
애플리케이션이 시작되면 초기화 과정(creationComplete 이벤트)에서 브레이크 포인트 지점의 코드가
수행된다.
브레이크 포인트 지점의 코드가 수행될 때 Flex Builder는 자동으로 디버깅 모드로 전환되고
애플리케이션은 정지상태가 된다.
디버깅 모드로 전환된 모습은 다음 그림과 같다.
①번 영역은 코드 실행을 제어하거나 세부적인 디버깅을 컨트롤할 때 사용하는 Debug view로 구성되어 있다.
②번 영역은 객체의 상세한 정보를 볼 수 있는 Variables view,
브레이크 포인트의 목록을 확인할 수 있는 Breakpoints view,
특정 변수를 주시하고 싶을때 사용하는 Expressions view 로 구성되어 있다.
그럼 이제부터 각각의 view에 대해 자세히 알아보겠다.
2-3-1. Debug view
Debug view에는 다음과 같이 디버깅을 제어할 수 있는 아이콘이 있다.
브레이크 포인트에 의해 애플리케이션이 정지상태가 되었을 때, 디버깅을 멈추고 정지된 애플리케이션을 이어서 가동시킨다. 만약 또 다른 브레이크 포인트를 만난다면 다시 디버깅 모드로 진입된다.
애플리케이션과 함께 디버깅을 일시 중지한다.
애플리케이션과 함께 디버깅을 종료한다.
Flex Builder의 디버거와 연결을 끊는다.
연결이 끊기면 브레이크 포인트를 만나더라도 디버깅 모드로 진입할 수 없다.
현재 라인이 함수라면 함수 내부의 첫번째 라인으로 이동한다.
만약 현재 라인이 함수가 아니라면 현재 라인을 실행하고 다음 라인으로 이동한다.
현재 라인을 실행하고 다음 라인으로 이동한다.
현재 실행중인 함수를 호출한 부모함수로 이동한다.
예를들면, Step Into에서 함수 내부로 이동했을 경우 다시 밖으로 빠져 나오게 된다.
다음 그림은 이번 장의 예제코드를 디버그 모드로 실행했을 때의 Variables view의 모습이다.
위 그림에서 this는 현재 브레이크 포인트가 위치한 클래스(또는 MXML문서) 이다.
트리의 노드 앞에 있는 아이콘은 해당 노드의 가시성을 나타낸다.
따라서 this 하위에 있는 a는 private, b는 protected, c는 public 가시성을 가지는 변수이다.
this의 트리를 계속 열면 수백 개의 변수를 확인할 수 있으며, Ctrl+F 키나 마우스 우클릭후 Find Variable 메뉴를 통해 다음 그림처럼 특정 변수를 검색할 수 있다.
위 그림처럼 변수명을 입력하면 실시간으로 원하는 변수를 찾을 수 있다.
Expressions view 는 특정 변수를 주시할 수 있고 변수들의 값을 원하는 형태의 결과로 볼 수 있는 기능을 제공한다.
먼저 예제 코드를 다음과 같이 변경하고 브레이크 포인트를 함수 종료부분에 지정하자.
코드를 변경 했다면 디버그 모드
애플리케이션이 브레이크 포인트를 만나 디버깅 모드로 진입하면 Variables view에서 주시하고자 하는 변수에
마우스 우클릭후 Create Watch Expression 메뉴를 클릭한다.
이제 Expressions view를 확인해보면 다음 그림처럼 선택한 변수가 추가되어 있을 것이다.
이렇게 Expressions view에 변수를 추가해 놓으면 디버깅 하는 동안 Variables 창의 수많은 변수들 중 해당 변수를 찾을 필요 없이 바로 확인할 수 있다.
이번에는 Expressions view를 보다 효과적으로 사용하는 방법에 대해 알아보겠다.
먼저 Expressions
그리고 다음 그림처럼 변수들을 사칙연산으로 표현해보자.
OK 버튼을 누르고 Expressions
이처럼 Expressions
변수들의 상호작용에 대한 결과를 바로 알 수 있다.
다음 그림을 살펴보자.
이처럼 Breakpoints view에는 지정한 브레이크 포인트들의 리스트가 보여진다.
그리고 Delete키 또는 마우스 우클릭후 Remove메뉴를 통해 브레이크 포인트를 바로 제거할 수 있다.
2-4. flash.debugger.enterDebugger()
브레이크 포인트는 빌더에서 관리되기 때문에 브레이크 포인트 자체를 소스코드와 함께 공유할 수는 없다.
그러나 브레이크 포인트와 똑같은 기능을 수행하는 함수가 있다.
바로 flash.debugger.enterDebugger() 함수이다.
이 함수는 브레이크 포인트와 똑같은 기능을 수행하기 때문에 소스코드와 함께 디버깅 지점을 공유할 수 있다.
다음 그림을 보자.
디버깅 지점을 소스코드로 명시했기 때문에 Breakpoints view에 나타나지 않는다.
하지만 그 외에 브레이크 포인트와 다른 점은 없다.
앞으로는 이 글에서 소개한 디버깅 방법으로 소프트웨어 품질을 향상시키는데 노력하자.
마지막으로 디버깅은 개발이 끝난 이후에 하는 것이 아니라 개발과정의 일부분 이라는 것을 명심해야 한다.
출처 : http://jjaeko.tistory.com/103
[FLEX] Memory leak 문제
Flex Memory Leak 해결 방법
public class BarGaugeGrid extends ContentBox { //멤버 변수 private var _barCount:int = 5; private var _dataProvider:ArrayCollection; private var textField:Label; private var valueLabelField:Label; private var itemValues:Array = []; private var dataChanged:Boolean = false; public function BarGaugeGrid() { super(); layout = "vertical"; setStyle("verticalGap", 0); } ... public function destroy():void { _dataProvider = null; textField = null; valueLabelField = null; itemValues = null; } |
데이터 유형 설명
프리미티브 데이터 유형에는 Boolean, int, Null, Number, String, uint 및 void 등이 포함됩니다. 또한 ActionScript 기본 클래스에서 Object, Array, Date, Error, Function, RegExp, XML 및 XMLList 등의 복합 데이터 유형을 정의합니다.
Boolean 데이터 유형
Boolean 데이터 유형은 true와 false 두 가지 값으로 구성됩니다. 그 외 다른 값은 Boolean 유형의 변수에 사용할 수 없습니다. 선언되었지만 초기화되지 않은 부울 변수의 기본값은 false입니다.
int 데이터 유형
int 데이터 유형은 내부적으로 32비트의 정수로 저장되며
-2,147,483,648 (-231)부터 2,147,483,647(231 - 1)까지의 정수 집합(-2,147,483,648 및 2,147,483,647 포함)으로 구성됩니다. 이전 버전의 ActionScript에서는 정수와 부동 소수점 숫자에 모두 사용되는 Number 데이터 유형만 제공했습니다. ActionScript 3.0에서는 이제 32비트의 부호가 있거나 없는 정수에 대한 저수준의 시스템 유형에 액세스할 수 있습니다. 변수에서 부동 소수점 숫자를 사용하지 않는 경우 Number 데이터 유형 대신 int 데이터 유형을 사용하는 것이 보다 빠르고 효율적입니다.
최소 및 최대 int 값의 범위를 벗어나는 정수 값인 경우 Number 데이터 유형을 사용하여 양수와 음수 각각 9,007,199,254,740,992(53비트 정수 값) 사이의 값을 처리할 수 있습니다. int 데이터 유형의 변수에 대한 기본값은 0입니다.
Null 데이터 유형
Null 데이터 유형에는 null 값만 포함됩니다. 이는 Object 클래스를 포함하여 복합 데이터 유형을 정의하는 모든 클래스와 String 데이터 유형의 기본값입니다. Boolean, Number, int 및 uint 같은 기타 프리미티브 데이터 유형에는 null 값이 포함되어 있지 않습니다. Boolean, Number, int 또는 uint 유형의 변수에 null을 지정하려고 하면 Flash Player 및 Adobe AIR에서 null 값을 적절한 기본값으로 변환합니다. 이 데이터 유형은 유형 약어로 사용할 수 없습니다.
Number 데이터 유형
ActionScript 3.0에서 Number 데이터 유형은 정수, 부호 없는 정수, 부동 소수점 숫자 등을 나타낼 수 있습니다. 그러나 성능을 최대화하려면 32비트int 및 uint 유형이 저장할 수 있는 값보다 큰 정수 값 또는 부동 소수점 숫자에만 Number 데이터 유형을 사용해야 합니다. 부동 소수점 숫자를 저장하려면 숫자 안에 소수점을 넣습니다. 소수점을 생략하면 숫자가 정수로 저장됩니다.
Number 데이터 유형에서는 이진 부동 소수점 산술에 대한 IEEE 표준(IEEE-754)에 지정된 64비트 배정밀도 형식을 사용합니다. 이 표준에는 64비트를 사용하여 부동 소수점 숫자를 저장하는 방법이 규정되어 있습니다. 1비트는 숫자가 양수 또는 음수인지 지정하는 데 사용됩니다. 11비트는 2를 밑수로 하는 지수를 저장하는 데 사용됩니다. 남은 52비트는 지수에 따라 거듭제곱되는 숫자인 유효 숫자(또는 가수)를 저장하는 데 사용됩니다.
Number 데이터 유형에서는 지수를 저장하는 데 일부 비트를 사용하므로 모든 비트가 유효 숫자를 저장하는 데 사용되는 경우에 비해 매우 큰 부동 소수점 숫자를 저장할 수 있습니다. 예를 들어, Number 데이터 유형에서 유효 숫자를 저장하는 데 64비트를 모두 사용하는 경우, 저장할 수 있는 가장 큰 숫자는 265 - 1입니다. 11비트를 사용하여 지수를 저장하면 Number 데이터 유형에서 유효 숫자를 21023으로 거듭제곱할 수 있습니다.
Number 유형에서 나타낼 수 있는 최소 및 최대 값은 Number.MAX_VALUE 및 Number.MIN_VALUE라는 Number 클래스의 정적 속성에 저장됩니다.
Number.MAX_VALUE == 1.79769313486231e+308 Number.MIN_VALUE == 4.940656458412467e-324
Number 데이터 유형의 경우 숫자의 범위는 방대하지만 정밀도는 떨어집니다. Number 데이터 유형에서는 유효 숫자를 저장하는 데 52비트를 사용하므로 정확하게 나타내기 위해 52비트 이상이 필요한 숫자(예: 분수 1/3)는 근삿값만 표현할 수 있습니다. 응용 프로그램에서 절대 정밀도의 십진수가 필요한 경우 이진 부동 소수점 산술과 대립되는 십진 부동 소수점 산술을 구현하는 소프트웨어를 사용해야 합니다.
Number 데이터 유형을 사용하여 정수 값을 저장하면 유효 숫자의 52비트만 사용됩니다. Number 데이터 유형에서는 해당 52비트 및 특수 은폐 비트를 사용하여 -9,007,199,254,740,992(-253)부터 9,007,199,254,740,992(253)까지의 정수를 나타냅니다.
Flash Player 및 Adobe AIR에서는 NaN 값을 Number 유형의 변수에 대한 기본값으로 사용할 뿐만 아니라 숫자를 반환해야 하지만 이를 반환하지 않는 모든 연산의 결과로도 사용합니다. 예를 들어, 음수의 제곱근을 계산하려고 하면 결과는 NaN이 됩니다. 기타 특수 Number 값에 양의 무한대와 음의 무한대가 포함됩니다.
String 데이터 유형
String 데이터 유형은 16비트 문자열을 나타냅니다. 문자열은 내부적으로 UTF-16 포맷을 사용하여 유니코드 문자로 저장합니다. 문자열은 Java 프로그래밍 언어와 마찬가지로 변경할 수 없는 값입니다. String 값에 대한 작업은 해당 문자열의 새 인스턴스를 반환합니다. String 데이터 유형으로 선언되는 변수의 기본값은 null입니다. null 값과 빈 문자열("") 모두 문자가 없다는 것을 나타내지만 서로 다릅니다.
uint 데이터 유형
uint 데이터 유형은 내부적으로 32비트의 부호 없는 정수로 저장되며 0부터 4,294,967,295(232 - 1)까지의 정수 집합(0 및 4,294,967,295 포함)으로 구성됩니다. 음이 아닌 정수를 호출하는 특수한 경우에는 uint 데이터 유형을 사용합니다. 예를 들어 int 데이터 유형에는 색상 값 처리에 적합하지 않은 내부 부호 비트가 포함되어 있기 때문에 픽셀의 색상 값을 나타내려면 uint 데이터 유형을 사용해야 합니다. 최대 uint 값보다 큰 정수 값의 경우에는 53비트 정수 값을 처리할 수 있는 Number 데이터 유형을 사용합니다. uint 데이터 유형의 변수에 대한 기본값은 0입니다.
Void 데이터 유형
void 데이터 유형에는 undefined 값만 포함됩니다. 이전 버전의 ActionScript에서 Object 클래스의 인스턴스에 대한 기본값으로 undefined를 사용했습니다. ActionScript 3.0에서 Object 인스턴스에 대한 기본값은 null입니다. Object 클래스의 인스턴스에 undefined 값을 지정하려고 하면 Flash Player 또는 Adobe AIR에서 이 값을 null로 변환합니다. undefined 값은 유형이 지정되지 않은 변수에만 지정할 수 있습니다. 유형이 지정되지 않은 변수는 유형 약어가 없거나 유형 약어에 별표(*) 기호를 사용하는 변수입니다. void는 반환 유형 약어로만 사용할 수 있습니다.
Object 데이터 유형
Object 데이터 유형은 Object 클래스에 의해 정의됩니다. Object 클래스는 ActionScript에서 모든 클래스 정의에 대한 기본 클래스 역할을 합니다. ActionScript 3.0 버전의 Object 데이터 유형은 세 가지 면에서 이전 버전의 Object 데이터 유형과 다릅니다. 첫 번째, Object 데이터 유형은 더 이상 유형 약어가 없는 변수에 지정되는 기본 데이터 유형이 아닙니다. 두 번째, Object 데이터 유형에는 Object 인스턴스의 기본값이었던 undefined 값이 더 이상 포함되지 않습니다. 세 번째, ActionScript 3.0에서는 Object 클래스의 인스턴스에 대한 기본값이 null입니다.
이전 버전의 ActionScript에서 유형 약어가 없는 변수는 자동으로 Object 데이터 유형으로 지정되었습니다. ActionScript 3.0에는 유형이 지정되지 않은 변수라는 개념이 도입되면서 이전 버전과는 다르게 처리됩니다. 이제 유형 약어가 없는 변수는 유형이 지정되지 않은 변수로 간주됩니다. 변수의 유형을 지정하지 않으려는 자신의 의도를 코드를 읽는 사람에게 분명히 밝히려면 유형 약어로 새 별표(*) 기호를 사용할 수 있습니다. 이는 유형 약어를 생략하는 것과 같습니다. 다음 예제에서는 유형이 지정되지 않은 변수 x를 선언하는 동일한 두 명령문을 보여 줍니다.
var x var x:*
유형이 지정되지 않은 변수에만 undefined 값을 저장할 수 있습니다. 데이터 유형이 있는 변수에 undefined 값을 지정하려고 하면 Flash Player 또는 Adobe AIR에서 undefined 값을 해당 데이터 유형의 기본값으로 변환합니다. Object 데이터 유형의 인스턴스인 경우 기본값은 null이며, 이는 Object 인스턴스에 undefined를 지정하려고 하면 Flash Player 또는 Adobe AIR에서 undefined 값을 null로 변환한다는 것을 의미합니다.
Apache FlexJS es un nuevo framework creado dentro del proyecto Apache Flex. El Objetivo detrás de este esfuerzo es permitir a los desarrolladores crear aplicaciones escritas en MXML y ActionScript y desplegarlas en todo tipo de entornos, tanto con Flash Player, como sin este, con HTML/JS/CSS.
FlexJS puede generar aplicaciones HTML y funcionar en cualquier navegador sin Flash Player. También puede obtener aplicaciones SWF
FlexJS está basado en el concepto de “frameworks paralelos”, donde los bloques y piezas de construcción existirán tanto en AS com en JS. El compilador (basado en Falcon, la evolución de última generación del compilador de AS3 y MXML), es el encargado de traducir MXML y AS a JS y obtener salidas tanto en SWF como en JS. Es en este último caso donde eliminamos la necesidad del Flash Player pudiendo correr la aplicación en cualquier tipo de navegador.
El siguiente diagrama describe la arquitectura sobre la cual se basa el diseño de FlexJS:
Compatibilidad con el SDK actual
El SDK actual tiene mucho código fruto de su evolución a lo largo de los años (tengamos en cuenta que el framework tiene ya alrededor de 10 años). Por tanto la dirección que se ha tomado es hacer una base de código desde cero. Esto en la práctica significa que las aplicaciones actuales creadas con versiones del SDK actual no podrán ser compiladas con FlexJS. No obstante, la reescritura de estas aplicaciones tenderá a ser más sencilla en FlexJS dada la similitud que con otra tecnología.
De hecho, el nuevo framework tiene que funcionar bajo un subconjunto de funcionalidades ya que la tecnología de los navegadores y Javascript todavía no es equiparable a las capacidades de Flash Player. Es por tanto evidente, que para conseguir aplicaciones HTML/JS/CSS estás tienen que estar pensadas bajo las restricciones de este entorno.
Ventajas
A parte del acceso a dispositivos donde Flash Player nunca estará presente (principalmente los navegadores iOS), las ventajas para el desarrollado de aplicaciones son claras. Usar el conjunto de herramientas y lenguajes altamente tipados y con orientación a objetos que son fundamentales para todas aquellas aplicaciones de tamaño y funcionalidad medio alto. Se trata pues de recuperar muchas de las características perdidas cuando pasamos del mundo Flash/Flex a un mundo puramente Javascript.
Demo
El ejemplo inicial creado por Alex Harui pone de manifiesto la potencia que puede tener este nuevo framework si observamos las versiones flash y html. La captura siguiente ofrece una visión de cada una de las versiones compiladas con FlexJS (SWF y JS):
En el ejemplo se recuperan valores de bolsa de una fuente de datos remota y nos permite pedír el precio. La versión SWF se encuentra aquí y la versión HTML/JS aquí. El código fuente se puede ver seleccionando la opción View Source del menú conextual en la versión Flash.
Estado
FlexJS está en desarrollo por un grupo pequeño pero constante de desarrolladores de Apache Flex encabezado por Alex Harui (desarrollador del SDK desde los comienzos de Flex en Macromedia, actual PMC Chair del proyecto y uno de los principales soportes para el resto de usuarios de Flex por sus contribuciones en la lista de flexcoders). Los puntos acordados para adquirir el estado alpha son:
- Integración del compilador con Flash Builder
- Soporte para la creación de Aplicación y Vistas
- Soporte de propiedades CSS personalizadas
- Implementación inicial del componente DataGrid
- Implementación inicial del componente Chars
Algunos de estos puntos ya están conseguidos o cerca de ser completados por lo que es de esperar que la fecha de salida de la versión alpha del producto esté cercana.
Desde el proyecto se pide ayuda de la comunidad para avanzar de forma más rápida. El modelo de Apache se basa en voluntariado y por tanto el avance de FlexJS está directamente relacionado con el soporte de toda la comunidad.
Enlaces
Para leer más sobre FlexJS os proporciono los siguientes enlaces de forma que podáis investigar más sobre este prometedor framework:
Wiki sobre FlexJS en Apache Flex
Charla sobre FlexJS ofrecida por Alex Harui por Adobe Connect
Using FlexJS with Adobe Flash Builder
Apache Flex Jira
Lista de correo en Apache Flex (mucho tráfico, para leer sobre FlexJS filtrar por el prefijo “[FlexJS]“)
Conclusión
Hablaremos mucho de FlexJS en Made In Flex, ya que ofrece no solo un futuro prometedor para los desarrolladores Flex que no quieren perder la rapidez y solvencia de sus entornos y lenguajes, para volver a las incomodidades de la programación JS, si no que además aporta verdadera innovación en la conceptualización del propio framework.
Lo veremos con la capacidad de FlexJS para añadir distintos juegos de componentes (html, jquery, createjs,…), la reducción extrema del peso y tamaño de la aplicación generada -tanto en SWF como en JS- o los nuevos conceptos de Strands y Beads, que permiten promocionar la composición sobre la herencia, eliminando la existencia de god classes(como UIComponent con 15.000 lineas de código en el SDK actual).
De forma rápida, en FlexJS un Bead es una funcionalidad que se puede añadir o quitar de un componente como un verdadero plugin, de forma que podemos usarla cuando realmente los necesitemos en la más pura filosofía “pay-as-you-go” (ej: Añadir funcionalidades de touch en dispositivos móviles, mientras que en navegadores y escritorio tenemos funcionalidades de roll over).
Volvemos en breve para ver más temas sobre Apache FlexJS.
¡Comparte este artículo si te ha parecido interesante!
Index of /asdoc/mx/charts
Name Last modified Size DescriptionApache/2.4.9 (Unix) OpenSSL/1.0.1h Server at flex.apache.org Port 80
Parent Directory - AreaChart.html 2014-05-03 13:10 304K AxisLabel.html 2014-05-03 13:10 13K AxisRenderer.html 2014-05-03 13:10 296K BarChart.html 2014-05-03 13:10 307K BubbleChart.html 2014-05-03 13:10 305K CandlestickChart.html 2014-05-03 13:10 304K CategoryAxis.html 2014-05-03 13:10 60K ChartItem.html 2014-05-03 13:10 28K ColumnChart.html 2014-05-03 13:10 313K DateTimeAxis.html 2014-05-03 13:10 101K GridLines.html 2014-05-03 13:10 252K HLOCChart.html 2014-05-03 13:10 303K HitData.html 2014-05-03 13:10 24K Legend.html 2014-05-03 13:21 418K LegendItem.html 2014-05-03 13:21 251K LineChart.html 2014-05-03 13:21 302K LinearAxis.html 2014-05-03 13:21 54K LogAxis.html 2014-05-03 13:21 51K PieChart.html 2014-05-03 13:21 294K PlotChart.html 2014-05-03 13:21 301K chartClasses/ 2014-05-03 13:35 - class-list.html 2014-05-03 13:10 2.0K effects/ 2014-05-03 13:35 - events/ 2014-05-03 13:35 - package-detail.html 2014-05-03 13:21 8.7K renderers/ 2014-05-03 13:35 - series/ 2014-05-03 13:35 - styles/ 2014-05-03 13:35 -
There are a lot of Flex Explorer applications available on line. These explorers can really help when you want to refresh your knowledge on the Flex capabilities or when you plan to fine tune specific styles or effects.
Whether you’re trying to design a good looking button or data grid, configuring a fancy glow effect, implementing a complex regular expression, configuring a BlazeDS channel or just searching for inspiration; there is probably a Flex Explorer for you.
Here is my own list of explorers, that I also gathered in my Flex Tool Box page for quick and easy reference.
Flex Component Explorer
The Flex Component Explorer lets you browse through the huge list of available Flex core components. For each of them, a small sample application (corresponding code is available) shows how the component is rendered and behaves. Even if you can’t change the component configuration, this remains a useful tool when you’d like to learn more of a component that you never used so far.
Tour de Flex Explorer
The Tour De Flex Explorer is available on line but can also be installed as an AIR application. It extends the Flex 3 Component Explorer by adding Flex 4 Spark components description, coding techniques examples, Flex Data Access information, Flex frameworks introduction and much more.
Flex Chart Sampler
The Flex Chart Sampler (from Ely Greenfield blog) lets you interactively discover the Flex Charting components.
Flex Style Explorer
The Flex Style Explorer is probably the one I use the most. You can use it to style interactively any component, see how looks the result and export the result as a CSS file that you can use in your projects.
Flex Enhanced Button Skin Explorer
The Flex Enhanced Button Skin Explorer(from Daniel Wabyick blog) is an explorer dedicated to the EnhancedButtonSkin available in FlexLib.
Adobe® Kuler™ Color Themes Explorer
The Adobe® Kuler™ Color Themes Explorerallows experimenting quickly with color variations and browse color themes from the Kuler community (can be downloaded as an AIR application).
Flex Filter Explorer
The Flex Filter Explorer (from Joe Johnston blog) lets you define your flex filter interactively and generates the corresponding MXML to include in your projects.
Flex Easing Function Explorer
The Flex Easing Function Explorer (fromJames Ward blog) allows experimenting with ease functions that allow applying non-linear motion to effects.
Flex Distortion Effect Explorer
The Flex Distortion Effect Explorer (from Alex Uhlmann blog) demonstrates fancy distortion effects such as Flip, Pop, Cube rotate, Door and much more.
Flex Reflection Explorer
The Flex Reflection Explorer (from Wietse Veenstra blog) demonstrates beautiful reflection effects using the ReflectionManager provided class.
Efflex Explorer
The Efflex Explorer (from Efflex.org) demonstrates the effects available when using the open source Efflex library.
Flex Regular Expression Explorer
The Flex Regular Expression Explorer (fromRyan Swanson blog) lets you define your regular expressions from the explorer and check immediately whether they work or not by providing test data and look at the result. This explorer also provides a very good on line help that should allow you not be afraid by regular expressions anymore.
BlazeDS Channel Designer
The BlazeDSChannel Designer isn’t really an explorer but I thought it would be useful to mention it here. It provides an easy and interactive way to configure your BlazeDS channels and outputs the services-config.xml corresponding to your options.
출처 : http://hapina.tistory.com/10
플렉스에서 하나의 어플리케이션에 모듈을 여러개 로딩하여 사이즈를 줄이는 방법으로 프로젝트가 많이 진행됩니다.
그러나 가끔 마우스 다운 이벤트 및 드래깅 시 다음과 같은 에러가 발생합니다.
TypeError: Error #1034: 유형 강제 변환에 실패했습니다. mx.managers::DragManagerImpl@a23f6f1을(를) mx.managers.IDragManager(으)로 변환할 수 없습니다.
또는 다음과 같은 에러...
ReferenceError: Error #1065: 변수 mx.managers::DragManager이(가) 정의되어 있지 않습니다.
데이터그리드를 넣은 모듈을 로드하여 폴더 열기/닫기 할 때 처음 이 에러를 만났습니다.
이 문제의 원인과 해결책에 대해 알아봅시다.
원인 및 해결책.
플렉스 프레임웤 상 DragManager 는 싱글톤으로 하나의 인스턴스만 존재해야 합니다.
App 에는 Manager DB 가 존재하여 모든 Manager 를 관리합니다.(모든 메니저의 인스턴스가 아닌 클래스)
처음 모듈을 로딩할 때 DragManager 가 해당 모듈에서 인스턴화되면..
두번째 모듈에서는 해당 인스턴스를 알아낼 방법이 없어집니다. 즉 혼란이 생긴거죠.
모듈의 Applicatoin shared code 때문에 생기는 일입니다.
따라서 처음 드래그메니저의 인스턴스를 App 에서 생성되도록하여 모든 모듈이 드래그메니저의 인스턴스를 App 에서 참조하도록 설정합니다.
해결책으로 이를 방지하기 위해 어플리케이션에서 다음과 같이 DragManager 를 import 하세요.
이는 아마도 버그일 수 있습니다. 최초 App 에서 드래그메니저의 인스턴스를 생성하면 되는데 그렇지 않고 있기 때문이죠.
<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"
<mx:Script>
<![CDATA[
import mx.managers.DragManager;
private var dummy:DragManager;
....
...
...
]]>
</mx:Script>
이 처럼 App 에서 DragManager 의 인스턴스를 생성하게 하여 클래스를 내포시킵니다.
그럼 굿럭!~
출처 : http://blog.jidolstar.com/666
Flash 애플리케이션을 제작할때 항상 고민되는 것은 메모리와 속도일 것이다. 이 문서는 이러한 고민을 했던 분들을 위해 본인의 경험을 반영하여 작성한 것이다. 주의할 것은 이 문서에서 다루는 Object Pool은 메모리, 속도 개선을 위한 하나의 방법일 뿐이지 전부는 아니다라는 것이다. Object Pool이 어떻게 당신의 애플리케이션의 속도와 메모리를 개선시켜줄 것인가 전달하는 것이 문서의 목표이다.
목차
- 객체 관리에 대한 질문!
- new 연산자는 비싸다!
- Object Pool이란?
- Object Pool을 도입할 때 고려사항
- Object Pool을 사용해보자.
- Object Pool과 new 연산자 사용 비교
- 메모리 사용의 폭이 급격한 애플리케이션은 좋지 못하다.
- 재사용할 수 있는 클래스 제작
- 정리하며
- 참고글
1. 객체 관리에 대한 질문!
제작된 애플리케이션에 필요한 최소한의 메모리에 대해서 생각해 본적이 있는가? 하나의 애플리케이션에 들어가는 최소 자원(resource)은 얼마나 될까? 그 자원은 얼마나 자주 사용하고 버리는 것일까?
슈팅 게임을 보면 동일한 적군전투기가 많이 나온다. 그 중에 적군전투기1이 있다고 하자. 적군전투기1이 한 화면에 5개가 나왔다. 아군전투기에 의해 죽든 화면에서 사라지든 어쨌든지 언젠간 화면에서 사라지고 쓸모없게 된다. 좀 있다가 또 적군전투기1이 3개가 나온다. 또 반복해서 사라진다. 당신이 이 슈팅 게임을 만든다면 적군전투기1 관리를 어떻게 할 것인가? 즉, 화면에 나타날때 new로 생성하고 사라지면 delete로 메모리에게 해지요청할 것인가? 아니면 어느 특정 영역에 화면에 나타날 전투기1을 미리 5개에서 10개 생성해놓고 필요할 때마다 재사용할 것인가?
2. new 연산자는 비싸다!
new는 클래스를 통해 객체를 생성하는 연산자이다. 이는 비교적 비용이 비싼편이다. 비용이 비싸다는 말을 구체적으로 설명하자면 new를 통해 객체를 생성하여 메모리에 올리고 다 사용했다고 판단될때 delete이나 null등을 이용해 객체참조를 제거하여 가비지 컬렉터에 맡기게 되며 결과적으로 가비지 컬렉터는 이 객체를 어느 특정시점에 제거하는 일련의 객체 생성과 삭제까지 동작이 꽤 복잡하여 메모리, CPU연산등의 자원소비가 크다라는 것을 의미한다. 특히나 클래스의 크기가 크고 복잡하다면 그 비용은 더욱 갑절로 들게 된다. new 연산 사용은 이처럼 비싼 비용을 지불해야 하기 때문에 할 수 있다면 최소한으로 사용하도록 하는 것이 속도와 메모리 관리에 도움을 준다.
앞서 설명한 슈팅 게임을 보자면 적군전투기1은 수시로 보였다가 안보였다가 한다. 이 경우 new, delete를 반복한다면 경우에 따라서 엄청난(?) 비싼 비용을 지불하게 된다.
다른 예를 들어보겠다. 본인은 얼마전에 스타플(http://starpl.com)이라는 사이트에서 제공하는 타임라인 애플리케이션을 Flash로 개발했다. 타임라인은 사용자의 기록, 앨범등을 시간순으로 보여주게 되며 좌우측으로 이동해서 내 인생의 기록을 보는듯한 느낌을 주도록 만들었다. 이때 화면에 보여지는 사용자의 기록들이 수시로 보여졌다가 사라진다. 이때 사용된 기록을 보여주는 시각객체(DisplayObject)를 new와 delete를 반복해서 생성 및 삭제한다면 너무 비싼 비용을 들이는 것과 같다.
스타플의 타임라인
참고글 : 스타플 타임라인 업그레이드 및 앨범 기능 추가 소식
앞의 2가지 예에서 언급했듯이 자주 보여지고 사라지는 객체를 필요할 때마다 new연산자를 사용하는 것보다는 어느 특정 영역에 미리 생성해놓고 재사용을 반복하는 구조를 도입한다면 메모리와 CPU연산에 있어서 큰 비용절감을 기대할 수 있게 된다.
3. Object Pool이란?
Object Pool(객체 풀)은 객체를 미리 만들어놓고 담아놓는 메모리 영역이다. 개발자는 이 Object Pool에 미리 만들어진 객체를 사용하고 다 사용한 뒤에는 다시 반환시킴으로서 재사용(또는 대여)을 하도록 한다. Object Pool을 사용하면 처음 객체를 생성할때와 더 필요한 객체가 있는 경우를 제외하고 new 연산을 최소화 할 수 있다.
4. Object Pool을 도입할 때 고려사항
Object Pool은 미리 객체를 생성하고 지속적으로 재사용하기 위해 사용한다. 이때 미리 객체를 생성한다는 것은 초반 애플리케이션 동작 퍼포먼스에 영향을 미칠 수 있다.
Object Pool을 사용했을때 다음과 같은 장점을 얻을 수 있다.
1. new 연산자를 자주 사용해야하는 경우에 도입하면 애플리케이션 속도 향상을 기대할 수 있다.
2. 가비지 컬렉터에 의존하지 않는 메모리 관리를 꾀할 수 있다.
하지만 일반적으로 10개가 필요한데 미리 100개를 만들어 항상 10% 미만 정도의 재사용률을 보인다면 그것은 메모리 낭비이다. 그러므로 다음과 같은 조건에서 Object Pool을 사용하고 관리하는 것이 좋겠다.
1. 비교적 큰 용량의 객체를 자주 사용되거나 반환해야하는 경우에 사용한다.
2. 미리 생성할 객체를 적당하게 정하도록 한다.
반대로 위 경우가 아니라면 쓸데없이 Object Pool을 사용하지 말자.
5. Object Pool을 사용해보자.
폴리고널 lab에서 간단한 Object Pool을 공개했다. 아래 링크에서 다운로드 받을 수 있다.
사용 방법은 매우 간단하다. ObjectPool 클래스는 아래 코드처럼 특정 클래스를 할당(allocate)하면 된다.
var isDynamic:Boolean = true;
var size:int = 100;
var pool:ObjectPool = new ObjectPool(isDynamic);
pool.allocate(MyClass, size);
isDynamic 플래그는 Object Pool내의 객체가 비어있는가 체크하기 위해 사용한다. 즉, true이면 Pool은 자동적으로 정해진 크기(size)를 넘어서더라도 Pool의 크기를 확장하여 새로운 객체를 줄 수 있으나 false이면 정해진 크기만큼 객체를 이미 준 경우에는 Pool이 비게 되므로 Error를 발생시킨다.
크기(size)를 100으로 지정했기 때문에 미리 Object Pool에 100개의 MyClass의 객체를 생성해 두도록 한다.
이제부터 MyClass는 아래와 같은 방식으로 사용하면 되겠다.
myObjectArray[i] = new MyClass();
//위 코드 대신 아래를 사용한다.
myObjectArray[i] = pool.instance;
다 사용한 MyClass객체는 Object Pool에 반환해줘야 한다. 이때 가비지 컬렉터의 기능을 이용하는게 아니라 재사용(recycle)을 위해서 다음과 같이 코딩하면 되겠다.
사용방법은 이게 전부다.
원한다면 자신의 애플리케이션에 맞게 이 공개된 ObjectPool을 개선시킬 수 있다. 가령, 100개의 Pool을 만들어놓지만 처음부터 100개를 생성해놓지는 않겠고 필요할 때마다 생성해주는 구조인 게으른(lazy) 생성을 기대할 수 있을 것이다. 또는 DisplayObject의 경우라면 사용한다는 것을 DisplayObject.parent 속성으로 판단할 수 있다. 위 처럼 pool.instance = '다 사용한 객체'와 같은 형태로 반환하는 것보다 자신의 부모가 존재하지 않는다면 재사용할 객체로 판단하도록 만들면 일종의 자동 Object Pool을 만들 수 있다. 자동 Object Pool은 다음 링크를 참고하면 좋겠다.
자동 풀링(auto pooling)을 구현해보자.
6. Object Pool과 new 연산자 사용 비교
Object Pool을 사용해야하는 타당성은 퍼포먼스 실험을 통해 쉽게 알 수 있다. 이러한 점에서 폴리고널 랩에서 제공하는 실험은 매우 유용한 자료이다.
실험 방법은 아래와 같다. (폴리고널 랩에서는 이 실험을 Window Vista, Flash Player 9.0.124에서 진행했다.) 그리고 Pool크기는 100으로 지정했다.
첫번째로 아래 코드처럼 k수 만큼 Object Pool로 부터 객체를 get만 한다.
for (var i:int = 0; i < k; i++) instances[i] = p.instance;
두번째로 아래 코드처럼 k수 만큼 Object Pool로부터 객체를 get/set 한다.
for (i = 0; i < k; i++) instances[i] = p.instance;
for (i = 0; i < k; i++) p.instance = instances[i];
세번째로 아래 코드처럼 k수 만큼 Object Pool대신 new 연산자를 사용한다.
for (i = 0; i < k; i++) instances[i] = new MyClass();
결과는 다음과 같다.
네이티브 Object 클래스의 경우 Object Pool을 사용하는 것이 new 연산자를 사용한 것보다 5배 정도 빨랐다.
Object 보다 더 크고 복잡한 flash.geom.Point의 경우는 약간더 빠른 결과를 보인다.
flash.display.Sprite 객체를 가지고 하면 무려 80배나 속도 개선이 되었다. 이는 복잡하고 큰 클래스에 Object Pool을 도입하면 효과적일 수 있다고 판단할 수 있다.
이 실험을 통해 적절한 방법으로 Object Pool을 사용하는 것은 애플리케이션의 퍼포먼스 증가에 지대한 영향을 줄 수 있다는 것을 깨달을 수 있다.
7. 메모리 사용의 폭이 급격한 애플리케이션은 좋지 못하다.
본인은 어떤 애플리케이션(Flash가 아니더라도)이든지 그 애플리케이션에 필요한 최소한의 메모리는 항상 존재한다고 생각한다. 무조건 메모리 사용을 줄여보겠다고 new, delete를 반복하는 것은 잘못된 것이다. new, delete를 반복하면 오히려 CPU 및 메모리 사용의 반복을 부축이며 애플리케이션의 전체적인 퍼포먼스를 저하시키는 경우가 발생할 수 있다. 실제로 new,delete를 반복하는 구조로 개발한 애플리케이션을 Flash Builder의 프로파일링 기능을 통해 메모리 사용을 살펴보면 큰폭으로 메모리 사용/해지가 일어난다는 것을 확인할 수 있다. 그러므로 몇몇 사람들이 Object Pool과 같은 개념과 같이 퍼포먼스 향상에 도움이 되는 개념을 전혀 사용하지 않고 Flash는 느리고 가비지 컬렉터 동작은 비정상적이다라고 말하는 것은 섣부른 판단이다라고 생각한다. 얼마든지 메모리를 효율적으로 사용하고 속도 개선을 할 수 있음에도 불구하고 잘못된 개발 방식때문에 전체적인 퍼포먼스가 나빠지는 것은 결국 개발자의 잘못인 것이다. 이는 Flash든 어떤 언어든 동일한 생각으로 접근해야 한다.
Flash Builder의 프로파일링 기능을 이용해 Object Pool과 new 연산자간의 메모리 변화를 확인할 수 있는 간단한 실험을 해보겠다. (프로파일링 기능을 사용하는 방법은 주제에서 벗어나므로 제외하겠다.)
먼저 Shape객체를 확장해서 크기와 색이 렌덤하게 그려지는 클래스를 만든다.
//화면에 출력할 Shape
class RandomShape extends Shape {
static private var colorPool:Array = [0xff0000,0x00ffff,0x0000ff,0x00ff00,0x0f0f0f,0xf0f0f0,0xffff00,0xf00cf0,0x00fcaa,0xff0c9a];
public function RandomShape():void {
}
public function draw():void {
var radius:Number = Math.random() * 40;
var color:uint = colorPool[ Math.floor(Math.random()*9) ];
graphics.clear();
graphics.beginFill(color,1.0);
graphics.drawCircle( 0, 0, radius );
graphics.endFill();
}
}
위 클래스를 가지고 자동 Pool을 구현한 클래스를 만든다.(참고 : http://www.diebuster.com/?p=1000)
//Shape용 자동 객체 Pool
class RandomShapeAutoPool {
private var _pool:Vector.<RandomShape>;
public function RandomShapeAutoPool() {
//적당히 수로 초기화한다.
_pool=new Vector.<RandomShape>();
}
public function getShape():RandomShape {
var key:*, result:RandomShape;
//먼저 기존의 pool에서 찾아본다.
for (key in _pool) {
//만약 해당 객체가 null이라면 new를 통해 생성한다.
if (_pool[key] === null) {
_pool[key]=new RandomShape;
result=_pool[key];
break;
//null이 아니라면 parent를 조사하여 사용가능한지 판단한다.
} else if (_pool[key].parent === null) {
result=_pool[key];
break;
}
}
//기존의 pool안에서 쓸만한 걸 찾지 못했다면
if (result === null) {
result=new RandomShape;
_pool[_pool.length]=result;
}
//인스턴스를 반환한다.
return result;
}
}
이제 테스트할 수 있는 호스트코드를 제작해보겠다.
package {
import flash.display.*;
import flash.events.*;
import flash.text.*;
import flash.utils.*;
[SWF(backgroundColor="#ffffff", frameRate="60", width="400", height="400")]
public class ObjectPoolTest extends Sprite {
private var pool:RandomShapeAutoPool=new RandomShapeAutoPool;
private var poolFlag:Boolean = false;
private var textPoolFlag:TextField;
private var shapeCanvas:Sprite;
public function ObjectPoolTest() {
addEventListener(Event.ADDED_TO_STAGE, ADDED_TO_STAGE);
}
private function ADDED_TO_STAGE($e:Event):void {
removeEventListener(Event.ADDED_TO_STAGE, ADDED_TO_STAGE);
stage.scaleMode=StageScaleMode.NO_SCALE;
stage.align=StageAlign.TOP_LEFT;
//rf)http://blog.jidolstar.com/656
if (stage.stageWidth === 0 && stage.stageHeight === 0) {
stage.addEventListener(Event.ENTER_FRAME,function($e:Event):void {
if (stage.stageWidth > 0 || stage.stageHeight > 0) {
stage.removeEventListener($e.type, arguments.callee);
init();
}
});
} else {
init();
}
}
private function init():void {
//shape의 부모객체
shapeCanvas = new Sprite;
addChild( shapeCanvas );
//PoolFlag 상태표시
textPoolFlag = new TextField();
textPoolFlag.text = "poolFlag=" + poolFlag;
addChild(textPoolFlag);
//마우스 클릭 영역
graphics.beginFill(0x000000,0);
graphics.drawRect(0,0,stage.stageWidth,stage.stageHeight);
graphics.endFill();
addEventListener(MouseEvent.CLICK, MOUSE_CLICK);
//반복동작
addEventListener(Event.ENTER_FRAME, ENTER_FRAME);
}
private function MOUSE_CLICK($e:MouseEvent):void {
//마우스 클릭때마다 poolFlag 상태를 전환
poolFlag = !poolFlag;
textPoolFlag.text = "poolFlag=" + poolFlag;
}
private function ENTER_FRAME($e:Event):void {
var shape:RandomShape, i:int, numChildren:int;
//poolFlag에 따라서 new연산자 또는 Object Pool에서 객체 참조
shape = poolFlag ? pool.getShape() : new RandomShape;
shape.draw();
shape.x = Math.random() * stage.stageWidth;
shape.y = 0;
shapeCanvas.addChild( shape );
//계속 아래로 떨어뜨림. 화면에서 벗어나면 removeChild시킴
numChildren = shapeCanvas.numChildren;
for( i=0; i<numChildren;i++) {
shape = shapeCanvas.getChildAt(i) as RandomShape;
shape.y += 5;
if( shape.y > stage.stageHeight ) {
shapeCanvas.removeChild( shape );
i--;
numChildren--;
}
}
}
}
}
동작은 매우 단순하다. poolFlag가 true이면 제작한 자동 Object Pool을 사용하겠다는 것이고 false이면 new 연산자를 사용해 RandomShape객체를 생성하겠다는 것이다. poolFlag 전환은 마우스 클릭으로 할 수 있다. 그리고 비가 내리는 것처럼 Shape 객체는 위에서 부터 아래로 떨어지며 맨 아래에 닿으면 removeChild 시킨다.
테스트 화면
실행 코드와 결과물은 http://wonderfl.net/code/805dc65d1f7800b7bee5dc3cd72ca21c0a87c2d6 에서도 볼 수 있다.
이제 Flash Builder의 프로파일링 기능을 이용해 메모리 변화 및 객체 생성빈도를 살펴보자.
먼저 new 연산자를 사용했을때 메모리 변화를 보자.
new 연산자로 객체생성시 Memory Usage
new 연산자를 사용해 객체를 생성하고 필요없을때 removeChild를 하게 되면 가비지 컬렉션 처리가 되기 때문에 일정한 시점에 메모리가 해지되는 것을 반복하는 것을 확인할 수 있다.
new 연산자로 객체생성시 Live Object
위 표는 축적된 객체수(Cumulative Instances), 현재 객체수(Instances), 축적된 메모리(Cumulative Memory), 현재 메모리(Memory)를 나타낸다. 변화 무쌍하며 실제로 보여지지 않는 객체도 가비지 컬렉션 처리가 일어날때까지 메모리에 남아 있다. 분명 비효율적으로 보인다.
반대로 Object Pool을 이용했을때 메모리 변화이다.
Object Pool로 객체관리시 Memory Usage
new 연산자를 사용했을때와 비교가 안될 정도로 고요한 느낌이 든다. 거의 일직선의 메모리 사용율을 보인다.
Object Pool로 객체관리시 Live Object
축적되는 객체도 81개를 넘지 않는다. 그만큼 객체 생성과 삭제에 민감하지 않게 되고 가비지 컬렉터 도움을 받지 않고 객체관리가 되는 것을 확인할 수 있다.
8. 재사용할 수 있는 클래스 제작
Object Pool을 사용시 한가지 고려할 사항은 재사용할 수 있는 클래스를 만들어야 한다는 점이다. 왜냐하면 Object Pool은 단순히 재사용을 위한 공간만 제공하는 것이지 재사용을 하기 위한 어떤 기능을 클래스에게 줄 수 없기 때문이다. 그래서 재사용이 가능한 클래스를 설계하는 것은 하나의 기술적 이슈가 될 수 있다.
재사용할 수 있는 클래스를 어떻게 만드는지 간단한 예를 들어보겠다.
final public class MyData {
public var type:String;
public var id:int;
public var name:String;
}
위 클래스는 데이터를 담는 클래스이다. id, name은 실제 사용하는 데이타 값들이고 이 데이타의 종류는 type으로 구분한다. 이 데이타를 사용하는 인터페이스를 제작해보겠다.
public interface IMyClass {
function init($data:MyData):void;
function clear():void;
function get data():MyData;
}
이 인터페이스는 앞으로 만들 MyClass01, MyClass02... 등을 구현하기 위한 것이다. 초기화 하기 위해 init()함수가 있고 인자로 위에서 제작한 MyData 객체를 받는다. 또한 사용할 필요가 없을때 내부 청소를 위해 clear()함수를 추가했다. 게다가 가지고 있는 data를 참고하기 위해 get data()도 만들었다. 이제 이 인터페이스를 구현한 클래스는 아래처럼 만든다.
final internal class MyClass01 extends Sprite implements IMyClass {
private var _data:MyData;
public function MyClass01() {
}
public function init($data:Mydata):void {
_data = $data;
//구현
}
public function clear():void {
_data = null;
}
public function get data():MyData {
return _data;
}
}
final internal class MyClass02 extends Sprite implements IMyClass {
private var _data:MyData;
public function MyClass02() {
}
public function init($data:Mydata):void {
_data = $data;
//구현
}
public function clear():void {
_data = null;
}
public function get data():MyData {
return _data;
}
}
final internal class MyClass03 extends Sprite implements IMyClass {
private var _data:MyData;
public function MyClass03() {
}
public function init($data:Mydata):void {
_data = $data;
//구현
}
public function clear():void {
_data = null;
}
public function get data():MyData {
return _data;
}
}
코드가 길어보이지만 3개 클래스 모두 IMyClass를 구현했고 Sprite를 확장했다. 단지 클래스 이름만 다르며 실제 구현부는 알아서 구현하면 된다.
개발자는 MyClass01, MyClass02, MyClass03은 매우 자주 new 연산자로부터 생성되는 클래스로 판단했고 그래서 Object Pool을 도입하겠다고 결정했다고 하자. 그럼 다음과 같이 시도해 볼 수 있다.
import de.polygonal.core.ObjectPool;
import flash.display.DisplayObject;
public class MyPool {
private static var poolList:Object;
static public function init():void {
poolList = {
'type01':new ObjectPool(true),
'type02':new ObjectPool(true),
'type03':new ObjectPool(true),
};
poolList.type01.allocate(20, MyClass01);
poolList.type02.allocate(10, MyClass02);
poolList.type03.allocate(100, MyClass03);
}
static public function getObject($data:MyData):IMyClass {
if( $data === null ) {
throw new Error('인자값은 null이면 안됩니다.');
}
try {
var object:IMyClass = poolList[$data.type].object;
object.init($data);
} catch {
throw new Error('데이타의 type값이 잘못된 값입니다.');
}
return object;
}
static public function returnObject($object:IMyClass):void {
if( $object === null ) {
throw new Error('인자값은 null이면 안됩니다.');
}
$object.clear();
if( DisplayObject($object).parent ) {
DisplayObject($object).parent.removeChild( DisplayObject($object) );
}
poolList[$object.data.type].object = $object;
}
}
위에서 제작된 MyPool은 static클래스이며 ObjectPool을 이용해 IMyClass 인터페이스를 구현한 클래스를 init()함수에서 적당하게 할당하는 것을 확인할 수 있다. 또한 getObject()를 통해 인자값 data를 참고하여 참조할 Object Pool을 찾아 IMyClass를 구현한 각각의 클래스의 객체를 받아올 수 있으며 returnObject를 통해 다 사용한 객체를 반환한다.
실제로 실무에서 이런 형태로 개발했고 이는 매우 유용했다. MyPool 클래스를 더 개선해서 더 많은 수 Object Pool을 감당할 수 있도록 확장할 수 있다면 더욱 유용해질 것이라 생각한다.
9. 정리하며
Object Pool 개념은 Flash에만 국한되는 유용한 개념이 아니다. 이런 개념들에 대한 노하우를 계속 쌓아간다면 당신의 애플리케이션은 더욱 좋은 작품으로 탈바꿈할 수 있을 것이다.
Flash Player가 느리고 메모리 관리가 안된다고 생각하지는 말자. Flash Player 태생자체가 그런것을 어찌하겠는가? 제작한 애플리케이션이 느리다면 그것은 결국 개발자가 잘못한 것임을 항상 인지하자. 중요한 것은 여전히 Flash Player는 유용하고 많이 사용되고 있으며 지금도 나날이 발전하여 Cross OS, Cross Browser를 넘어 Cross Device 세계로 뻗어가고 있다는 점이다.
10. 참고글
- [폴리노널 랩]Using object Pools
- 가비지컬렉터 와 메모리 관리
- Memory Pooling in as3
- 자동 풀링(auto pooling)
- [Lost in ActionScript]Object Pooling in AS3
- [BigRoom]ObjectPool
- [Hype 프레임워크]Object Pool
- AS3 is FAST! Redux - CopyPixels vs. Sprite Pooling
- [Adobe]Object Pool
- boost flex performance with object pooling manager api
- 컨텐츠 품질을 향상시키는 FlashLite와 메모리 최적화 - 로그인 필요
- 스타플 타임라인을 제작하며 도움이 되었던 개념들
글쓴이 : 지돌스타(http://blog.jidolstar.com/666)
TextInput 의 restrict property 에 대해...
<mx:TextInput id="txt" restrict="ㄱ-힣">
restrict = "ㄱ-힣" : 한글만 입력가능.
restrict = "A-z" : 영어만 입력가능.
restrict = "0-9" : 숫자만 입력가능.
restrict = "0-9\-" : 숫자와 '-'만 입력가능.
restrict = "^a-z" : 소문자 a부터 z까지 제외한 모든문자 입력가능.
restrict = "A-z0-9\@\." : 이메일 형식만 입력가능.
restrict = "\\\" : '\' 문자만 입력가능.
restrict = "\^" : '^' 문자만 입력가능.
여러가지 응용도 하실 수 있겠습니다.
출처 : http://2rang.tistory.com/15
<mx:DataGridColumn headerText="선택" width="50">
<mx:itemRenderer>
<mx:Component>
<mx:HBox horizontalAlign="center">
<mx:Script>
<![CDATA[
public override function set data(value:Object):void
{
super.data = value;
}
]]>
</mx:Script>
<mx:CheckBox id="cb1" selected="{data.chk}" change="{data.chk = cb1.selected}"/>
</mx:HBox>
</mx:Component>
</mx:itemRenderer>
</mx:DataGridColumn>
출처 : http://blog.naver.com/PostView.nhn?blogId=neodijkstra&logNo=50050005320
기본적인 개념으로.. 데이터 그리드등에 ItemRenderer를 사용하여 checkBox 나 기타 등등의 컴포넌트를 넣었을때
데이터그리드를 스크롤하게 되면 체크박스의 데이터가 뒤죽박죽이 되는 문제가 발생한다.
이는.. 보이는영역에서 사라진 컴포넌트들이 재사용(!!) 되면서 일어나는 현상으로 다음과 같은 방법으로 해결한다.
<Adobe Flex4 Help에서 발췌>
Creating a recyclable item renderer
With virtual layout disabled, the DataGroup and SkinnableDataContainer containers create one instance of the item renderer for each child. With virtual layout enabled, the container only creates enough item renderers to display its currently visible children. Virtual layout greatly reduces the overhead required to use the DataGroup and SkinnableDataContainer containers.
With virtual layout enabled, when a child is moved off the visible area of the container, its item renderer is recycled. First, the item renderer’s data property is set to null. When the item renderer is reused, its data property is set to the data item representing the new child. Therefore, if a recycled item renderer performs any actions based on the value of the data property, it must first check that the property is not null. When the item renderer is reassigned, Flex also calls the updateRenderer() method of the item renderer owner. This method must set the owner and label properties on the item renderer. Subclasses of SkinnableDataContainer, can use the updateRenderer() method to set additional properties on the item renderer. Because a container can reuse an item renderer, ensure that you fully define its state. For example, you use a CheckBox control in an item renderer to display a true (checked) or
false (unchecked) value based on the current value of the data property. A common mistake is to assume that the CheckBox control is always in its default state of unchecked and only inspect the data property for a value of true.
However, remember that the CheckBox can be recycled and had previously been checked. Therefore, inspect the data property for a value of false , and explicitly uncheck the control if it is checked, as the following example shows:
<ItemRenderer의 dataChange 이벤트를 이용하는 방법>
Prerelease - 5 October 2009
USING FLEX 4 442
Building the user interface
<?xml version="1.0" encoding="utf-8"?>
<!-- containers\spark\myComponents\MySimpleItemRendererCB.mxml -->
<s:ItemRenderer xmlns:fx="http://ns.adobe.com/mxml/2009" xmlns:mx="library://ns.adobe.com/flex/halo"xmlns:s="library://ns.adobe.com/flex/spark" dataChange="setMgr();">
<fx:Script>
<![CDATA[
private function setMgr():void {
// Check to see if the data property is null.
if (data == null) return;
// If the data property is not null,
// set the CheckBox control appropriately..
if (data.manager == "yes") {
mgr.selected = true;
}
else {
mgr.selected = false;
}
}
]]>
</fx:Script>
<s:states>
<s:State name="normal"/>
<s:State name="hovered"/>
<s:State name="selected"/>
</s:states>
<s:HGroup verticalCenter="0" left="2" right="2" top="2" bottom="2">
<s:Label text="{data.lastName}, {data.firstName}"/>
<s:Label text="{data.companyID}"/>
<s:CheckBox id="mgr"/>
</s:HGroup>
</s:ItemRenderer>
Use the dataChange event of the ItemRenderer class to detect the change to its data property. This event is dispatched whenever the data property changes. Alternatively, you can override the data property. Alternatively, you can override the ItemRenderer.data
property itself, as the following example shows:
<data 속성을 오버라이드하는 방법>
Prerelease - 5 October 2009
USING FLEX 4 443
Building the user interface
<?xml version="1.0" encoding="utf-8"?>
<!-- containers\spark\myComponents\MySimpleItemRendererCBData.mxml -->
<s:ItemRenderer xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:mx="library://ns.adobe.com/flex/halo"
xmlns:s="library://ns.adobe.com/flex/spark">
<fx:Script>
<![CDATA[
override public function set data(value:Object):void {
super.data = value;
// Check to see if the data property is null.
if (value== null) return;
// If the data property is not null,
// set the CheckBox control appropriately..
if (value.manager == "yes") {
mgr.selected = true;
}
else {
mgr.selected = false;
}
}
]]>
</fx:Script>
<s:states>
<s:State name="normal"/>
<s:State name="hovered"/>
<s:State name="selected"/>
</s:states>
<s:HGroup verticalCenter="0" left="2" right="2" top="2" bottom="2">
<s:Label text="{data.lastName}, {data.firstName}"/>
<s:Label text="{data.companyID}"/>
<s:CheckBox id="mgr"/>
</s:HGroup>
</s:ItemRenderer>
상단의 두가지 방법을 이용하여 새로운 ItemRenderer를 생성한 후 다음과 같은 방법으로 사용한다.
<?xml version="1.0" encoding="utf-8"?>
<!-- containers\spark\SparkDataGroupContainerTypicalItem.mxml -->
<s:Application xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:mx="library://ns.adobe.com/flex/halo"
xmlns:s="library://ns.adobe.com/flex/spark">
<s:layout>
<s:VerticalLayout/>
</s:layout>
<fx:Script>
<![CDATA[
[Bindable]
public var typicalObj:Object = {
firstName:"Long first name",
lastName:"Even longer last name",
companyID:"123456",
manager:"yes"
};
]]>
</fx:Script>
<s:Scroller>
<s:DataGroup itemRenderer="myComponents.MySimpleItemRendererCB"
height="100"
typicalItem="{typicalObj}" >
<s:layout>
<s:VerticalLayout useVirtualLayout="true"/>
</s:layout>
<mx:ArrayList>
<fx:Object firstName="Bill" lastName="Smith" companyID="11233" manager="yes"/>
<fx:Object firstName="Dave" lastName="Jones" companyID="13455" manager="no"/>
<fx:Object firstName="Mary" lastName="Davis" companyID="11543" manager="yes"/>
<fx:Object firstName="Debbie" lastName="Cooper" companyID="14266" manager="no"/>
<fx:Object firstName="Bob" lastName="Martins" companyID="11233" manager="yes"/>
<fx:Object firstName="Jack" lastName="Jones" companyID="13455" manager="no"/>
<fx:Object firstName="Sam" lastName="Johnson" companyID="11543" manager="yes"/>
<fx:Object firstName="Tom" lastName="Fitz" companyID="14266" manager="no"/>
<fx:Object firstName="Dave" lastName="Mead" companyID="11233" manager="yes"/>
<fx:Object firstName="Dave" lastName="Jones" companyID="13455" manager="no"/>
<fx:Object firstName="Mary" lastName="Davis" companyID="11543" manager="yes"/>
<fx:Object firstName="Debbie" lastName="Cooper" companyID="14266" manager="no"/>
</mx:ArrayList>
</s:DataGroup>
</s:Scroller>
</s:Application>
[출처] ItemRender 안의 데이터 유지|작성자 찬밥덩이
출처 : http://igna84.blogspot.kr/2010/03/flex-datagrid-itemrenderer-2-%EC%A0%84%EB%A9%B4%EC%A0%84.html
Flex DataGrid ItemRenderer #2 - 전면전
경력사원임에도 불구하고 나이가 어려서 그런지 신입사원 연수를 다녀오라는 지시에 지난 목요일 금요일 다녀오는 바람에 ItemRenderer 이야기가 늦어졌습니다. 기다리신 분 계신가요?? 죄송합니다. 본의아니게;;
아무튼 DataGrid ItemRenderer 라는 뜨거운 감자를 삼키기위해 어떤 모양의 ItemRenderer를 만들지 한번 생각해보도록 할까요.
처음이니 간단하게 CheckBox 아이템 렌더러를 만들도록 해봅시다.
일단 메인 어플리케이션을 만들어 놓습니다.
[code xml]
<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml">
<mx:Script>
<![CDATA[
import mx.collections.ArrayCollection;
[Bindable]
private var ac:ArrayCollection = new ArrayCollection([
{index:1, description:"test1", toggle:"Y"},
{index:2, description:"test2", toggle:"N"},
{index:3, description:"test3", toggle:"N"},
{index:4, description:"test4", toggle:"Y"},
{index:5, description:"test5", toggle:"N"},
{index:6, description:"test6", toggle:"N"},
{index:7, description:"test7", toggle:"Y"},
{index:8, description:"test8", toggle:"N"},
{index:9, description:"test9", toggle:"N"},
{index:10, description:"test10", toggle:"N"},
{index:11, description:"test11", toggle:"N"},
{index:12, description:"test12", toggle:"N"}
]);
]]>
</mx:Script>
<mx:DataGrid dataProvider="{ac}">
<mx:columns>
<mx:DataGridColumn headerText="index" dataField="index" />
<mx:DataGridColumn headerText="description" dataField="description" />
<mx:DataGridColumn headerText="check" dataField="toggle" />
</mx:columns>
</mx:DataGrid>
</mx:Application>
[/code]
위와같이 코딩하고 컴파일하게되면 아래와 같이 산출물이 나오게 됩니다.
그럼 우리는 ratio라고 적혀있는 곳에 ItemRenderer를 적용할겁니다.
CheckBox를 상속받아 작업할겁니다. 왜 "UIComponent 안쓰고!"라고 말씀하신다면
그 이유는 UIComponent를 상속받은 클래스이기도 하고 지난시간 말했던 IDataRenderer, IDropInListitemRenderer, IListItemRenderer 이렇게 세 인터페이스가 이미 구현되어있으며 우리가 원하는 아이템 렌더러는 CheckBox아이템 렌더러이기 때문이지요.
[code as3]
package classes.controls.renderers
{
//-----------------------------------------------------------------------------
//
// Import
//
//-----------------------------------------------------------------------------
import mx.core.IDataRenderer;
import mx.controls.CheckBox;
import mx.controls.listClasses.BaseListData;
import mx.controls.listClasses.IDropInListItemRenderer;
import mx.controls.listClasses.IListItemRenderer;
import mx.events.FlexEvent;
public class CheckBoxRenderer extends CheckBox
{
//-----------------------------------------------------------------------------
//
// Constructor
//
//-----------------------------------------------------------------------------
/**
* Constructor.
*/
public function CheckBoxRenderer()
{
super();
}
//-----------------------------------------------------------------------------
//
// Variables
//
//-----------------------------------------------------------------------------
/**
* @private
* 데이터 변경 확인 플래그
*/
private var dataChanged:Boolean = false;
//-----------------------------------------------------------------------------
//
// Override Methods
//
//-----------------------------------------------------------------------------
/**
* @private
*/
override protected function createChildren():void
{
super.createChildren();
}
/**
* @private
*/
override protected function commitProperties():void
{
super.commitProperties();
}
/**
* @private
*/
override protected function updateDisplayList(unscaledWidth:Number, unscaledHeight:Number):void
{
super.updateDisplayList(unscaledWidth, unscaledHeight);
}
}
}
[/code]
초벌로 createChildren, commitProperties, updateDisplayList도 확장해 두고 아이템 랜더러로 사용하기 위한 준비를 마쳤습니다.
그리고 메인 어플리케이션은 아래와 같이 수정하고 컴파일 합니다.
[code xml]
<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml">
<mx:Script>
<![CDATA[
import mx.collections.ArrayCollection;
[Bindable]
private var ac:ArrayCollection = new ArrayCollection([
{index:1, description:"test1", toggle:"Y"},
{index:2, description:"test2", toggle:"N"},
{index:3, description:"test3", toggle:"N"},
{index:4, description:"test4", toggle:"Y"},
{index:5, description:"test5", toggle:"N"},
{index:6, description:"test6", toggle:"N"},
{index:7, description:"test7", toggle:"Y"},
{index:8, description:"test8", toggle:"N"},
{index:9, description:"test9", toggle:"N"},
{index:10, description:"test10", toggle:"N"},
{index:11, description:"test11", toggle:"N"},
{index:12, description:"test12", toggle:"N"}
]);
]]>
</mx:Script>
<mx:DataGrid dataProvider="{ac}">
<mx:columns>
<mx:DataGridColumn headerText="index" dataField="index" />
<mx:DataGridColumn headerText="description" dataField="description" />
<mx:DataGridColumn headerText="radio" dataField="toggle" itemRenderer="classes.controls.renderers.CheckBoxRenderer" />
</mx:columns>
</mx:DataGrid>
</mx:Application>
[/code]
일단 아래와 같은 결과가 나오면 성공입니다.
CheckBox 가 "Y"냐 "N"이냐에 따라 selected가 처리되는 로직이 필요합니다. 그러기 위해서는 그 코드를 어디에 넣는것이 좋을지 고민해봐야하는데 아까도 말했듯 모든 속성처리는 commitProperties에서 하게 됩니다. commitProperties를 호출하기 위해서는 저번에 이야기 했었던 대로 invalidateProperties() 메서드를 호출하면 됩니다. 그렇다면 코드를 아래와 같이 수정합니다.
[code as3]
package classes.controls.renderers
{
//-----------------------------------------------------------------------------
//
// Import
//
//-----------------------------------------------------------------------------
import mx.core.IDataRenderer;
import mx.controls.CheckBox;
import mx.controls.dataGridClasses.DataGridListData;
import mx.controls.listClasses.BaseListData;
import mx.controls.listClasses.IDropInListItemRenderer;
import mx.controls.listClasses.IListItemRenderer;
import mx.events.FlexEvent;
public class CheckBoxRenderer extends CheckBox
{
//-----------------------------------------------------------------------------
//
// Constructor
//
//-----------------------------------------------------------------------------
/**
* Constructor.
*/
public function CheckBoxRenderer()
{
super();
}
//-----------------------------------------------------------------------------
//
// Variables
//
//-----------------------------------------------------------------------------
/**
* @private
* 데이터 변경 확인 플래그
*/
private var dataChanged:Boolean = false;
//-----------------------------------------------------------------------------
//
// Override Properties
//
//-----------------------------------------------------------------------------
override public function set data(value:Object):void
{
super.data = value;
dataChanged = true;
invalidateProperties();
}
//-----------------------------------------------------------------------------
//
// Override Methods
//
//-----------------------------------------------------------------------------
/**
* @private
*/
override protected function createChildren():void
{
super.createChildren();
}
/**
* @private
*/
override protected function commitProperties():void
{
super.commitProperties();
if(dataChanged)
{
dataChanged = false;
updateProperties();
}
}
/**
* @private
*/
override protected function updateDisplayList(unscaledWidth:Number, unscaledHeight:Number):void
{
super.updateDisplayList(unscaledWidth, unscaledHeight);
}
//-----------------------------------------------------------------------------
//
// Methods
//
//-----------------------------------------------------------------------------
/**
* @private
* CheckBox의 속성을 변경
*/
private function updateProperties():void
{
if(listData.label == "Y")
{
selected = true;
}
else
{
selected = false;
}
invalidateDisplayList();
}
}
}
[/code]
위와 같이 코딩하게 되면 아래와 같은 산출물이 나오게 됩니다.
썩 잘나오는거 같습니다. 몇가지 마음에 안 드는 것이 있는데 일단 CheckBox가 셀의 중앙에 갔으면 좋겠고 CheckBox를 클릭하고 휠을 돌려보면 Check가 풀려버리는 버그가 있다는 것이지요.
CheckBox를 중앙에 놓으려면 CheckBox의 너비를 알아야하는데 그것은 CheckBox는 기본적으로 14픽셀의 넓이를 갖습니다. 그것을 어떻게 아냐구요?
measuredWidth를 trace 해보면 간단하게 알 수 있습니다. 아, measuredWidth는 컨포넌트 객체의 기본 너비를 이야기 합니다.
위의 두가지 문제점을 해결하기 위해 아래와 같이 코드를 수정합니다.
[code as3]
package classes.controls.renderers
{
//-----------------------------------------------------------------------------
//
// Import
//
//-----------------------------------------------------------------------------
import flash.events.MouseEvent;
import mx.core.IDataRenderer;
import mx.controls.CheckBox;
import mx.controls.dataGridClasses.DataGridListData;
import mx.controls.listClasses.BaseListData;
import mx.controls.listClasses.IDropInListItemRenderer;
import mx.controls.listClasses.IListItemRenderer;
import mx.core.mx_internal;
import mx.events.FlexEvent;
use namespace mx_internal;
public class CheckBoxRenderer extends CheckBox
{
//-----------------------------------------------------------------------------
//
// Constructor
//
//-----------------------------------------------------------------------------
/**
* Constructor.
*/
public function CheckBoxRenderer()
{
super();
}
//-----------------------------------------------------------------------------
//
// Variables
//
//-----------------------------------------------------------------------------
/**
* @private
* 데이터 변경 확인 플래그
*/
private var dataChanged:Boolean = false;
//-----------------------------------------------------------------------------
//
// Override Properties
//
//-----------------------------------------------------------------------------
override public function set data(value:Object):void
{
super.data = value;
dataChanged = true;
invalidateProperties();
}
//-----------------------------------------------------------------------------
//
// Override Methods
//
//-----------------------------------------------------------------------------
/**
* @private
*/
override protected function createChildren():void
{
super.createChildren();
}
/**
* @private
*/
override protected function commitProperties():void
{
super.commitProperties();
if(dataChanged)
{
dataChanged = false;
updateProperties();
}
}
/**
* @private
*/
override protected function updateDisplayList(unscaledWidth:Number, unscaledHeight:Number):void
{
super.updateDisplayList(unscaledWidth, unscaledHeight);
currentIcon.x = (unscaledWidth - currentIcon.width) / 2;
}
//-----------------------------------------------------------------------------
//
// Methods
//
//-----------------------------------------------------------------------------
/**
* @private
* CheckBox의 속성을 변경
*/
private function updateProperties():void
{
if(listData.label == "Y")
{
selected = true;
}
else
{
selected = false;
}
invalidateDisplayList();
}
//-----------------------------------------------------------------------------
//
// Override EventHandler
//
//-----------------------------------------------------------------------------
/**
* @private
*/
override protected function clickHandler(event:MouseEvent):void
{
super.clickHandler(event);
//data[DataGridListData(listData).dataField] = selected ? "Y" : "N";
if(selected)
{
data[DataGridListData(listData).dataField] = "Y";
}
else
{
data[DataGridListData(listData).dataField] = "N";
}
}
}
}
[/code]
currentIcon이라는 생소한 속성을 위에서 보실 수 있습니다. 그것은 체크박스에서 체크되는 아이콘을 지칭합니다. 그 currentIcon이란 녀석은 mx_internal이라는 접근자를 이용하고 있고 LanguageReference에는 표시되지 않고 있어서 그 존재를 모르는 사람이 많습니다.
예전에 저도 그것을 몰라서 HBox에다가 넣고 horizontalAlign="center"를 주는 바보같은 짓을 일삼았는데 이 사실을 알고 난뒤에 Container를 사용하지 않고 좀더 가볍게 itemRenderer를 만들어 낼 수 있었지요.
그래서 updateDisplayList() 메서드에서 좌표를 잡아서 셀의 중앙에 놓고 clickHandler라는 메서드를 확장하여 변경된 값을 data에 반영하게 합니다. 그렇게 되면 아래와 같은 결과물이 나오게 되지요.
오. 그럴싸하게 작동하는 것 같습니다만 몇가지 아쉬운점이 있습니다.
1. "Y", "N" 값으로만 작동하게 되어있다.
2. CheckBox를 클릭하면 뭔가 다른 행동을 하고 싶을땐 어떻게 하나.
이렇게 두가지 사항인데요.
이 부분에 대해서는 다음시간에 알아보도록 해요~
각 컨트롤 속성에서
Mouse.cursor = MouseCursor.ARROW; 이렇게 주면된다.
AUTO : 자동
ARROW : 화살표
BUTTON : 버튼 누를때 나오는 한손꾸락
HAND : 잡아당길때 나오는 손바닥
IBEAM : 텍스트 창에서 나오는 I모양
예) mouseOver="Mouse.cursor = MouseCursor.BUTTON;" 이렇게 하면 된다.
[출처] FLEX 마우스 커서 모양 바꾸기 (시스템)|작성자 빵구똥꾸
출처 : http://blog.naver.com/PostView.nhn?blogId=sehwanq&logNo=50091363895
위 처럼 컨트롤 포인트를 이용하여 사이즈를 조절하는 컴포넌트를 만들려면...
1. 우선 각각의 포인트가 되는 사각형을 만듭니다.
<?xml version="1.0" encoding="utf-8"?>
<s:Group xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
xmlns:mx="library://ns.adobe.com/flex/mx"
width="9" height="9">
<s:Rect width="9" height="9">
<s:fill>
<s:SolidColor />
</s:fill>
</s:Rect>
</s:Group>
2. 그 다음 위 검정색 사각형을 위치시켜 놓을 컨트롤 박스(Group)를 만들고 위 사각형을 제 위치에 놓습니다.
<s:Group xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
xmlns:mx="library://ns.adobe.com/flex/mx"
xmlns:control="org.jhl.control.*"
width="100" height="100">
<fx:Script>
<![CDATA[
private var _currentPointID:String;
private var _startPointX:Number = 0;
private var _startPointY:Number = 0;
private var _x:Number;
private var _y:Number;
private var _width:Number;
private var _height:Number;
protected function mouseDownListener( event:MouseEvent ):void
{
_startPointX = this.parent.mouseX;
_startPointY = this.parent.mouseY;
_currentPointID = ControlPoint(event.target).id;
_x = this.x;
_y = this.y;
_width = this.width;
_height = this.height;
this.systemManager.addEventListener(MouseEvent.MOUSE_MOVE, moveListener);
this.systemManager.addEventListener(MouseEvent.MOUSE_UP, upListener);
}
protected function moveListener( event:MouseEvent ):void
{
var pointer:ControlPoint = event.target as ControlPoint;
var dx:Number = this.parent.mouseX - _startPointX;
var dy:Number = this.parent.mouseY - _startPointY;
if( _currentPointID == "topLeft" )
{
this.x = _x + dx;
this.width = _width - dx;
this.y = _y + dy;
this.height = _height - dy;
}
else if( _currentPointID == "topCenter" )
{
this.y = _y + dy;
this.height = _height - dy;
}
else if( _currentPointID == "topRight" )
{
this.width = _width + dx;
this.y = _y + dy;
this.height = _height - dy;
}
else if( _currentPointID == "middleLeft" )
{
this.x = _x + dx;
this.width = _width - dx;
}
else if( _currentPointID == "middleRight" )
{
this.width = _width + dx;
}
else if( _currentPointID == "bottomLeft" )
{
this.x = _x + dx;
this.width = _width - dx;
this.height = _height + dy;
}
else if( _currentPointID == "bottomCenter" )
{
this.height = _height + dy;
}
else if( _currentPointID == "bottomRight" )
{
this.width = _width + dx;
this.height = _height + dy;
}
event.updateAfterEvent();
}
protected function upListener( event:MouseEvent ):void
{
this.systemManager.removeEventListener(MouseEvent.MOUSE_MOVE, moveListener);
this.systemManager.removeEventListener(MouseEvent.MOUSE_UP, upListener);
}
override protected function updateDisplayList( unscaledWidth:Number, unscaledHeight:Number ):void
{
super.updateDisplayList(unscaledWidth, unscaledHeight);
var g:Graphics = this.graphics;
g.clear();
g.lineStyle(1, 0x000000);
g.drawRect(0, 0, unscaledWidth, unscaledHeight);
}
]]>
</fx:Script>
<control:ControlPoint id="topLeft" left="-4" top="-4"
mouseDown="mouseDownListener(event)" />
<control:ControlPoint id="topCenter" top="-4" horizontalCenter="0"
mouseDown="mouseDownListener(event)" />
<control:ControlPoint id="topRight" right="-4" top="-4"
mouseDown="mouseDownListener(event)" />
<control:ControlPoint id="middleLeft" left="-4" verticalCenter="0"
mouseDown="mouseDownListener(event)" />
<control:ControlPoint id="middleRight" right="-4" verticalCenter="0"
mouseDown="mouseDownListener(event)"/>
<control:ControlPoint id="bottomLeft" left="-4" bottom="-5"
mouseDown="mouseDownListener(event)" />
<control:ControlPoint id="bottomCenter" bottom="-5" horizontalCenter="0"
mouseDown="mouseDownListener(event)" />
<control:ControlPoint id="bottomRight" right="-4" bottom="-5"
mouseDown="mouseDownListener(event)" />
</s:Group>
이 컴포넌트를 이용하여 다음과 같은 결과물을 얻을 수 있습니다.
p.s. 하양감자님의 지적부분을 수정하여 업데이트 하였습니다.
Flex 4 배포버전 : 4.0.0.14159 [2010/03/21]
Flex 4 현재버전 : 4.1.0.16076 [2010/06/10]
위 그림 처럼 외부는 컨트롤하는 외곽선과 컨트롤 점들로 그리고 내부는 원, 여기서 원이 사각형 또는 다각형의 모양일 수도 있구요
이렇게 구성하기 위해 먼저 원을 그리면..
<?xml version="1.0" encoding="utf-8"?>
<s:Group xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
xmlns:mx="library://ns.adobe.com/flex/mx"
width="100" height="100">
<fx:Script>
<![CDATA[
override protected function updateDisplayList( unscaledWidth:Number, unscaledHeight:Number ):void
{
super.updateDisplayList(unscaledWidth, unscaledHeight);
var g:Graphics = this.graphics;
g.clear();
g.lineStyle(1, 0x000000);
g.beginFill(0xEEEEEE);
g.drawEllipse(0, 0, unscaledWidth, unscaledHeight);
g.endFill();
}
]]>
</fx:Script>
</s:Group>
그리고 어제 만든 컴포넌트를 원 컴포넌트에 추가하면...
(원 컴포넌트를 컨트롤 하기 위해 컨트롤 컴포넌트에 target 이라는 속성을 둬서 원 컴포넌트를 컨트롤 하게 합니다.)
<?xml version="1.0" encoding="utf-8"?>
<s:Group xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
xmlns:mx="library://ns.adobe.com/flex/mx"
xmlns:control="org.jhl.control.*"
width="100" height="100">
<fx:Script>
<![CDATA[
override protected function updateDisplayList( unscaledWidth:Number, unscaledHeight:Number ):void
{
super.updateDisplayList(unscaledWidth, unscaledHeight);
var g:Graphics = this.graphics;
g.clear();
g.lineStyle(1, 0x000000);
g.beginFill(0xEEEEEE);
g.drawEllipse(0, 0, unscaledWidth, unscaledHeight);
g.endFill();
}
]]>
</fx:Script>
<control:ControlBox target="{this}" width="{this.width}" height="{this.height}"/>
</s:Group>
그리고 아래는 수정된 컨트롤 컴포넌트...
<?xml version="1.0" encoding="utf-8"?>
<s:Group xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
xmlns:mx="library://ns.adobe.com/flex/mx"
xmlns:control="org.jhl.control.*"
width="100" height="100" >
<fx:Script>
<![CDATA[
import mx.core.UIComponent;
private var _currentPointID:String;
private var _startPointX:Number = 0;
private var _startPointY:Number = 0;
private var _x:Number;
private var _y:Number;
private var _width:Number;
private var _height:Number;
private var _target:UIComponent = null;
public function set target( value:UIComponent ):void
{
_target = value;
}
protected function mouseDownListener( event:MouseEvent ):void
{
_startPointX = _target.parent.mouseX;
_startPointY = _target.parent.mouseY;
_currentPointID = ControlPoint(event.target).id;
_x = _target.x;
_y = _target.y;
_width = _target.width;
_height = _target.height;
this.systemManager.addEventListener(MouseEvent.MOUSE_MOVE, moveListener);
this.systemManager.addEventListener(MouseEvent.MOUSE_UP, upListener);
}
protected function moveListener( event:MouseEvent ):void
{
var dx:Number = _target.parent.mouseX - _startPointX;
var dy:Number = _target.parent.mouseY - _startPointY;
if( _currentPointID == "topLeft" )
{
_target.x = _x + dx;
_target.width = _width - dx;
_target.y = _y + dy;
_target.height = _height - dy;
}
else if( _currentPointID == "topCenter" )
{
_target.y = _y + dy;
_target.height = _height - dy;
}
else if( _currentPointID == "topRight" )
{
_target.width = _width + dx;
_target.y = _y + dy;
_target.height = _height - dy;
}
else if( _currentPointID == "middleLeft" )
{
_target.x = _x + dx;
_target.width = _width - dx;
}
else if( _currentPointID == "middleRight" )
{
_target.width = _width + dx;
}
else if( _currentPointID == "bottomLeft" )
{
_target.x = _x + dx;
_target.width = _width - dx;
_target.height = _height + dy;
}
else if( _currentPointID == "bottomCenter" )
{
_target.height = _height + dy;
}
else if( _currentPointID == "bottomRight" )
{
_target.width = _width + dx;
_target.height = _height + dy;
}
event.updateAfterEvent();
}
protected function upListener( event:MouseEvent ):void
{
this.systemManager.removeEventListener(MouseEvent.MOUSE_MOVE, moveListener);
this.systemManager.removeEventListener(MouseEvent.MOUSE_UP, upListener);
}
override protected function updateDisplayList( unscaledWidth:Number, unscaledHeight:Number ):void
{
super.updateDisplayList(unscaledWidth, unscaledHeight);
var g:Graphics = this.graphics;
g.clear();
g.lineStyle(1, 0x000000);
g.drawRect(0, 0, unscaledWidth, unscaledHeight);
}
]]>
</fx:Script>
<control:ControlPoint id="topLeft" left="-4" top="-4"
mouseDown="mouseDownListener(event)" />
<control:ControlPoint id="topCenter" top="-4" horizontalCenter="0"
mouseDown="mouseDownListener(event)" />
<control:ControlPoint id="topRight" right="-4" top="-4"
mouseDown="mouseDownListener(event)" />
<control:ControlPoint id="middleLeft" left="-4" verticalCenter="0"
mouseDown="mouseDownListener(event)" />
<control:ControlPoint id="middleRight" right="-4" verticalCenter="0"
mouseDown="mouseDownListener(event)"/>
<control:ControlPoint id="bottomLeft" left="-4" bottom="-5"
mouseDown="mouseDownListener(event)" />
<control:ControlPoint id="bottomCenter" bottom="-5" horizontalCenter="0"
mouseDown="mouseDownListener(event)" />
<control:ControlPoint id="bottomRight" right="-4" bottom="-5"
mouseDown="mouseDownListener(event)" />
</s:Group>
아래는 위 코드의 결과물입니다.
Flex 4 배포버전 : 4.0.0.14159 [2010/03/21]
Flex 4 현재버전 : 4.1.0.16076 [2010/06/10]
<s:Group xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
xmlns:mx="library://ns.adobe.com/flex/mx"
xmlns:control="org.jhl.control.*"
width="64" height="64">
<fx:Script>
<![CDATA[
override protected function updateDisplayList( unscaledWidth:Number, unscaledHeight:Number ):void
{
super.updateDisplayList(unscaledWidth, unscaledHeight);
img.width = unscaledWidth;
img.height = unscaledHeight;
}
]]>
</fx:Script>
<s:BitmapImage id="img" source="@Embed('assets/rss.png')" />
<control:ControlBox target="{this}" width="{this.width}" height="{this.height}"/>
</s:Group>
import java.io.*;
import java.net.*;
/* 843포트에서 대기하는 서버소켓은 클라이언트가 접속하여 정책파일을 요청하면 정책파일의 내용을
* 출력스트림으로 전송해 주고, 소켓을 닫는다.
* 액션스크립트 클라이언트(Flex)에서 서버(채팅서버 등)에 접속하기 위해서는 서버가 실행중인 호스트에
* 이 정책파일서버가 먼저 실행되고 있어야 한다.
*/
public class PolicyServerServlet {
public static void main(String[] args) throws Exception {
ServerSocket ss = new ServerSocket(843);
Socket client = null;
PrintWriter toClient = null;
System.out.println("정책파일서버실행....");
while (true) {
client = ss.accept();
InputStreamReader isr = new InputStreamReader(
client.getInputStream());
char[] buf = new char[1024];
int read = isr.read(buf);
String request = new String(buf, 0, read);
if (request.equals("<policy-file-request/>\0")) {
System.out.println("정책파일요청접수됨");
} else {
System.out.println("정책파일요청아님");
continue;
}
// System.out.println("정책파일서버요청문자열:"+request);
toClient = new PrintWriter(client.getOutputStream());
String policy = "<?xml version='1.0'?>";
policy += "<cross-domain-policy>";
policy += "<site-control permitted-cross-domain-policies='*'/>";
policy += "<allow-access-from domain='*' to-ports='*' />";
policy += "</cross-domain-policy>";
toClient.println(policy);
toClient.flush();
client.close();
System.out.println("정책파일 전송완료");
}
}
}
https://github.com/flex-users/flex-iframe/downloads
1. flexiframe.swc를 lib폴더에 삽입
2. xmlns:code="http://code.google.com/p/flex-iframe/"
3. <code:IFrame source="http://www.naver.com" width="100%" height="100%"/>
출처 : http://distress.tistory.com/18
<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute" creationComplete="init()"
verticalScrollPolicy="off" horizontalScrollPolicy="off"
fontSize="12" width="420" height="600">
<mx:Script>
<![CDATA[
import mx.collections.ArrayCollection;
private var fontListAc:ArrayCollection;
//Flex 에서 적용되는 폰트의 종류를 알아보도록 하자.
//리스트에 나오는 폰트는 현재 시스템에 설치된 폰트일뿐...모두 사용가능한것은 아니다.
//바뀌는것도 있고 아닌것도 있고....이걸 구별해서 적용 가능한 폰트만 쓸수있으면 좋겠는데....
private function init():void{
var allFonts:Array = Font.enumerateFonts(true);
var text:String;
fontListAc = new ArrayCollection();
allFonts.sortOn('fontName', Array.CASEINSENSITIVE);
for each(var font:Font in allFonts){
fontListAc.addItem(font.fontName);
//이 함수가 test_str 이 정상적으로 표시가능한지 아닌지 구별해 준다고는 나와있는데..
//뭐 해봐도 항상 false 만 뱉어낸다. 사용을 잘못한건지....음....
//font.hasGlyphs(test_str);
}
fontList.dataProvider = fontListAc;
}
]]>
</mx:Script>
<mx:TextArea id="sampleText" x="10" y="10" width="400" height="320">
<mx:text>이 문장은 번역기일 뿐입니다.
This statement is only a translation date.
該聲明只是一個翻譯日期.
この文は翻訳機である場合のみです.
★☆●◎◆□▲▽→♤♧⊙◈▤☎☜
</mx:text>
</mx:TextArea>
<mx:Text x="339" y="340" text="fontSize:" width="70" textAlign="center" fontSize="13"/>
<mx:NumericStepper id="fontSize" minimum="5" maximum="100" value="25"
change="sampleText.setStyle('fontSize', fontSize.value);" creationComplete="sampleText.setStyle('fontSize', fontSize.value);"
x="339" y="370" width="70" textAlign="center"/>
<mx:List id="fontList" x="10" y="338" width="321" height="250"
change="sampleText.setStyle('fontFamily', fontListAc.getItemAt(fontList.selectedIndex))"/>
</mx:Application>
TextInput 으로 사용자에게 어떤 입력을 받게 되는 경우가 있습니다.
이때에 보다 많은 내용(예를 들어 사용자의 자기소개 같은 값)을 입력 받아야 하는 경우가 생기는데, 이때 사용하면 좋은 컴포넌트가 TextArea 입니다. TextArea 컴포넌트로 여러줄로 입력을 받게되면 사용자는 필연적으로 엔터키를 눌러 개행(줄바꿈)을 하게 되는데 오늘 이야기해보고자 하는 것은 이 개행에 대한 처리입니다.
문자열에 엔터를 처리하기 위해서 이스케이프 문자인 백슬래시(\)를 사용하여 처리를 하게 되는데, 보통 흔히 알고 있기로는 엔터를 처리하기 위해 \r\n 을 문자열에 포함시키면 된다고 아실 겁니다. 저도 학교에서 C수업을 들을때 그렇게 배웠습니다.
\n : 라인피드(linefeed) - 커서를 해당 줄의 다음줄로 옮김
커서를 해당줄의 왼쪽으로 옮겨 다음줄로 내리면 엔터키를 친 것과 동일한 효과가 나오게 됩니다.
하지만 TextArea 에서는 \r 만으로 엔터키 처리를 하고 있습니다.
위와 같이 엔터키를 넣게 되면 실제로 TextArea의 text 속성에서 받는 문자열에는 "텍스트\r입력" 이라고 들어있게 되는 것이죠.
사용자에게 입력받은 해당 데이터를 서버에 저장하고 불러와 다시 TextArea 에 넣어줄때에 아무런 처리를 하지 않아도 상관없습니다. 어짜피 넣어진대로 다시 넣는 것이니까요. 하지만 서로 다른 언어에서 같은 데이터를 사용시에는 문제가 됩니다. 언어들마다 엔터의 처리가 다르기 때문이죠.
위에서 처럼 \r\n 으로 엔터키를 처리하는 경우도 있고, \r 만으로도, \n 만으로도 처리하는 경우가 있습니다. 만약 \r\n으로 엔터를 처리하는 시스템에서 만들어 놓은 문자열을 TextArea 에 대입하면 어떻게 될까요? "텍스트\r\n입력" 이라고 TextArea의 text 속성에 넣으면 아래와 같은 현상이 발생합니다.
위에서 보시는 것 처럼 \r 과 \n 둘다 엔터키로 인식되네요.
참고로 말씀드리자면 MXML 상에서 위의 이스케이스 문자를 인식시키실때는 아래와 같이 하시면 됩니다.
- <mx:TextArea text="텍스트\r\n입력" /> <!-- 백슬러시(\)를 문자열로 인식함 -->
- <mx:TextArea text="{'텍스트\r\n입력'}" /> <!-- 백슬러시(\)를 특수문자로 인식함 -->
물론 더 좋은 방법들이 있을꺼라 생각되지만, 저는 플렉스쪽에서 변환해서 쓰고 저장시에도 변환해서 보내기로 결정을 했습니다.
그래서 아래와 같은 코드를 구현하였습니다.
- public static function getStringForFlex(value:String):String
- {
- var javaReturnExp:RegExp = /\r\n/g; // \r\n 으로 된 문자열을 모두 찾는다.
- return value.replace(javaReturnExp, "\r"); // \r 으로 변경하여 반환한다.
- }
- public static function getStringForJava(value:String):String
- {
- var flexReturnExp:RegExp = /\r/g; // \r 으로 된 문자열을 모두 찾는다.
- return value.replace(flexReturnExp, "\r\n"); // \r\n 으로 변경하여 반환한다.
- }
네이밍이 좀 별로네요.. ;;
엔터키의 처리가 다르다는 것을 알아두시면 좋겠네요. ;)
ps. Flash Player 10 이 공개된 이후 FP10 이 설치된 브라우저에서 이전 포스트의 swf 파일을 로드하지 못하는 경우가 생기네요.
정확하게 이유는 모르겠습니다. 티스토리 포럼에 같은 내용의 버그신고가 이어지고 있는데, 자세히 알아봐야겠습니다.
from http://blog.flashplatform.kr/189
[출처] Flex Textarea 개행문자|작성자 맹
var result:String = value.toXMLString(); //String으로 변환
var pattern:RegExp = /\n */g; //개행문자를 없앨 정규식
result = result.replace(pattern, ""); //개행문자를 없엔다
var pathsCollection:ArrayCollection = new ArrayCollection();
for(var i:int = 0 ; i < paths.length ; i++){
var folderArr:Array = paths[i].split('/');
var folderNum:int = folderArr.length;
var folderLabel:String = '';
for(var j:int = 0 ; j < folderNum; j++){
trace(folderLabel+folderArr[j]);
pathsCollection.addItem({label:folderLabel+folderArr[j],level:j,path:folderArr});
folderLabel += '...';
}
}
출처 : http://sjp007.mireene.com/tc/entry/Tip-5?category=4
*세션 가져오는 법
SessionV fooAttrib = new SessionV();
Vector objList = new Vector();
try{
fooAttrib=(SessionV)flashgateway.Gateway.getHttpRequest().getSession().getAttribute("ss_SVS");
objList.add(fooAttrib);
DataGrid
createApp => if(aReDeviceInfo == undefined || aReDeviceInfo.length<1){
dgDevice.rowCount = 0;
addRow();
}else{
dgDevice.rowCount = aReDeviceInfo.length;
}
funciton showDetail(event):Void{
cidVboxSub.visible = true;
cidVboxSub.height = "100%";
stackNum.text = dgDevice.getItemAt(event.ItemIndex).STACK;
cidNum.text = event.itemIndex;
cidDeviceSLSizeX.text = dgDevice.getItemAt(event.itemIndex).SLSIZE_X;
for(var j=0;j<3;j++){
if(dia.getItemAt(j).LABEL == dgDevice.getIntemAt (event.itemIndex.WFDIAMETER){
dia.selectedIndex = j;
}
}
function addRow():Void{
aReDeviceInfo.push({STACK:String(aReDeviceInfo.length+1),GCM_CODE:" ",......
MASTER_SEQ:" "
});
dgDevice.dataProvider = aReDeviceInfo;
crateApp();
<focusing Out 할 때마다>
private function setGridValues(compId):Void{
if(compId == "cidDeviceSLSizeX"){
dgDevice.getItemAt(Number(cidNum.text)).SLSIZE_X = this[compId].text;
}
< mx:DataGrid id="dgDevice" width="766" cellpress="showDetail(event)"
showHeaders="false" cellFocusOut="sizeChange(event)"
<mx:DataGridColumn cellRendrer="{com.infonia.rendere.DeviceDelRenderer}"
출처 : http://sjp007.mireene.com/tc/entry/Flex-Log-log4j-%C3%B3%B7%B3-%BB%E7%BF%EB%C7%CF%B1%E2?category=4
///////////////////////////////////////////////////////////////
// LogUtil.as
///////////////////////////////////////////////////////////////
package utils
{
import flash.utils.getQualifiedClassName;
import mx.logging.ILogger;
import mx.logging.Log;
import mx.logging.LogEventLevel;
import mx.logging.targets.TraceTarget;
public class LogUtil
{
public function LogUtil()
{
}
public static function initLogging():void{
//create a Target
var logTarget:TraceTarget = new TraceTarget();
logTarget.filters = ["none"]; //이부분을 적절히 변경해주면 된다. Ex> "comp.*"
//Log all log levels
logTarget.level = LogEventLevel.DEBUG;
//Add category, date, time, and log level to the output
logTarget.includeCategory = false;
logTarget.includeDate = true;
logTarget.includeTime = true;
logTarget.includeLevel = true;
//Begging logging
Log.addTarget(logTarget);
}
public static function getLogger(c:*):ILogger{
var className:String = getQualifiedClassName(c).replace("::", ".");
return Log.getLogger(className);
}
}
}
///////////////////////////////////////////////////////////////
// Main Application
///////////////////////////////////////////////////////////////
<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:fx="http://ns.adobe.com/mxml/2009"
layout="vertical"
xmlns:mx="library://ns.adobe.com/flex/mx" minWidth="955" minHeight="600"
xmlns:comp="comp.*"
creationComplete="creationCompleteHdlr()">
<fx:Declarations>
<!-- Place non-visual elements (e.g., services, value objects) here -->
</fx:Declarations>
<fx:Script>
<![CDATA[
import utils.LogUtil;
private function creationCompleteHdlr():void{
LogUtil.initLogging();
}
]]>
</fx:Script>
<comp:LogComp1/>
<comp:LogComp2/>
</mx:Application>
///////////////////////////////////////////////////////////////
// LogComp1.mxml
///////////////////////////////////////////////////////////////
<?xml version="1.0" encoding="utf-8"?>
<s:Group xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
xmlns:mx="library://ns.adobe.com/flex/mx" width="400" height="300">
<fx:Declarations>
<!-- Place non-visual elements (e.g., services, value objects) here -->
</fx:Declarations>
<fx:Script>
<![CDATA[
import flash.utils.getQualifiedClassName;
import mx.logging.ILogger;
import mx.logging.Log;
import utils.LogUtil;
private var logger:ILogger = LogUtil.getLogger(this);
private function test():void{
logger.debug("comp1 debug test");
}
]]>
</fx:Script>
<s:Button label="log comp1" click="test()"/>
</s:Group>
///////////////////////////////////////////////////////////////
// LogComp2.mxml
///////////////////////////////////////////////////////////////
<?xml version="1.0" encoding="utf-8"?>
<s:Group xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
xmlns:mx="library://ns.adobe.com/flex/mx" width="400" height="300">
<fx:Declarations>
<!-- Place non-visual elements (e.g., services, value objects) here -->
</fx:Declarations>
<fx:Script>
<![CDATA[
import mx.logging.ILogger;
import mx.logging.Log;
import utils.LogUtil;
private var logger:ILogger = LogUtil.getLogger(this);
private function test():void{
logger.debug("comp2 debug test");
}
]]>
</fx:Script>
<s:Button label="log comp2" click="test()"/>
</s:Group>
출처 : http://sjp007.mireene.com/tc/?page=5
구성
Adobe Flash Builder 4.6 Premium
Spring 3.2 FrameWork
Tomcat 7.0(Web Application Server)
MyBatis 3.2(iBatis 2.5 이후로 아파치 재단 지원력에서는 현재의 정보와 신기술을 맞춰갈 수 없다 판단하여 구글 코드 팀으로 옮겼단다.)
blazeds 4.0(데이타 서비스 통신)
spring-flex-1.5 integration(Spring 과 Flex를 통신하기 위해 직접 클래스를 만들어 써야 하는 수고가 없도록 spring project 팀들이 고맙게도 만들어 두었다. ㄱㅅㄳ~)
SLF4J(Simple logging Facade for Java) (로깅찍는거) 요즘 이거 많이쓴다.
이유는 : http://devday.tistory.com/entry/SLF4J%EC%9D%98-%EC%9E%A5%EC%A0%90 참고.
일단 플렉스를 이클립스의 플러그인으로 깔고 톰캣도 설치되있다는 전제하에 시작.
먼저 이클립스의 왼쪽 빈공간에서 마우스 오른쪽을 눌러 New > Flex Project를 선택.
Project name에 자기가 입력하고 싶은 프로젝트 이름을 입력. Next
Application Server Type은 자바를 이용할것이니 Java, Use remote object access service는 blazeds를 이용할 것이니 그곳을 선택.
Java source folder 는 src로 그냥 나두어도 되는데 나는 java_src(flex는 flex_src로 되있는데 자바는 src로 되있으면 이샹해서;;)로 바꿔줬다.
Output folder 역시 기본은 bin-debug나 WebContent로 바꿔 주었다.(즉 나는 컴파일된 파일을 WebContent 밑으로 바로 놓겠다는 거다)
Target runtime 옆의 New 버튼을 클릭.
Apach 폴더의 자기에 맞는(나는 7.0)버젼을 찾아 선택해 주면 아래의 항목들이 기본으로 보인다. Finish를 눌러 완료.
이제 톰캣을 설정하였으므로 Target runtime에서 콤보박스 화살표를 누르면 방금 설치한 이름의 톰캣이 보인다.
그걸로 바꿔주고 Next.
Output folder URL에 일단 로컬에서 돌리기때문에 톰캣의 기본설정 포트 8080으로 Context root가 TEST 이므로 TEST 위와 같이 한다. Finish.
보면 output folder를 바꿨기 때문에 bin-debug 폴더가 안보이고 WebContent 폴더밑으로 기본 파일들이 생긴걸 확인할 수 있다.
다음으로 Spring과 Flex를 연동 해야한다.
모든 jar파일들은 합해놓은게 http://blog.naver.com/sjp007774/150153185137 여기에 있다.
먼저 Spring을 사용하기 위하여 다운.
http://www.springsource.org/download/community
다운받은 Spring 폴더의 libs(3.2 기준)밑의 필요한 파일들(난 그냥 잘몰라서 다 복사하였다;;)을 복사.
MyBatis를 사용하기 위하여 MyBatis를 다운.
http://code.google.com/p/mybatis/downloads/list?can=3&q=Product%3DMyBatis
다운 받은 MyBatis 폴더의 jar파일(mybatis-3.2.0-SNAPSHOT.jar)을 복사.
MyBatis와 Spring을 연동하기 위하여 필요한 jar파일을 다운.
http://code.google.com/p/mybatis/downloads/list?q=label:Product-Spring
다운받은 폴더의 jar파일(mybatis-spring-1.1.1.jar)을 복사.
Spring의 Aspect을 사용하기 위해(3.0 부터는 Aspectj 관련 jar파일들이 함께 있지 않다.) 다운.
http://www.eclipse.org/aspectj/downloads.php
주의 할게 내가 잘몰라 이상한걸 받았을수도 있는데 다른 파일들은 모두 zip으로 압축이 되있는반면 jar로 압축이 되있었다. 압축을 풀면 lib폴더가 보인다.
(이것때문에 엄청 고생했다. 당연히 jar파일이라서 그대로 넣으면 될줄 알았다 ㅠㅠ)
압축을 푼 lib 폴더 밑의 jar파일들(aspectjrt.jar, aspectjtools.jar, aspectjweaver.jar, org.aspectj.matcher.jar 이역시 몰라서 다 복사했다;;)을 복사.
Spring의 AOP alliance(namespace AOP를 사용할 수 있게 해줌)를 사용하기 위하여 다운.
http://sourceforge.net/projects/aopalliance/
이아이는 바로 jar파일로되있었다.
aopalliance.jar 파일을 복사.
SLF4J(log4j 같은거) 를 사용하기 위하여 다운.
http://slf4j.org/download.html
다운받은 폴더의 jar파일들(slf4j-api-1.7.2.jar, slf4j-log4j12-1.7.2.jar)을 복사.
log4jdbc(쿼리의 ?에 바로 값을 대입하여 보여줌)를 사용하기 위하여 다운.
http://code.google.com/p/log4jdbc/downloads/list
다운받은 lib폴더의 jar파일(slf4j-api-1.6.0.jar)을 복사.
log4j(로깅 찍는거)를 사용하기 위하여 다운.
log4j-1.2.16.jar 파일을 복사
지금까지 복사한 모든 파일들을 WebContent/WEB-INF/lib 폴더밑으로 다 넣는다.
다음으로 WEB-INF/web.xml 파일을 수정해야 된다.
[web.xml]
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" id="WebApp_ID" version="3.0">
<display-name>TEST</display-name>
<context-param>
<param-name>flex.class.path</param-name>
<param-value>/WEB-INF/flex/hotfixes,/WEB-INF/flex/jars</param-value>
</context-param>
<!-- Http Flex Session attribute and binding listener support
<listener>
<listener-class>flex.messaging.HttpFlexSession</listener-class>
</listener>-->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!-- MessageBroker Servlet
<servlet>
<servlet-name>MessageBrokerServlet</servlet-name>
<servlet-class>flex.messaging.MessageBrokerServlet</servlet-class>
<init-param>
<param-name>services.configuration.file</param-name>
<param-value>/WEB-INF/flex/services-config.xml</param-value>
</init-param>
<init-param>
<param-name>flex.write.path</param-name>
<param-value>/WEB-INF/flex</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>MessageBrokerServlet</servlet-name>
<url-pattern>/messagebroker/*</url-pattern>
</servlet-mapping>-->
<servlet>
<servlet-name>MessageBrokerServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>
/WEB-INF/configs/web-application-config.xml
/WEB-INF/configs/bean-remoting-destination.xml
/WEB-INF/configs/mybatis-config.xml
</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>MessageBrokerServlet</servlet-name>
<url-pattern>/messagebroker/*</url-pattern>
</servlet-mapping>
<welcome-file-list>
<welcome-file>index.html</welcome-file>
<welcome-file>index.htm</welcome-file>
<welcome-file>index.jsp</welcome-file>
<welcome-file>default.html</welcome-file>
<welcome-file>default.htm</welcome-file>
<welcome-file>default.jsp</welcome-file>
</welcome-file-list>
<!-- for WebSphere deployment, please uncomment -->
<!--
<resource-ref>
<description>Flex Messaging WorkManager</description>
<res-ref-name>wm/MessagingWorkManager</res-ref-name>
<res-type>com.ibm.websphere.asynchbeans.WorkManager</res-type>
<res-auth>Container</res-auth>
<res-sharing-scope>Shareable</res-sharing-scope>
</resource-ref>
-->
</web-app>
servlet-name을 MessageBrokerServlet으로 하였기때문에 spring에서 MessageBrokerServlet-servlet.xml을 찾으려 할것이다.
하지만 파일들을 다른걸로 할거기 때문에 ContextLoaderListener를 달아준다.
xml파일 하나에 모든걸 다 때려넣을 수도 있지만 spring에서도 권장하듯이 서비스단, 데이타단, 화면단, 보안단 등등 이렇게 분할해 놓는게 나중에 유지보수 하기에도 좋다.
나는 설정을 가지고 있는 web-application-config.xml, flex의 remote object의 destination과 해당 자바정보를 갖고 있는 bean-remoting-destination.xml, 마지막으로
MyBatis에 관련한 mybatis-config.xml 세가지 xml로 분류하여 WEB-INF/configs 라는 폴더 밑에 넣어두었다.
servlet-mapping의 url-pattern의 /messagebroker/* (flex의 AMF통신) 오는건 spring의 DispatcherServlet 클래스에서 처리하게된다.
[web-application-config.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:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.2.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.2.xsd">
<!-- MessageBroker Configuration -->
<bean id="widwinMessageBroker" class="org.springframework.flex.core.MessageBrokerFactoryBean"/>
<bean class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping"
p:mappings="/*=widwinMessageBroker"/>
<bean class="org.springframework.flex.servlet.MessageBrokerHandlerAdapter"/>
<!-- Properties Configuration -->
<bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="locations">
<list>
<value>classpath*:resources/properties/**/*.properties</value>
</list>
</property>
</bean>
<!-- MyBatis Configuration -->
<bean id="sqlSessionFactoryBean" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="configLocation">
<value>/WEB-INF/configs/mybatis-config.xml</value>
</property>
<property name="dataSource" ref="dataSource" />
<property name="mapperLocations" value="classpath*:resources/mappers/**/*.xml" />
</bean>
<bean id="sqlSessionTemplate" class="org.mybatis.spring.SqlSessionTemplate">
<constructor-arg index="0" ref="sqlSessionFactoryBean" />
</bean>
<!-- DataSource Configuration -->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="${jdbc.widwin.driverClassName}"/>
<property name="url" value="${jdbc.widwin.url}"/>
<property name="username" value="${jdbc.widwin.username}"/>
<property name="password" value="${jdbc.widwin.password}"/>
</bean>
<!-- Transaction Configuration -->
<bean id="transactionProxy" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean" abstract="true">
<property name="transactionManager">
<bean class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
</property>
<property name="transactionAttributes">
<props>
<prop key="insert*">PROPAGATION_REQUIRED</prop>
<prop key="update*">PROPAGATION_REQUIRED</prop>
<prop key="*">PROPAGATION_REQUIRED,readOnly</prop>
</props>
</property>
</bean>
<!-- Common Logger Configuration -->
<aop:aspectj-autoproxy/>
<bean id="commonLogger" class="test.widwin.common.CommonLogger"/>
<!-- <bean id="nativeJdbcExtractor" class="org.springframework.jdbc.support.nativejdbc.CommonsDbcpNativeJdbcExtractor"/>
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource">
<ref local="dataSource"/>
</property>
<property name="nativeJdbcExtractor">
<ref local="nativeJdbcExtractor"/>
</property>
</bean> -->
</beans>
properties configuration에서 반드시 classpath*를 붙여주어야한다. 상대경로로 찾기때문에 안붙여주면 찾지 못한다.
[jdbc.test.properties]
#jdbc.widwin.driverClassName= oracle.jdbc.OracleDriver
jdbc.widwin.driverClassName=net.sf.log4jdbc.DriverSpy
#jdbc.widwin.url=jdbc:oracle:thin:@xxx.xxx.x.x:1521:TEST
jdbc.widwin.url=jdbc:log4jdbc:oracle:thin:@xxx.xxx.x.x:1521:TEST
jdbc.widwin.username=TEST
jdbc.widwin.password=test
slf4j를 사용하면 driverClassName과 url에 log4jdbc를 끼어넣어야 함.(xxx 를 아이피로 바꿔주면 됨)
[bean-remoting-destination.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:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:flex="http://www.springframework.org/schema/flex"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.2.xsd
http://www.springframework.org/schema/flex
http://www.springframework.org/schema/flex/spring-flex-1.5.xsd">
<!-- <context:annotation-config/> -->
<context:component-scan base-package="test.widwin.blogics.board"/>
<bean class="test.widwin.blogics.board.BoardController">
<flex:remoting-destination destination-id="BoardRO" message-broker="widwinMessageBroker"/>
</bean>
</beans>
<context:component-scan... 이 있으면 <bean에 class를 설정하지 않아도 됨. destination에 message-broker 꼭 설정해줘야 함. remoting-config.xml 파일은 관리안함.
[mybatis-config.xml]
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<!-- <environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</dataSource>
</environment>
</environments>
<settings>
<setting name="cacheEnabled" value="true"/>
<setting name="lazyLoadingEnabled" value="true"/>
<setting name="multipleResultSetsEnabled" value="true"/>
<setting name="useColumnLabel" value="true"/>
<setting name="useGeneratedKeys" value="false"/>
<setting name="autoMappingBehavior" value="PARTIAL"/>
<setting name="defaultExecutorType" value="SIMPLE"/>
<setting name="defaultStatementTimeout" value="25000"/>
<setting name="safeRowBoundsEnabled" value="false"/>
<setting name="mapUnderscoreToCamelCase" value="false"/>
<setting name="localCacheScope" value="SESSION"/>
<setting name="jdbcTypeForNull" value="OTHER"/>
<setting name="lazyLoadTriggerMethods" value="equals,clone,hashCode,toString"/>
</settings> -->
<!-- <mappers>
<mapper resource="resources/mappers/BoardMapper.xml"/>
</mappers> -->
<typeAliases>
<typeAlias alias="Board" type="test.widwin.blogics.board.vo.BoardBean"/>
</typeAliases>
</configuration>
<mappers>부분을 주석을 풀어서 관리해도 되나 web-application-config.xml에서 mapperLocations를 *로 잡아주었으므로
따로 관리하지 않아도 된다.
[TEST.mxml]
<?xml version="1.0" encoding="utf-8"?>
<s:Application xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
xmlns:mx="library://ns.adobe.com/flex/mx" minWidth="955" minHeight="600">
<fx:Script>
<![CDATA[
import mx.collections.ArrayCollection;
import mx.controls.Alert;
import mx.rpc.events.FaultEvent;
import mx.rpc.events.ResultEvent;
import test.widwin.modules.board.vo.BoardVO;
import test.widwin.utils.RemoteObjectUtil;
private function test2():void{
RemoteObjectUtil.doMethod("BoardRO", "getBoardList", getBoardListRstHdlr, [1]);
}
private function insertBoard():void{
var vo:BoardVO = new BoardVO();
vo.name = tiName.text;
vo.title = tiTitle.text;
vo.description = tiDescription.text;
vo.pass = tiPass.text;
RemoteObjectUtil.doMethod("BoardRO", "insertBoard", insertBoardRstHdlr, [vo]);
}
private function getBoardListRstHdlr(evt:ResultEvent):void{
adg.dataProvider = evt.result as ArrayCollection;
}
private function insertBoardRstHdlr(evt:ResultEvent):void{
Alert.show("등록 성공");
}
private function faultHdlr(evt:FaultEvent):void{
Alert.show(evt.fault.message.toString());
}
]]>
</fx:Script>
<fx:Declarations>
</fx:Declarations>
<s:layout>
<s:VerticalLayout/>
</s:layout>
<s:Button label="test" click="test2()"/>
<mx:AdvancedDataGrid id="adg">
<mx:columns>
<mx:AdvancedDataGridColumn headerText="이름" dataField="name"/>
<mx:AdvancedDataGridColumn headerText="제목" dataField="title"/>
<mx:AdvancedDataGridColumn headerText="내용" dataField="description"/>
</mx:columns>
</mx:AdvancedDataGrid>
<s:Form id="frm" defaultButton="{btnIns}">
<s:FormHeading label="게시판" backgroundColor="haloSilver" />
<s:FormItem sequenceLabel="i)" label="이름:" required="true">
<s:TextInput id="tiName" maxChars="64" />
</s:FormItem>
<s:FormItem sequenceLabel="ii)" label="제목:">
<s:TextInput id="tiTitle" maxChars="64" />
</s:FormItem>
<s:FormItem sequenceLabel="iii)" label="내용:" required="true">
<s:TextInput id="tiDescription" maxChars="32" />
</s:FormItem>
<s:FormItem sequenceLabel="iv)" label="비밀번호:" required="true">
<s:TextInput id="tiPass" maxChars="32" displayAsPassword="true" />
</s:FormItem>
<s:FormItem>
<s:Button id="btnIns" label="등록" click="insertBoard()" />
</s:FormItem>
</s:Form>
</s:Application>
[BoardVO.as]
package test.widwin.modules.board.vo
{
[RemoteClass(alias="test.widwin.blogics.board.vo.BoardBean")]
public class BoardVO
{
public var board_code:int;
public var registration_date:String;
public var name:String;
public var title:String;
public var description:String;
public var email:String;
public var homepage:String;
public var ip:String;
public var pass:String;
public var pos:int;
public var hit:int;
public var file_yn:String;
public var depth:int;
public var is_delete:String;
public var created_timestamp:Date;
public var creator_code:String;
public var updated_timestamp:Date;
public var updater_code:String;
public var total_row:int;
public var page:int;
}
}
[BoardController.java]
package test.widwin.blogics.board;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import test.widwin.blogics.board.service.IBoardService;
import test.widwin.blogics.board.vo.BoardBean;
@Controller
public class BoardController {
// final Logger logger = LoggerFactory.getLogger(BoardController.class);
@Autowired
private IBoardService iBoardService;
public List<BoardBean> getBoardList(int pageView){
return iBoardService.getBoardList(pageView);
}
public int insertBoard(BoardBean boardBean){
return iBoardService.insertBoard(boardBean);
}
}
[IBoardService.java]
package test.widwin.blogics.board.service;
import java.util.List;
import test.widwin.blogics.board.vo.BoardBean;
public interface IBoardService {
public List<BoardBean> getBoardList(int pageView);
public int insertBoard(BoardBean boardBean);
}
[BoardServiceImpl.java]
package test.widwin.blogics.board.service;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import test.widwin.blogics.board.dao.BoardDao;
import test.widwin.blogics.board.vo.BoardBean;
@Service("iBoardService")
public class BoardServiceImpl implements IBoardService {
@Autowired
private BoardDao boardDao;
@Override
public List<BoardBean> getBoardList(int pageView) {
return boardDao.getBoardList(pageView);
}
@Override
public int insertBoard(BoardBean boardBean) {
return boardDao.insertBoard(boardBean);
}
}
[BoardDao.java]
package test.widwin.blogics.board.dao;
import java.util.List;
import org.mybatis.spring.support.SqlSessionDaoSupport;
import org.springframework.stereotype.Repository;
import test.widwin.blogics.board.vo.BoardBean;
@Repository("boardDao")
public class BoardDao extends SqlSessionDaoSupport{
// @Resource(name="sqlSessionTemplate")
// private SqlSession sessionTemplate;
public List<BoardBean> getBoardList(int pageView){
// IBoardMapper iBoardMapper = sessionTemplate.getMapper(IBoardMapper.class);
// return iBoardMapper.getBoardList(pageView);
return getSqlSession().selectList("test.widwin.blogics.board.dao.IBoardMapper.getBoardList", pageView);
}
public int insertBoard(BoardBean boardBean){
return getSqlSession().insert("test.widwin.blogics.board.dao.IBoardMapper.insertBoard", boardBean);
}
}
신기한게 내부적으로 구현이 되있는진 몰라도 패키지 인터페이스명.메소드 이름하고 BoardMapper.xml 에서 같은 인터페이스이름으로 namespace를 정의하면 된다.
[IBoardMapper.java]
package test.widwin.blogics.board.dao;
import java.util.List;
import test.widwin.blogics.board.vo.BoardBean;
public interface IBoardMapper {
public List<BoardBean> getBoardList(int pageView);
public int insertBoard(BoardBean boardBean);
}
[BoardMapper.xml]
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="test.widwin.blogics.board.dao.IBoardMapper">
<select id="getBoardList" parameterType="int" resultType="Board">
SELECT *
FROM (SELECT BOARD.* ,ROWNUM AS RNUM ,FLOOR(((ROWNUM-1)/15)+1) AS PAGE
,COUNT(*) OVER() AS TOTAL_ROW
FROM (SELECT *
FROM BOARD
ORDER BY BOARD_CODE DESC) BOARD)
WHERE PAGE = #{pageView}
ORDER BY POS ASC
</select>
<insert id="insertBoard" parameterType="Board">
INSERT INTO BOARD(BOARD_CODE, REGISTRATION_DATE, NAME, TITLE, DESCRIPTION, PASS, IP)
VALUES((SELECT TRIM(NVL(MAX(BOARD_CODE + 1),1)) FROM BOARD), '201212', #{name}, #{title}, #{description}, #{pass}, '192.168.0.9')
</insert>
</mapper>
namespace가 인터페이스랑 같아야한다.
[log4j.xml] log4j.properties로 관리해도 됨(log4j.xml이 가독성도 좋고 관리가 용이함)
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">
<!-- An example log4j configuration xml file for log4jdbc -->
<!-- Logging levels are: -->
<!-- DEBUG < INFO < WARN < ERROR < FATAL -->
<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">
<appender name="stdout-appender" class="org.apache.log4j.ConsoleAppender">
<layout class="org.apache.log4j.PatternLayout">
<param name="ConversionPattern" value="%d{yyyy-MM-dd HH:mm:ss.SSS} %5p %c{1}: %m%n"/>
</layout>
</appender>
<appender name="sql-appender" class="org.apache.log4j.FileAppender">
<param name="File" value="./logs/sql.log"/>
<param name="Append" value="false"/>
<layout class="org.apache.log4j.PatternLayout">
<param name="ConversionPattern" value="-----> %d{yyyy-MM-dd HH:mm:ss.SSS} <%t> %m%n%n"/>
</layout>
</appender>
<appender name="sql-timing-appender" class="org.apache.log4j.FileAppender">
<param name="File" value="./logs/sqltiming.log"/>
<param name="Append" value="false"/>
<layout class="org.apache.log4j.PatternLayout">
<param name="ConversionPattern" value="-----> %d{yyyy-MM-dd HH:mm:ss.SSS} %m%n%n"/>
</layout>
</appender>
<appender name="jdbc-appender" class="org.apache.log4j.FileAppender">
<param name="File" value="./logs/jdbc.log"/>
<param name="Append" value="false"/>
<layout class="org.apache.log4j.PatternLayout">
<param name="ConversionPattern" value="%d{yyyy-MM-dd HH:mm:ss.SSS} %m%n"/>
</layout>
</appender>
<appender name="jdbc-connection" class="org.apache.log4j.FileAppender">
<param name="File" value="./logs/connection.log"/>
<param name="Append" value="false"/>
<layout class="org.apache.log4j.PatternLayout">
<param name="ConversionPattern" value="%d{yyyy-MM-dd HH:mm:ss.SSS} %m%n"/>
</layout>
</appender>
<!--
The Following 5 logs can be turned on and off while the server is running
LIVE in order to trace the SQL and/or all JDBC coming out of the application.
To turn a log on, set the level value to INFO or DEBUG (to see class name and
line number information in the log) The DEBUG setting is much more inefficient
but the output is much more useful.
To turn off JDBC logging completely, you must set all 5 logs to a level higher
than ERROR (FATAL is suggested.)
-->
<!-- log SQL (pre-execution) plus exceptions caused by SQL -->
<logger name="jdbc.sqlonly" additivity="false">
<level value="debug"/>
<appender-ref ref="sql-appender"/>
</logger>
<!-- log SQL with timing information, post execution -->
<logger name="jdbc.sqltiming" additivity="false">
<level value="debug"/>
<appender-ref ref="stdout-appender"/>
<!-- <appender-ref ref="sql-timing-appender"/> -->
</logger>
<!-- only use the two logs below to trace ALL JDBC information,
NOTE: This can be very voluminous! -->
<!-- log all jdbc calls except ResultSet calls -->
<logger name="jdbc.audit" additivity="false">
<level value="fatal"/>
<appender-ref ref="jdbc-appender"/>
</logger>
<!-- log the jdbc ResultSet calls -->
<logger name="jdbc.resultset" additivity="false">
<level value="fatal"/>
<appender-ref ref="jdbc-appender"/>
</logger>
<!-- log connection open/close events and dump of all open connection numbers -->
<logger name="jdbc.connection" additivity="false">
<level value="fatal"/>
<appender-ref ref="connection-appender"/>
</logger>
<!-- this log is for internal debugging of log4jdbc, itself -->
<!-- debug logging for log4jdbc itself -->
<logger name="log4jdbc.debug" additivity="false">
<level value="debug"/>
<appender-ref ref="stdout-appender"/>
</logger>
<!-- by default, log everything to the console with a level of WARN or higher -->
<root>
<level value="debug"/>
<appender-ref ref="stdout-appender"/>
</root>
</log4j:configuration>
[applicationContext.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:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:flex="http://www.springframework.org/schema/flex"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.2.xsd
http://www.springframework.org/schema/flex
http://www.springframework.org/schema/flex/spring-flex-1.5.xsd">
</beans>
리스너를 따로 달아줬어도 applicationContext.xml은 WEB-INF/밑에 위치하여야 함.
[service-config.xml]
<?xml version="1.0" encoding="UTF-8"?>
<services-config>
<services>
<service-include file-path="remoting-config.xml" />
<service-include file-path="proxy-config.xml" />
<service-include file-path="messaging-config.xml" />
<default-channels>
<channel ref="my-amf"/>
</default-channels>
</services>
<security>
<login-command class="flex.messaging.security.TomcatLoginCommand" server="Tomcat"/>
<!-- Uncomment the correct app server
<login-command class="flex.messaging.security.TomcatLoginCommand" server="JBoss">
<login-command class="flex.messaging.security.JRunLoginCommand" server="JRun"/>
<login-command class="flex.messaging.security.WeblogicLoginCommand" server="Weblogic"/>
<login-command class="flex.messaging.security.WebSphereLoginCommand" server="WebSphere"/>
-->
<!--
<security-constraint id="basic-read-access">
<auth-method>Basic</auth-method>
<roles>
<role>guests</role>
<role>accountants</role>
<role>employees</role>
<role>managers</role>
</roles>
</security-constraint>
-->
</security>
<channels>
<channel-definition id="my-amf" class="mx.messaging.channels.AMFChannel">
<endpoint url="http://{server.name}:{server.port}/{context.root}/messagebroker/amf" class="flex.messaging.endpoints.AMFEndpoint"/>
</channel-definition>
<channel-definition id="my-secure-amf" class="mx.messaging.channels.SecureAMFChannel">
<endpoint url="https://{server.name}:{server.port}/{context.root}/messagebroker/amfsecure" class="flex.messaging.endpoints.SecureAMFEndpoint"/>
<properties>
<add-no-cache-headers>false</add-no-cache-headers>
</properties>
</channel-definition>
<channel-definition id="my-polling-amf" class="mx.messaging.channels.AMFChannel">
<endpoint url="http://{server.name}:{server.port}/{context.root}/messagebroker/amfpolling" class="flex.messaging.endpoints.AMFEndpoint"/>
<properties>
<polling-enabled>true</polling-enabled>
<polling-interval-seconds>4</polling-interval-seconds>
</properties>
</channel-definition>
<!--
<channel-definition id="my-http" class="mx.messaging.channels.HTTPChannel">
<endpoint url="http://{server.name}:{server.port}/{context.root}/messagebroker/http" class="flex.messaging.endpoints.HTTPEndpoint"/>
</channel-definition>
<channel-definition id="my-secure-http" class="mx.messaging.channels.SecureHTTPChannel">
<endpoint url="https://{server.name}:{server.port}/{context.root}/messagebroker/httpsecure" class="flex.messaging.endpoints.SecureHTTPEndpoint"/>
<properties>
<add-no-cache-headers>false</add-no-cache-headers>
</properties>
</channel-definition>
-->
</channels>
<logging>
<target class="flex.messaging.log.ConsoleTarget" level="Error">
<properties>
<prefix>[BlazeDS] </prefix>
<includeDate>false</includeDate>
<includeTime>false</includeTime>
<includeLevel>false</includeLevel>
<includeCategory>false</includeCategory>
</properties>
<filters>
<pattern>Endpoint.*</pattern>
<pattern>Service.*</pattern>
<pattern>Configuration</pattern>
</filters>
</target>
</logging>
<system>
<redeploy>
<enabled>false</enabled>
<!--
<watch-interval>20</watch-interval>
<watch-file>{context.root}/WEB-INF/flex/services-config.xml</watch-file>
<watch-file>{context.root}/WEB-INF/flex/proxy-config.xml</watch-file>
<watch-file>{context.root}/WEB-INF/flex/remoting-config.xml</watch-file>
<watch-file>{context.root}/WEB-INF/flex/messaging-config.xml</watch-file>
<watch-file>{context.root}/WEB-INF/flex/data-management-config.xml</watch-file>
<touch-file>{context.root}/WEB-INF/web.xml</touch-file>
-->
</redeploy>
</system>
</services-config>
<default-channels 꼭 달아주어야 함.
BoardRO destination을 못 찾을때
[.flexProperties]
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<flexProperties enableServiceManager="false" flexServerFeatures="4" flexServerType="8" flexWarLocation="C:/자료/교육시이트/blazeds-bin-4.0.0.14931/blazeds.war" serverContextRoot="/TEST" serverRoot="C:/testWorkspace/TEST/WebContent" serverRootURL="http://localhost:8080/TEST" toolCompile="true" useServerFlexSDK="false" version="2"/>
serverContextRoot가 /WebContent로 잡혀있는데 꼭 맞는 contextRoot 바꿔줘야 함(여기서는 /TEST)
flex bug report에 기재되있음 http://bugs.adobe.com/jira/browse/FB-20894