39. 명명 패턴보다 애너테이션을 사용하라. 핵심

무한성장개발자·2025년 9월 25일

package me.whiteship.chapter06.item39.naming_pattern;

public class Sample {

    public static void testM1() {
    }        // 성공해야 한다.

    public static void testM2() {
    }        // 성공해야 한다.

    public static void testM3() {    // 실패해야 한다.
        throw new RuntimeException("실패");
    }

    public static void m4() {
    }  // 테스트가 아니다.

    public void testM5() {
    }   // 잘못 사용한 예: 정적 메서드가 아니다.

    public static void testM6() {
    }

    public static void testM7() {    // 실패해야 한다.
        throw new RuntimeException("실패");
    }

    public static void m8() {
    }
}

@Tests인 경우 실행이 안된다.
테스트가 실행되는 도중에 어떠한 예외를 발생시키고 싶다고 하면 testThrowException.... 등으로 모든 매개변수를 표현하는 것은 쉽지 않다.

package me.whiteship.chapter06.item39.naming_pattern;

// 코드 39-3 마커 애너테이션을 처리하는 프로그램 (239-240쪽)

import me.whiteship.chapter06.item39.markerannotation.Test;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class RunTests {
    public static void main(String[] args) throws Exception {
        int tests = 0;
        int passed = 0;
        Class<?> testClass = Sample.class;
        for (Method m : testClass.getDeclaredMethods()) {
            if (m.getName().startsWith("test")) {
                tests++;
                try {
                    m.invoke(null);
                    passed++;
                } catch (InvocationTargetException wrappedExc) {
                    Throwable exc = wrappedExc.getCause();
                    System.out.println(m + " 실패: " + exc);
                } catch (Exception exc) {
                    System.out.println("잘못 사용한 @Test: " + m);
                }
            }
        }
        System.out.printf("성공: %d, 실패: %d%n",
                passed, tests - passed);
    }
}

명명패턴을 이용해서 개발을 한다.

애노테이션을 정의해서 사용하는 것을 권장한다.

package me.whiteship.chapter06.item39.markerannotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

// 코드 39-1 마커(marker) 애너테이션 타입 선언 (238쪽)


