
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);
}
}
리플렉션을 사용한다.


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() { } // 실패해야 한다. (예외가 발생하지 않음)
}


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));
}
}


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을 하면 아래와 같이 로그가 나온다.
