리플렉션(Reflection)으로 DI 구현해보기 +단점

2024. 12. 22. 12:19·💻 Dev/Java & OOP

자바 기본서를 읽던 중 리플렉션 파트가 나왔다.
특히 스프링 프레임워크의 DI(Dependency Injection)나 어노테이션 처리에 사용되고, 자사 라이브러리를 만들 때 사용한다고 한다.
아직 한 번도 사용해 본 적은 없어서 DI 구현을 통해 이해해보고자 한다!

 

@TestAutowired 구현하기

리플렉션의 활용을 이해하기 위해 간단한 @TestAutowired 어노테이션을 만들어보자. 이 어노테이션은 클래스의 필드에 붙여 의존성을 자동으로 주입하는 기능을 한다.

 

1. @TestAutowired 어노테이션 정의

먼저 @TestAutowired 어노테이션을 정의한다.
이 어노테이션은 필드에만 사용할 수 있고, 런타임에도 유지되어야 하므로 @Retention과 @Target을 설정한다.

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)  
public @interface TestAutowired {

특정 클래스의 멤버필드에 @TestAutowired 어노테이션이 붙어있을 경우 해당 리플렉션을 통해 객체를 생성한다.

 

2. 의존성을 주입하는 클래스 작성

그리고 해당 어노테이션이 붙은 필드를 주입하는 클래스를 작성한다.

public class DependencyInjector {
    public static void injectDependencies(Object target) {
        Class<?> clazz = target.getClass();

        for (Field field : clazz.getDeclaredFields()) {
            if (field.isAnnotationPresent(AutoWired.class)) {
                field.setAccessible(true); // private 필드 접근 허용
                try {
                    Object dependency = field.getType().newInstance();
                    field.set(target, dependency); // 객체를 필드에 주입
                } catch (Exception e) {
                    throw new RuntimeException("Dependency injection failed: " + e.getMessage(), e);
                }
            }
        }
    }
}

 

3. 테스트 클래스 작성

Service와 Controller 클래스를 만들어 실제로 DI(Dependency Injection)가 작동하는지 확인한다.

class Service {  
    public void serve() {  
        System.out.println("DI가 됐어유");  
    }  
}  

class Controller {  
    @AutoWired  
    private Service service;  

    public void handleRequest() {  
        service.serve();  
    }  
}  

public class Main {  
    public static void main(String[] args) {  
        Controller controller = new Controller();  

        DependencyInjector.injectDependencies(controller);  

        controller.handleRequest();  
    }

 

실행 결과

DI가 됐어유

결과를 보면 Controller 클래스에 Service 클래스가 정상적으로 주입되어 serve()메서드가 실행된 것을 확인할 수 있다.

 

 

리플렉션의 단점

하지만 리플렉션의 단점도 분명한데, 대표적으로 2가지 단점이 있다.

 

1. 일반 메서드 보다 성능이 떨어진다.

리플렉션은 일반적인 메서드 호출보다 성능이 느리다.
런타임에 동적으로 클래스를 탐색하고 메서드를 호출하기 때문이다.
즉, 해당 클래스의 타입이 맞는지, 생성자가 존재하는지 등의 validation(유효성 검증) 과정을 런타임 시 처리해야 하기 때문에 성능이 떨어진다.

 

반면 일반적인 메서드 호출은 컴파일 타임에 코드가 정적으로 결정된다.

자바 컴파일러는 메서드 호출 시 필요한 모든 정보를 미리 알고 있기 때문에, 바이트코드를 효율적으로 생성할 수 있다.

 

다음 코드는 리플렉션 호출과 일반적인 메서드 호출의 성능을 비교하는 예제이다.

public class ReflectionTest {  
    public void normalMethod() {  
        // 단순 메서드 호출  
    }  

