자바 리플렉션(Reflection)이란? (부제: [초간단] 스프링 의존성 주입은 어떻게 일어나는가?)

3
post-thumbnail

이 글은 "그래서 스프링 어노테이션을 어떻게 인식하길래 어노테이션만으로 동작하는건데?"라는 궁금증을 해결한 글입니다.
"스프링 어노테이션 동작 원리", "스프링 DI 원리" 따위의 검색어로는 스프링에서 어노테이션에 어떻게 기능을 부착하는지 궁금증을 해소할 수 없었는데, 리플렉션을 이용한 방식이었다.

reflection
명사 (거울 등에 비친) 상[모습]

리플렉션이란?

  • 리플렉션은 힙 영역에 로드된 Class 타입의 객체를 통해, 원하는 클래스의 인스턴스를 생성할 수 있도록 지원하고, 인스턴스의 필드와 메소드를 접근 제어자와 상관 없이 사용할 수 있도록 지원하는 API이다.
  • 여기서 로드된 클래스란, JVM 클래스 로더에서 클래스 파일에 대한 로딩을 완료한 후, 해당 클래스의 정보를 담은 Class 타입의 객체를 생성해 메모리의 힙 영역에 저장해 둔 것을 의미한다.
  • 컴파일타임이 아닌 런타임에 동적으로 특정 클래스의 정보를 추출할 수 있는 프로그래밍 기법이다.
  • 리플렉션을 모른채 특정 설정파일에서 Constructor(SomeClass.class); 라고 사용한 경험이 있을텐데, .class부분이 리플렉션 객체를 호출하는 부분이다.

내가 이해한 바로는 개발자가 눈으로 코드를 확인하는 것이 아닌, 런타임 환경의 자바 클래스가 다른 자바 클래스의 정보를 확인 할 수 있는 것이다.

어떤 경우에 사용할까?

컴파일 시점에는 어떤 타입의 클래스를 사용할지 모르지만, 런타임 시점에 가져와 실행해야 하는 경우 필요하다.
리플렉션을 사용한 예시로는

  • Intellij의 자동완성
  • 스프링의 어노테이션
어노테이션만 붙였을 뿐인데 클래스가 컨트롤러로 인식되고, url 매핑까지 자동(사실 스프링이 함)으로 된다.

등이 있다고 한다.

리플렉션은 어떤 정보를 가져올 수 있을까?

  • Class
  • Constructor
  • Method
  • Field
  • Annotation
  • extends, implements

예제를 통해 아주 간단하게 사용법을 알아보자

Dog Class

public class Dog {
	public String name = "poppy";

	public Dog() {
	}
	public void run() {
		System.out.println(name + "run");
	}
}

사용 예시

public class DogMain {
	public static void main(String[] args) throws Exception {
		Dog dog = new Dog();
		Class<Dog> dogClass = Dog.class;

		// 클래스 이름 불러오기
		System.out.println("클래스명 -> " + dogClass.getName());
		// 클래스명 -> reflection.Dog

		// 생성자 불러오기
		System.out.println("생성자 -> " + dogClass.getDeclaredConstructor());
		// 생성자 -> public reflection.Dog()

		// 메서드 불러오기
		System.out.println("메서드 -> " + dogClass.getDeclaredMethod("run"));
		// 메서드 -> public void reflection.Dog.run()
		
		// 변수 불러오기
		System.out.println("변수명 -> " + dogClass.getDeclaredField("name"));
		// 변수명 -> public java.lang.String reflection.Dog.name

		// 변수의 값 불러오기 및 변경하기
		Field field = dogClass.getDeclaredField("name");
        
		System.out.println("변수값 -> " + field.get(dog));
		// 변수값 -> poppy
        
		field.set(dog, "chulsu");
		System.out.println("변수값 변경 -> " + field.get(dog));
		// 변수값 변경 -> chulsu
	}
}

