더 이상 사용하지 않는 사내 레거시 프레임워크를 제거하는 작업 중 기존 프레임워크에서 제공하던 기능들을 모듈화 하여, 스프링 MVC 로 넘어가는 과도기 동안에 두 종류의 프레임워크에서 동시에 사용 가능한, 호환성을 제공하는 모듈을 개발하는 작업을 진행했다.
모듈화 작업 중 데이터 액세스(Data Access) 기능은 레거시 프레임워크가 제공하던 기존의 틀은 그대로 가져가면서 알맹이는 스프링에서 생성/관리하는 데이터 액세스 관련 빈(DataSource, SqlMapClinet 등)을 레거시 껍데기에 주입하여 사용하는 형태로 변경하고자 했다.
하지만 위와 같이 사용 시 레거시 프레임워크 틀에서 제공하는 class 파일에 수정이 필요하여 이를 해결하기 위해 레거시 프레임워크 jar 내에 class 파일을 변경해줘야 했다.
서비스 중인 프로젝트 대부분은 Ant 빌드를 사용하고 있다. Ant에서 제공하는 기본적인 명령어을 이용해 jar 앞축을 풀고, class 파일을 삭제하고, 다른 파일을 추가하고, jar를 다시 압축하는 작업을 할 수 있다. 위 작업을 통해 변경이 필요한 class를 원하는 형태로 동작하는 다른 class로 바꿔치기해줄 수 있었다.
이해를 쉽게 하기 위해 기존 프레임워크에서 제공하는 jar는 module_old, 문제가 되는 클래스 명은 Old, 신규로 제공되는 데이터 액세스(Data Access) 모듈은 module_new라고 가정해보자.
해당 작업 전 몇 가지 주의할 점이 있다.
<?xml version="1.0" encoding="UTF-8"?>
<project default="dist">
<target name="repackage">
<property name="line.seperator" value=" "/>
<propertyregex property="result.total.lib"
input="${toString:total.lib}"
regexp="${spliter.regexp}"
replace="${line.seperator}"/>
<property name="new.name.prefix" value="module_new-"/>
<property name="old.name.prefix" value="module_old-"/>
<!-- 변경하고자 하는 jar의 full path 찾기 -->
<for list="${result.total.lib}" delimiter="${line.seperator}" param="select.current.lib">
<sequential>
<if>
<contains string="@{select.current.lib}" substring="${old.name.prefix}"/>
<then>
<property name="old.jar.path" value="@{select.current.lib}"/>
</then>
</if>
<if>
<contains string="@{select.current.lib}" substring="${new.name.prefix}"/>
<then>
<property name="new.jar.path" value="@{select.current.lib}"/>
</then>
</if>
</sequential>
</for>
<!-- module_new.jar 압축 해제 -->
<property name="new.temp.dir" value="${project.classes.dir}/${new.name.prefix}unjar"/>
<unjar dest="${new.temp.dir}" src="${new.jar.path}"/>
<!-- module_old.jar 내에 com/old/Old.class 를 module_new.jar 의 com/old/Old.class 로 변경 -->
<antcall target="change-class-in-library">
<param name="source.file.package.name" value="com/old/"/>
<param name="source.file.classpath" value="${new.temp.dir}/com/old/Old.class"/>
<param name="destination.lib.name.prefix" value="${old.name.prefix}"/>
<param name="destination.lib.path" value="${old.jar.path}"/>
</antcall>
<delete dir="${new.temp.dir}" failonerror="true"/>
</target>
<!-- jar 내에 파일 변경 Task-->
<target name="change-class-in-library">
<available property="source.file.exist" file="${source.file.classpath}"/>
<if>
<equals arg1="${source.file.exist}" arg2="true"/>
<then>
<property name="destination.lib.temp.jar" value="${destination.lib.name.prefix}temp.jar"/>
<property name="destination.lib.temp.dir"
value="${project.classes.dir}/${destination.lib.name.prefix}unjar"/>
<!-- module_old.jar 압축 해제 -->
<unjar dest="${destination.lib.temp.dir}" src="${destination.lib.path}"/>
<!-- com/old/Old.class 바꿔치기 -->
<copy file="${source.file.classpath}"
todir="${destination.lib.temp.dir}/${source.file.package.name}"
overwrite="true" verbose="true" failonerror="true"/>
<!-- module_oldtemp.jar 로 압축 -->
<jar destfile="${project.webinf.lib.dir}/${destination.lib.temp.jar}">
<fileset dir="${destination.lib.temp.dir}"/>
</jar>
<!-- module_oldtemp.jar 를 module_old.jar 로 바꿔치기 -->
<copy file="${project.webinf.lib.dir}/${destination.lib.temp.jar}"
tofile="${destination.lib.path}"
overwrite="true" verbose="true" failonerror="true"/>
<!-- module_old.jar 를 압축 해제했던 디렉토리 삭제 -->
<delete file="${project.webinf.lib.dir}/${destination.lib.temp.jar}" failonerror="true"/>
<delete dir="${destination.lib.temp.dir}" failonerror="true"/>
</then>
<else>
<fail message="${source.file.classpath} do not exist."/>
</else>
</if>
</target>
</project>
개인적으로 기존 라이브러리 내부 파일을 이와 같은 방식으로 동작 방식을 바꿔 사용하는 것은 특별한 케이스가 아니고서는 매우 위험한 해결 방법이라고 생각한다. 이는 해당 라이브러리 사용 시 개발자가 예상치 못한 다른 기능에 심각한 오류를 발생시킬 수 있다.
사실 처음에 고안된 해결책은 이처럼 빌드 타임에 Ant Task를 통해 자동화된 형태로 class 파일을 변경해주는 방식이 아니었다.
수동으로 jar 파일을 디컴파일하여 원하는 방식으로 동작하는 Java 파일을 넣어주고 재컴파일하여 별도의 버전으로 jar를 생성해주는 방식을 생각했다. 이런 식으로 해결하게 될 경우 해당 jar 를 영문도 모른채 다른 누군가가 사용하게 될 경우 문제가 될 수 있고, 혹여나 아주 희박한 가능성으로 레거시 프레임워크에 패치 작업이 있을 경우가 있을 수 있다.
그래서 이와 같은 불편함을 없애기 위해 컴파일 타임, 빌드 타임 혹은 서버 로딩 타임에 자동화된 형태로 class 파일을 변경해줄 수 있는 방법이 없을까 여러 방법을 찾는 와중에 위와 같이 Ant 빌드를 이용해 빌드타임에 교체해주는 방법을 고안하게 되었다.