[Java] 리플랙션(reflection)에 대해서 알아보자.

devdo·2022년 1월 17일
0

Java

목록 보기
33/59
post-thumbnail

자바 리플랙션에 대해서 알아보자.

리플렉션 (Reflection)이란?

  • 힙 영역에 로드돼 있는 클래스 타입의 객체를 통해 필드/메소드/생성자를 접근 제어자와 상관 없이 사용할 수 있도록 지원하는 API
  • 컴파일 시점이 아닌 런타임 시점에 동적으로 특정 클래스의 정보를 추출해낼 수 있는 프로그래밍 기법
  • 주로 프레임워크 또는 라이브러리 개발 시 사용됨
  • ex. Spring 프레임워크 (ex. DI), Test 프레임워크 (ex. JUnit), JSON Serialization/Deserialization 라이브러리 (ex. Jackson)
    참고) https://www.baeldung.com/reflections-library

정리하면,

리플렉션은 구체적인 클래스 타입을 알지 못해도 그 클래스의 메소드와 타입 그리고 변수들을 접근할 수 있도록 해주는 자바 API 이다.

클래스로더를 통해 읽어온 클래스 정보(거울에 반사된 정보)를 사용하는 기술

리플랙션을 사용해 클래스를 읽어오거나, 인스턴스를 만들거나, 메서드를 실행하거나, 필드의 값을 가져오거나 변경하는 것이 가능하다는 것이다.

위 설명에 의하면 구체적인 클래스 타입을 모를때 사용하는 방법을 리플렉션이라는 것이다.

특정 어노테이션이 붙어있는 필드 또는 메소드 읽어올 때(JUnit, Spring)
특정 이름 패턴에 해당하는 메서드 목록 가져와 호출할 때(getter, setter)

여기서 의문점이 든다.

내가 짠 코든데 내가 만든 클래스의 이름도 모르는게 말이 되나?

하지만 가끔 어떤 타입의 클래스나 변수 혹은 메소드를 사용할지 모르는 경우가 생길 수 있다.

예를 들어 변수의 값을 조건에 따라서 다르게 사용해야하는 경우라던가.

어플리케이션이 실행되고 나서 생성되는 클래스라던가.

이럴경우에 리플렉션을 사용할 수 있는 것이다.


사용방법

사용방법은 간단하다.

Class 객체의 forName() 메소드를 사용하여 클래스이름에 해당하는 클래스를 찾아온다.

하지만 없으면, ClassNotFound 에러가 날 수 있다.

Class c = Class.forName("클래스이름");

// 메소드
Method[] m = c.getMethods();                     

// 필드
Field[] f = c.getFields();

// 구조체
Constructor[] cs = c.getConstructors();
Class[] inter = c.getInterfaces();
Class superClass = c.getSuperclass();

getClass() vs forName()

여기서 클래스 Class 객체를 얻는데 getClass() 메서드와도 헷갈릴 수 있는데,

getClass()는 Object의 메서드로써 해당클래스가 객체를 생성했을 때만 사용가능한 것이다!

forName()은 객체를 생성하기 전에 직접 클래스를 얻을 수 있는 것이 차이이다!


jdk 프록시, CGLib에도 적용돼

네, JDK 동적 프록시와 CGLib은 모두 런타임에 리플렉션을 사용하여 프록시 객체를 생성합니다.

jdk 동적 프록시

인터페이스를 기반으로 동작, 인터페이스의 구현 클래스를 자동으로 생성하여 프록시 객체를 생성, 해당 프록시에서 메서드 호출 등 런타임시 실제 객체의 메서드를 호출해서 확인할 수 있다.

jdk 동적 프록시는 아래 클래스를 사용하여 프록시를 생성한다.
java.lang.reflect.Proxy

CGLib

클래스를 기반으로 동작, 런타임에 클래스의 서브클래스를 생성하여 프록시 객체를 생성, CGLib은 바이트 코드 생성 라이브러리를 사용하여 클래스의 서브 클래스를 만들고, 해당 서브 클래스에서 메서드 호출을 가로채서 실제 객체의 메서드를 호출하거나 필요한 작업을 수행한다.

