현재 진행 중인 프로젝트에서 오픈 소스를 분석해서 설계서를 역으로 만들고 있는데(클래스 설계서),

 

약 400여개의 클래스를 손수 MS 워드 문서로 옮겨야 하니 노가다의 절정이라고 생각된다..

 

R&D 사업이니 설계 산출물 자체에 의미가 있다기보다는 예산에 대한 증빙 자료인 셈이다.

 

 

 

각론하고, 다행히 오픈 소스가 Java로 작성되어 있어 리플렉션으로 클래스 정보를 읽은 후 HTML 문서로 출력하고,

 

워드로 옮기면 되겠다. 라고 생각하고 작업을 했는데, 메서드의 파라미터 이름을 가지고 올 수 없었다.

 

 

 

이 문제를 해결하기 위한 방법을 두 가지 찾았는데, 하나는 Java 8의 리플렉션에 추가된 Method.getParamter()를 통해 얻은 파라미터 정보에서 getName()을 이용해 파라미터의 이름을 알아내는 것이고,

 

두 번째는 Java Parser를 이용하는 것이었다.

 

 

 

수작업을 줄이려고 시작한 건데 일이 커지기 시작했다.

 

거기에 더해 오픈소스를 분석한다는 의미는 사라지고 설계서를 만든다는 것만 남았다. (...)

 

 

 

 

 

Java Parser로 테스트를 해 보기 전에 잠시 Java 8에서 테스트를 해보았는데, 파라미터 이름이 arg0 과 같이 실제 이름이 아닌 컴파일러에서 치환된 이름으로 변환되어 나와서 더 찾아보지 않고 Java Parser로 테스트를 했다.

 

 

 

Java Parser를 이용할 때는 원하는 대로 파라미터를 출력할 수는 있었지만, 구조화된 타입이 아닌 파라미터 선언부의 문자열로만 값을 가져올 수 있었고, 한 가지 문제점으로는 public void test(/*Param1*/String param) 과 같은 메서드 선언에서 주석을 같이 읽어오게 되어 예외 케이스에 대한 처리가 복잡해질 것 같아서 다시 Java 8 리플렉션을 통해 파라미터 이름을 얻어올 수 있는 방법을 찾아보게 되었다.

 

 

 

검색을 하면서 하나 놓친 것이 있었는데, 자바 컴파일 옵션에서 -parameters 옵션을 주면 메서드 파라미터의 이름이 그대로 컴파일 된 바이트코드에서도 유지된다.

 

Eclipse에서도 아래와 같이

Store information about method parameters (usable via reflection) 체크박스에 체크하면 파라미터 옵션을 줄 수 있다. (Java 1.8로 컴파일러가 설정된 경우에 활성화된다)

 

 

 

 

 

 

 

 

파라미터 이름을 얻는 소스코드는 아래와 같다.

 

 

 

 

 

 

이 외에도 paranamer라는 라이브러리가 있었지만, 매뉴얼을 대강 읽어보니 Java8에 추가되었다고 노트되어있었다.

 

 

 

 

 

참고 자료

 

https://docs.oracle.com/javase/tutorial/reflect/member/methodparameterreflection.html

 

https://github.com/javaparser/javaparser

http://stackoverflow.com/questions/2237803/can-i-obtain-method-parameter-name-using-java-reflection

 

 

덧붙여,

이 작업을 하고 있는 둘은 한 명은 클래스 명세표 양식을 출력하는 웹 애플리케이션을 만들고 있고, 한 명은 클래스를 리플렉션해서 필요한 메타데이터를 뽑아내는 툴을 만들고 있다. 이쯤 되면 배보다 배꼽의 배꼽 수준인 듯 ㅋㅋ

안드로이드 NFC 예제를 보면서 괜찮은 기법이 있어서 소개할까합니다. (저만 몰랐던 걸 수도 있구요. :D ) 

제가 본 소스의 일부를 쓰자면

BiMap<Byte, String> URI_PREFIX = BiMap.<Byte, String>builder()
            .put((byte) 0x00, "")
            .put((byte) 0x01, "http://www.")
            .put((byte) 0x02, "https://www.")
            .put((byte) 0x03, "http://")
            .put((byte) 0x04, "https://")
            .put((byte) 0x05, "tel:")
            .build();



