Item 39. Annotation

λͺ…λͺ… νŒ¨ν„΄λ³΄λ‹€ μ• λ„ˆν…Œμ΄μ…˜μ„ μ‚¬μš©ν•˜λΌ.

μ˜ˆμ „μ—” λͺ…λͺ… νŒ¨ν„΄μ„ μ‚¬μš©ν•΄ ν”„λ‘œκ·Έλž¨ μš”μ†Œμ— 정보λ₯Ό ν‘œμ‹œν–ˆλ‹€. ν•˜μ§€λ§Œ λͺ…λͺ… νŒ¨ν„΄μ€ ν”„λ‘œκ·Έλž¨ μš”μ†Œλ₯Ό 맀우 λΆˆνŽΈν•˜κ²Œ λ§Œλ“ λ‹€. 예λ₯Όλ“€μ–΄ μ΄μ „μ˜ Junit은 ν…ŒμŠ€νŠΈ λ©”μ„œλ“œλ₯Ό test둜 μ‹œμž‘ν•˜λŠ” μ΄λ¦„μœΌλ‘œ 지어야 ν…ŒμŠ€νŠΈλ₯Ό μ‹€ν–‰ν–ˆκΈ° λ•Œλ¬Έμ—, μ˜€νƒ€λ‚˜ μ˜λ„ν•˜μ§€ μ•Šμ€ λ©”μ„œλ“œκ°€ ν…ŒμŠ€νŠΈ λŒ€μƒμ΄ λ˜λŠ” κ²½μš°κ°€ λ§Žμ•˜λ‹€. μ• λ„ˆν…Œμ΄μ…˜ λ°©λ²•μœΌλ‘œ λͺ…λͺ… νŒ¨ν„΄μ„ μ™„μ „νžˆ λŒ€μ²΄ν•  수 있기 λ•Œλ¬Έμ— λͺ…λͺ… νŒ¨ν„΄μ„ μ‚¬μš©ν•˜μ§€ 말고 μ• λ„ˆν…Œμ΄μ…˜μ„ μ‚¬μš©ν•˜λŠ” 것이 μ’‹λ‹€.

Annotation

μ• λ„ˆν…Œμ΄μ…˜μ€ 클래슀, λ©”μ„œλ“œ, ν•„λ“œ λ“±μ˜ ν”„λ‘œκ·Έλž¨ μš”μ†Œμ— λΆ€κ°€ 정보λ₯Ό λ§λΆ™μ΄λŠ” 방법이닀. 이 방법을 μ‚¬μš©ν•˜λ©΄ μœ„μ˜ λͺ…λͺ… νŒ¨ν„΄μ˜ 단점을 λͺ¨λ‘ ν•΄κ²°ν•˜λ©΄μ„œ κ°„κ²°ν•˜κ³  λͺ…ν™•ν•˜κ²Œ ν”„λ‘œκ·Έλž¨ μš”μ†Œμ˜ 의미λ₯Ό 전달할 수 μžˆλ‹€.

마컀 μ• λ„ˆν…Œμ΄μ…˜


@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Test {
}

@Test μ• λ„ˆν…Œμ΄μ…˜μ„ μ‚΄νŽ΄λ³΄λ©΄ @interfaceλΌλŠ” ν‚€μ›Œλ“œλ‘œ μ„ μ–Έλ˜μ–΄ 있고, @Retentionκ³Ό @Targetμ΄λΌλŠ” μ• λ„ˆν…Œμ΄μ…˜μ„ 가지고 μžˆλ‹€. 이와 같이 μ• λ„ˆν…Œμ΄μ…˜ 선언에 λ‹€λŠ” μ• λ„ˆν…Œμ΄μ…˜μ„ 메타 μ• λ„ˆν…Œμ΄μ…˜(meta-annotation)이라고 ν•œλ‹€.

  • @Retention: ν•΄λ‹Ή μ• λ„ˆν…Œμ΄μ…˜μ„ μ–Έμ œκΉŒμ§€ μœ μ§€ν•  것인지λ₯Ό 지정, μ—¬κΈ°μ„œλŠ” RetentionPolicy.RUNTIME으둜 μ§€μ •λ˜μ–΄ μžˆμ–΄ @Testκ°€ λŸ°νƒ€μž„μ—λ„ μœ μ§€λ˜μ–΄μ•Ό ν•œλ‹€λŠ” 것을 μ˜λ―Έν•¨

  • @Target: ν•΄λ‹Ή μ• λ„ˆν…Œμ΄μ…˜μ„ 어디에 μ‚¬μš©ν•  수 μžˆλŠ”μ§€λ₯Ό 지정, μ—¬κΈ°μ„œλŠ” ElementType.METHOD둜 μ§€μ •λ˜μ–΄ μžˆμ–΄ @Testκ°€ λ©”μ„œλ“œ μ„ μ–Έμ—λ§Œ μ‚¬μš©ν•  수 있음

