* 이 포스팅은 부산대학교 2023 백엔드 미니 부트캠프 2주차 학습 내용을 정리한 글입니다.
대부분 언어에서 객체는 자기 자신에 대한 정보를 가지고 있다. 자바에서도 마찬가지로 객체가 자기 자신에 대한 정보 즉, 메타데이터를 가지고 있다.
Class<?> clazz = Class.forName("reflection.example.SampleClass");
System.out.println("Class name: " + clazz.getName());
// 기본 생성자
Constructor<?> defaultConstructor = clazz.getDeclaredConstructor();
// 파라미터가 있는 생성자
Constructor<?> paramConstructor = clazz.getDeclaredConstructor(String.class);
Field privateField = clazz.getDeclaredField("privateField");
privateField.setAccessible(true);
Field publicField = clazz.getField("publicField");
Method privateMethod = clazz.getDeclaredMethod("privateMethod");
privateMethod.setAccessible(true);
Method publicMethod = clazz.getMethod("publicMethod");
publicMethod.invoke(instance);
Class<?>[] interfaces = clazz.getInterfaces();
Annotation[] annotations = clazz.getAnnotations();
스프링부트는 이 리플렉션으로 해당 클래스의 여러 작업을 수행한다.
.class
파일의 관계결론부터 말하면, 다른 과정이다.
처음 리플렉션 개념을 공부할 때, javac로 컴파일된 .class 파일에서 리플렉션이 일어나는 줄 알았다. 우테코 유튜브 세미나 자료를 보고 제대로 이해할 수 있었다.
링크된 영상을 요약하자면, JVM 동작 과정에서 .class 파일에서 해당 클래스의 인스턴스는 call stack, operand stack에 담기고 내부 프레임에서 인덱스가 부여된다고 한다. 정확한 정보는 영상에서 자세하게 설명했으니 참고바란다.
결국 모든 필드와 메소드 선언 또한 JVM이 .class 파일로 변환하고, execution engine을 통해서 실행한다. 리플렉션도 결국 자바 소스 코드로 구현된 클래스이니, 위 과정을 거친다. 단지 클래스 메타데이터를 가진다는 점에서 특별한 것이다.
리플렉션을 사용해 동적으로 클래스/패키지를 로딩할 수 있다. 이런 유연성은 파이썬과 C++ 같은 객체 지향 언어에서도 다른 모듈이나 디렉토리를 어느 정도 컨트롤할 수 있다.
스프링이나 하이버네이트와 같은 플러그인에서 자주 사용되는 기능이다. 클래스와 더불어 어떤 일련된 동작을 캡슐화하기 좋다.
JVM의 코드 구조를 분석하거나 디버깅 도구를 개발하는 데 유용하다고 한다. 하지만, 나는 이렇게 리플렉션을 사용해보지 않아 잘 모르겠다.
리플렉션은 종종 어노테이션과 같이 사용된다. 어노테이션에서 해당 필드/메소드를 가져와 비교적 자유롭게 사용할 수 있다.
리플렉션 연산은 일반적인 자바 연산에 비해 상대적으로 느리다. 따라서, 반복적이고 빈번한 리플렉션 호출은 성능에 부정적인 영향을 줄 수 있다.
privateMethod.setAccessible(true);
를 선언하면 private 메소드에 접근할 수 있다. 외부에서 객체를 불러와서 이런 연산을 한다면, 숨겨진 객체에 접근할 가능성이 있다.
리플렉션은 언제까지나 메타데이터를 컨트롤하기 위해 사용되야 한다. 물론 인스턴스를 만들 때, getDeclaredConstructor()
메소드로 굳이 만들 수 있지만, 앞서 말했던 상대적으로 느린 연산과 더불어 가독성이 떨어진다.
예를 들어 필드와 메소드를 리플렉션을 통해 불러와서 이를 String
이나 Integer
로 다운 캐스팅할 때, 에러가 많이 발생한다.
파이썬에는 데코레이터(decorator)라는 객체가 있다. 어노테이션을 보는데 파이썬 데코레이터가 필요한지 의문이 들 수 있지만, 두 개념을 비교하며 공부하면 재밌을 것 같아 가져와봤다.
둘 다 코드에 메타데이터를 부여한다. 동작 제어, 특정 도구/라이브러리와 상호작용할 때 정보를 제공한다.
Flask
@app.route('/home')
def home():
return "Hello, Home!"
Spring
@RestController
public class HomeController {
@GetMapping("/home")
public String home() {
return "Hello, Home!";
}
}
코드 구조, 로직의 변경 없이 기능을 변경할 수 있다. 코드 중복을 줄이고 모듈성이 증가한다.
python 실행 시간 출력 함수
import time
def timer(func):
def wrapper(*args, **kwargs):
start_time = time.time()
result = func(*args, **kwargs)
end_time = time.time()
print(f"{func.__name__} took {end_time - start_time:.2f} seconds.")
return result
return wrapper
@timer
def example_func():
time.sleep(2)
java 의존성 주입
@Service
public class ExampleService { }
@RestController
public class ExampleController {
@Autowired
private ExampleService exampleService;
}
행위가 아닌 상태에 집중한 코드를 작성하게 도와준다. 코드 의도를 명확하게 드러내고, 복잡한 구현 디테일을 숨길 수 있다.
python 로깅
def logger(func):
def wrapper(*args, **kwargs):
print(f"Calling {func.__name__}...")
return func(*args, **kwargs)
return wrapper
@logger
def say_hello(name):
return f"Hello, {name}!"
java JPA에서 엔티티와 필드 매핑
@Entity
public class User {
@Id
private Long id;
@Column(name = "username")
private String name;
}
파이썬의 데코레이터는 런타임에 동작한다. 함수/클래스가 정의될 때 데코레이터가 바로 실행되어 원래 함수/클래스를 수정 혹은 대체할 수 있다. 반면, 자바의 어노테이션은 주로 컴파일 타임에 동작한다. 런타임에서도 동작하긴 하지만, 어노테이션이 코드를 수정 혹은 대체하지 않는다. 대신 메타데이터를 제공해 외부에서 이를 활용한다.
python 로거
def logger(func):
def wrapper(*args, **kwargs):
print(f"Calling {func.__name__}")
return func(*args, **kwargs)
return wrapper
@logger
def say_hello():
print("Hello!")
java @Override
public class Example {
@Override
public String toString() {
return "This is an example.";
}
}
파이썬 데코레이터는 함수와 클래스에 적용되어 수정된 함수나 클래스를 반환하는 반면, 자바 어노테이션은 메소드, 클래스, 패키지, 파라미터, 변수 등 여러 대상에 적용된다.
python class
def decorator(cls):
cls.new_attribute = "New attribute added!"
return cls
@decorator
class Example:
pass
java class, method
@Entity
public class User {
@Id
private Long id;
}
파이썬 데코레이터는 일반 함수/클래스로 정의된다.
def simple_decorator(func):
return func
반면, 자바 어노테이션은 인터페이스의 특별한 형태로 정의된다. 자체 로직을 가지지 못하고 메타데이터로 동작한다.
public @interface CustomAnnotation {
String value() default "";
}