맵에 요소를 넣으면서 URI_PREFIX에 인스턴스를 할당하는 구문입니다. 

처음 봤을 때는 저게 원래 자바에서 지원하는 문법인가 싶어서  객체 안에 있는 메서드
위처럼 세미콜론 없이 닷만 찍어서 계속 해보려했는데 당연히도 에러가 나더군요. 

위 문장을 보고 저게 뭐? 라고 생각하셨다면 아래에 쓰는 체이닝 기법을 적용하지 않고 작성한 소스를 보고 비교해보세요.

BiMap<Byte, String> URI_PREFIX =
BiMap.<Byte, String>builder().build(); 
URI_PREFIX.put((byte) 0x00, "");
URI_PREFIX.put((byte) 0x01, "http://www.");
URI_PREFIX.put((byte) 0x02, "https://www.");
URI_PREFIX.put((byte) 0x03, "http://");
URI_PREFIX.put((byte) 0x04, "https://");
URI_PREFIX.put((byte) 0x05, "tel:"); 


개인적인 차이일지도 모르겠지만 저는 체이닝 기법을 적용한 소스가 더 깔끔하고 직관적으로 느껴지네요. 서론은 여기까지 하고 실제로 어떻게 체이닝 기법을 적용할 수 있는지 예제를 만들면서 알아봅시다.

만들 클래스는 간단한 학생 정보 클래스입니다. 체이닝 기법을 적용한다고 해서 그리 효율적인 클래스가 되진 않겠지만 간단한 예를 위해서 이 클래스를 씁니다.

/** 학생정보 클래스 */
class Student{
private String name; // 학생이름
private String number; // 학번
private int birthyear; // 생년
private String dept; // 학과 

public void setName(String name){
this.name = name;
}

public void setNumber(String number){
this.number = number;
}

public void setBirthyear(int birthyear){
this.age = age;
}

public void setDept(String dept){
this.dept = dept;
}

/* getter 생략 */ 
}



위 클래스에서 생성자를 두지 않는다고 가정하면 학생 인스턴스에 정보를 넣기위해서는 아래와 같이 코드를 작성하게 되겠죠

Student student = new Student();
student.setName("세종대왕");
student.setNumber("7284910");
student.setBirthyear(1397);
student.setDept("국어국문학과");



체이닝 기법을 적용하기 위해서 뮤테이터(세터) 메서드의 반환형을 void에서 오브젝트를 반환하도록 Student 타입으로 바꿔 줍니다.
그리고 메서드 구현부의 끝에 return this;를 써서 객체를 반환하도록 하구요.
DSL에서는 setName과 같은 메서드 이름도 더 직관적으로 바꾸지만 익숙함을 위해서 그대로 쓰도록 하겠습니다. 위의 Student 클래스를 체이닝 기법을 적용하기 위한 소스로 바꿔보면 아래와 같습니다. 빨간색 글씨가 수정된 부분입니다

/** 학생정보 클래스 */
class Student{
private String name; // 학생이름
private String number; // 학번
private int birthyear; // 생년
private String dept; // 학과 

public Student setName(String name){
this.name = name;
return this;
}

public Student setNumber(String number){
this.number = number;
return this;
}

public Student setBirthyear(int birthyear){
this.age = age;
return this;
}

public Student setDept(String dept){
this.dept = dept;
return this;
}

/* getter 생략 */ 
}



클래스를 바꿨으니 인스턴스를 할당할 때도 바로 적용해 봅시다.

Student student = new Student()
.setName("세종대왕")
.setNumber("7284910")
.setBirthyear(1397)
.setDept("국어국문학과");



네. 이상입니다.
원리는 간단합니다. 계속해서 객체를 반환하니 메서드를 다시 호출해서 쓸 수 있고. 역시 student 변수는 Student 클래스 타입이니 반환한 객체 그대로 받을 수 있게 됩니다.  

'밤을 지새다 > Java' 카테고리의 다른 글

Java StAX: XMLInputFactory  (0) 2012.04.30
Java StAX  (0) 2012.04.30
Exceptions  (0) 2011.09.05
자바. 얼마나 알고 사용하고 계신가요?  (0) 2011.08.25
객체를 바이트 배열로, 바이트 배열을 객체로  (0) 2011.08.22

+ Recent posts