ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 자바 리플렉션
    Java 2023. 6. 29. 11:22
    학습 순서
    1. 리플렉션이란 무엇일까?
    2. 리플렉션에 주요 클래스와 인터페이스 살펴보기
    3. 리플렉션을 이해하기 위한 선행 어노테이션 학습 하기
    4. 리플렉션을 이해하기 위한 시나리오 코드 3단계

     

    1. 리플렉션이란 무엇일까?

    리플렉션(Reflection)은 자바의 핵심 기능 중 하나로, 프로그램 실행 중에 클래스의 정보를 분석하고 조작할 수 있는 부분을 의미합니다.
    즉, 리플렉션을 사용하면 컴파일 시점에 클래스의 구조를 미리 알 필요 없이, 실행 시점에 동적으로 클래스의 메서드, 필드, 생성자 등에 접근하고 호출할 수 있습니다 이는 일부 프레임워크나 라이브러리에서 매우 유용하게 사용되고 있습니다.

     

    2. 주요 클래스와 인터페이스 살펴보기

    자바에서 리플렉션을 사용하려면 java.lang.reflect 패키지에 있는 클래스와 인터페이스를 사용할 수 있습니다.

    keyword : Class, Field, Method, Constructor

     

    2.1 클래스 정보 가져오기

    Class 객체를 통해 클래스 정보에 접근할 수 있습니다. 클래스 정보를 가져오려면 Class.forName() 메서드를 사용하거나, 객체의 getClass() 메서드를 호출 또는 리터럴로 직접 xxx.class 를 붙여서 참조합니다

     

    // Class.forName() 사용 
    Class<?> clazz = Class.forName("com.example.MyClass");
    
    // 객체.getClass() 사용 
    MyClass obj = new MyClass();
    Class<?> clazz = obj.getClass();
    
    // .class 사용 
    Class<?> clazz = MyClass.class;

     

    2.2 필드 정보 가져오기

    Class 객체의 getField() 또는 getDeclaredField() 메서드를 사용하여 필드 정보에 접근할 수 있습니다. getField()는 public 필드에만 접근할 수 있고, getDeclaredField()는 모든 접근 제한자의 필드에 접근할 수 있습니다.

     

    // public 필드에만 접근 가능 
    Field field = clazz.getField("fieldName");
    // 모든 접근제한자에 접근 가능 
    Field field = clazz.getDeclaredField("fieldName");

     

    2.3 메서드 정보 가져오기

    Class 객체의 getMethod() 또는 getDeclaredMethod() 메서드를 사용하여 메서드 정보에 접근할 수 있습니다. getMethod()는 public 메서드에만 접근할 수 있고, getDeclaredMethod()는 모든 접근 제한자의 메서드에 접근할 수 있습니다.

     

    // public 메서드에만 접근 가능
    Method method = clazz.getMethod("methodName", parameterTypes);
    // 모든 접근제한자 메서드에 접근 가능 
    Method method = clazz.getDeclaredMethod("methodName", parameterTypes);

     

    2.4 생성자 정보 가져오기

    Class 객체의 getConstructor() 또는 getDeclaredConstructor() 메서드를 사용하여 생성자 정보에 접근할 수 있습니다. getConstructor()는 public 생성자에만 접근할 수 있고, getDeclaredConstructor()는 모든 접근 제한자의 생성자에 접근할 수 있습니다.

    // 클래스 정보 가져오기
    Class<?> clazz = MyClass.class;
    // public 생성자 정보 가져오기
    Constructor<?> publicConstructor = clazz.getConstructor(String.class);
    
    // 모든 생성자 정보 가져오기
    Constructor<?>[] constructors = clazz.getDeclaredConstructors();
    for (Constructor<?> constructor : constructors) {
        System.out.println(constructor);
    }

     

    package ch01;
    
    import java.lang.reflect.Constructor;
    import java.lang.reflect.Field;
    import java.lang.reflect.Member;
    import java.lang.reflect.Method;
    
    public class MainTest {
        public static void main(String[] args) throws Exception {
    
            Class<?> clazz = Class.forName("ch01.UserController");
            System.out.println(clazz);
            System.out.println("clazz : " + clazz.toString());
    
            // 필드 접근
            Field publicField = clazz.getField("name");
            Field privateField = clazz.getDeclaredField("age");
    
            System.out.println("name Field : " + publicField);
            System.out.println("age Field : " + privateField);
    
            // 메서드 접근 방법
            Method publicMethod = clazz.getMethod("login");
            Method privateMethod = clazz.getDeclaredMethod("join");
    
            System.out.println("method login : " + publicMethod);
            System.out.println("method join : " + privateMethod);
    
            // 생성자 접근 방법
            Constructor<?>[] constructors = clazz.getConstructors();
            for (Constructor<?> cls: constructors) {
                System.out.println("생성자 : " + cls);
            }
        }
    }

     

    3. 리플렉션을 이해하기 위한 선행 어노테이션 학습 하기

    자바의 어노테이션(Annotation)은 프로그램의 코드에 메타데이터를 추가하는 기능을 제공하는 것으로, 컴파일러나 런타임 시에 코드를 처리하는 도구들에게 추가적인 정보를 제공합니다.

    어노테이션은 @어노테이션이름(속성1=값1, 속성2=값2, ...) 형태로 사용하며, 주로 클래스, 메서드, 변수, 매개변수 등에 부착하여 사용됩니다.

    자바에서 제공하는 어노테이션은 크게 세 가지 유형으로 나눌 수 있습니다.

    1.빌트인 어노테이션(Built-in Annotation)
    @Override: 상위 클래스나 인터페이스의 메서드를 오버라이드함을 나타냅니다.
    @Deprecated: 해당 요소(클래스, 메서드, 필드 등)가 더 이상 사용되지 않음을 나타냅니다.
    @SuppressWarnings: 컴파일러의 경고를 무시하도록 지정합니다.

    2.메타 어노테이션(Meta Annotation)
    @Retention: 어노테이션의 유지 정책을 지정합니다. (소스, 클래스, 런타임)
    @Target: 어노테이션을 부착할 수 있는 대상(클래스, 메서드, 필드 등)을 지정합니다.

    3.커스텀 어노테이션(Custom Annotation)
    개발자가 직접 정의한 어노테이션으로, 애플리케이션에 맞는 사용자 정의 어노테이션을 만들 수 있습니다.

    어노테이션은 주로 코드의 가독성을 높이고, 자동화된 코드 생성, 테스트, 디버깅 등에 활용됩니다.

     

    3.1 커스텀 어노테이션

    커스텀 어노테이션 정의 하는 방법

    public @interface MyCustomAnnotation {
        String value() default ""; // value() - 요소 , default - 기본값 정의 가능  
    }

     

    커스텀 어노테이션 사용하는 방법

    @MyCustomAnnotation(value = "값지정가능")
    public class MyClass {
        // ... 
    }

     

    커스텀 어노테이션 만들어 보기

    @Retention(value = RetentionPolicy.RUNTIME) // 이 어노테이션은 런타임에도 수명 주기 유지 
    @Target(ElementType.TYPE) // 이 어노테이션을 클래스 레벨에서만 사용할 수 있도록 정의
    public @interface CustomAnnotation { // 커스텀 어노테이션을 정의
        String value(); // 어노테이션 요소를 정의
    }

     

    @Retention(속성=값) - 어노테이션의 수명,유지되는 범위를 지정 하는데 사용

    RetentionPolicy.SOURCE - 소스 코드에서만 유지, 컴파일 과정에서는 제거 RetentionPolicy.CLASS - 컴파일러에 의해 클래스 파일까지 유지되지만, 런타임 시 무시 RetentionPolicy.RUNTIME - 이 런타임 시에도 유지되어야 하며, 따라서 런타임 시에 리플렉션을 통해 어노테이션 정보를 읽을 수 있다는 것을 나타냅니다

    @Target(속성=값) - 사용될 수 있는 Java 요소를 지정

    ElementType.TYPE - 클래스, 인터페이스 (비롯한 애노테이션 타입), 또는 열거형에서 사용 선언 가능 ElementType.FIELD - 필드 선언 (즉, 인스턴스 변수)에 사용 가능

    ElementType.METHOD: 메서드 에서 사용 가능

     

    4. 리플렉션을 이해하기 위한 시나리오 코드 3단계

    package ex02;
    
    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    
    
    @Retention(RetentionPolicy.RUNTIME)  // 생명 주기 (런타임)
    @Target(ElementType.METHOD) // 메서드 앞에 사용하는 어노테이션으로 정의
    public @interface RequestMapping {
        String uri(); // 어노테이션 요소(메서드)
    }

     

    package ex02;
    
    public class UserController {
    
        @RequestMapping(uri = "/login")
        public void login() {
            System.out.println("login()  메서드 호출");
        }
    
        @RequestMapping(uri = "/join")
        public void join() {
            System.out.println("join()  메서드 호출");
        }
    
        @RequestMapping( uri = "/save")
        public void save() {
            System.out.println("save 메서드 호출");
        }
    }

     

    package ex02;
    
    import java.lang.annotation.Annotation;
    import java.lang.reflect.Method;
    import java.util.Scanner;
    
    public class App {
    
        // ex02 UserController 입니다.
        public static void findUri(UserController uc, String uri) throws Exception {
            boolean isFind = false;
    
            // 리플렉션을 활용해서 UserController 선언된 메서드 정보를 가져오자
            Method[] methods = uc.getClass().getMethods();
    
            for (Method mt : methods) {
                // 해당 메서드에 선언된 RequestMapping 어노테이션을 가져오기
                Annotation anno = mt.getDeclaredAnnotation(RequestMapping.class);
                RequestMapping rm = (RequestMapping) anno;
                if (rm != null) {
                    if (rm.uri().equals(uri)) {
                        isFind = true;
                        mt.invoke(uc);
                        // break;
                    }
                }
    
            }
            if (isFind == false) {
                System.out.println("404 Not Found");
            }
        }
    
    
        public static void main(String[] args) throws Exception {
    
            // 사용자 입력을 받는 객체 필요
            Scanner sc = new Scanner(System.in);
            String uri = sc.nextLine();
            findUri(new UserController(), uri);
    
        }
    }

     

    2단계 코드에서 App.java 파일에서 findUri 함수안에서 리플렉션을 기법을 사용하고 있기 때문에 커스텀 어노테이션을 활용한 UserController에서는 /login, /join 메서드 이 외에 추가적인 메서드를 만들더라도 잘 동작할 수 있는 구조로 설계하였습니다. 하지만 UserController 클래스 이외에 다른 클래스를 사용할 수 없는 한계점을 가지고 있습니다.

     

    4.3 단계별 학습 3단계 진행

    3단계에서 리플렉션과 커스텀어노테이션을 활용한 컴포넌트 스캔이라는 개념에 대해 알아 봅시다. 스프링 부트에서 컴포넌트 스캔(Component Scan)은 스프링 프레임워크에서 제공하는 기능 중 하나로, 자동으로 빈(Bean)으로 등록할 클래스를 스캔하여 스프링 애플리케이션 컨텍스트에 등록하는 기능입니다. 이를 통해 개발자는 수동으로 빈을 등록하지 않고도 자동으로 컴포넌트를 찾아 스프링의 의존성 주입(Dependency Injection)과 관리 기능을 사용할 수 있습니다.

     

     

    package ex03;
    
    import ex02.UserController;
    
    import java.io.File;
    import java.lang.annotation.Annotation;
    import java.lang.reflect.Method;
    import java.net.URL;
    import java.util.HashSet;
    import java.util.Scanner;
    import java.util.Set;
    
    public class App {
    
        /**
        * 이 함수는 특정 패키지 내외(ex02 or ex03) 모든 클래스를 스캔하고
        * @Controller 어노테이션이 붙어 있는 클래스들을 확인하여 Set<Class<?>>
        * 형태의 자료구조에 담고 반환합니다.
         * */
        public static Set<Class<?>> componentScan(String pkg) throws Exception{
    
            // 현재 스레드의 컨텍스트 클래스 로더를 가져 옵니다.
            // 여기서는 특정 스레드가 어떤 클래스를 로드해야 하는지 결정하는데 사용(클래스 로더)
            ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
            Set<Class<?>> classes = new HashSet<>();
    
            // pkg -> ex02, ex03
            // 클래스 로더를 사용하여, 주어진 패키지명에 해당하는 URL을 가져 옵니다.
            URL packageUrl = classLoader.getResource(pkg);
            File packageDirectory = new File(packageUrl.toURI());
            // 해당 패키지(디렉토리)에 있는 모든 파일을 순회 합니다.
            for (File file : packageDirectory.listFiles()){
                // App.class, UserController.class, ..
                if (file.getName().endsWith(".class")){
                    // .class 문자열 값 제거
                    String className = pkg + "." + file.getName().replace(".class", "");
                    System.out.println("className" + className);
                    Class<?> cls = Class.forName(className);
                    // 자료 구조에 추출한 클래스 이름 넣기
                    classes.add(cls);
                }
            }
    
            // System.out.println(packageDirectory);
            // System.out.println(packageUrl);
    
            return classes;
        }
    
        /**
         *이 함수는 사용자가 입력한 URI 형식에 값(uri)을 넘겨받아 componentScan 함수에서
         * 실행한 Set<Class<?>> 타입에 데이터들 중에 사용자가 입력한 값과
         * @RequestMapping 어노테이션의 요소 값(uri)을 비교하여 일치한다면
         * 해당 메서드를 호출 시키는 역할을 합니다.
         * */
        public static void findUri(Set<Class<?>> classes, String uri) throws Exception{
            Boolean isFind = false;
    
            for(Class<?> cls : classes){
                // @Controller 어노테이션이 존재하는지 확인
                if (cls.isAnnotationPresent(Controller.class)){
                    // 생성자를 통해서 메모리에 올리고 참조 변수를 담아 둔다.
                    Object instance = cls.getDeclaredConstructor().newInstance();
                    // 해당 클래스에 모든 메서드 정보를 담아 둔다.
                    Method[] methods = cls.getDeclaredMethods();
                    for (Method mt : methods){
    
                        Annotation anno = mt.getDeclaredAnnotation(RequestMapping.class);
                        if(anno != null){
                            RequestMapping rm = (RequestMapping) anno;
                            if (rm.uri().equals(uri)){
                                isFind = true;
                                // uri 값이 일치 한다면 해당 메서드를 호출 합니다.
                                mt.invoke(instance);
                                break;
                            }
                        }
                    }
    
                }
            } //end of for
            if(isFind == false){
                System.out.println("404 Not Found");
            }
        }
    
    
    
        // main 함수
        public static void main(String[] args) throws Exception {
    
            // 컴퍼넌트 스캔 수행
            Set<Class<?>> classes =  componentScan("ex03"); // ex02, ex03
    
            //사용자 입력을 받는 객체 필요 (브라우저 주소값 입력)
            Scanner sc = new Scanner(System.in);
            String uri = sc.nextLine();
    
            // 실제 URI 맵핑 확인
            findUri(classes, uri);
    
        }
    }

     

    package ex03;
    
    @Controller
    public class BoardController {
    
        @RequestMapping(uri = "/put")
        public void put(){
            System.out.println("put 메서드가 실행 됨");
        }
    
    }

     

    package ex03;
    
    
    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.TYPE) // 클래스 요소에 선언 가능한 어노테이션으로 정의
    public @interface Controller {
    
    }

     

    package ex03;
    
    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.METHOD) // 메서드에 선언 가능한 어노테이션
    public @interface RequestMapping {
    
        String uri(); // 어노테이션 요소 (메서드)
    
    }

     

    package ex03;
    
    @Controller // 커스텀 어노테이션 활용
    public class UserController {
    
        @RequestMapping(uri= "/login")
        public void login(){System.out.println("login() 메서드 호출");}
    
        @RequestMapping(uri="/join")
        public void join(){System.out.println("join() 메서드 호출");}
    
        @RequestMapping(uri="/save")
        public void save(){System.out.println("save() 메서드 호출");}
    
    }

     

    'Java' 카테고리의 다른 글

    Stream  (0) 2023.04.06
    버퍼 스트림  (0) 2023.03.14
    자바의 I/O 스트림  (0) 2023.03.02
    자료구조 연습문제  (0) 2023.02.21
    자료구조  (0) 2023.02.21
Designed by Tistory.