Skip to content

김현우, 이경민 리플랙션

KyungMin Lee edited this page Jul 8, 2024 · 1 revision

3주차 - Java Reflection

요구사항 해체분석기

요구사항 1 - 클래스 정보 출력

ReflectionTest의 showClass() 메소드를 구현해 Question 클래스의 모든 필드, 생성자, 메소드에 대한 정보를 출력한다.

  • ⚙️ 실행 코드

    @Test
    @DisplayName("테스트1: 리플렉션을 이용해서 클래스와 메소드의 정보를 정확하게 출력해야 한다.")
    public void showClass() {
        Class<Question> clazz = Question.class;
        logger.debug("Classs Name {}", clazz.getName());
        for (Field field : clazz.getDeclaredFields()) {
            logger.debug("Field : {}", field);
        }
        for (var constructor : clazz.getDeclaredConstructors()) {
            logger.debug("Constructor : {}", constructor);
        }
        for (Method method : clazz.getDeclaredMethods()) {
            logger.debug("Method : {}", method);
        }
    }
  • 🖥️ 결과

    14:45:18.742 [main] DEBUG next.reflection.ReflectionTest - Classs Name next.reflection.Question
    14:45:18.745 [main] DEBUG next.reflection.ReflectionTest - Field : private long next.reflection.Question.questionId
    14:45:18.745 [main] DEBUG next.reflection.ReflectionTest - Field : private java.lang.String next.reflection.Question.writer
    14:45:18.745 [main] DEBUG next.reflection.ReflectionTest - Field : private java.lang.String next.reflection.Question.title
    14:45:18.745 [main] DEBUG next.reflection.ReflectionTest - Field : private java.lang.String next.reflection.Question.contents
    14:45:18.746 [main] DEBUG next.reflection.ReflectionTest - Field : private java.util.Date next.reflection.Question.createdDate
    14:45:18.746 [main] DEBUG next.reflection.ReflectionTest - Field : private int next.reflection.Question.countOfComment
    14:45:18.746 [main] DEBUG next.reflection.ReflectionTest - Constructor : public next.reflection.Question(java.lang.String,java.lang.String,java.lang.String)
    14:45:18.746 [main] DEBUG next.reflection.ReflectionTest - Constructor : public next.reflection.Question(long,java.lang.String,java.lang.String,java.lang.String,java.util.Date,int)
    14:45:18.746 [main] DEBUG next.reflection.ReflectionTest - Method : public boolean next.reflection.Question.equals(java.lang.Object)
    14:45:18.747 [main] DEBUG next.reflection.ReflectionTest - Method : public java.lang.String next.reflection.Question.toString()
    14:45:18.747 [main] DEBUG next.reflection.ReflectionTest - Method : public int next.reflection.Question.hashCode()
    14:45:18.747 [main] DEBUG next.reflection.ReflectionTest - Method : public void next.reflection.Question.update(next.reflection.Question)
    14:45:18.747 [main] DEBUG next.reflection.ReflectionTest - Method : public java.lang.String next.reflection.Question.getContents()
    14:45:18.747 [main] DEBUG next.reflection.ReflectionTest - Method : public java.lang.String next.reflection.Question.getWriter()
    14:45:18.747 [main] DEBUG next.reflection.ReflectionTest - Method : public java.lang.String next.reflection.Question.getTitle()
    14:45:18.747 [main] DEBUG next.reflection.ReflectionTest - Method : public long next.reflection.Question.getQuestionId()
    14:45:18.747 [main] DEBUG next.reflection.ReflectionTest - Method : public java.util.Date next.reflection.Question.getCreatedDate()
    14:45:18.747 [main] DEBUG next.reflection.ReflectionTest - Method : public long next.reflection.Question.getTimeFromCreateDate()
    14:45:18.747 [main] DEBUG next.reflection.ReflectionTest - Method : public int next.reflection.Question.getCountOfComment()

요구사항 2 - test로 시작하는 메소드 실행

