모 이런 거 어딘가 있다는 얘길 들었는데, 내가 만들면 더 잘 할 수 있을 거 같아서 한번 만들어 봤다.
하다가 귀찮아져서 겨우 문제 없이 돌아가는 수준에서 멈췄으니 고쳐 쓸 사람은 얼마든지 고쳐서 쓰시길...

용도는 아무래도 바이트 단위의 길이로 정의된 DB 컬럼에 데이타를 넣을 때,
어쩔 수 없이 잘라서 넣어야 하는 경우가 생기는데 이럴 때 이 메소드 한방으로 해결.
바이트 단위라는 부분에서 냄새를 맡았겠지만, 한글을 비롯한 한글자가 2바이트 이상을 사용할 때에 유용하겠다.
오라클 같은 경우 VARCHAR2 길이가 4000바이트 밖에 안 되다 보니 긴 내용을 넣기에 좀 부족한 느낌이 있는데,
그렇다고 LONG이나 CLOB를 쓰는 건 성능도 안 좋고, 다루기도 불편하니
이런 식으로 잘라서 VARCHAR2 컬럼에 넣는 방법을 종종 사용한다고 한다.

첫번째 메소드는 원하는 바이트수만큼만으로 이루어진 String 리턴
getMaxByteString(str, 400) 을 호출하면 str이라는 문자열에서 앞에서 최대 400바이트만큼 잘라서 문자열으로 만들어 리턴
즉, 리턴한 문자열을 보면 str이 400바이트 이상이면 399바이트 아니면 400바이트이겠고, 그보다 짧으면 통째로 리턴할 것이다.
[code java] // str이라는 문자열의 맨앞부터 최대 maxLen만큼의 바이트수로 이루어진 문자열을 잘라서 리턴 public static StringgetMaxByteString(String str, int maxLen) { StringBuilder sb = new StringBuilder(); int curLen = 0; String curChar; for (int i = 0; i < str.length(); i++) { curChar = str.substring(i, i+1); curLen += curChar.getBytes().length; if (curLen > maxLen) break; else sb.append(curChar); } return sb.toString(); } [/code]
두번 째 메소드는 원하는 바이트수(maxLen)만큼만으로 이루어진 String 배열을 str 문자열 끝까지 처리해서 리턴
getMaxByteStringArray(str, 400) 을 호출하면 str이라는 문자열에서 앞에서 최대 400바이트만큼 잘라서 문자열로 만들고
그 후에 문자열이 또 있으면 또 이어서 최대 400바이트만큼 잘라서 문자열을 만들고. 이걸 문자열 끝까지...
만들어진 문자열을 배열로 만들어서 리턴
별도 테이블을 만들어서 각 문자열을 하나의 로우에 저장하도록 구성한다면 이것도 괜찮을 듯.
[code java] // str이라는 문자열의 맨앞부터 끝까지 최대 maxLen만큼의 바이트수로 이루어진 문자열을 // 연속으로 잘라서 String 배열로 리턴 public static String[]getMaxByteStringArray(String str, int maxLen) { return getMaxByteStringArray(str, maxLen, -1); } [/code]
세번째 메소드는 원하는 바이트수(maxLen)만큼만으로 이루어진 String 배열을 최대 maxArrays만큼 길이로 리턴
getMaxByteStringArray(str, 400, 3) 을 호출하면 str이라는 문자열에서 앞에서 최대 400바이트만큼 잘라서 문자열로 만들고
그 후에 문자열이 또 있으면 또 이어서 최대 400바이트만큼 잘라서 문자열을 만들고. 이걸 문자열을 최대 3번까지...
만들어진 문자열을 배열로 만들어서 리턴
한 테이블의 3개의 컬럼에 문자열을 쪼개서 저장한다면 유용할 듯.
[code java] // str이라는 문자열의 맨앞부터 최대 maxLen만큼의 바이트수로 이루어진 문자열을 // 연속으로 잘라서 최대 maxArrays 길이의 배열로 리턴 public static String[]getMaxByteStringArray(String str, int maxLen, int maxArrays) { StringBuilder sb = new StringBuilder(); ArrayList strList = new ArrayList(); int curLen = 0; String curChar; for (int i = 0; i < str.length(); i++) { curChar = str.substring(i, i+1); curLen += curChar.getBytes().length; if (curLen > maxLen) { if (maxArrays == -1 || strList.size() <= maxArrays-2) { strList.add(sb.toString()); sb = new StringBuilder(); curLen = 0; i--; } else break; } else sb.append(curChar); } strList.add(sb.toString()); String[] strArr = new String[strList.size()]; for (int i = 0; i < strList.size(); i++) { strArr[i] = strList.get(i); } return strArr; } [/code]
아래는 위의 메소드를 호출하는 샘플 코드
이 코드 돌려 보고 싶으면, 위의 메소드들과 아래의 main 메소드 합쳐서 한 클래스에 넣고 돌리면 된다.
[code java] public static void main(String[] args) { StringBuilder sb = new StringBuilder(); for (int i = 0; i < 1000; i++) { sb.append("codePointAt을 사용하면 문자의 유니코드값을 구할 수 있잖아요.. 근데.. 문자의 유니코드값을 다시 문자로 변환해"); } Stringstr = sb.toString(); String newStr = getMaxByteString(str, 4000); System.out.println(newStr + ":" + newStr.getBytes().length); System.out.println("-----------------------------------------"); String[] strArr = getMaxByteStringArray(str, 4000); for (int i = 0; i < strArr.length; i++) { System.out.println(strArr[i] + ":" + strArr[i].getBytes().length); } System.out.println("-----------------------------------------"); String[] strArr2 = getMaxByteStringArray(str, 4000, 3); for (int i = 0; i < strArr2.length; i++) { System.out.println(strArr2[i] + ":" + strArr2[i].getBytes().length); } } [/code]
한 글자씩 잘라 붙이기라서 오래 걸릴 줄 알았는데, 꽤 긴 문자열도 0.1초 이내에 잘라 내니 쓸 만하군...

