Jrebel Ibatis Plugin.

DevStory 2010.04.14 10:23
내가 만들어서 올려두었던
Jrebel Ibatis Plugin이..


jrebel을 만든 회사의 사이트에서 거론되고 있었다.

내 블로그와 내가 올린 구글코드의 소스가.. ㅋ

기분이 묘하다. ㅎㅎ

http://www.zeroturnaround.com/forum/topic.php?id=448

문제는 저 코드가..
우리 사이트에 맞게 만들어진 것이라..
다른 사이트에서는 동작을 안 한다는거 -_-;;


저작자 표시 비영리 변경 금지
신고
Posted by 용식
TAG iBATIS, jrebel
Ibatis의 resultMap 클래스로 사용되는 Domain 클래스들에 대해서
리로드시 캐싱에서 지우는 것으로 만들어봤고..이제는 쿼리와 resultMap 클래스가 정의 되어 있는
xml 파일들을 모니터링하여 xml 파일 변경시 xml 파일을 리로드 하도록 해야 합니다.

다만, ibatis에서 최소 read 단위가 sqlMapConfig.xml 이어서...
이 xml이 커지면 로딩하는데 시간이 걸리게 됩니다.

더 작은 단위로 모니터링하여 로드되도록 하고 싶은데
아직 거기까지는 못 하였습니다.

일단,, 최소 ibatis에서 buildSqlMapClient 메서드를 실행 할 때 로딩하는 xml 파일을 모니터링 할 수 있도록
어딘가에 저장해두도록 해야 합니다.

그 역할을 하는 파일을
SqlMapFilesManager라고 이름을 지어봤습니다.

이 클래스를 호출하여 모니터링 대상 xml을 등록하는 메서드는 최초 ibatis에서 sql을 로딩하는 단계에 호출해주면 됩니다.
이 예제에서는 SqlMapXmlCache 클래스의 Init 메서드가 호출 될 때 xml 파일들을 등록해주면 될 것 입니다.

이 부분은 역시 바이트 코드를 직접 write하여 작성해야 합니다.

앞서 IbatisPlugin 클래스의 preinit 메서드에서


SqlMapXmlCache 클래스의 바이트코드를 직접 핸들링하기 위해 IntegrationFactory에 등록을 해주었습니다.

이제, ClassInfoCBP 클래스처럼 SqlMapXmlCacheCBP 클래스의 init() 메서드가 호출 될 때
(초기화를 위해 처음 한번만 호출 될 것 입니다. ) sqlMap.xml 파일들을 SqlMapFilesManager에 등록하고
SqlMapXmlCache.getSqlMapClient(); 메서드가 호출될때 마다  xml 파일의 변경 여부를 체크하여
변경시 다시
SqlMapClientBuilder.buildSqlMapClient(Reader reader) 메서드를
실행 SqlMapClient를 다시 만들도록 하는 것이 컨셉입니다.

SqlMapXmlCacheCBP 클래스를 대략 구현해 볼게요..



init 메서드가 실행 되기 전에 SqlMapXmlFielsManager의 registerXmlResource($1, $2) 메서드를 실행하고
SqlMapXmlCache 클래스에 checkAndReload라는 메서드를 새로 만들어 넣습니다.
그리고 SqlMapXmlCache.getInstance() 메서드가 실행 될 때 마다 chceckAndReload 메서드를 실행하도록
되어 있습니다.

