Java 리플렉션과 내성(Introspection) 기술

오늘의 학습 포인트: 리플렉션(Reflection)이란 무엇인가? Class 클래스 객체 획득 방법 Field(get/set) Method(invoke) Constructor(newInstance) 내성(Introspection) BeanInfo readMethod writeMethod

1. 리플렉션(Reflection)

Java에서 리플렉션은 프로그램 실행 시간에 동적으로 클래스의 객체, 메서드 및 속성을 검사, 획득하고 조작하는 능력을 의미합니다. 리플렉션을 통해 실행 시간에 임의의 클래스 정보를 얻을 수 있으며, 클래스 이름, 메서드, 필드 등을 포함하며 실행 시간에 객체를 생성하고 메서드를 호출하며 속성에 접근할 수 있습니다.

리플렉션: 프로그램 실행 중에 동적으로 클래스에 정의된 속성, 메서드 및 생성자 메커니즘을 얻는 기술
    리플렉션의 핵심은 Class 클래스입니다. 프로그램에서 사용되는 모든 클래스는 고유한 Class 객체를 가집니다
    리플렉션 API: Field, Method, Constructor
        API 패키지명: java.lang.reflect
    리플렉션은 클래스의 캡슐화를 깨뜨릴 수 있습니다. 상황에 따라 장단점이 있으니 주의해야 합니다
    객체가 있으려면 먼저 클래스가 있어야 하며, static은 클래스 속성과 메서드를 수정합니다
    Java에는 클래스 내용이 저장되어 있으며, 이 내용도 객체여야 합니다
    Java의 각 클래스는 메모리에 저장되며, 각 메모리는 객체입니다
    이러한 객체들은 클래스에 선언된 속성, 메서드 및 생성자를 기록합니다
    Java는 이러한 클래스를 Class라는 유형으로 추상화합니다

1.1 Class 클래스

Class 객체는 new로 생성할 수 없습니다
이 클래스의 객체에는 클래스에 정의된 내용(속성/메서드/생성자)이 저장됩니다
클래스 객체 획득 방법 (3가지)
클래스 이름으로 클래스 객체 획득
Class clazz= SampleClass.class;
객체로부터 클래스 객체 획득
clazz=new SampleClass().getClass();
Class 클래스의 forName 메서드를 통한 획득
clazz=Class.forName("com.sample.reflection.SampleColor");

1.2 클래스 속성 획득

Java에서 클래스 속성을 기록하는 클래스는 Field라 합니다 fName 변수가 가리키는 객체는 Sample 클래스의 name 속성입니다

Field fName=c.getField("name");

Sample 클래스 객체의 name 속성 값 획득

Object objectName=fName.get(sample);

속성 값 주입

fName.set(sample,"홍길동");

getField getFields는 클래스에서 public으로 선언된 속성만 획득할 수 있습니다

비public 속성은 getDeclaredField()를 사용하여 획득할 수 있습니다. getDeclaredField는 정의된 속성을 획득합니다

        Field fCode=c.getDeclaredField("code");
        fCode.set(sample,"10001");
        Object objCode=fCode.get(sample);//리플렉션을 통해 sample 객체의 code 속성 값 획득
        Field fGender=c.getDeclaredField("gender");
        fGender.set(sample,"여성");

부모 클래스 속성 획득: 클래스 객체의 getSuperclass 메서드를 호출하여 자식 클래스 객체로 부모 클래스 객체를 생성하고, 속성 획득은 자식 클래스와 유사합니다

Class<?> super = c.getSuperclass();
리플렉션으로 private 속성에 접근하려면 먼저 접근 권한을 획득해야 합니다. 현재 권한 외의 속성에 접근하려면 먼저 권한을 획득해야 합니다  
권한은 setAccessible 메서드를 통해 설정하며, true로 설정하면 됩니다
        Field fAddress=c.getDeclaredField("address");
        fAddress.setAccessible(true);
        fAddress.set(sample,"서울");

