본문 바로가기
백엔드

[Java] Generic이란? Generic class Class field 가져오기, 동적 메소드 실행

by BeforB 2021. 10. 1.
728x90

 

Java에서 Generic이 무엇인지와 이를 이용하여 여러 객체에 공통적으로 들어가는 항목에 대해서 반드시 초기화가 필요할 경우 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;
  }
}

 

 

 

2. Generic의 장점

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

 

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

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

 

 

 


 

 

 

 

3. 클래스 생성하기

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; // **
    
}

 

 

 

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

 매개변수로 받은 클래스의 모든 속성을 읽어오는 getAllFields 메소드를 만들고, 각 속성을 출력한다. 타입이 정해져있지 않은 Generic을 이용하기 위해 매개변수와 리턴 타입에 T를 사용해주었다.

 일반적으로 제네릭에서는 Type의 약자인 T를 사용하긴 하지만 파라미터를 여러 개 받거나 중복될 경우 K, M, MyType 등 자유롭게 사용할 수 있다.

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

 

 

 

 

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

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

 

 

 

 

 

 

 

728x90

댓글