Junit3에서는 test로 시작하는 메소드를 자동으로 실행한다. 이와 같이 Junit3Test 클래스에서 test로 시작하는 메소드만 Java Reflection을 활용해 실행하도록 구현한다.

  • ⚙️ 실행 코드

    @Test
    public void runner() throws Exception {
        Class<Junit3Test> clazz = Junit3Test.class;
        for (Method method : clazz.getDeclaredMethods()) {
            if (method.getName().startsWith("test")) {
                method.invoke(clazz.getDeclaredConstructor().newInstance());
            }
        }
    }
  • 🖥️ 결과

    Running Test1
    Running Test2

요구사항 3 - @Test 애노테이션 메소드 실행

Junit4에서는 @Test 애노테이션일 설정되어 있는 메소드를 자동으로 실행한다. 이와 같이 Junit4Test 클래스에서 @MyTest 애노테이션으로 설정되어 있는 메소드만 Java Reflection을 활용해 실행하도록 구현한다.

  • ⚙️ 실행 코드

    @Test
    public void run() throws Exception {
        Class clazz = Junit4Test.class;
        for (Method method : clazz.getDeclaredMethods()) {
            if (method.isAnnotationPresent(MyTest.class)) {
                Constructor<Junit4Test> constructor = clazz.getDeclaredConstructor();
                constructor.setAccessible(true);
                method.invoke(clazz.getDeclaredConstructor().newInstance());
            }
        }
    }
  • 🖥️ 결과

    Running Test1
    Running Test2
  • 📚 공부하기

    • .isAnnotationPresent(Class<? extends Annotation>)가 NPE를 발생하는 경우는 어떤 경우일까?
    • 매개변수로 들어가는 값이 null 인 경우 NPE가 발생합니다!

요구사항 4 - private field에 값 할당

자바 Reflection API를 활용해 다음 Student 클래스의 name과 age 필드에 값을 할당한 후 getter 메소드를 통해 값을 확인한다.

  • ⚙️ 실행 코드

    @Test
    public void privateFieldAccess()
        throws NoSuchFieldException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
        Class<Student> clazz = Student.class;
        logger.debug(clazz.getName());
        Field nameField = clazz.getDeclaredField("name");
        Field ageField = clazz.getDeclaredField("age");
    
        nameField.setAccessible(true);
        ageField.setAccessible(true);
    
        Student student = clazz.getConstructor().newInstance();
    
        nameField.set(student, "홍길동");
        ageField.set(student, 20);
    
        assertThat(student.getName()).isEqualTo("홍길동");
        assertThat(student.getAge()).isEqualTo(20);
    }
  • 🖥️ 결과

    15:05:29.750 [main] DEBUG next.reflection.ReflectionTest - next.reflection.Student
  • 📚 공부하기

    • .set(Object obj, Object value)으로 값 지정하기
      • 값을 지정할 객체 obj 가 null인 경우에는 NPE가 발생!
      • 지정하고자 하는 필드에 대해 접근할 수 없는 경우(ex. private) IllegalAccessException이 발생!
      • 필드에 적합한 형식의 인자가 아닌 경우 IllegalArgumentException 발생!

요구사항 5 - 인자를 가진 생성자의 인스턴스 생성

  • ⚙️ 실행 코드

    @Test
        void constructorAccess() throws Exception {
        String userName = "홍길동";
        int userAge = 30;
        Class<User> clazz = User.class;
        Constructor[] constructors = clazz.getDeclaredConstructors();
        for (Constructor<User> constructor : constructors) {
            User user = constructor.newInstance(userName, userAge);
            assertAll(
                () -> assertThat(user.getName()).isEqualTo(userName),
                () -> assertThat(user.getAge()).isEqualTo(userAge)
            );
        }
    }
  • 🖥️ 결과

    15:11:56.625 [main] DEBUG next.reflection.ReflectionTest - User : User{name='홍길동', age=30}

보너스 미션 해체분석기