리플렉션의 단점?

  • 컴파일러 최적화를 전혀 받지 못함 -> 성능 저하
    - 동적으로 클래스를 생성하기 때문에 JVM 컴파일러가 최적화 할 수 있는 여지가 없다.
  • private 메서드/변수 접근 가능 문제
    - field.setAccesible(true)를 설정하게 되면 private으로 설정해놓은 것도 직접 접근이 가능해지기 때문에 내부를 노출해서 추상화가 파괴된다.
  • 코드가 더러워짐
    - 장황한 코드로 인해 가독성이 저하된다.

그래서 스프링 어노테이션이랑 뭔상관인데?

스프링에서 어노테이션만으로도 수많은 기능이 동작하는 마법이 바로 이 리플렉션을 사용한 것이다.

// 뭘 구현한건진 모르겠지만,,, 스프링의 어떤 클래스 내부의 메서드 중 하나
public <T extends Annotation> T getAnnotation(Class<T> annotationClass) {
		for (Annotation annotation : getAnnotations()) {
			if (annotation.annotationType() == annotationClass) {
				return (T) annotation;
			}
		}
		return null;
	}
// 그냥 인텔리제이에서 shift * 2 한 뒤 찾은 메서드라 의미는 모릅니다...

위 코드와 같이 스프링에서는 리플렉션 객체를 인자로 받아서 엄청난 일을 벌인다. 그 중 일부가 바로 스프링 코드가 존재하는 패키지를 스캔하며 @Component를 찾아 빈을 등록하고, @GetMapping과 같은 어노테이션이 붙은 메서드에 url을 매핑해주는 것이다.
물론 이 이상의 깊은 내용은 아직 내 초식에선 알 수 없지만, 아무튼 아래와 같은 과정을 거칠것이다.

  1. 스프링이 실행되면 지정된 패키지의 클래스들을 스캔한다.(어떻게 하는지는 모름)
  2. 스캔하면서 어노테이션에 대한 정보를 쫘아아악 불러온다.(뇌피셜: 아마 클래스 레벨 먼저 하면서 빈 등록하고, 그 뒤에 필드와 메서드를 하지 않을까 싶다)
  3. 어노테이션의 이름과, 추가 정보(value = "blahblah"같은것) 에 맞춰 스프링에서 기능을 붙여준다.
  4. 개발자는 동작원리는 몰라도 아무튼 쓸 순 있다 대박

그럼 내가 리플렉션을 쓸 일이 있나?

여러 내용을 찾아본 결과 대답은 "No"인듯 하다. 프레임워크나 라이브러리를 만드는 천상계 개발자들은 단점들을 최소화 하며 장점만 극대화 시키고 사용하겠지만, 우리같은 응애들은 쓸 상황도 없을 것이며, 쓰더라도 제대로 못쓸것이기 때문에 다른 방식을 찾아보는게 좋을거같다.

하지만 스프링으로 개발을 하는 이상, 스프링에서 어떻게 어노테이션으로 기능을 구현하는지 알기 위해 리플렉션은 기초 지식중 하나인듯 하다. 그러니 일단 알고있으면 무적권 이득인거같고, 앞으로 더 깊게 알아가야겠다!!





Source

JAVA - 리플렉션 (Reflection)이란?
[Java] 리플렉션 (Reflection)이란 무엇일까? (개념/ 예시)
[10분 테코톡] 파랑, 아키의 리플렉션

1개의 댓글

comment-user-thumbnail
2024년 8월 25일

꼭 라이브러리나 프레임워크가 아니더라도, 간단하게 API 목록을 뽑아낼 때 활용도가 좋습니다^^ 저는 Reflection으로 Swagger와 유사한 API 테스트를 만들었습니다

  1. Custom Annotation 개발
  2. Spring의 RestController 내에서 API 목록을 만들 메소드에 해당 어노테이션 적용
  3. API 목록을 뽑아내는 함수에서 해당 어노테이션이 있는 메소드만 뽑아냄

형태로 개발했습니다

답글 달기