CGLIB의 동적으로 생성하는 클래스 이름은 다음과 같은 규칙으로 생성한다.
대상클래스$$EnhancerByCGLIB$$임의코드

참고) https://velog.io/@mooh2jj/Spring-Proxy


스프링에도 사용돼

Reflection을 사용하는 기술을 나열하자면, jdk 동적 프록시, CGLib 프록시, 우리가 잘 아는 스프링 프레임워크, 대표적 ORM 기술인 하이버네이트, jackson라이브러리 등에 사용된다.

Reflection을 사용해서 스프링에서는 런타임 시에 개발자가 등록한 빈을 애플리케이션에서 가져와 사용할 수 있게 되는 것이다.
(일종의 reflection 기술이었음!)

자세한 내용은 이 블로그에서 잘 설명해 놓았다.
https://medium.com/msolo021015/%EC%9E%90%EB%B0%94-reflection%EC%9D%B4%EB%9E%80-ee71caf7eec5

예시를 들자면,

1) 컴포넌트 스캔(Component Scan)

  • 스프링에서는 @Component 어노테이션을 사용하여 클래스를 컴포넌트(Component)로 등록합니다.
  • 컴포넌트 스캔은 스프링이 애플리케이션을 시작할 때 지정된 패키지에서
  • @Component 어노테이션이 붙은 클래스를 찾아서 객체를 생성합니다.
  • 이때 스프링은 리플렉션을 사용하여 클래스의 정보를 조회하고 객체를 생성합니다.

2) 의존성 주입(Dependency Injection)

  • 스프링에서는 의존성 주입을 사용하여 객체 간의 의존 관계를 느슨하게 결합합니다.
  • 의존성 주입을 사용하면 객체 간의 의존 관계를 설정하기 위해 직접 코드를 작성할 필요가 없습니다.
  • 스프링은 리플렉션을 사용하여 의존 관계가 필요한 클래스의 생성자를 호출하고 필요한 객체를 주입합니다.

3) AOP(Aspect Oriented Programming)

  • 스프링에서는 AOP를 사용하여 애플리케이션의 여러 부분에 공통으로 적용되는 기능을 모듈화합니다.
  • AOP는 프록시 객체를 생성하여 핵심 로직을 실행하기 전과 후에 공통으로 수행할 코드를 추가합니다.
  • 이때 스프링은 리플렉션을 사용하여 프록시 객체를 생성하고 필요한 메소드를 호출합니다.

코드예시

// 스프링 리플렉션 사용 코드
public static Field findField(Class<?> clazz, String name, Class<?> type) {
	// Assert.notNull(clazz, "Class must not be null");
	// Assert.isTrue(name != null || type != null, "Either name or type of the field must be specified");
	
    Class<?> searchType = clazz;
	while (!Object.class.equals(searchType) && searchType != null) {
		Field[] fields = searchType.getDeclaredFields();
		for (Field field : fields) {
			if ((name == null || name.equals(field.getName())) && (type == null || type.equals(field.getType()))) {
				return field;
			}
		}
		searchType = searchType.getSuperclass();
	}
	return null;
}

JPA 엔티티 생성시 기본 생성자가 필요한 이유!

✅ JPA Entity 생성시, 리플렉션을 활용하여 생성한다. 이때 기본 생성자가 필요하다. 리플렉션은 생성자의 매개변수를 읽을 수가 없어서
반드시 기본 생성자를 정의해 줘야 하기 때문이다!

그 이유는 다음과 같다.
JPA 내 엔티티는 hibernate가 내부적으로 JPA 엔티티를 만들때 Class.newInstance()라는 리플렉션을 이용해 해당 "Entity의 기본 생성자"를 호출해서 객체를 생성한다.

리플렉션은 생성자의 매개변수를 읽을 수가 없어서 반드시 기본 생성자를 정의해 줘야 한다.
java Reflection이 가져올 수 없는 정보 중 하나가 바로 생성자의 매개변수 정보이다.

따라서 기본 생성자 없이 파라미터가 있는 생성자만 존재한다면
java Reflection이 객체를 생성할 수 없게 되는 것이다.



참고

https://kingname.tistory.com/164

profile
배운 것을 기록합니다.

0개의 댓글