보너스 미션 1 - 수행시간 측정

  • ⚙️ 실행 코드

    package next;
    
    import java.io.File;
    import java.io.IOException;
    import java.lang.reflect.Method;
    import java.net.URL;
    import java.util.ArrayList;
    import java.util.Enumeration;
    import java.util.List;
    import org.junit.jupiter.api.Test;
    
    public class ElapsedTimeTest {
    
        @Test
        public void 지난시간() throws IOException, ClassNotFoundException {
            for(Class<?> clazz : getClassesForPackage("next")) {
                for(Method method : clazz.getDeclaredMethods()) {
                    if(method.isAnnotationPresent(ElapsedTime.class)) {
                        elapsedTime(method, clazz);
                    }
                }
            }
        }
    
        public void elapsedTime(Method method, Class<?> clazz) {
            long start = System.currentTimeMillis();
            try {
                method.invoke(clazz.getDeclaredConstructor().newInstance());
            } catch (Exception e) {
                e.printStackTrace();
            }
            long end = System.currentTimeMillis();
            System.out.println("Elapsed Time: " + (end - start));
        }
    
        public static List<Class<?>> getClassesForPackage(String packageName)
            throws IOException, ClassNotFoundException {
            List<Class<?>> classes = new ArrayList<>();
            String path = packageName.replace('.', '/');
            ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
            Enumeration<URL> resources = classLoader.getResources(path);
    
            while (resources.hasMoreElements()) {
                URL resource = resources.nextElement();
                File directory = new File(resource.getFile());
    
                if (directory.exists()) {
                    findClasses(directory, packageName, classes);
                }
            }
            return classes;
        }
    
        private static void findClasses(File directory, String packageName, List<Class<?>> classes) throws ClassNotFoundException {
            if (!directory.exists()) {
                return;
            }
    
            File[] files = directory.listFiles();
            if (files == null) {
                return;
            }
    
            for (File file : files) {
                if (file.isDirectory()) {
                    findClasses(file, packageName + "." + file.getName(), classes);
                } else if (file.getName().endsWith(".class")) {
                    String className = packageName + '.' + file.getName().substring(0, file.getName().length() - 6);
                    classes.add(Class.forName(className));
                }
            }
        }
    }
  • 🖥️ 설명 및 결과

    파일 중에서 @ElaspedTime 어노테이션이 적용된 메서드를 모두 조회하고 조회된 메서드들에 대해서 실행 시간을 측정합니다.

보너스 미션 2 - 바이트 코드 확인하기

  • ⚙️ 실행 코드

    package org.example
    
    public class Main{
        public static void Main(String[] args) {
            System.out.println("Hello world!");
        }
    }
  • 🖥️ 결과

    Compiled from "Main.java"
    public class org.example.Main {
      public org.example.Main();
        Code:
           0: aload_0
           1: invokespecial #1                  // Method java/lang/Object."<init>":()V
           4: return
    
      public static void main(java.lang.String[]);
        Code:
           0: getstatic     #7                  // Field java/lang/System.out:Ljava/io/PrintStream;
           3: ldc           #13                 // String Hello world!
           5: invokevirtual #15                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
           8: return
    }
    • 0: aload_0: 자기 자신 객채를 로드합니다!

    • 1: invokespecial #1: Object 클래스의 생성자를 호출하는데 이 때 #1이 Method java/lang/Object."<init>":()V를 가리킵니다.

    • 4: return: 메서드를 종료합니다.

    • 0: getstatic #7: 상수 풀에서 #7을 스택에 로드합니다. #7은 Field java/lang/System.out:Ljava/io/PrintStream;를 가리킵니다.

    • 3: ldc #13: #13 문자열을 스택에 로드합니다. #13은 String Hello world!를 나타냅니다.

    • 5: invokevirtual #15: 상수 풀에서 #15 메서드를 호출 및 스택의 문자열을 출력합니다. #15는 Method java/io/PrintStream.println:(Ljava/lang/String;)V 를 나타냅니다.

    • 8: return: 메서드를 종료합니다.

👼 개인 활동을 기록합시다.

개인 활동 페이지

🧑‍🧑‍🧒‍🧒 그룹 활동을 기록합시다.

그룹 활동 페이지

🎤 미니 세미나

미니 세미나

🤔 기술 블로그 활동

기술 블로그 활동

📚 도서를 추천해주세요

추천 도서 목록

🎸 기타

기타 유용한 학습 링크

Clone this wiki locally