    public static void main(String[] args) throws Exception {  
        ReflectionTest test = new ReflectionTest();  

        long start = System.nanoTime();  
        for (int i = 0; i < 1_000_000; i++) {  
            test.normalMethod();  
        }  
        long end = System.nanoTime();  
        System.out.println("Normal method time: " + (end - start) + " ns");  

        // Reflection 호출  
        start = System.nanoTime();  
        for (int i = 0; i < 1_000_000; i++) {  
            ReflectionTest.class.getMethod("normalMethod").invoke(test);  
        }  
        end = System.nanoTime();  
        System.out.println("Reflection method time: " + (end - start) + " ns");  
    }

실행 결과 리플렉션 호출이 일반 메서드 호출보다 훨씬 느리다는 것을 알 수 있다.

Normal method time: 2437500 ns
Reflection method time: 63187083 ns

따라서 성능이 중요한 코드에서는 리플렉션을 남용하지 않아야 한다.

 

2. 안전성 문제

또한 리플렉션은 프로그램의 안정성을 저하시킬 수 있다.
왜냐하면 개발자가 의도치 않은 방식으로 내부 구조에 접근할 수 있기 때문에 예상치 못한 에러가 발생할 수 있다.

 

(1) 캡슐화 원칙 위반

자바는 객체지향 언어의 기본 원칙 중 하나인 캡슐화(encapsulation)를 강력히 지원한다. 캡슐화는 객체 내부의 필드와 메서드를 외부에서 임의로 접근하지 못하도록 제한하여 코드의 안정성과 유지보수성을 높인다. 그러나 리플렉션은 이러한 제한을 무시하고, private 필드와 메서드에도 접근할 수 있는 기능을 제공한다.

 

(2) 컴파일 타임 검증의 상실

리플렉션은 런타임에 동적으로 코드를 실행하기 때문에, 컴파일 타임에서의 타입 및 메서드 검증이 불가능하다. 즉, 개발자가 작성한 코드가 올바른지 여부를 컴파일 단계에서 확인할 수 없고, 실행 시점에야 오류가 발생할 수 있다.

 

 

 

 

 

참고
Reflection / 개념 / 예제 / 단점 / DI 프레임워크 구현

자바 리플렉션(reflection)이란?

저작자표시 비영리 (새창열림)

'💻 Dev > Java & OOP' 카테고리의 다른 글

스레드 로컬(Thread Local)이란?  (1) 2024.12.29
자바 직렬화(Java Serializable)  (0) 2024.12.27
자바는 왜 Lambda&Stream을 도입했을까? feat.함수형 프로그래밍  (0) 2024.12.26
Stack은 왜 상속의 실패 사례일까?  (0) 2024.12.12
System.out.println을 실무에서 사용하면 안되는 이유  (0) 2024.12.05
가비지 컬렉터(GC)의 Roots  (0) 2024.12.04
'💻 Dev/Java & OOP' 카테고리의 다른 글
  • 자바 직렬화(Java Serializable)
  • 자바는 왜 Lambda&Stream을 도입했을까? feat.함수형 프로그래밍
  • Stack은 왜 상속의 실패 사례일까?
  • System.out.println을 실무에서 사용하면 안되는 이유
현주먹
현주먹
대구 불주먹 출신 현주먹의 개발.log
  • 현주먹
    현주먹의 개발로그
    현주먹
  • 전체
    오늘
    어제
    • 전체글 (165) N
      • 👶🏻 CS (15)
        • Operating System (8)
        • Database (4)
        • Data Structure (2)
        • Software Engineering (1)
      • 💻 Dev (54)
        • Java & OOP (24)
        • Spring (4)
        • JPA (5)
        • Test Code (1)
        • Database (1)
        • JSP & Servlet (13)
        • Etc (6)
      • 💡 Algorithm (25)
        • 인프런 (9)
        • 백준 (16)
      • 🛠 DevOps & Tool (11)
        • Linux (4)
        • AWS (1)
        • Git (2)
        • Etc (4)
      • 📝 끄적끄적 (60) N
        • 후기 및 회고 (5)
        • TDD, 클린 코드 with Java 17기 (3)
        • F-Lab (23)
        • 🖥️ 자바의 정석 (11)
        • 📖 Clean Code (3)
        • 항해99 코테 스터디 (11)
        • 📖 가상 면접 사례로 배우는 대규모 시스템 설계 .. (3) N
  • 블로그 메뉴

    • 🐈‍⬛ GitHub
    • TIL repository
  • 인기 글

  • 최근 글

  • 최근 댓글

  • 태그

    데브클럽
    개발자멘토링
    F-Lab
    99클럽
    ==와 equals()
    백준
    에프랩 후기
    인프런 단어뒤집기
    백준10250
    C
    오라클
    에프랩
    인프런 특정문자뒤집기
    객체지향
    개발자취업
    자바의신절판
    NextSTEP
    til
    코테스터디
    오블완
    TDD 클린 코드 with Java
    로또 미션
    코딩테스트준비
    PostGreSQL함수
    자바의정석
    JPA
    jsp
    f-lab 후기
    항해99
    티스토리챌린지
  • hELLO· Designed By정상우.v4.10.2
현주먹
리플렉션(Reflection)으로 DI 구현해보기 +단점
상단으로

티스토리툴바