⬛ 애노테이션의 필요성
🟢 애노테이션 필요성
- 기존의 리플렉션 서블릿으로는 메서드와 URL 매핑의 유연성이 부족.
- 메서드와 URL을 별도로 매핑하거나, 주석과 같은 정보를 활용해 프로그램이 동적으로 동작할 수 있는 방법이 필요.
- 애노테이션은 컴파일러나 런타임에서 처리 가능한 메타데이터를 제공하여 이런 문제를 해결.
@Retention(RetentionPolicy.RUNTIME)
public @interface SimpleMapping {
String value();
}
public class TestController {
@SimpleMapping("/home")
public void home() { System.out.println("홈 실행"); }
}
여기서 핵심은 애노테이션은 메타데이터를 활용해 동적인 동작을 가능하게 한다는 것이다.
🔷 애노테이션 데이터 조회와 실행 흐름
import java.lang.reflect.Method;
public class TestControllerMain {
public static void main(String[] args) {
TestController testController = new TestController();
// 1. 클래스 정보 탐색
Class<? extends TestController> aClass = testController.getClass();
// 2. 메서드 순회
for (Method method : aClass.getDeclaredMethods()) { // 클래스의 선언된 메서드 찾기
// 3. 애노테이션 확인
SimpleMapping simpleMapping = method.getAnnotation(SimpleMapping.class); // 메서드에 들어있는 애노테이션을 찾기
// 4. 애노테이션 값 활용
if (simpleMapping != null)
System.out.println("["+simpleMapping.value() + "] -> " + method); // 애노테이션에 지정된 값 조회 가능
}
}
}
⬛ 애노테이션 정의와 사용
🟢 애노테이션 정의 방법
- @interface로 선언.
- 기본 타입, 배열, enum 등만 사용 가능.
- default로 기본값 설정 가능.
- value 속성 하나만 있을 경우 이름 생략 가능. ex) @AnnoExample("데이터")
🔷 사용 예시
@Retention(RetentionPolicy.RUNTIME)
public @interface AnnoExample {
String value();
int count() default 0;
}
@AnnoExample(value = "데이터", count = 10)
public class ExampleClass {}
⬛ 메타 애노테이션
메타 애노테이션: 애노테이션을 정의할때 필요한 도구
🟢 @Retention
애노테이션의 유지 범위(생존 기간) 설정.
- SOURCE: 컴파일 시 제거.
- CLASS: class 파일에 포함. (자바실행시점에 제거, 기본값)
- RUNTIME: 런타임에도 유지.
🟢 @Target
애노테이션 적용 위치 지정 (METHOD, FIELD 등).
+)ElementType.TYPE의 의미
- 적용 대상: 클래스, 인터페이스, 열거형(enum), annotation.
- 적용 제한: @Target(ElementType.TYPE)이 지정되면 메서드, 필드, 생성자 등에는 애노테이션을 붙일 수 없음.
🔷 사용 예시
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface MethodAnno {}
⬛ @Inherited
애노테이션이 클래스 상속 시 자식 클래스에 자동으로 적용되도록 설정.
🟢 주요 특징
- 부모 클래스에 적용된 애노테이션이 자식 클래스에도 상속.
- 클래스 상속에만 적용되며, 인터페이스에는 적용되지 않음.
- 애노테이션에 @Inherited를 추가해 상속 기능을 활성화할 수 있음.
🔷 사용 방법
1. @Inherited를 사용한 애노테이션 정의
import java.lang.annotation.*;
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface InheritedAnnotation {
}
2. 부모 클래스에 애노테이션 적용
@InheritedAnnotation
public class ParentClass {
}
3. 자식 클래스
public class ChildClass extends ParentClass {
}
4. 리플렉션을 통한 확인
public class InheritedExample {
public static void main(String[] args) {
// true
System.out.println("ParentClass: " + ParentClass.class.isAnnotationPresent(InheritedAnnotation.class));
// true
System.out.println("ChildClass: " + ChildClass.class.isAnnotationPresent(InheritedAnnotation.class));
}
}
인터페이스에는 상속이 적용되지 않는다.
@Inherited는 클래스 상속에서만 작동하며, 인터페이스와는 무관하다. 즉,인터페이스에 붙은 애노테이션은 인터페이스 구현체로 상속되지 않는다. 인터페이스는 메서드의 구현을 강제하지 않는 선언적 성격을 갖기 때문이다.
🔷 예제
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface MyAnnotation {
}
@MyAnnotation
public interface MyInterface {
}
public class MyImplementation implements MyInterface {
}
public class InterfaceTest {
public static void main(String[] args) {
// true
System.out.println("MyInterface: " + MyInterface.class.isAnnotationPresent(MyAnnotation.class));
// false
System.out.println("MyImplementation: " + MyImplementation.class.isAnnotationPresent(MyAnnotation.class));
}
}
⬛ 애노테이션 활용: 검증기
문제상황: 중복된 검증 코드로 인해 유지보수가 어려운 기존의 코드
public class ValidatorV1Main {
public static void main(String[] args) {
// User와 Team 객체를 생성
User user = new User("user1", 0);
Team team = new Team("", 0);
// User 검증 실행
try {
log("== user 검증 ==");
validateUser(user);
} catch (Exception e) {
log(e); // 검증 실패 시 예외 로그 출력
}
// Team 검증 실행
try {
log("== team 검증 ==");
validateTeam(team);
} catch (Exception e) {
log(e); // 검증 실패 시 예외 로그 출력
}
}
// User 객체 검증 메서드
private static void validateUser(User user) {
// 이름이 null이거나 비어있는지 확인
if (user.getName() == null || user.getName().isEmpty()) {
throw new RuntimeException("이름이 비어있습니다.");
}
// 나이가 1~100 범위 내에 있는지 확인
if (user.getAge() < 1 || user.getAge() > 100) {
throw new RuntimeException("나이는 1과 100 사이여야 합니다.");
}
}
// Team 객체 검증 메서드
private static void validateTeam(Team team) {
// 이름이 null이거나 비어있는지 확인
if (team.getName() == null || team.getName().isEmpty()) {
throw new RuntimeException("이름이 비어있습니다.");
}
// 회원 수가 1~999 범위 내에 있는지 확인
if (team.getMemberCount() < 1 || team.getMemberCount() > 999) {
throw new RuntimeException("회원 수는 1과 999 사이여야 합니다.");
}
}
}
위의 문제는 어노테이션을 통해 해결 가능하다.필드 이름이나 메시지가 달라도 유연하게 처리할 수 있게 되었다.
- @NotEmpty: 빈 값 검증.
- @Range: 숫자 범위 검증.
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Range {
int min();
int max();
String message() default "범위를 벗어남";
}
public class User {
@Range(min = 1, max = 100, message = "나이는 1~100 사이여야 합니다.")
private int age;
}
public class ValidatorV2Main {
public static void main(String[] args) {
User user = new User("user1", 0);
Team team = new Team("", 0);
try {
log("== user 검증 ==");
Validator.validate(user);
} catch (Exception e) {
log(e);
}
try {
log("== team 검증 ==");
Validator.validate(team);
} catch (Exception e) {
log(e);
}
}
}
⬛ 자바 기본 애노테이션
🟢 자바 애노테이션
- @Override: 메서드 재정의 체크.
- @Deprecated: 더 이상 사용되지 않는 코드에 적용.
- @SuppressWarnings: 경고 억제.
@Override
public String toString() { return "예시"; }
// 사용하지않게된버전 정보, 미래 버전 코드 제거 예정 여부
@Deprecated(since = "2.4", forRemoval = true)
public void oldMethod() { System.out.println("사용하지 마세요"); }
@SuppressWarnings("unchecked")
public void suppressWarning() {
List list = new ArrayList(); // unchecked 경고 억제
}
🟢 @SuppressWarnings
- all: 모든 경고를 억제
- deprecation: 사용이 권장되지 않는(deprecated) 코드를 사용할 때 발생하는 경고를 억제
- unchecked: 제네릭 타입과 관련된 unchecked 경고를 억제
- serial: Serializable 인터페이스를 구현할 때 serialVersionUID 필드를 선언하지 않은 경우 발생하는 경고를 억제
- rawtypes: 제네릭 타입이 명시되지 않은(raw) 타입을 사용할 때 발생하는 경고를 억제
- unused: 사용되지 않는 변수, 메서드, 필드 등을 선언했을 때 발생하는 경고를 억제
'Java' 카테고리의 다른 글
[Java] 김영한의 실전 자바 - 고급 3편 섹션1,2 람다 (2) | 2025.03.31 |
---|---|
[Java] 김영한의 실전 자바 - 고급 2편 섹션15 HTTP 서버 활용 (0) | 2024.12.02 |
[Java] 김영한의 실전 자바 - 고급 2편 섹션13 리플렉션 (0) | 2024.11.25 |
[Java] 김영한의 실전 자바 - 고급 2편 섹션12 HTTP 서버 만들기, 서블릿 (3) | 2024.11.21 |
[Java] 김영한의 실전 자바 - 고급 2편 섹션6 네트워크,자원 정리 (0) | 2024.11.04 |