자바 Reflection 기능.
특정 DB 테이블을 csv 파일로 변환하는 도중 테이블의 필드 정보를 사용해야 할 경우 Reflection 기능을 통해서 확인 할 수 있다. Spring JPA를 사용하다 보니 DB 테이블을 직접 생성하지 않고 @Entity 어노테이션을 통해서 생성을 하면 자바의 Reflection 기능을 통해 쉽게 테이블의 컬럼 정보 (Class 정보)를 가져올 수 있는 것이다.
기능확인을 위해 Reflection 이라는 클래스를 만들었다.
@NoArgsConstructor
@AllArgsConstructor
@Entity
public class Reflection {
@Column(name = "integer_num")
private Integer integerNum;
@Setter
@Getter
@JsonIgnore(value = false)
@Column(name = "integer_num_public")
public Integer integerNumPublic;
private int intNum;
public int intNumPublic;
private Long longNum;
public Long longNumPublic;
private String stringStr;
public String stringStrPublic;
public void testFunc(){
;
}
public String test2Func(){
return "stringFunc() 실행...";
}
public String test3Func(String str){
return str;
}
}
이 클래스를 통해서 Reflection 기능을 확인해 볼 건데, 우선 클래스 정보를 가져오는 방법은 크게 3가지로 사용할 수 있는데 첫 번째는 클래스에서 직접 정보를 가져오는 방법, 두 번째는 생성된 인스턴스를 통해서 클래스 정보를 가져오는 방법, 그리고 마지막으로는 클래스 이름을 통해서 클래스 정보를 가져오는 방법이 있다.
단 Generic 을 통해 클래스 정보를 가져오게 될 경우 직접 클래스 정보를 가져올 때는 오류를 발생시키지 않지만 인스턴스를 통해 클래스를 가져올 경우 어떤 클래스 정보를 가져올 것 인지 casting 을 해줘야 한다는 차이점이 있다.
Class<Reflection> classInfo1_1 = Reflection.class;
Class<Reflection> classInfo1_2 = (Class<Reflection>) reflection.getClass();
Class classInfo2_1 = Reflection.class;
Class classInfo2_2 = reflection.getClass();
try{
Class<Reflection> classInfo3_1 = (Class<Reflection>) Class.forName("class full 경로");
Class classInfo3_2 = Class.forName("class full 경로");
}catch (Exception e){
e.printStackTrace();
}
이렇게 클래스 정보를 가져오면 각 컬럼의 필드 정보와 메소드 정보를 확인할 수 있다.
Field[] fields = Reflection.class.getFields(); // public field 만 가져올 수 있음.
Field[] allFields = Reflection.class.getDeclaredFields(); // 선언된 모든 field 가져올 수 있음.
각각의 필드를 통해서 얻을 수 있는 정보들을 확인해 보면 필드의 이름, 어노테이션 정보, 필드 값, 포함된 어노테이션 종류 등 각 필드의 다양한 정보들을 확인 할 수 있다. 이 부분은 실제로 코드를 작성하면서 확인하면 되기 때문에 내용을 따로 추가하지 않았다. 주의 깊게 살펴본 부분은 어노테이션 부분이다. 내가 맨 처음 Reflection을 정리한 이유는 특정 어노테이션의 특정 값을 얻고 싶었기 때문이었다. 하지만 구글링 결과 필드에 달린 어노이션의 특정 값을 뽑아내는 코드는 어디에서도 찾아볼 수 없었고 사용자 어노테이션을 통해서만 값을 뽑아낼수 있었기에 사용자 어노테이션을 하나 생성해줬다.
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface TestAnnotation {
String name();
String value();
}
이렇게 생성된 어노테이션을 이용해 기존 Reflection 클래스의 특정 컬럼을 아래와 같이 변경해주고 for 문을 통해서 TestAnnotation이 있는 필드의 값을 확인해보면
@NoArgsConstructor
@AllArgsConstructor
@Entity
public class Reflection {
.....
@TestAnnotation(name = "testName", value = "testValue")
public String stringStrPublic;
....
}
for(Field field : Reflection.class.getDeclaredFields()){
// 해당 annotation 객체가 존재할 경우에만 값이 들어옴
TestAnnotation testAnnotation = field.getAnnotation(TestAnnotation.class);
if(testAnnotation != null){
logger.info("field name : {}", field.getName());
logger.info("name : {}, value : {}", testAnnotation.name(), testAnnotation.value());
}
}
위와 같은 결과를 얻을 수 있다. 내가 찾던 어노테이션의 값들이었다. 근데 생각해보니 어노테이션을 저렇게 클래스 처럼 사용한다면 이미 라이브러리를 통해 제공되는 어노테이션도 같은 방식으로 사용할 수 있지 않을까?
for(Field field : Reflection.class.getDeclaredFields()){
JsonIgnore jsonIgnore = field.getAnnotation(JsonIgnore.class);
if(jsonIgnore != null){
logger.info("field name : {}", field.getName());
logger.info("value : {}", jsonIgnore.value());
}
}
된다!! 이리저리 돌아다녀서 확인해봤는데 결국에는 간단하게 해결될 문제였던 거다.
특정 테이블의 정보를 CSV 파일로 매핑하는 과정에서 각각의 컬럼이 몇번째 순서로 어떤 컬럼명을 가지고 파싱되는지 정의하고 싶을때 어떻게 해야 할 까 로부터 시작한 포스팅이었고 결국 적합한 해결방법을 찾을 수 있었다. 실제로 실무에서 사용될 확률은 극히 적은것 같지만 Reflection 기능에 대해서 잘 알수 있는 기회였다.