
GIF 출처 : https://www.amigoscode.com/courses/java
1 ) 리플랙션 ( Reflection ) 이란?
2 ) 리플랙션 클래스 & 인터페이스
3 ) 리플랙션 특징
: 자바의 리플렉션이란 런타임에 클래스, 메서드, 필드, 생성자 등의 정보를 동적으로 조회하고 조작할 수 있는 기능이다. 리플랙션을 통해 다음과 같은 작업을 수행할 수 있다.
| 작업 종류 | 예시 |
|---|---|
| 클래스 정보 조회 | 클래스 이름, 메서드, 필드 등 |
| 객체 생성 | 생성자를 호출하여 인스턴스 생성 |
| 메서드 실행 | 이름으로 메서드를 찾아 실행 |
| 필드 값 읽기/쓰기 | private 필드도 접근 가능 |
| 어노테이션 정보 조회 | 메타데이터 사용 |
자바 리플랙션의 경우 모두 java.lang.Class 및. java.lang.reflect패키지에 포함되어있다.
| 클래스/인터페이스 | 설명 |
|---|---|
Class<?> | 클래스 자체를 표현하는 객체 |
Field | 클래스의 필드(멤버 변수)를 표현 |
Method | 클래스의 메서드를 표현 |
Constructor | 클래스의 생성자를 표현 |
Modifier | 접근 제어자(public, private 등)를 확인하는 유틸리티 |
Class<?>: class<?> 클래스는 클래스의 자체를 표현하는 객체이다.
ex )
class Person {
private static final String name;
private void sayHello(String name) {
System.out.println("Hello My name is " + name);
}
}
Class<?> classGet = Class.forName("com.example.person"); // 방법1 . forName 메소드로 클래스 가져오기
Person person = new Person();
Class<?> classGet = person.getClass(); // 방법2. 객체를 생성한 뒤 클래스 가져오기
Class<Person> classGet = Person.class; // 방법3. 제네릭 타입에 가져올 클래스를 정의하기
Field: 클래스의 필드를 표현하는 클래스로 클래스 필드에 접근할 수 있다.
ex )
Field field = classGet.DeclaredField("name"); // name 필드를 가져온다.
field.setAccessible(true); // private 필드 접근가능 허용
field.set(person,"홍길동"); // 필드 초기화
String name = (String) field.get(p); // 필드 가져오기
System.out.println(field.getName()); // 필드 이름
System.out.println(field.getType()); // 필드 타입
System.out.println(Modifier.isPrivate(field.getModifiers())); // 접근 제어자 체크
Method: 클래스에서 메소드를 표현하는 클래스로 Field 클래스와 비슷하게 메소드에 접근할 수 있다.
ex )
Method method = classGet.DeclaredMethod("sayHello");
method.setAccessible(true);
Object returnValue = method.invoke(person,"우주");
Constructor: 클래스에서 생성자를 표현하는 클래스이다.
ex )
Constructor<Person> constructor = Person.class.getDeclaredConstructor(String.class); // 생성자 정의
constructor.setAccessible(true); // private 접근허용
Person newPerson = constructor.newInstance("우주"); // 새로운 인스턴스 생성
Modifier: 클래스, 필드, 메서드, 생성자 등에 적용된 접근 제어자 및 기타 수식자를 정수형 비트 플래그로 표현하고 분석한다.
Modifier 클래스는 상수값에 따라 서로 다른 의미를 지니고 있다. 다음 은 리턴되는 상수값을 정리한 표이다.
| Modifier | 의미 | 상수 값 |
|---|---|---|
public | 공개 접근 | 0x0001 |
private | 비공개 | 0x0002 |
protected | 상속 클래스에게만 공개 | 0x0004 |
static | 클래스 단위(static 영역) | 0x0008 |
final | 변경 불가(상수, 오버라이딩 금지) | 0x0010 |
synchronized | 동기화 (메서드 전용) | 0x0020 |
volatile | 필드가 동기화 없이 읽기 가능 | 0x0040 |
transient | 직렬화 제외 (필드 전용) | 0x0080 |
native | 자바 외부 (JNI) 메서드 | 0x0100 |
abstract | 구현 없음 (클래스/메서드) | 0x0400 |
strict | float 연산을 엄격하게 | 0x0800 |
interface | 인터페이스 타입 | 0x0200 |
enum | enum 타입 | 0x4000 |
ex )
Field field = Person.class.getDeclaredField("name");
int mod = field.getModifiers();
System.out.println("isPrivate? " + Modifier.isPrivate(mod)); // true
System.out.println("isStatic? " + Modifier.isStatic(mod)); // true
System.out.println("isFinal? " + Modifier.isFinal(mod)); // true
System.out.println("isPublic? " + Modifier.isPublic(mod)); // false
자바의 리플랙션은 다음과 같은 장점과 단점을 지니고 있다.
| 항목 | 설명 |
|---|---|
| 유연성 | 코드를 수정하지 않고도 다른 클래스나 메서드를 다룰 수 있음 |
| 동적 처리 | 실행 중에 새로운 클래스를 분석하고 동작을 결정할 수 있음 |
| 툴 개발에 유리 | 디버깅, 테스트 도구, 직렬화 도구 등에 활용됨 |
| 항목 | 설명 |
|---|---|
| 성능 저하 | 내부적으로 많은 검사를 거치기 때문에 일반 호출보다 느림 |
| 타입 안전성 ↓ | 컴파일러가 타입을 체크하지 못하므로 ClassCastException 가능 |
| 캡슐화 파괴 | private 필드나 메서드도 접근할 수 있음 |
그럼 자바의 리플랙션은 어떻게 활용될까? POJO 의 관점과 프레임워크의 관점으로 나누어볼 수 있다.
객체 구조 분석 (Class, Field, Method 등)
: 객체 구조를 런타임에 조사하고 디버깅&개발 툴에서 객체 내부의 정보를 출력하도록 한다.
ex )
for (Field f : obj.getClass().getDeclaredFields()) {
f.setAccessible(true);
System.out.println(f.getName() + ": " + f.get(obj));
}
setAccessible 메소드를 통해 테스트에서도 private필드나 메소드에 접근할 수 있도록한다. ex )
Method m = clazz.getDeclaredMethod("internalLogic");
m.setAccessible(true);
m.invoke(obj);
ex )
import java.lang.annotation.*;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface MyLog {
String value() default "기본 로그";
}
public class TransactionHandler {
private Object target;
public TransactionHandler(Object target) {
this.target = target;
}
public Object invoke(String methodName, Object... args) throws Exception {
Method method = target.getClass().getMethod(methodName);
if (method.isAnnotationPresent(MyTransactional.class)) {
try {
beginTransaction();
Object result = method.invoke(target, args);
commit();
return result;
} catch (Exception e) {
rollback();
throw e;
}
} else {
return method.invoke(target, args);
}
}
}
Config 클래스 혹은 어노테이션을 통해 객체를 동적으로 생성하고 의존성을 주입한다. 이때 , 리플랙션을 통해 생성자 & 필드 & 메서드에 접근한다. ex )
Constructor<?> ctor = clazz.getDeclaredConstructor();
Object bean = ctor.newInstance();
Field f = clazz.getDeclaredField("repository");
f.setAccessible(true);
f.set(bean, repositoryInstance);
AOP ⭐️
: 기본적으로 Spring AOP는 자바의 리플랙션을 기반으로 원하는 클래스 or 경로 ( PointCut ) 에 원하는 시점을 설정하여 특정로직 ( Advice ) 을 추가할 수 있다.
AOP에 대한 자세한 설명은 Spring AOP 포스팅 참조!
ex )
Object result = method.invoke(target, args); // Around advice
ex )
Method test = testClass.getDeclaredMethod("shouldRun");
test.setAccessible(true);
test.invoke(testInstance);