[java] 2. generic class Class field 가져오기, 동적 메소드 실행

 


목차

  1. Generic 이란?
  2. 클래스 생성하기
  3. 클래스 속성(Field) 가져오기
  4. 클래스 내 메소드 가져와서 실행시키기(동적 메소드 실행)


프로젝트 진행 시 여러 객체에 공통적으로 들어가는 항목에 대해서 반드시 초기화가 필요할 경우 setter 메소드를 이용하여 초기화 시키는 방법을 알아보려고 한다.

예를 들어 중요한 데이터는 삭제 요청에 대해서 실제로 데이터를 삭제하지 않고 delete 여부 컬럼을 따로 둬서 true/false로 설정하곤 하는데, 이 경우 매번 초기화를 시켜주기 번거롭고 각 객체마다 따로 함수를 설정하는건 비효율적이다.

이런 경우 Generic 을 이용하여 임의의 객체의 필드에 접근하고 대해 동적 메소드를 호출할 수 있다.



1. Generic이란?

Generic이란? 클래스에서 사용할 타입을 외부에서 선언하는 것을 말한다. 선언할 때 타입 파라미터가 구체적인 타입으로 변경되고, 멀티 타입으로 선언할 수 있다.

public class Member<T, M>() {
  private T one;
  private M another;
  
  void setOne(T one) {
    this.one = one;
  }
  void setAnother(M another) {
    this.another = another;
  }
}
// 객체 생성 시 직접 타입을 명시하여 사용할 수 있다.
Member<String, Integer> member = new Member();
member.setOne("member");
member.setAnother(10);


Generic의 장점

기존 JDK1.5 이하에서 Generic이 없을 경우엔, 여러 타입을 사용해야 할 필요가 있을 때 Object를 사용했다. 하지만 이 경우 Object 타입을 계속해서 원하는 타입으로 변환을 해야 하는 번거로움과 오류가 발생할 위험이 있다.

Generic을 사용할 경우 컴파일 타임에 타입이 미리 정해지므로, 타입 변환이나 타입 검사가 별도로 필요하지 않고 객체의 재사용성도 높일 수 있다는 장점이 있다.



// 객체 생성 시 직접 타입을 명시하여 사용할 수 있다.
Member<String, Integer> member = new Member();
member.setOne("member");
member.setAnother(10);




2. 클래스 생성하기

GenericVO 객체를 생성해 준 후 몇 가지 테스트용 속성을 설정해 주었다. 아래 필드 중 Chk로 끝나는 속성들이 초기화되어 있지 않을 경우(즉, null일 경우) INITIALIZE로 초기화 해줄 예정이다.

package com.example.spring.generic.model;

import java.time.LocalDate;

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.ToString;

@Getter
@Setter
@ToString
public class GenericVO {

    int id;
  
    String selectChk;	// **
    String insertChk; // **

    String name;
    LocalDate date;

    String updateChk; // **
    String deleteChk; // **
    
}




3. 클래스 속성(Field) 가져오기

private <T> void getAllFields(T t) {
    String className = t.getClass().getName();
    
    Class targetClass;

    try {
        // class가 존재하지 않을 경우 ClassNotFoundException 발생
        targetClass = Class.forName(className);
    } catch(ClassNotFoundException e) {
        return;
    }
	  
    // class 내의 모든 필드 가져오기
    Field[] fields = targetClass.getDeclaredFields();
    for(int i = 0; i<fields.length; i++) {
        Field field = fields[i];
        log.info(field.getName()); // 필드 이름 출력
    }
        
}


출력결과

스크린샷 2021-04-03 오후 9 45 05




4. 클래스 내 메소드 가져와서 실행시키기(동적 메소드 실행)

ObjectVO 클래스 내에 정의된 모든 메소드를 체크하고 값이 초기화되어 있지 않을 경우 set###Chk 메소드를 실행시켜 INITIALIZE로 초기화하기

// GenericController.java
package com.example.spring.generic.controller;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.time.LocalDate;

import com.example.spring.generic.model.GenericVO;

import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.View;
import org.springframework.web.servlet.view.json.MappingJackson2JsonView;

import lombok.extern.slf4j.Slf4j;

@Slf4j
@Controller
@RequestMapping("generic")
public class GenericController {

    @RequestMapping("genericTest")
    private View genericTest(ModelMap model) {

        GenericVO genericVo = new GenericVO();;
        genericVo.setId(1);
        genericVo.setInsertChk("insert");
        genericVo.setUpdateChk("update");
        genericVo.setName("SujinHope");
        genericVo.setDate(LocalDate.now());

        model.addAttribute("beforeChange", genericVo);
        getAllFields(genericVo);		// 클래스 내 모든 필드 접근
        setDefaultChk(genericVo);		// 클래스 내 모든 메소드 접근
        model.addAttribute("afterChange", genericVo);

        return new MappingJackson2JsonView();
    }
}
// GenericController.java
private <T> void setDefaultChk(T t) {
    String className = t.getClass().getName();
        
    Class targetClass;
    Method methods[];

    try {
        // class가 존재하지 않을 경우 ClassNotFoundException 발생
        targetClass = Class.forName(className);

        // targetClass에 선언된 모든 메소드 가져오기
        methods = targetClass.getDeclaredMethods();
    } catch(ClassNotFoundException e) {
        return;
    }

    for(int i = 0; i<methods.length; i++) {

        Method method = methods[i];
        String methodName = method.getName();
            
        // Chk로 끝나는 필드의 setter 메소드
        if(methodName.endsWith("Chk") && methodName.startsWith("set")) {

            String getMethodName = methodName.replaceAll("set", "get");
            try {
                // 해당 필드의 getterMethod 가져오기(ex - getInsertChk())
                Method getterMethod = targetClass.getMethod(getMethodName);

                // 해당 필드가 null일 경우 "INITIALIZE"로 초기화시키기
                if(getterMethod.invoke(t) == null) {
                    methods[i].invoke(t, "INITIALIZE");
                }
            } catch(Exception e) {
                continue;
            }
        }
    }

}



실행 결과

스크린샷 2021-04-03 오후 9 34 55