'Java_Web' 카테고리의 다른 글

오라클에서 SYSDATE를 이용한 시간 연산  (0) 2009.02.12
Derby 사용하기  (2) 2008.09.29
ASM - 자바 바이트코드 분석하기  (1) 2008.03.07
클래스 위치 찾아 주는 프로그램  (0) 2007.08.15
놀라운 발견  (0) 2007.08.12
AND

전부터 자바소스코드를 읽어들여 분석할 수 있는 방법이 없을까 하는 생각을 많이 했었다.

그러려면 거의 자바 컴파일러 수준이 되어야 할 것 같아서 엄두를 못 내다가 얼마전에 자바 바이트코드를 다룰 수 있는 라이브러리가 있다는 사실을 알게 되었다. 대표적인 것이 아파치의 BCEL과 지금 살펴 보려고 하는 ASM이다.

근데 이런 게 있다는 걸 알긴 했는데 도무지 사람들이 관심이 없어서인지 쓸만한 참고자료 찾기가 너무 힘들었다.

결국 ASM 사이트에서 이것저것 파일을 다운받아서 보다보니 내가 딱 원하는 기능의 예제까지 찾아낼 수가 있었다.

 

일단, BCEL(Byte Code Engineering Library)과 ASM은 자바소스파일을 컴파일해서 얻은 클래스파일(바이트코드)을 읽어 들여 변경하거나 분석하는 데 사용하는 라이브러리다. 자바소스파일 같은 경우는 코드상에 문제가 있을 수 있으니까 이걸 분석하는 건 문제가 있을 가능성이 있지만, 클래스파일은 자바소스코드상에 오류가 없어야 컴파일을 통해 만들 수 있는 것이므로 클래스파일을 분석하는 것이 보다 신뢰성 있는 분석결과를 얻을 수 있지 않겠는가? (나는 아직 클래스파일을 동적으로 변경하거나 이런 건 별로 관심이 없다 보니...)

여기저기 자료를 찾다 보니 BCEL보다 ASM이 속도가 빠르다고 해서 나는 ASM을 써 보기로 결정했다.

 

1. 라이브러리 준비

http://asm.objectweb.org/download/index.html에 가서 ASM 관련 파일을 다운받는다.

여기서는 asm관련 모든 패키지가 들어 있는 asm-all-3.1.jar를 사용하는데, 그러려면 asm-3.1-bin.zip을 다운받아서 압축을 풀면 그 안의 lib/all에 이 파일이 있다.

이 파일 외에도 다른 가이드나 예제 같은 것이 많으니 필요에 따라 함께 다운받으면 된다.

 

2. 분석프로그램 생성

※ 이 클래스가 컴파일되려면 컴파일할 때 클래스패스에 1에서 받은 asm-all-3.1.jar를 등록시켜야 한다.


import java.util.List; 
import org.objectweb.asm.ClassReader; 
import org.objectweb.asm.Opcodes; 
import org.objectweb.asm.tree.ClassNode; 
import org.objectweb.asm.tree.MethodNode; 