여기서, $1, $2는 init 메서드가 받은 파라메터입니다.
(http://www.csg.is.titech.ac.jp/~chiba/javassist/tutorial/tutorial.html 참조..)

그다음에 SqlMapFilesManager 클래스의 registerXmlResource 메서드와 checkAndReload 메서드를 구현하여
xml파일을 등록하고, checkAndReload 메서드에서 xml파일의 최종 변경 시간을 확인하여 변경이 되었을 시
SqlMapClientBuilder 클래스의 buildSqlMapClient 메서드를 실행하도록 하면 될 것 입니다.

이를 위해서 xml 파일의 경로 URL와 이름등이 필요하겠죠...


굳이, 막 널리 알려지지 않은... Jrebel이라는 국한된 라이브러리의 플러그인을 이렇게 정리하는 이유는..
저도 우연한 기회에 이 플러그인을 만들어보게 되었고
그 과정에서 javassist 도 알게 되고.. 그리고 플러그인을 통해 기존 사용되고 있는 프레임워크에
자신들이 만든 라이브러리를 통합시키는 이 아이디어가 참 좋은 것 같아서 입니다..

소스 구현보다도 개념이나 이런 부분에 많은 도움을 받은 것 같습니다..


저작자 표시 비영리 변경 금지
신고
Posted by 용식
일단 Jrebel 플러그인의 메인이 되는 클래스는 org.zeroturnaround.javarebel.Plugin 인터페이스를 구현해야 합니다.


여기서 중요한 메서드가 preinit() 메서드 입니다. 앞서 얘기했듯이 Jrebel이 다양한 프레임워크에서 플러그인을 통해
클래스 실시간 로딩을 지원하는 방법은 컴파일 된 바이트코드를 핸들링하는 것 입니다.
이렇게 핸들링 할 클래스를 합쳐주는 메서드가 바로 preinit() 메서드입니다.

그리고 , void org.zeroturnaround.javarebel.Integration.addIntegrationProcessor(ClassLoader arg0, String arg1, ClassBytecodeProcessor arg2) 를 통해 등록되는 형식을 보면 arg1은 핸들링 할 클래스의 fullname이 그리고
arg2에는 그 클래스의 바이트코드를 핸들링 하는 클래스가 들어갑니다.

직접 CBP 클래스들을 보겠습니다.



ClassInfoCBP.java 입니다.

IbatisPlugin 클래스에서 addIntegrationProcessor에서 넘긴 ClassLoader와 핸들링 대상 클래스가 이 CBP 클래스의
ClassLoader와 CtClass 파라메터로 넘어옵니다.

보면, CtMethod m4 = ct.getMethod("getInstance","(Ljava/lang/Class;)Lcom/ibatis/common/beans/ClassInfo;");
ct즉, CtClass 파라메터로 넘어온 ClassInfo 클래스에서 getInstance() 메서드를 얻어오고 그 메서드가 실행되기 전에
IbatisBORemover.getInstance().checkAndRealodBO(Class clazz, CLASS_INFO_MAP); 을 실행하라는 의미입니다.

$1은 ClassInfo의 getInstance()메서드의 파라메터인 Class clazz를 뜻합니다.

 CLASS_INFO_MAP은 ClassInfo 클래스가 private static으로 가지고 있는 캐시 역할을 하는 Map 인스턴스 입니다.
SqlMap.xml 에서 사용되는 resultMap 클래스들은 최초 로딩시에 이 Map에 캐싱되어 있습니다. 따라서, jrebel이 resultMap으로 사용되는 클래스를 다시 클래스로더에 로딩을 해도, Ibatis는 그것을 인식하지 못 합니다. 왜냐면, Ibatis의 캐싱 방식이 클래스
자체를 캐싱해 놓는 방식이 아니라, 클래스에 선언되어 있는 메서드, 필드등을 뽑아내어 저장해 두었다가 사용하는 방식이기
때문입니다.

즉, 클래스가 변경되면 그 클래스를 캐시에서 없애 버려서 다시 loading해서 메서드와 필드정보를 뽑아내도록 해줘야
Ibatis에서 새롭게 변경된 필드등을 인식 할 수 있게 됩니다.

다만, 그전에 그 클래스가 reload대상인지 확인하고 reload대상이면 ibatis에서 제거하기전 reload를 시키고 그것을
CLASS_INFO_MAP에서 제거해야합니다. 그래야 나중에 xml 파일이 변경 되어서 sqlMapClient를 다시 로드 하더라도
문제가 발생하지 않습니다.

Jrebel은 내부적으로 변경 된 클래스가 new를 통해 생성 될 때 reload하게 되어 있는 것 같습니다.

따라서, xml파일이 변경되어 sqlMapClient를 다시 초기화 할 때 resultMap클래스로 되어 있는 Domain 클래스가 Jrebel에 의해
리로드 되기 전 상태 class를 다시 로딩 해 갈 가능성이 있기 때문입니다.

이 Map 인스턴스가 private이기 때문에 외부에서 접근을 할 수 없기 때문에, 이렇게 내부적으로 바이트코드를 직접 write하여 AOP와 비슷하게  중간에 도메인클래스 캐싱을 삭제 할 메서드를 호출 하도록 해주는 방식입니다.
(http://www.csg.is.titech.ac.jp/~chiba/javassist/tutorial/tutorial.html)

그럼, 저 IbatisBORemover는 어떻게 로딩 된 클래스를 알고, 그것을 캐시에서 지우는가...



Jrebel에서 제공하는 ClassEventListener 인터페이스를 사용해서 구현 할 수 있습니다.
Jrebel은 위 인터페이스를 구현하고 자기 자신을 Reloader에 add하고 있습니다.이렇게 되면 Jrebel에서 재 로딩 한 클래스를
받아 볼 수 있게 됩니다. 그 클래스를 , Ibatis 캐시에서 강제로 지워버려서 Ibatis가 새로운 클래스 정보를 다시 읽어 오도록
하는 것이 이 CBP 클래스의 컨셉입니다.

그리고 reloader.checkAndReload(clazz) 메서드를 사용해서
Ibatis에서 호출하는 클래스가 리로드 대상이면 먼저 리로드를 시키고 그 다음에 Ibatis 캐시 맵에서 지워버리는 로직을
구현했습니다. 그러면, Ibatis에서는 자동적으로 Jrebel에 의해 리로드 된 클래스를 다시 읽어 갈 것 입니다.

저작자 표시 비영리 변경 금지
신고
Posted by 용식
회사에서 jrebel을 사용하면서
ibatis 에서 사용하는 sqlMap.xml과 Domain 클래스들의 변경에 대한 이슈가 있어서
플러그인을 만들었습니다.

회사에서는 프레임웍이 있어서 순수 ibatis를 위한 플러그인은 아니라서
소스를 그대로는 못 보여드리고..
순수 ibatis (2.3.x)에서 사용 가능한 ibatis 플러그인을 한번.. 의사 코드를 사용해서
만들어 보려고 합니다.

우선 jrebel의 플러그인은 이미 컴파일된 클래스의 바이트 코드에
원하는 코드를 끼워넣어서
thread를 가로채는 방식으로 원하는 메서드를 먼저 실행하거나
나중에 실행하도록 하는 것이 기본적인 구현 방식입니다.

기본적으로 ibatis를 사용 할 때는

이런식이 됩니다.

일반적으로 매번 저렇게 SqlMapClient를 build하지 않고 was 구동시나 초기화 단계해서 sqlMapClient를 생성해 놓고
가져다가 쓰는 방식을 사용 할 것 입니다. 최초 초기화는 SqlMapXmlCache의 init()메서드에서 실행한다고 가정하고 초기화 단계에서 초기화 된 SqlMapClient를 SqlMapXmlCache라는 클래스의 getSqlMapClient(); 메서드를 사용해 가져와서 쿼리를 수행한다고 가정 하겠습니다. 즉,

이런식이 되겠지요....


만들려고 하는  플러그인의 컨셉은 이렇습니다.

쿼리를 실행하기 위해서 위 SqlMapXmlCahce.getSqlMapClient()의 메서드가 호출 되는 순간을 캐치하여
저 메서드 호출 전에 sqlMap.xml 파일들의 변경 여부를 체크하여, 변경된 xml이 있으면 SqlMapClientBuilder.buildSqlMapClient(reader); 메서드를 실행하여 xml을 다시 로딩하는 것 입니다.

(엄격히 얘기하면 캐치한다기 보다는 그냥 컴파일 된 클래스에 끼워 넣는다고 하는 것이  맞는 것 같습니다.)

단, Ibatis에서는 최초 xml 로딩시 내부에서 resultMap 클래스로 사용된 클래스들을 static Map에 캐시하여 놓고 사용
하기 때문에, 만약에 resultMap으로 사용된 Domain 클래스에 필드가 추가되거나 하는 것들은 buildSqlMapClient 메서드를
통해 초기화 하더라도 ibatis에서 인식하지 못 하게 됩니다.


따라서, resultMap으로 사용된 클래스가 jrebel에 의해 다시 로딩되었다면, 그 클래스를 ibatis의 캐시에서 지워버리는
부분이 추가적으로 필요합니다.

이 부분은 jrebel sdk에 있는 인터페이스 ClassEventListener 를 사용하여 처리해보도록 하겠습니다.


저작자 표시 비영리 변경 금지
신고
Posted by 용식

Jrebel Ibatis plugin.

Java 2009.11.26 10:00
보니까.. Jrebel 에서 플러그인 제공하는 방식이
프레임워크에 소스를 끼워넣어서
인터셉트 혹은 특정 메서드 실행 후에 동작 할 수 있도록
해주는 방식이 있어서...

아주 심플하게 생각해서...
sqlmapclient를 사용하기 위해
SqlMapClientBuilder를 사용해서

SqlMapClient client = SqlMapClientBuilder.buildSqlMapClient(reader);
를 실행 할 때마다 reader를 체크하여 reload해주면 되지 않을까 생각...

그냥 쓸 때마다 xml을 읽어오도록 구성 되어 있으면 문제 없지만 보통은 최초 기동시에 xml 정보를 읽어 놓고
그것을 재사용 하는 방법을 사용하고 있을 것이므로... 재기동이 문제가 되는 경우가 있다.

일단 , 최종 수정일을 체크해야겠지만
동작되는지 보는게 급해서..그냥 만들어 보았다.






위 소스는 SqlMapClientBuilder.buildSqlMapClient(Reader reader) 가 호출 될 때마다
그전에 reader를 다시 읽어들이는 방식이라 굉장히 비효율적이다.
아니 비효율 정도가 아니라..SqlMapConfig.xml이 커지면..매번 저걸 호출하게 되므로
개발하기가 쉽지 않다 -.-;

반드시 파일 변경 여부를 체크하는 로직이 들어가야 할 듯....

어차피 사이트마다 SqlMapClient를 호출하는 부분이 다르기 때문에
어디에서 인터셉트를 해야 할지 천차만별이라...
아무튼...

Manager 클래스에서 적당히 xml을 체크해서 변경된 녀석이 있으면 reader를 다시 읽도록 하던가...
아니면..Ibatis를 더 까고 들어가서 (사실 IbatisPlugin이라고 하면 이게 맞을 듯...)
어딘가 Map 같은 걸로 관리 되고 있을 각각의 쿼리 XML 파일만 다시 load되도록 해주면
좀 더 멋진 코드가 작성 될 수 있을 것 같다.

라고 하지만..아이바티스..2.3 버전은
JavassistClassBytecodeProcessor 의 제약에 너무 많이 걸린다..


암튼..일단, 플러그인 작동 방식이 궁금해서 작성해본 것... 우리 사이트에 맞게 수정해서 만들었는데
꽤 잘 된다...
JavassistClassBytecodeProcessor를 알게 된 것도.. 큰 행운..


저런 생각을 하는 사람들은
정말 뭐하는 사람들일까.. --; AOP 비슷하기도 하고... 대단하다..

저작자 표시 비영리 변경 금지
신고
Posted by 용식
TAG iBATIS, jrebel