트러블 슈팅

JAVA Reflection, annotation value 값 찾기

수달하나 2022. 12. 2. 11:57

자바 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 기능에 대해서 잘 알수 있는 기회였다.