public class ClassInfo implements Opcodes { 
public static void main(final String[] args) throws Exception { 
ClassReader cr = new ClassReader("test.Test"); 
ClassNode cn = new ClassNode(); 
cr.accept(cn, ClassReader.SKIP_CODE); 

System.out.println("Class Name : " + cn.name + "\n"); 
System.out.println("Super Class : " + cn.superName + "\n"); 

System.out.println("Interfaces :"); 
List interfaces = cn.interfaces; 
for (int i = 0; i < interfaces.size(); i++) { 
System.out.println(interfaces.get(i)); 

System.out.println("\nMethods :"); 
List methods = cn.methods; 
for (int i = 0; i < methods.size(); ++i) { 
MethodNode method = (MethodNode) methods.get(i); 
System.out.println(method.name + method.desc); 
}

ClassInfo.java

 

코드설명 :

9: 분석하려는 클래스의 패캐지를 포함한 full-name을 인자로 ClassReader 객체를 생성한다.

    여기서 읽기 위해서는 분석하려는 클래스가 클래스패스 경로 안에 반드시 있어야 한다.

12: 아마 클래스파일을 파싱하는 작업이 아닐까 생각이 된다.(아직 잘 몰라서 그만...)

14: 클래스 이름 출력

16: 클래스가 상속한 클래스 출력

18~22: 클래스가 구현한 인터페이스 리스트 출력

24~29: 클래스 내의 메소드 정보 출력

 

3. 분석 대상 클래스를 생성 및 컴파일

위의 ClassInfo 클래스가 test.Test라는 클래스를 분석하려고 하는데 그러려면 아래와 같이 미리 Test를 만들어서 컴파일해서 클래스파일이 위의 클래스가 실행하는 클래스패스 안에 들어가 있어야 한다.

package test; 

import java.io.Serializable; 
import java.util.HashMap; 

public class Test extends HashMap implements Serializable { 

public static String str; 

static { 

str = "abced"; 

public Test() { 

super(); 

 }


public static void main(String[] args) throws Exception { 

String str = "abc.dfd.efef.fjskdl"; 

System.out.println(str.replace(".", "/")); 

Test test = new Test(); 

String msg = "message to be printed"; 

int count = 5; 

double d = 1.3; 

float f = 1.3f; 

long l = 170343702; 

HashMap map = new HashMap(); 

test.printMsg(msg, count, d, f, l, map); 

}


public void printMsg(String msg, int count, double d, float f, long l, HashMap map) { 

for (int i = 0; i < count; i++) System.out.println(msg); 

}


public String getString(int i, String str) { 

return "abc"; 

}

}

Test.java

 

4. 분석 실행 및 결과 확인

 

이렇게 한 다음 ClassInfo 클래스를 실행하면 아래와 같은 결과가 나온다.


Class Name : test/Test 
Super Class : java/util/HashMap 
Interfaces : 
java/io/Serializable 
Methods : 
<clinit>()V 
<init>()V 
main([Ljava/lang/String;)V 
printMsg(Ljava/lang/String;IDFJLjav 
getString(ILjava/lang/String;)Ljava


클래스나 수퍼클래스, 인터페이스는 무슨 내용인지 바로 알겠는데, 메소드 부분은 바로 안 들어온다.

짧은 지식으로 대강 설명을 드리자면,

9: <clinit>는 클래스의 static 블럭. ()는 인자가 없음. V는 void로 리턴타입이 없다는 뜻

10: <init>는 constructor. 나머지는 위와 동일

11: main은 main 메소드.[Ljava/lang/String;에서 [로 시작하니까 배열이고, L로 시작하니까 자바클래스일 것 같고, V니까 void

      다 합치면 void main(String[]) 이런 형태가 되려나?

12: 인자에서 L로 시작하는 String과 HashMap은 감이 오는데, IDFJ는 뭔가?

        Test.java 소스를 보면서 맞추면 I는 int, D는 double, F는 float, J는 long가 될 것 같네.

       primitive type은 바이트코드에서 한글자로 줄여서 표현하는 모양이다.

13: 맨 마지막이 V가 아니고 Ljava/lang/String; 이니까 String을 리턴하는 메소드로군...

 

 

이렇게 해서 간단하게 클래스파일에서 간단한 정보를 뽑아 보는 방법을 알아 보았다.

여기서 Test.java를 클래스패스에 연결이 영 안 되는 분은 ClassInfo.java의 8라인에 "test.Test" 대신 "java.lang.String"을 입력해서 실행해 보면 String 클래스의 정보가 쭉 나오는 것을 확인할 수 있다.

 

클래스파일을 이용한 분석의 좋은 점은 JSP 파일을 분석할 수도 있다는 것이다.

JSP를 실행하려면 웹컨테이너가 java파일로 변환하고, 이걸 다시 클래스파일로 컴파일해서 실행하는데 JSP를 실행하고 나면 웹컨테이너 디렉토리 어딘가에 클래스파일이 남아 있다. 그러니까 이 경로만 찾아서 적당히 클래스패스로 잡아 주면 JSP도 분석이 가능한 것이다.

참고로 톰캣의 경우는 {Tomcat_Root}/work/Catalina/localhost 디렉토리 아래에 생기니 클래스패스 잘 잡아서 테스트해 보는 것도 재미있을 수 있겠다.

 

사실, 이 정도 정보 외에도 메소드 내의 각 코드 라인별로도 분석이 가능하다.

그런데 이 정도까지 가게 되면 거의 어셈블리 언어 수준까지 읽을 수 있는 능력이 필요하다.

(ASM이 무엇의 약자인지 아무리 해도 못 찾겠는데, 내 생각에는 ASM이 어셈블리의 약자가 아닌가 하는 생각도 든다.)

나도 학교 다닐 때 보던 기억을 더듬어 어느정도 읽어내긴 했지만 그런 걸 모르는 사람은 거의 암호 수준일 것 같다.

모 어려운 것도 있고, 호기심이 발동한 분들이 열심히 공부하길 바라는 마음도 있고, 이걸 보면서 머리 속에 굉장한 아이디어가 떠오른 것도 있고 해서 더 이상 심화된 내용은 다루지 않아야겠다.

 

궁금하면 공부합시다!


AND

 


WAS에 jar 파일과 class 파일들을 만들어 올려서 사용하다 보면, 서버에서 에러가 나면서 어떤 클래스 파일에 문제가 있다고 메시지가 뜨는 경우가 많다.

그런데 이 클래스가 도무지 어디에 박혀 있는지 찾기가 쉽지 않다.

그냥 클래스파일이면 경로를 뒤지다 보면 나올 수도 있지만 jar 파일 안에라도 들어가 있으면 하나씩 열어 가면서 확인해야 한다.

그래서, jar 파일과 class 파일이 있는 디렉토리만 입력해 주면 포함된 모든 클래스 파일을 뒤져서 해당 클래스 파일을 찾아 주는 프로그램을 만들어 보았다.

본 S/W Architect 카테고리의 이전 포스트 중 '클래스 중복 확인 프로그램'과 기본 로직은 비슷하고 단지 이것은 그냥 필요한 것만 찾아 줄 뿐이다.

대신, 해당 클래스 파일을 모두 찾아 주기 때문에 같은 경로의 클래스 파일이 결과에 여러번 반복되어 나타난다면 중복되어 올라갔다고 볼 수 있으며 이런 것은 되도록 하나만 남도록 하는 것이 찾기 어려운 에러가 발생하는 것을 방지하는 좋은 방법이다.


사용법 :

1. 첨부된 zip 파일의 압축을 푼다.

2. lib.properties에 WAS 동작시 로딩되는 jar 파일 디렉토리(lib)와 class 파일 디렉토리(classes)를 모두 입력한다.

   jar를 포함한 디렉토리는 jar로 시작해서 일련번호를 붙이고, class파일을 포함한 디렉토리는 class로 시작해서 일련번호를 붙인다.

   단, 경로는 절대경로로 입력하며 경로구분자는 반드시 "/"로 해야 한다. "\"일 경우는 바꿔 주어야 한다.(이건 귀차니즘 때문에.. ^^;)

   웹애플리케이션을 개발할 경우, 보통 jar를 포함하는 lib 디렉토리들은 WAS 루트의 lib, server/lib, 웹애플리케이션의 WEB-INF/lib 정도가 되고,

   클래스파일 디렉토리는 웹애플리케이션의 WEB-INF/classes 정도가 된다.

3. runFinder.bat를 실행한다.

   C:\> runFinder [클래스명 전체 또는 일부]

    단, 여기서 클래스명은 대소문자를 구분한다.

4. 생성되는 result.txt 파일을 열어 클래스들의 위치를 확인한다.


프로젝트마다 lib.properties 하나만 세팅해 놓으면, 끝날 때까지 편리하게 사용할 수 있을 것이다.

클래스 중복 확인 프로그램과 형식이 똑같으므로 같은 디렉토리에 놓고 공유해서 사용해도 괜찮다.



이것도 역시, 아무쪼록 많은 도움이 되길...


추가>

이것도 원래 JDK1.5에서만 돌아가서 추가로 1.4로 컴파일한 프로그램을 올립니다.


AND