/**
 * 테스트 메서드임을 선언하는 애너테이션이다.
 * 매개변수 없는 정적 메서드 전용이다.
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Test {
}
package me.whiteship.chapter06.item39.markerannotation;

// 코드 39-2 마커 애너테이션을 사용한 프로그램 예 (239쪽)
public class Sample {
    @Test
    public static void m1() { }        // 성공해야 한다.
    public static void m2() { }
    @Test public static void m3() {    // 실패해야 한다.
        throw new RuntimeException("실패");
    }
    public static void m4() { }  // 테스트가 아니다.
    @Test public void m5() { }   // 잘못 사용한 예: 정적 메서드가 아니다.
    public static void m6() { }
    @Test public static void m7() {    // 실패해야 한다.
        throw new RuntimeException("실패");
    }
    public static void m8() { }
}
package me.whiteship.chapter06.item39.markerannotation;

// 코드 39-3 마커 애너테이션을 처리하는 프로그램 (239-240쪽)

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class RunTests {
    public static void main(String[] args) throws Exception {
        int tests = 0;
        int passed = 0;
        Class<?> testClass = Sample.class;
        for (Method m : testClass.getDeclaredMethods()) {
            if (m.isAnnotationPresent(Test.class)) {
                tests++;
                try {
                    m.invoke(null);
                    passed++;
                } catch (InvocationTargetException wrappedExc) {
                    Throwable exc = wrappedExc.getCause();
                    System.out.println(m + " 실패: " + exc);
                } catch (Exception exc) {
                    System.out.println("잘못 사용한 @Test: " + m);
                }
            }
        }
        System.out.printf("성공: %d, 실패: %d%n",
                passed, tests - passed);
    }
}

리플렉션을 사용한다.

아이템 39. 핵심 정리 2: 애너테이션

package me.whiteship.chapter06.item39.annotationwithparameter;

// 코드 39-5 매개변수 하나짜리 애너테이션을 사용한 프로그램 (241쪽)
public class Sample2 {
    @ExceptionTest(ArithmeticException.class)
    public static void m1() {  // 성공해야 한다.
        int i = 0;
        i = i / i;
    }
    @ExceptionTest(ArithmeticException.class)
    public static void m2() {  // 실패해야 한다. (다른 예외 발생)
        int[] a = new int[0];
        int i = a[1];
    }
    @ExceptionTest(ArithmeticException.class)
    public static void m3() { }  // 실패해야 한다. (예외가 발생하지 않음)
}
package me.whiteship.chapter06.item39.annotationwithparameter;

// 코드 39-4 매개변수 하나를 받는 애너테이션 타입 (240-241쪽)

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 ExceptionTest {
    Class<? extends Throwable> value();
}

value 는 키워드이다. 딱 한번, 매개변수를 정의할때만 사용할 수 있다.

value는 생략 가능하다.

package me.whiteship.chapter06.item39.annotationwithparameter;

// 코드 39-4 매개변수 하나를 받는 애너테이션 타입 (240-241쪽)

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 ExceptionTest {
    Class<? extends Throwable> value();
    String text();
}
package me.whiteship.chapter06.item39.annotationwithparameter;

// 코드 39-5 매개변수 하나짜리 애너테이션을 사용한 프로그램 (241쪽)
public class Sample2 {
    @ExceptionTest(value = ArithmeticException.class, text = "qwe")
    public static void m1() {  // 성공해야 한다.
        int i = 0;
        i = i / i;
    }
    @ExceptionTest(ArithmeticException.class)
    public static void m2() {  // 실패해야 한다. (다른 예외 발생)
        int[] a = new int[0];
        int i = a[1];
    }
    @ExceptionTest(ArithmeticException.class)
    public static void m3() { }  // 실패해야 한다. (예외가 발생하지 않음)
}
package me.whiteship.chapter06.item39.annotationwithparameter;

// 코드 39-4 매개변수 하나를 받는 애너테이션 타입 (240-241쪽)

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 ExceptionTest {
    Class<? extends Throwable> value();
    String text() default "";
}

default를 이용하여 기본값 설정 가능하다.

package me.whiteship.chapter06.item39.annotationwithparameter;

import me.whiteship.chapter06.item39.naming_pattern.Sample;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

// 마커 애너테이션과 매개변수 하나짜리 애너태이션을 처리하는 프로그램 (241-242쪽)
public class RunTests {
    public static void main(String[] args) throws Exception {
        int tests = 0;
        int passed = 0;
        Class<?> testClass = Sample2.class;
        for (Method m : testClass.getDeclaredMethods()) {
            if (m.isAnnotationPresent(Test.class)) {
                tests++;
                try {
                    m.invoke(null);
                    passed++;
                } catch (InvocationTargetException wrappedExc) {
                    Throwable exc = wrappedExc.getCause();
                    System.out.println(m + " 실패: " + exc);
                } catch (Exception exc) {
                    System.out.println("잘못 사용한 @Test: " + m);
                }
            }

            if (m.isAnnotationPresent(ExceptionTest.class)) {
                tests++;
                try {
                    m.invoke(null);
                    System.out.printf("테스트 %s 실패: 예외를 던지지 않음%n", m);
                } catch (InvocationTargetException wrappedEx) {
                    Throwable exc = wrappedEx.getCause();
                    ExceptionTest annotation = m.getAnnotation(ExceptionTest.class);
                    Class<? extends Throwable> excType =
                            annotation.value();
                    if (excType.isInstance(exc)) {
                        passed++;
                    } else {
                        System.out.printf(
                                "테스트 %s 실패: 기대한 예외 %s, 발생한 예외 %s%n",
                                m, excType.getName(), exc);
                    }
                } catch (Exception exc) {
                    System.out.println("잘못 사용한 @ExceptionTest: " + m);
                }
            }
        }

        System.out.printf("성공: %d, 실패: %d%n",
                passed, tests - passed);
    }
}

package me.whiteship.chapter06.item39.annotationwithparameter;

// 코드 39-5 매개변수 하나짜리 애너테이션을 사용한 프로그램 (241쪽)
public class Sample2 {
    @ExceptionTest(ArithmeticException.class)
    public static void m1() {  // 성공해야 한다.
        int i = 0;
        i = i / i;
    }
    @ExceptionTest(IndexOutOfBoundsException.class)
    public static void m2() {  // 실패해야 한다. (다른 예외 발생)
        int[] a = new int[0];
        int i = a[1];
    }
    @ExceptionTest(ArithmeticException.class)
    public static void m3() { }  // 실패해야 한다. (예외가 발생하지 않음)
}

아이템 39. 핵심 정리 3: 반복 사용 가능한 애너테이션

package me.whiteship.chapter06.item39.annotationwitharrayparameter;

import java.util.ArrayList;
import java.util.List;

// 배열 매개변수를 받는 애너테이션을 사용하는 프로그램 (242-243쪽)
public class Sample3 {
    // 이 변형은 원소 하나짜리 매개변수를 받는 애너테이션도 처리할 수 있다. (241쪽 Sample2와 같음)
    @ExceptionTest(ArithmeticException.class)
    public static void m1() {  // 성공해야 한다.
        int i = 0;
        i = i / i;
    }
    @ExceptionTest(ArithmeticException.class)
    public static void m2() {  // 실패해야 한다. (다른 예외 발생)
        int[] a = new int[0];
        int i = a[1];
    }
    @ExceptionTest(ArithmeticException.class)
    public static void m3() { }  // 실패해야 한다. (예외가 발생하지 않음)

    // 코드 39-7 배열 매개변수를 받는 애너테이션을 사용하는 코드 (242-243쪽)
    @ExceptionTest({ IndexOutOfBoundsException.class,
                     NullPointerException.class })
    public static void doublyBad() {   // 성공해야 한다.
        List<String> list = new ArrayList<>();

        // 자바 API 명세에 따르면 다음 메서드는 IndexOutOfBoundsException이나
        // NullPointerException을 던질 수 있다.
        list.addAll(5, null);
    }
}
package me.whiteship.chapter06.item39.annotationwitharrayparameter;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

// 코드 39-6 배열 매개변수를 받는 애너테이션 타입 (242쪽)
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface ExceptionTest {
    Class<? extends Exception>[] value();
}
package me.whiteship.chapter06.item39.annotationwitharrayparameter;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

// 마커 애너테이션과 배열 매개변수를 받는 애너테이션을 처리하는 프로그램 (243쪽)
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)) {
                tests++;
                try {
                    m.invoke(null);
                    passed++;
                } catch (InvocationTargetException wrappedExc) {
                    Throwable exc = wrappedExc.getCause();
                    System.out.println(m + " 실패: " + exc);
                } catch (Exception exc) {
                    System.out.println("잘못 사용한 @Test: " + m);
                }
            }

            // 배열 매개변수를 받는 애너테이션을 처리하는 코드 (243쪽)
            if (m.isAnnotationPresent(ExceptionTest.class)) {
                tests++;
                try {
                    m.invoke(null);
                    System.out.printf("테스트 %s 실패: 예외를 던지지 않음%n", m);
                } catch (Throwable wrappedExc) {
                    Throwable exc = wrappedExc.getCause();
                    int oldPassed = passed;
                    Class<? extends Throwable>[] excTypes =
                            m.getAnnotation(ExceptionTest.class).value();
                    for (Class<? extends Throwable> excType : excTypes) {
                        if (excType.isInstance(exc)) {
                            passed++;
                            break;
                        }
                    }
                    if (passed == oldPassed)
                        System.out.printf("테스트 %s 실패: %s %n", m, exc);
                }
            }
        }
        System.out.printf("성공: %d, 실패: %d%n",
                passed, tests - passed);
    }
}

배열인경우 위와 같이 사용가능하다.

package me.whiteship.chapter06.item39.annotationwitharrayparameter;

import java.util.ArrayList;
import java.util.List;

// 배열 매개변수를 받는 애너테이션을 사용하는 프로그램 (242-243쪽)
public class Sample3 {
    // 이 변형은 원소 하나짜리 매개변수를 받는 애너테이션도 처리할 수 있다. (241쪽 Sample2와 같음)
    @ExceptionTest(IndexOutOfBoundsException.class)
    @ExceptionTest(ArithmeticException.class)
    public static void m1() {  // 성공해야 한다.
        int i = 0;
        i = i / i;
    }
    @ExceptionTest(ArithmeticException.class)
    public static void m2() {  // 실패해야 한다. (다른 예외 발생)
        int[] a = new int[0];
        int i = a[1];
    }
    @ExceptionTest(ArithmeticException.class)
    public static void m3() { }  // 실패해야 한다. (예외가 발생하지 않음)

    // 코드 39-7 배열 매개변수를 받는 애너테이션을 사용하는 코드 (242-243쪽)
    @ExceptionTest({ IndexOutOfBoundsException.class,
                     NullPointerException.class })
    public static void doublyBad() {   // 성공해야 한다.
        List<String> list = new ArrayList<>();

        // 자바 API 명세에 따르면 다음 메서드는 IndexOutOfBoundsException이나
        // NullPointerException을 던질 수 있다.
        list.addAll(5, null);
    }
}

이거는 안 된다.

package me.whiteship.chapter06.item39.repeatableannotation;

import java.lang.annotation.*;

// 코드 39-8 반복 가능한 애너테이션 타입 (243-244쪽)
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Repeatable(ExceptionTestContainer.class)
public @interface ExceptionTest {
    Class<? extends Throwable> value();
}
package me.whiteship.chapter06.item39.repeatableannotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

// 반복 가능한 애너테이션의 컨테이너 애너테이션 (244쪽)
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface ExceptionTestContainer {
    ExceptionTest[] value();
}

반복이 되게 하려면 Repeatable을 사용해야 한다.

package me.whiteship.chapter06.item39.repeatableannotation;

import java.util.ArrayList;
import java.util.List;

// 반복 가능한 애너테이션을 사용한 프로그램 (244쪽)
public class Sample4 {
    @ExceptionTest(ArithmeticException.class)
    public static void m1() {  // 성공해야 한다.
        int i = 0;
        i = i / i;
    }

    @ExceptionTest(ArithmeticException.class)
    public static void m2() {  // 실패해야 한다. (다른 예외 발생)
        int[] a = new int[0];
        int i = a[1];
    }

    @ExceptionTest(ArithmeticException.class)
    public static void m3() { }  // 실패해야 한다. (예외가 발생하지 않음)

    // 코드 39-9 반복 가능 애너테이션을 두 번 단 코드 (244쪽)
    @ExceptionTest(IndexOutOfBoundsException.class)
    @ExceptionTest(NullPointerException.class)
    public static void doublyBad() {
        List<String> list = new ArrayList<>();

        // 자바 API 명세에 따르면 다음 메서드는 IndexOutOfBoundsException이나
        // NullPointerException을 던질 수 있다.
        list.addAll(5, null);
    }
}
    @ExceptionTestContainer(
            {@ExceptionTest(ArithmeticException.class),
            @ExceptionTest(IndexOutOfBoundsException.class)}
    )
    public static void m4() { }  // 실패해야 한다. (예외가 발생하지 않음)

이렇게 해도 된다.

사실상 눈에 안보이는 @ExceptionTestContainer이 감싸고 있다고 보면 된다.

public static void main(String[] args) throws NoSuchMethodException {
        Class<Sample4> sample4Class = Sample4.class;
        Method m1 = sample4Class.getMethod("m1");
        ExceptionTest[] annotationsByType = m1.getAnnotationsByType(ExceptionTest.class);
        for (ExceptionTest exceptionTest : annotationsByType) {
            System.out.println(exceptionTest);
        }

        ExceptionTestContainer[] containers = m1.getAnnotationsByType(ExceptionTestContainer.class);
        for (ExceptionTestContainer container : containers) {
            System.out.println(container);
        }

        System.out.println(m1.isAnnotationPresent(ExceptionTest.class));
        System.out.println(m1.isAnnotationPresent(ExceptionTestContainer.class));
    }

 public static void main(String[] args) throws NoSuchMethodException {
        Class<Sample4> sample4Class = Sample4.class;
//        Method m1 = sample4Class.getMethod("m1");
//        ExceptionTest[] annotationsByType = m1.getAnnotationsByType(ExceptionTest.class);
//        for (ExceptionTest exceptionTest : annotationsByType) {
//            System.out.println(exceptionTest);
//        }
//
//        ExceptionTestContainer[] containers = m1.getAnnotationsByType(ExceptionTestContainer.class);
//        for (ExceptionTestContainer container : containers) {
//            System.out.println(container);
//        }
//
//        System.out.println(m1.isAnnotationPresent(ExceptionTest.class));
//        System.out.println(m1.isAnnotationPresent(ExceptionTestContainer.class));

        Method doublyBad = sample4Class.getDeclaredMethod("doublyBad");
        ExceptionTest[] annotationsByType1 = doublyBad.getAnnotationsByType(ExceptionTest.class);
        for (ExceptionTest exceptionTest : annotationsByType1) {
            System.out.println(exceptionTest);
        }
        ExceptionTestContainer[] annotationsByType2 = doublyBad.getAnnotationsByType(ExceptionTestContainer.class);
        for (ExceptionTestContainer exceptionTest : annotationsByType2) {
            System.out.println(exceptionTest);
        }

        System.out.println(doublyBad.isAnnotationPresent(ExceptionTest.class));
        System.out.println(doublyBad.isAnnotationPresent(ExceptionTestContainer.class));
    }

package me.whiteship.chapter06.item39.repeatableannotation;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;

// 반복 가능한 애너테이션을 사용한 프로그램 (244쪽)
public class Sample4 {
    @ExceptionTest(ArithmeticException.class)
    public static void m1() {  // 성공해야 한다.
        int i = 0;
        i = i / i;
    }

    @ExceptionTest(ArithmeticException.class)
    public static void m2() {  // 실패해야 한다. (다른 예외 발생)
        int[] a = new int[0];
        int i = a[1];
    }

    @ExceptionTest(ArithmeticException.class)
    public static void m3() { }  // 실패해야 한다. (예외가 발생하지 않음)

    @ExceptionTestContainer(
            {
                    @ExceptionTest(ArithmeticException.class),
                    @ExceptionTest(IndexOutOfBoundsException.class)
            }
    )
    public static void m4() { }  // 실패해야 한다. (예외가 발생하지 않음)

    // 코드 39-9 반복 가능 애너테이션을 두 번 단 코드 (244쪽)
    @ExceptionTest(IndexOutOfBoundsException.class)
    @ExceptionTest(NullPointerException.class)
    public static void doublyBad() {
        List<String> list = new ArrayList<>();

        // 자바 API 명세에 따르면 다음 메서드는 IndexOutOfBoundsException이나
        // NullPointerException을 던질 수 있다.
        list.addAll(5, null);
    }

    public static void main(String[] args) throws NoSuchMethodException {
        Class<Sample4> sample4Class = Sample4.class;
//        Method m1 = sample4Class.getMethod("m1");
//        ExceptionTest[] annotationsByType = m1.getAnnotationsByType(ExceptionTest.class);
//        for (ExceptionTest exceptionTest : annotationsByType) {
//            System.out.println(exceptionTest);
//        }
//
//        ExceptionTestContainer[] containers = m1.getAnnotationsByType(ExceptionTestContainer.class);
//        for (ExceptionTestContainer container : containers) {
//            System.out.println(container);
//        }
//
//        System.out.println(m1.isAnnotationPresent(ExceptionTest.class));
//        System.out.println(m1.isAnnotationPresent(ExceptionTestContainer.class));

        Method doublyBad = sample4Class.getDeclaredMethod("doublyBad");
        ExceptionTest[] annotationsByType1 = doublyBad.getAnnotationsByType(ExceptionTest.class);
        for (ExceptionTest exceptionTest : annotationsByType1) {
            System.out.println(exceptionTest);
        }
        ExceptionTestContainer[] annotationsByType2 = doublyBad.getAnnotationsByType(ExceptionTestContainer.class);
        for (ExceptionTestContainer exceptionTest : annotationsByType2) {
            System.out.println(exceptionTest);
        }

        System.out.println(doublyBad.isAnnotationPresent(ExceptionTest.class));
        System.out.println(doublyBad.isAnnotationPresent(ExceptionTestContainer.class));
    }
}

완벽 공략 51. 애너테이션 프로세서

package me.whiteship.log;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Log {
}
package me.whiteship.log;

import com.google.auto.service.AutoService;

import javax.annotation.processing.*;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.util.Elements;
import javax.tools.Diagnostic;
import java.io.IOException;
import java.io.PrintWriter;
import java.lang.annotation.Retention;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Set;

import javax.annotation.processing.*;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.util.Elements;
import javax.tools.Diagnostic;
import java.util.Set;

@SupportedAnnotationTypes("me.whiteship.log.Log")
@SupportedSourceVersion(SourceVersion.RELEASE_17)
@AutoService(Processor.class)
public class LogProcessor extends AbstractProcessor {

    private Elements elementUtils;
    private Messager messager;

    @Override
    public synchronized void init(ProcessingEnvironment processingEnv) {
        super.init(processingEnv);
        elementUtils = processingEnv.getElementUtils();
        messager = processingEnv.getMessager();
    }

    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        messager.printMessage(Diagnostic.Kind.WARNING, "Processing Log annotation");
        for (Element element : roundEnv.getElementsAnnotatedWith(Log.class)) {
            if (element instanceof ExecutableElement) {
                ExecutableElement method = (ExecutableElement) element;
                TypeElement classElement = (TypeElement) method.getEnclosingElement();
                String className = classElement.getQualifiedName().toString();
                String methodName = method.getSimpleName().toString();

                // 로그 출력
                messager.printMessage(Diagnostic.Kind.WARNING, "Processing method: " + className + "." + methodName);

                // 여기서 추가 작업 수행 가능
            }
        }
        return true;
    }
}

clean -> compile을 하면 아래와 같이 로그가 나온다.

0개의 댓글