전체 코드 예제:

public class ReflectionDemo {
    public static void main(String[] args) throws ClassNotFoundException, 
NoSuchFieldException, IllegalAccessException {
        //클래스 객체에는 클래스에 정의된 내용(속성, 메서드, 생성자)이 저장됩니다
        //클래스 객체 획득
        Class c=Sample.class;
        c=new Sample().getClass();
        c=Class.forName("com.sample.reflection.Sample");

        //클래스 속성 획득
        //Java에서 클래스 속성을 기록하는 클래스는 Field입니다
        //fName 변수가 가리키는 객체는 Sample 클래스의 name 속성입니다
        Field fName=c.getField("name");

        Sample sample=new Sample();
        sample.name="김철수";
        System.out.println(sample.name);
        //Sample 클래스 객체의 name 속성 값 획득
        Object objectName=fName.get(sample);
        System.out.println(objectName+"---------");
        //속성 값 주입
        fName.set(sample,"이영희");
        System.out.println(sample.name);
        System.out.println("------------------");
        //getField getFields는 클래스에서 public으로 선언된 속성만 획득할 수 있습니다
        Field fCode=c.getDeclaredField("code");
        fCode.set(sample,"10001");
        Object objCode=fCode.get(sample);//리플렉션을 통해 sample 객체의 code 속성 값 획득
        System.out.println(objCode);

        Field fGender=c.getDeclaredField("gender");
        Field fAddress=c.getDeclaredField("address");
        fGender.set(sample,"여성");
        //리플렉션으로 private 속성에 접근하려면 먼저 접근 권한을 획득해야 합니다
        fAddress.setAccessible(true);
        fAddress.set(sample,"부산");
        System.out.println(fAddress.get(sample));
    }
}

class Sample {
    public String name;
    protected String code;
    String gender;
    private String address;
    static int maxAge;
    public static final transient String test=null;
    public Sample(){}
    public Sample(String name){
        this.name=name;
    }
}

1.3 리플렉션을 사용하여 객체 속성 설정

리플렉션을 통해 인스턴스 획득, 해당 클래스의 객체 생성

 T t=tClass.newInstance();//클래스 객체의 무인자 생성자를 통해 객체 생성
public class ReflectionPropertySetter {
    public static  <T> T createInstance(Class<T> targetClass, Map<String, Object> values) throws 
InstantiationException, IllegalAccessException {
        //리플렉션을 통해 인스턴스 획득, 해당 클래스의 객체 생성
        T instance=targetClass.newInstance();//클래스 객체의 무인자 생성자를 통해 객체 생성
        //리플렉션을 통해 클래스에 정의된 속성 획득
        Field[] fields=targetClass.getDeclaredFields();
        //System.out.println(Arrays.toString(fields));
        for (Field field:fields){
            //속성 이름 획득
            String fieldName=field.getName();
            //Map에서 해당 속성의 키-값 쌍 획득, 속성에 해당하는 값
            Object value=values.get(fieldName);
            //속성 접근 권한 설정
            field.setAccessible(true);
            //값 주입
            field.set(instance,value);
        }
        return instance;
    }

    public static void main(String[] args) throws 
InstantiationException, IllegalAccessException {
        Map<String, Object> propertyMap=new HashMap<>();
        propertyMap.put("studentId","S1001");
        propertyMap.put("studentName","박지성");
        propertyMap.put("studentGender","남성");
        Student student=createInstance(Student.class,propertyMap);
        System.out.println(student);
    }
}
class Student{
    private String studentId;
    private String studentName;
    private String studentGender;

    @Override
    public String toString() {
        return "Student{" +
                "studentId='" + studentId + '\'' +
                ", studentName='" + studentName + '\'' +
                ", studentGender='" + studentGender + '\'' +
                '}';
    }
}

1.4 클래스 메서드 획득

public 메서드 획득: getMethod() getDeclaredMethod로 비공개 메서드 획득

Method methodA=c.getMethod("processA");
Method methodB=c.getMethod("processB",int.class,int.class);