μ΄λ ‡κ²Œ μƒμ„±λœ @Test μ• λ„ˆν…Œμ΄μ…˜μ€ λ‹€μŒκ³Ό 같이 μ‚¬μš©ν•˜λŠ”λ°, 이처럼 λ§€κ°œλ³€μˆ˜ 없이 μ‚¬μš©ν•  수 μžˆλŠ” μ• λ„ˆν…Œμ΄μ…˜μ„ 마컀(marker) μ• λ„ˆν…Œμ΄μ…˜μ΄λΌκ³  ν•œλ‹€.

class Sample {
    @Test
    public static void m1() {
        // Test m1
    }
}

μ• λ„ˆν…Œμ΄μ…˜μ€ 말 κ·ΈλŒ€λ‘œ λΆ€κ°€ 정보λ₯Ό λ§λΆ™μ΄λŠ” 것이기 λ•Œλ¬Έμ—, ν•΄λ‹Ή μš”μ†Œμ— 직접적인 영ν–₯을 주지 μ•Šκ³ , 이 μ• λ„ˆν…Œμ΄μ…˜μ„ μ²˜λ¦¬ν•  ν”„λ‘œκ·Έλž¨μ—κ²Œ μΆ”κ°€ 정보λ₯Ό μ œκ³΅ν•˜λŠ” 것이 전뢀이닀. μ΄λŸ¬ν•œ 마컀 μ• λ„ˆν…Œμ΄μ…˜μ„ μ²˜λ¦¬ν•˜λŠ” μ½”λ“œ μ˜ˆμ‹œλŠ” λ‹€μŒκ³Ό κ°™λ‹€.

public class RunTests {
    public static void main(String[] args) throws Exception {
        int tests = 0;
        int passed = 0;
        Class<?> testClass = Class.forName(args[0]);
        for (Method m : testClass.getDeclaredMethods()) { // 클래슀의 λͺ¨λ“  λ©”μ„œλ“œλ₯Ό 순회
            if (m.isAnnotationPresent(Test.class)) { // @Test μ• λ„ˆν…Œμ΄μ…˜μ΄ μ‘΄μž¬ν•˜λŠ”μ§€ 확인
                tests++;
                try {
                    m.invoke(null);
                    passed++;
                } catch (InvocationTargetException wrappedExc) {
                    Throwable exc = wrappedExc.getCause();
                    System.out.println(m + " failed: " + exc);
                } catch (Exception exc) {
                    System.out.println("Invalid @Test: " + m);
                }
            }
        }
        System.out.printf("Passed: %d, Failed: %d%n", passed, tests - passed);
    }
}

λ§€κ°œλ³€μˆ˜λ₯Ό λ°›λŠ” μ• λ„ˆν…Œμ΄μ…˜

μ• λ„ˆν…Œμ΄μ…˜μ—λ„ λ§€κ°œλ³€μˆ˜λ₯Ό λ°›μ•„ μ •μ˜ν•  수 μžˆλŠ”λ°, 이λ₯Ό μ‚¬μš©ν•œ μ˜ˆμ‹œλŠ” λ‹€μŒκ³Ό κ°™λ‹€.


@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface ExceptionTest {
    Class<? extends Throwable> value();
}

@ExceptionTest μ• λ„ˆν…Œμ΄μ…˜μ€ @Test μ• λ„ˆν…Œμ΄μ…˜κ³Ό λΉ„μŠ·ν•˜μ§€λ§Œ, valueλΌλŠ” λ§€κ°œλ³€μˆ˜λ₯Ό λ°›μ•„μ„œ ν…ŒμŠ€νŠΈ λ©”μ„œλ“œκ°€ νŠΉμ •ν•œ μ˜ˆμ™Έλ₯Ό λ˜μ Έμ•Όλ§Œ μ„±κ³΅ν•˜λŠ” ν…ŒμŠ€νŠΈμž„μ„ λͺ…μ‹œν•œλ‹€.

class Sample {
    @ExceptionTest({IndexOutOfBoundsException.class, NullPointerException.class})
    public static void doublyBad() {
        List<String> list = new ArrayList<>();
        list.addAll(5, null);
    }
}

ν•΄λ‹Ή μ• λ„ˆν…Œμ΄μ…˜μ„ μ²˜λ¦¬ν•˜λŠ” μ½”λ“œλŠ” λ‹€μŒκ³Ό κ°™λ‹€.

public class RunTests {
    public static void main(String[] args) throws Exception {
        int tests = 0;
        int passed = 0;
        Class<?> testClass = Class.forName(args[0]);
        for (Method m : testClass.getDeclaredMethods()) { // 클래슀의 λͺ¨λ“  λ©”μ„œλ“œλ₯Ό 순회
            if (m.isAnnotationPresent(ExceptionTest.class)) { // @ExceptionTest μ• λ„ˆν…Œμ΄μ…˜μ΄ μ‘΄μž¬ν•˜λŠ”μ§€ 확인
                tests++;
                try {
                    m.invoke(null);
                    System.out.printf("Test %s failed: no exception%n", m);
                } catch (Throwable wrappedExc) {
                    Throwable exc = wrappedExc.getCause();
                    int oldPassed = passed;
                    Class<? extends Throwable>[] excTypes = m.getAnnotation(ExceptionTest.class).value(); // @ExceptionTest의 valueλ₯Ό κ°€μ Έμ˜΄
                    for (Class<? extends Throwable> excType : excTypes) { // @ExceptionTest의 valueλ₯Ό 순회
                        if (excType.isInstance(exc)) {
                            passed++;
                            break;
                        }
                    }
                    if (passed == oldPassed) {
                        System.out.printf("Test %s failed: %s %n", m, exc);
                    }
                }
            }
        }
        System.out.printf("Passed: %d, Failed: %d%n", passed, tests - passed);
    }
}

