티스토리 뷰
리플렉션(Reflection)이란?
- 리플렉션은 구체적인 클래스 타입을 알지 못하더라도 그 클래스의 메서드, 타입, 변수들에 접근할 수 있도록 해주는 자바 API입니다.
- 💡 컴파일 시간이 아닌 실행 시간에 동적으로 특정 클래스의 정보를 추출할 수 있는 프로그래밍 기법입니다.
리플렉션은 언제 사용될까?
- 동적으로 클래스를 사용해야할 때 필요합니다.
- 다시 말해 작성 시점에는 어떠한 클래스를 사용해야 할지 모르지만 런타임 시점에서 클래스를 가져와서 실행해야하는 경우 필요합니다.
- 대표적으로는 Spring 프레임워크의 어노테이션 같은 기능들이 리플렉션을 이용하여 프로그램 실행 도중 동적으로 클래스의 정보를 가져와서 사용합니다.
리플렉션은 어떤 정보를 가져올 수 있을까?
- 아래와 같은 정보들을 가져올 수 있으며 해당 정보들을 가져와서 객체를 생성하거나 메서드를 호출하거나 변수의 값을 변경할 수 있습니다.
- Class
- Constructor
- Method
- Field
예제
Step 01 - Animal Class
public class Animal {
public String name = "myName ?";
private String city = "myCity ?";
public Animal() {
}
private void sleep(){
System.out.println("sleep");
}
private void eat(String x){
System.out.println("eat : " + x);
}
private void walk(){
System.out.println("walk");
}
}
Step 02 - Dog Class
public class Dog extends Animal{
private String myName = "뽀삐";
public String myCity = "서울";
public Dog() {
}
private Dog(String myName) {
this.myName = myName;
}
private void myName(String name){
System.out.println("myName : " + name);
}
private void myCity(String city){
System.out.println("myCity : " + city);
}
private void hello(){
System.out.println("hello~");
}
}
Step 03 - Example Class
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.lang.reflect.Field;
public class Example {
public static void main(String[] args) throws Exception{
}
}
Class 찾기
- 클래스 Class 객체는 클래스 또는 인터페이스를 가리킵니다. java.lang.Class이며 import를 하지 않고 사용할 수 있습니다.
Case 01 - class를 알고 있다는 전제
- 아래 코드를 보시면 Dog.class 처럼 클래스 정보를 할당할 수 있습니다. Class객체는 여러 메서드를 제공하고 있으며 getName()은 클래스의 이름을 리턴합니다.
public class Example {
public static void main(String[] args) throws Exception{
Class cls = Dog.class;
System.out.println("Class Name : " + cls.getName());
// Class Name : test.Dog 출력
}
}
Case 02 - class를 참조할 수 없고 이름만 알고 있는 상황이라면?
- 아래 예제는 클래스 이름만으로 클래스의 정보를 가져옵니다.
- Class,forName() 메서드에 클래스 이름을 인자로 전달하여 클래스 정보를 가져올 수 있습니다.
- 패키지 네임이 포함된 클래스 이름으로 작성해야 합니다.
public class Example {
public static void main(String[] args) throws Exception{
Class cls = Class.forName("test.Dog");
System.out.println("Class Name : " + cls.getName());
// Class Name : test.Dog 출력
}
}
Constructor 찾기
Case 01 - 인자가 없는 생성자 가져오기
- getDeclaredConstructor() 메서드에 아무런 내용을 작성하지 않으면 인자가 없는 기본 생성자를 가져올 수 있습니다.
- 기본 생성자가 없고 오버로딩된 생성자만 있다면 java.lang.NoSuchMethodException 예외를 발생시킵니다.
public class Example {
public static void main(String[] args) throws Exception{
Class cls = Class.forName("test.Dog");
Constructor constructor = cls.getDeclaredConstructor();
System.out.println("Constructor : " + constructor.getName());
// Constructor : test.Dog 출력
}
}
Case 02 - 인자가 있는 생성자 가져오기
- getDeclaredConstructor(Param)에 인자를 넣으면 해당 타입과 일치하는 생성자를 찾습니다.
public class Example {
public static void main(String[] args) throws Exception{
Class cls = Class.forName("test.Dog");
Constructor constructor = cls.getDeclaredConstructor(String.class);
System.out.println("Constructor : " + constructor.getName());
// Constructor : test.Dog 출력
}
}
Case 03 - 모든 생성자 가져오기
- getDeclaredConstructors() 메서드를 사용하면 클래스의 private, public 등의 모든 생성자를 리턴해줍니다.
public class Example {
public static void main(String[] args) throws Exception{
Class cls = Class.forName("test.Dog");
Constructor constructors[] = cls.getDeclaredConstructors();
for (Constructor item : constructors){
System.out.println("Get constructors : " + item);
// Get constructors : public test.Dog()
// Get constructors : public test.Dog(java.lang.String)
}
}
}
Case 04 - public 생성자만 가져오기
- getConstructors() 메서드를 사용하면 클래스의 public 생성자를 리턴할 수 있습니다.
public class Example {
public static void main(String[] args) throws Exception{
Class cls = Class.forName("test.Dog");
Constructor constructors[] = cls.getConstructors();
for (Constructor item : constructors){
System.out.println("Get public constructors : " + item);
// Get public constructors : public test.Dog()
}
}
}
Method 찾기
- getDeclaredMethod() 메서드를 사용하여 인자로 메서드의 파라미터 정보를 넘겨주면 일치하는 것을 찾을 수 있습니다.
Case 01 - 인자가 있는 메서드 가져오는 방법
import java.lang.reflect.Method;
public class Example {
public static void main(String[] args) throws Exception{
Class cls = Class.forName("test.Dog");
Method method = cls.getDeclaredMethod("myName", String.class);
System.out.println("Method : " + method);
// Method : private void test.Dog.myName(java.lang.String)
}
}
Case 02 - 인자가 없는 메서드 가져오는 방법
import java.lang.reflect.Method;
public class Example {
public static void main(String[] args) throws Exception{
Class cls = Class.forName("test.Dog");
Method method = cls.getDeclaredMethod("hello", null);
System.out.println("Method : " + method);
// Method : Method : private void test.Dog.hello()
}
}
Case 03 - 모든 메서드를 가져오는 방법
import java.lang.reflect.Method;
public class Example {
public static void main(String[] args) throws Exception{
Class cls = Class.forName("test.Dog");
Method methods[] = cls.getDeclaredMethods();
for (Method item : methods) {
System.out.println("Method : " + item);
// Method : private void test.Dog.hello()
// Method : private void test.Dog.myName(java.lang.String)
// Method : private void test.Dog.myCity(java.lang.String)
}
}
}
Case 04 - 상속받은 메서드와 public 메서드만 가져오는 방법
- getMethods() 메서드는 public 메서드를 리턴하며, 상속받은 메서드들도 모두 찾아줍니다.
import java.lang.reflect.Method;
public class Example {
public static void main(String[] args) throws Exception{
Class cls = Class.forName("test.Dog");
Method methods[] = cls.getMethods();
for (Method item : methods) {
System.out.println("Method : " + item);
}
}
}
Output:
Method : public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException
Method : public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException
Method : public final void java.lang.Object.wait() throws java.lang.InterruptedException
Method : public boolean java.lang.Object.equals(java.lang.Object)
Method : public java.lang.String java.lang.Object.toString()
Method : public native int java.lang.Object.hashCode()
Method : public final native java.lang.Class java.lang.Object.getClass()
Method : public final native void java.lang.Object.notify()
Method : public final native void java.lang.Object.notifyAll()
Field 찾기
Case 01 - getDeclaredField() 메서드를 사용하여 전달된 이름과 일치하는 필드를 찾아줍니다.
public class Example {
public static void main(String[] args) throws Exception{
Class cls = Class.forName("test.Dog");
Field field = cls.getDeclaredField("myName");
System.out.println(field);
// private java.lang.String test.Dog.myName
}
}
Case 02 - getDeclaredFields() 메서드를 사용하여 객체에 선언된 모든 필드를 찾아줍니다. 단 상속받은 객체의 정보는 찾아주지 않습니다.
public class Example {
public static void main(String[] args) throws Exception{
Class cls = Class.forName("test.Dog");
Field fields[] = cls.getDeclaredFields();
for (Field item : fields) {
System.out.println(item);
// private java.lang.String test.Dog.myName
// public java.lang.String test.Dog.myCity
}
}
}
Case 03 - getFields() 메서드를 사용하면 상속받은 객체의 public 필드까지 찾아줍니다.
import java.lang.reflect.Field;
public class Example {
public static void main(String[] args) throws Exception{
Class cls = Class.forName("test.Dog");
Field fields[] = cls.getFields();
for (Field item : fields) {
System.out.println(item);
// public java.lang.String test.Dog.myCity
// public java.lang.String com.kdg.bootStudy.Animal.name
}
}
}
Field 변경
- 클래스로부터 변수 정보를 가져와 객체의 변수의 값을 변경할 수 있습니다.
Case 01 - getField() 메서드를 사용하면 객체의 public 필드를 찾아서 값을 변경할 수 있습니다.
public class Example {
public static void main(String[] args) throws Exception{
Dog dog = new Dog();
Class cls = Class.forName("test.Dog");
Field field = cls.getField("myCity");
System.out.println("default field : " + field.get(dog));
// default field : 서울 출력
field.set(dog, "제주도");
System.out.println("update field : " + field.get(dog));
// update field : 제주도 출력
}
}
Case 02 - setAccessible() 메서드를 사용하면 private로 선언한 필드에 접근
- setAccessible() 메서드를 사용하면 private 인스턴스 변수나 메서드는 해당 클래스의 외부에서는 접근할 수 없는데 setAccessible(true)를 사용하면 문제없이 접근을 할 수 있습니다.
public class Example {
public static void main(String[] args) throws Exception{
Dog dog = new Dog();
Class cls = Class.forName("test.Dog");
Field field = cls.getDeclaredField("myName");
field.setAccessible(true);
System.out.println("default field : " + field.get(dog));
// default field : 뽀삐 출력
field.set(dog, "펩시");
System.out.println("update field : " + field.get(dog));
// update field : 펩시 출력
}
}
리플렉션을 이용한 동적 메서드 호출 예제
- Hello 클래스의 callA() 메서드와 callB() 메서드를 호출하는 부분만 다를 뿐 나머지는 동일한 코드 구조로 아루어져 있다고 생각을 하고 예제를 진행하겠습니다.
- 아래처럼 호출하는 메서드만 다를 뿐 나머지는 코드 중복이 발생하고 있습니다.
@Slf4j
public class ReflectionTest {
@Test
void reflection() {
Hello target = new Hello();
// 공통 로직 1 시작
log.info("start");
String result1 = target.callA();
log.info("result1={}", result1);
// 공통 로직 1 종료
// 공통 로직 2 시작
log.info("start");
String result2 = target.callB();
log.info("result2={}", result2);
// 공통 로직 2 종료
}
@Slf4j
static class Hello {
public String callA(){
log.info("CallA");
return "A";
}
public String callB(){
log.info("CallB");
return "B";
}
}
}
리플렉션을 이용한 동적 메서드로 변경
- getMethod() 메서드를 활용하여 호출하고자하는 메서드의 이름을 인자값으로 넘기면서 처리를 할 수 있습니다.
@Slf4j
public class ReflectionTest {
@Test
void reflection() throws Exception {
// 클래스 정보 획득
Class classHello = Class.forName("jdkdynamic.ReflectionTest$Hello");
Hello target = new Hello();
// callA 메서드 정보
Method methodCallA = classHello.getMethod("callA");
dynamicCall(methodCallA, target);
// callB 메서드 정보
Method methodCallB = classHello.getMethod("callB");
dynamicCall(methodCallB, target);
}
// 동일한 메서드 처리
private void dynamicCall(Method method, Hello target) throws Exception {
log.info("start");
Object result = method.invoke(target);
log.info("result={}", result);
}
@Slf4j
static class Hello {
public String callA(){
log.info("CallA");
return "A";
}
public String callB(){
log.info("CallB");
return "B";
}
}
}
주의 사항
- Field.setAccessible() 메서드를 사용하면 외부에서 접근할 수 없는 private 멤버 변수에도 접근할 수 있으니 주의가 필요합니다.
- 컴파일 에러가 아닌 런타임시에 에러가 발생하기 때문에 상당한 주의가 필요하며 되도록 사용을 하지 않는것이 좋습니다.
'JAVA > JAVA기본' 카테고리의 다른 글
JAVA - 변수 (feat.스코프와 라이프 타임 & 타입 변환, 캐스팅, 프로모션) (0) | 2021.12.08 |
---|---|
JAVA - 프리미티브 타입이란? (feat.레퍼런스 타입) (0) | 2021.12.08 |
JAVA - Garbage Collection이란? (0) | 2021.11.28 |
JAVA - JVM이란 무엇인가 (0) | 2021.11.27 |
JAVA - 이미지 base64를 디코딩하여 파일 생성 후 DB에 디렉토리 저장 (0) | 2021.07.13 |
- Total
- Today
- Yesterday
- polling publisher spring boot
- redis 대기열 구현
- spring boot excel download oom
- redis sorted set으로 대기열 구현
- 람다 표현식
- transactional outbox pattern
- spring boot redis 대기열 구현
- 서비스 기반 아키텍처
- JDK Dynamic Proxy와 CGLIB의 차이
- @ControllerAdvice
- redis sorted set
- spring boot excel download paging
- transactional outbox pattern spring boot
- 공간 기반 아키텍처
- spring boot redisson sorted set
- 자바 백엔드 개발자 추천 도서
- 트랜잭셔널 아웃박스 패턴 스프링 부트 예제
- java ThreadLocal
- pipeline architecture
- java userThread와 DaemonThread
- microkernel architecture
- 레이어드 아키텍처란
- service based architecture
- spring boot redisson 분산락 구현
- pipe and filter architecture
- spring boot 엑셀 다운로드
- spring boot redisson destributed lock
- 트랜잭셔널 아웃박스 패턴 스프링부트
- space based architecture
- spring boot poi excel download
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | ||||||
2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 |