메서드 호출: 객체.메서드이름() 객체 지향 방식 method.invoke(객체) 리플렉션 invoke의 반환값은 모두 Object 타입입니다

methodA.invoke(sample);
methodB.invoke(sample,23,45);

전체 코드 예제:

public class MethodReflectionDemo {

    public static void main(String[] args) throws NoSuchMethodException, 
InstantiationException, IllegalAccessException, InvocationTargetException {
        //클래스 객체 획득
        Class<Sample> c= Sample.class;
        //리플렉션으로 메서드 획득 Method
        Sample sample=(Sample) c.newInstance();
        //public 메서드 획득 getDeclaredMethod로 비공개 메서드 획득
        Method methodA=c.getMethod("processA");

        //메서드 호출 객체.메서드이름()
        //method.invoke(객체) 리플렉션
        methodA.invoke(sample);

        Method methodB=c.getMethod("processB",int.class,int.class);
        //sample.processB(12,34)
        methodB.invoke(sample,23,45);
    }
}
public class Sample {
    public String name;
    protected String code;
    String gender;
    private String address;
    static int maxAge;
    public static final transient String test=null;
    public Sample(){}
    public Sample(String name){
        this.name=name;
    }

    public void processA(){
        System.out.println("processA 실행");
    }
    public void processB(int a,int b){
        System.out.println("processB 실행");
        System.out.println("두 파라미터 값: "+a+","+b);
    }
}

1.5 생성자 획득

(1) 무인자 생성자

클래스 객체.newInstance() 먼저 Constructor<Sample> con=c.getConstructor(); 그 다음 con.newInstance();

위의 두 방법 모두 가능합니다

(2) 인자 있는 생성자
        Constructor<Sample> con=c.getConstructor(String.class);
        Sample instance=con.newInstance("김민준");

이 방법만 가능하며, 전달되는 인자는 가변 인자입니다

public class ConstructorReflectionDemo {
    public static void main(String[] args) throws NoSuchMethodException, 
InvocationTargetException, InstantiationException, IllegalAccessException, 
NoSuchFieldException, IntrospectionException {
        //리플렉션으로 생성자 획득
        Class<Sample> c=Sample.class;
        c.newInstance();//클래스 객체의 무인자 생성자를 통해 객체 생성
        //무인자 생성자 획득
        Constructor<Sample> con=c.getConstructor();
        con.newInstance();
        con=c.getConstructor(String.class);

        Sample instance=con.newInstance("박지민");
        //System.out.println(instance.name);
    }
}

1.6 수정자(Modifier)

  <strong>Modifier 클래스의 메서드를 사용하여 메서드, 속성, 생성자의 수정자 판별</strong>
        Field f=c.getDeclaredField("test");
        int modifier=f.getModifiers();
        System.out.println(modifier);
        boolean isStatic=Modifier.isStatic(modifier);
        System.out.println(isStatic);

2. 내성(Introspection)

<strong>리플렉션을 통해 구현되며, 내성은 캡슐화를 깨지 않습니다</strong>

2.1 BeanInfo 획득

BeanInfo beanInfo=Introspector.getBeanInfo(c);

여기서 c는 클래스 객체입니다

2.2 속성의 쓰기 메서드와 읽기 메서드 획득

        PropertyDescriptor[] propertyDescriptors=beanInfo.getPropertyDescriptors();
        String propertyName=propertyDescriptors[0].getName();//속성 이름 획득
        System.out.println("속성 이름: "+propertyName);
        Method readMethod=propertyDescriptors[0].getReadMethod();//속성의 getter 메서드
        Method writeMethod=propertyDescriptors[0].getWriteMethod();//속성의 setter 메서드

2.3 객체 생성 및 획득된 메서드 호출

        Sample sample=c.newInstance();
        writeMethod.invoke(sample,"김하늘");

태그: 리플렉션 내성 Class Field Method

6월 28일 23:52에 게시됨