@Repeatable

Java 8λΆ€ν„°λŠ” μ• λ„ˆν…Œμ΄μ…˜μ„ λ°˜λ³΅ν•΄μ„œ μ μš©ν•˜μ—¬ μ—¬λŸ¬ 개의 값을 λ°›λŠ” 방법을 μ œκ³΅ν•œλ‹€. κΈ°μ‘΄ μ• λ„ˆν…Œμ΄μ…˜ μ •μ˜μ— @Repeatable을 μΆ”κ°€ν•˜κ³ , μ• λ„ˆν…Œμ΄μ…˜μ„ 담을 μ»¨ν…Œμ΄λ„ˆ μ• λ„ˆν…Œμ΄μ…˜μ„ μ •μ˜ν•˜λ©΄ λœλ‹€.

// @Repeatable을 μΆ”κ°€ν•˜μ—¬ μ• λ„ˆν…Œμ΄μ…˜μ„ λ°˜λ³΅ν•΄μ„œ μ μš©ν•  수 μžˆλ„λ‘ 함
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Repeatable(ExceptionTestContainer.class)
public @interface ExceptionTest {
    Class<? extends Throwable> value();
}

// μ• λ„ˆν…Œμ΄μ…˜μ„ 담을 μ»¨ν…Œμ΄λ„ˆ μ• λ„ˆν…Œμ΄μ…˜ μ •μ˜
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface ExceptionTestContainer {
    ExceptionTest[] value();
}

μ΄λ ‡κ²Œ μ •μ˜λœ μ• λ„ˆν…Œμ΄μ…˜μ„ μ‚¬μš©ν•˜κΈ° μœ„ν•΄μ„  λ‹¨μˆœνžˆ μ—¬λŸ¬ 개의 @ExceptionTest μ• λ„ˆν…Œμ΄μ…˜μ„ μ μš©ν•˜λ©΄ λœλ‹€.

class Sample {
    @ExceptionTest(IndexOutOfBoundsException.class)
    @ExceptionTest(NullPointerException.class)
    public static void doublyBad() {
        List<String> list = new ArrayList<>();
        list.addAll(5, null);
    }
}

μΆ”κ°€μ μœΌλ‘œ μ• λ„ˆν…Œμ΄μ…˜μ„ μ²˜λ¦¬ν•˜λŠ” μ½”λ“œλ„ ν•˜λ‚˜λ§Œ λ‹¬μ•˜μ„ λ•Œμ™€ μ—¬λŸ¬ 개λ₯Ό λ‹¬μ•˜μ„ λ•Œλ₯Ό κ΅¬λΆ„ν•˜μ—¬ μ²˜λ¦¬ν•΄μ•Ό ν•œλ‹€.

public class RunTests {
    public static void main(String[] args) throws Exception {
        int tests = 0;
        int passed = 0;
        Class<?> testClass = Class.forName(args[0]);
        for (Method m : testClass.getDeclaredMethods()) { // 클래슀의 λͺ¨λ“  λ©”μ„œλ“œλ₯Ό 순회
            if (m.isAnnotationPresent(ExceptionTest.class)
                || m.isAnnotationPresent(ExceptionTestContainer.class)) { // @ExceptionTest λ˜λŠ” @ExceptionTestContainer μ• λ„ˆν…Œμ΄μ…˜μ΄ μ‘΄μž¬ν•˜λŠ”μ§€ 확인
                tests++;
                try {
                    m.invoke(null);
                    System.out.printf("Test %s failed: no exception%n", m);
                } catch (Throwable wrappedExc) {
                    Throwable exc = wrappedExc.getCause();
                    int oldPassed = passed;
                    ExceptionTest[] excTests = m.getAnnotationsByType(ExceptionTest.class); // @ExceptionTest μ• λ„ˆν…Œμ΄μ…˜μ„ κ°€μ Έμ˜΄
                    for (ExceptionTest excTest : excTests) { // @ExceptionTest μ• λ„ˆν…Œμ΄μ…˜μ„ 순회
                        if (excTest.value().isInstance(exc)) {
                            passed++;
                            break;
                        }
                    }
                    if (passed == oldPassed) {
                        System.out.printf("Test %s failed: %s %n", m, exc);
                    }
                }

            }
        }
        System.out.printf("Passed: %d, Failed: %d%n", passed, tests - passed);
    }
}

Last updated