Reflection API는 자바 언어를 볼때에는 공부해보지 못했던 개념이었다.
그래서 스프링을 공부하면서 DI에대해 공부하다 접하게 된 용어인데 이번기회에 스프링의 동작방식에 대해 조금이라도 이해해 보려고 한다.
Reflection API란?
- 구체적인 클래스 타입을 알지 못해도 그 클래스의 메소드,타입,변수들에 접근 할 수 있도록 해주는 자바 API이다.
- 컴파일 시간이아닌 런타임 시간에 동적으로 특정 클래스의 정보를 가져올 수 있다.
- 런타임 시점에 동적으로 타입을 분석하고 정보를 가져오므로 JVM 최적화를 할 수 없으므로 성능 오버헤드가 발생할 수 있다.
- private으로 선언된 필드나 메서드에도 직접 접근이 가능하므로 캡슐화를 무시하게 된다.
- 즉 애플리케이션 개발목적보단 프레임워크나 라이브러리에서 많이 사용된다.(ex ) spring프레임워크 DI 사용, Hibernate : @Entity 클래스에 Setter가 없으면 해당 필드에 값을 바로 주입, MVC : View에서 넘어오는 데이터를 객체에 바인딩할때 사용, jackson 등등)
- 특히 spring에서 Bean이 실행될때 런타임에 Bean에 등록된 객체가 호출되면 동적으로 객체인스턴스를 생성하는데 이때 Spring Container의 BeanFactory에서 리플렉션을 사용한다.
리플렉션은 c나 c++과 같이 다른 언어에서는 볼 수 없는 API이다.
자바에서는 JVM이 실행될때 작성된 자바코드가 컴파일러를 거쳐 바이트 코드로 변환되며 static영역에 저장된다. Reflection API는 static영역의 정보를 가져와서 활용한다.
spring data jpa에서 entity에 기본 생성자가 필요한 이유도 동적으로 객체 생성시 reflection api를 활용하기 떄문입니다. reflection api로 가져올 수 없는 정보중 하나가 생성자의 인자정보입니다. 기본생성자로 객체를 생성하면 필드값등은 reflection api로 가져올 수 있습니다.
Reflection API 사용법
클래스 정보 로드
Class<People> peopleClass = People.class;
System.out.println(peopleClass); //class People
Class peopleClass = Class.forName("test.People"); // forName으로 찾으려면 ClassNotFoundException 처리를 해줘야한다
System.out.println(peopleClass); //class People
System.out.println(peopleClass2.getName()); // People
getName을 통해 해당 클래스의 이름을 리턴받을수 있고, 만약 클래스를 참조할 수 없고 이름만 알고있다면 Class.forName에 패키지 네임이 포함된 클래스 이름을 파라미터로 전달하여 클래스 정보를 가져올 수 있다.
생성자 찾기
Class cl = People.class;
// 기본 생성자
Constructor constructor = cl.getDeclaredConstructor();
System.out.println(constructor.getName());// test.People
//String을 Parameter로 받는 생성자
Constructor constructor1 = cl.getDeclaredConstructor(String.class);
System.out.println(constructor);// public test.People()
//해당 클래스의 모든 생성자를 배열로 받을수도 있다.
Constructor[] constructor2 = cl.getConstructors();
Arrays.stream(constructor2).forEach(System.out::println);
getDeclaredConstructor()를 통해 인자가 없는 생성자를 가져온다.
Spring Data JPA에서 Entity에 기본 생성자가 필요한 이유이다.
Reflection API로는 생성자의 인자정보를 가져올 수 없으므로 기본생성자가 반드시 있어야 객체를 생성할 수 있다.
메서드 찾기
//해당 이름의 메서드 찾기
Method method = cl.getDeclaredMethod("privateMethod");
System.out.println(method);// private void test.People.privateMethod()
//모든 메서드 찾기 super 클래스의 정보는 가져오지 않음
Method[] method = cl.getDeclaredMethods();
System.out.println(Arrays.toString(method));
//모든 메서드 찾기, public 메서드만 리턴, 상속받은 메서드들도 모두 리턴
Method[] method1 = cl.getMethods();
System.out.println(Arrays.toString(method1));
//메서드가 파라미터값을 받는경우
Class[] paramTypes = new Class[2];
paramTypes[0] = int.class;
paramTypes[1] = int.class;
Method method = cl.getDeclaredMethod("sum",paramTypes);
System.out.println(method);// public int test.People.sum(int,int)
필드(변수) 찾기
Class cl = People.class;
//해당 필드 찾기
Field field = cl.getDeclaredField("PSS");
System.out.println(field);// private static java.lang.String test.People.PSS
//모든 필드 찾기
Field[] field1 = cl.getDeclaredFields();
System.out.println(Arrays.toString(field1));
메서드 호출
People people = new People();
Class cl = People.class;
// 메서드가 파라미터값을 받는경우
Class[] paramTypes = new Class[2];
paramTypes[0]=int.class;
paramTypes[1] = int.class;
Method method = cl.getDeclaredMethod("sum",paramTypes);
int returnVal = (int) method.invoke(people, 2,5);
System.out.println(returnVal); // 7
만약 method가 private이라서 접근이 불가능하다면
method.setAccessible(true);
로 설정해주면 private 메서드에 접근가능하도록 변경된다.
필드(변수) 변경
People people = new People();
Class cl = People.class;
Field field = cl.getDeclaredField("PSS");
field.setAccessible(true);
System.out.println(field.get(people)); // privateStaticPeople
field.set(people,"publicPeople");
System.out.println(field.get(people));// publicPeople
'스프링' 카테고리의 다른 글
[스프링] CORS(Cross-Origin Resource Sharing) 해결 방법 (0) | 2023.02.26 |
---|---|
[스프링 시큐리티] 시큐리티 동작 과정, UserDetails, UserDetailsService. (0) | 2023.02.17 |
[스프링] DI, IoC, Bean (0) | 2023.02.13 |
[스프링] 즉시로딩과 지연로딩 (0) | 2023.02.10 |
[스프링] 프록시란 (0) | 2023.02.10 |