[자바] 애노테이션

ChoRong0824·2023년 7월 27일
0

Java

목록 보기
31/31
post-thumbnail

제네릭스,열거형,애노테이션을 공부하면서 내가 모르거나, 의아하거나(애매), 틀리게 안 것들 위주로 작성해봤습니다.
필자의 공부를 위해 작성한 글임을 양해바랍니다.

💁‍♂️ 애노테이션

  • 프로그램의 소스코드 안에 다른 프로그램을 위한 정보를 미리 약속된 형식으로 포함시킨 것이다.
    어노테이션은 주석처럼 프로그래밍 언어에 영향을 미치지 않으면서도 다른 프로그램에게 유용한 정보를 제공할 수 있다는 장점이 있다. (어노테이션의 뜻이 주석,메모)

  • 예를들어, 소스코드 중에서 특정 메서드만 테스트하기를 원한다면, @Test라는 어노테이션을 메서드에 붙인다면 --> @Test는 '이 메서드를 테스트 해야한다'는 것을 테스트 프로그램에게 알리는 역할을 한다. --> 즉, 주석과 유사하다.

👉 자주사용되는 어노테이션

🌝 자바

  • @Override : 컴파일러에게 오버라이딩 하는 메서드임을 알린다.

  • @Target : 메타 어노테이션이며, 어노테이션이 적용가능한 대상을 지정하는데 사용한다.

  • @Inherited : 메타 어노테이션이며, 어노테이션이 자손 클래스에 상속되도록 한다.



🌝 스프링

웬만하면 자주 쓰이는 것들이니 외우는 것을 권장합니다.

  • @Component: 클래스를 스프링 빈으로 등록할 때 사용합니다.

  • @Repository: 데이터베이스와 관련된 작업을 수행하는 DAO(Data Access Object) 클래스에 사용됩니다.

  • @Service: 비즈니스 로직을 수행하는 서비스 클래스에 사용됩니다.

  • @Controller: 웹 요청과 관련된 작업을 수행하는 컨트롤러 클래스에 사용됩니다.

  • @RestController: RESTful 웹 서비스용 컨트롤러 클래스에 사용됩니다.

  • @Autowired: 스프링 빈 간의 의존성 주입을 위해 사용됩니다.

  • @RequestMapping: 요청을 처리하는 메서드에 사용되며, 요청 URL과 메서드를 매핑시킵니다.

  • @GetMapping, @PostMapping, @PutMapping, @DeleteMapping: HTTP 메서드별로 요청을 처리하는 메서드에 사용됩니다.

  • @RequestParam: 요청 파라미터를 메서드 인자로 전달할 때 사용합니다.

  • @PathVariable: 요청 URL 경로에 있는 변수를 메서드 인자로 전달할 때 사용합니다.

  • @RequestBody: 요청 본문의 JSON 데이터를 메서드 인자로 전달할 때 사용합니다.

  • @ResponseBody: 메서드가 반환하는 값이 응답 본문으로 전달되도록 합니다.

  • @ExceptionHandler: 예외 처리 메서드에 사용됩니다.

  • @Configuration: 스프링 설정 클래스에 사용됩니다.

  • @Bean: 설정 클래스에서 스프링 빈을 정의하는 메서드에 사용됩니다.

  • @Scope: 빈의 스코프를 설정할 때 사용합니다.

  • @Profile: 빈을 특정 프로파일에서만 활성화하도록 설정할 때 사용합니다.

  • @ComponentScan: 지정된 패키지 경로에서 @Component가 적용된 클래스를 검색하여 스프링 컨테이너에 빈으로 등록할 때 사용합니다.

  • @EnableAutoConfiguration: 스프링 부트 어플리케이션에서 개발자가 처리하지 않아도 기본 구성을 자동으로 완성해 주는 어노테이션입니다.

  • @Validated: 빈의 메서드 파라미터에 유효성 검사를 수행하도록 지정할 때 사용하는 어노테이션입니다.

  • @Value: 외부 설정 파일 또는 프로퍼티 소스로부터 값을 주입받아 사용할 때 사용하는 어노테이션입니다.

  • @Scheduled: 메서드에 이 어노테이션을 추가하면 해당 메서드를 일정 시간 간격이나 특정 시간에 실행할 수 있습니다.

  • @Order: 같은 타입의 여러 빈들이나, 같은 어노테이션을 가진 메서드들 간의 순서를 지정할 때 사용하는 어노테이션입니다.

  • @Conditional: 조건부로 빈을 등록하거나, 메서드를 실행할 때 사용되는 어노테이션입니다. 조건을 만족할 때만 해당 빈이 등록되거나 메서드가 실행됩니다.

  • @Aspect: AOP(Aspect Oriented Programming)에서 사용되며, 이 어노테이션을 통해 부가기능을 클래스에 통합할 수 있습니다.


🤜 @Override 참고사항

Class A{
	void zxcvbMethod(){}
}
Class B extends A{
	void zxcvbmethod(){}
}

<정답>
Class A{
	void zxcvbMethod(){}
}
@Override
Class B extends A{
	void zxcvbMethod(){}
}
이유 : 컴파일러는 그저 새로운 이름의 메서드가 추가된 것으로 인식하기 때문이다.
			따라서, 실행시에도 오류가 발생하지 않고 조상의 메서드가 호출되므로 어디서 잘못되었는지 잡을 수 없는
			아주아주아주 위험한 녀석이다.
            이를 해결하기 위해선 @Override를 붙여주기만 해도, ERR를 잡을 수 있게된다.

🤜 @SafeVarargs

자바에서 해당 메서드에 선언된 가변인자의 타입이 non-reifiable타입일 경우,
해당 메서드를 선언하는 부분과 호출하는 부분에서 "unchecked"경고가 발생한다.
해당 코드에 문제가 없다면, 이 경고를 억제하기 위해 해당 어노테이션을 사용해야 한다.
참고로, 이 어노테이션은 static 이나 final 이 붙은 메서드와 생성자에만 붙일 수 있다.

  • 그렇다면 static과 final이 붙은 메서드와 생성자는 무엇일까??
    --> 그렇다. 오버라이드될 수 있는 메서드에는 사용할 수 없다는 것이다.


👉 메타 어노테이션

어노테이션을 위한 어노테이션이다.
--> 쉽게 생각하면, 어노테이션에 붙이는 어노테이션으로 어노테이션을 정의할 때 어노테이션의 target(적용대상)이나 유지기간 등을 지정하는 용도로 사용한다.

🤜 @Inherited

어노테이션이 자손 클래스에 상속되도록 한다.
해당 어노테이션를 조상클래스에 붙이면, 자손클래스도 이 어노테이션이 붙은 것과 동일하게 인식된다.

🤜 @Repeatable

보통, 하나의 대상에 한 종류의 어노테이션만 가능하다.
하지만 @Repeatable이 붙은 어노테이션은 같은 타입의 어노테이션을 여러번 붙일 수 있게 된다.
코드로 알아보자.

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

// Repeatable 어노테이션인 Tag를 정의합니다.
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Repeatable(Tags.class)
public @interface Tag {
    String value();
}

// Tag 어노테이션을 담을 컨테이너 어노테이션인 Tags를 정의합니다.
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@interface Tags {
    Tag[] value();
}

이해하겠는가?
--> 당연하다. 필자가 내용 추가를 안해줬기 때문이다.

// @Tag 어노테이션을 여러 번 사용할 수 있는 클래스를 정의
@Tag("Java")
@Tag("Programming")
@Tag("Tutorial")
public class Example {
// 생략...
}

이해하겠는가?
--> 넹. (죄송합니다. 더위먹은 것 같네요..ㅎ)


지금까지 긴 글 읽느라 고생하셨습니다. 이제 마무리 하겠습니다.
그 전에, 개인적으로 어노테이션의 꽃???(매우 주관적임)으로 생각되는 어노테이션 타입 정의하기에 대해 알아보겠습니다.

👉 어노테이션 타입 정의하기

이제 직접 어노테이션을 만들어서 사용하는 방법을 설명하겠다.
사실 별 거 없다.
--> 그저, @ 기호를 붙이는 것을 제외한다면, 인터페이스를 정의하는 것과 동일하기 때문이다.

어노테이션의 요소 : 어노테이션 내에 선언된 메서드를 의미한다.

  • 만약, 어노테이션의 요소가 오직 하나뿐이고 이름이 value인 경우, 어노테이션을 적용할 때 요소의 이름을 생략하고 값만 적어도 된다.
@interface TestInfo{
	String value();
}
@TestInfo("passed") // @TestInfo(value ="passed") 와 동일하다.
class NewClass{
}
  • 만약, 요소의 타입이 배열인 경우, 괄호{}를 사용해서 여러 개의 값을 지정할 수 있다.
@interface TestInfo{
	String[] testTools();
}
@Test(testTools={"JUnit", "AutoTester"}) // 값이 여러 개인 경우
@Test(testTools={"JUnit"}) //값이 하나일 때엔 괄호 {} 생략가능
@Test(testTools={}) //값이 없을 때는 괄호 {}가 반드시 필요

// 참고로, 기본값을 지정할 때도 마찬가지로 괄호{} 사용가능.

🤜 어노테이션을 코드로 알아보자

	@interface 어노테이션이름{
    	타입요소이름(); // 어노테이션의 요소를 선언
    }

🤜 어노테이션, 자세한 코드예시

// << 1. 사용자 정의 어노테이션 타입 정의하기 >>

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.TYPE)
public @interface MyAnnotation {
    String value() default "";
    int number() default 0;
}

// << 2. 사용자 정의 어노테이션 적용하기>>

@MyAnnotation(value = "샘플 어노테이션", number = 42)
public class Example {
    // 생략...
}

// << 3. 런타임에 어노테이션 정보 추출하기>>

public class Main {
    public static void main(String[] args) {
        MyAnnotation myAnnotation = Example.class.getAnnotation(MyAnnotation.class);
        
        if (myAnnotation != null) {
            System.out.println("Value: " + myAnnotation.value());
            System.out.println("Number: " + myAnnotation.number());
        }
    }
}
/*
<<실행 결과>>
Value: 샘플 어노테이션
Number: 42
*/


👉 java.lang.annotation.*

  • (java.lang.annotation.Annotation)

모든 어노테이션의 조상은 Annotation이다.
BUt, 어노테이션은 상속이 허용되지 않는다.
--> 명시적으로 Annotation을 조상으로 지정할 수 없다.

@interface TestInfo extends Annotation{ // ERR. 허용되지않는 표현
}
  • 또한, 어노테이션은 어노테이션으로 정의(구현)되어 있지않다.
    어떻게 구현되어있지 ? --> 일반적인 인터페이스로 정의되어 있다.

🤜 코드로 알아보자.

package java.lang.annotation;
public interface Annotation{ // Annotation 자기자신은 인터페이스다.
	boolean equals (Object obj);
    int hashCode();
    String toString(0;
    
    Class<? extends Annotation> annotationType(); // 어노테이션의 타입을 반환
}

위와 같이, 모든 어노테이션의 조상인 Annotation 인터페이스가 위와 같이 정의되어 있기 때문에, 모든 어노테이션 객체에 대해 equals(), hashCode(), toString()과 같은 메서드를 호출하는 것이 가능한 것이다.

🤜 코드로 알아보자

Class<AnnotationTest> cls = AnnotationTest.class;
Annotation[] annoArr = nAnnotationTest.class.getAnnotation();

for(Annotation a : annoArr){
	System.out.println("toString() : " + a.toString());
    	System.out.println("hashCode() : " + a.hashCode());
        	System.out.println("equals() : " + a.equals(a));
            	System.out.println("annotationType() : " + a.annotationType());
}

해당 코드는, AnnotationTest클래스에 적용된 모든 어노테이션에 대해 toString(),hashCode().equals()를 호출하는 코드이다.




어노테이션에 대해 자동으로 toString(), hashCode(), equals()를 호출하는 것은 일반적으로 어렵습니다.
어노테이션은 주로 컴파일 시간에 사용되는 메타데이터이며, 해당 정보를 실행 시간에 동적으로 가져오기 어렵기 때문입니다.
따라서 컴파일 시간에 어노테이션을 처리하는 프로세서를 만들어 사용하거나 리플렉션을 이용하여 실행 시간에 어노테이션 정보를 추출해야 합니다.

그러나 일반적인 방법으로 AnnotationTest 클래스의 어노테이션 정보를 가져와서 toString(), hashCode(), equals()를 호출하는 코드를 예시로 보여드리며, 이를 위해 Java의 리플렉션을 사용합니다.

🤜 코드로 알아보자

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.Arrays;

public class AnnotationProcessor {

    public static void main(String[] args) {
        AnnotationTest testInstance = new AnnotationTest();

        // Call the method to process annotations and invoke toString(), hashCode(), equals()
        processAnnotations(testInstance);
    }

    public static void processAnnotations(Object obj) {
        Class<?> clazz = obj.getClass();

        // Process annotations for the class
        processClassAnnotations(clazz);

        // Process annotations for methods
        Method[] methods = clazz.getDeclaredMethods();
        for (Method method : methods) {
            processMethodAnnotations(method, obj);
        }
    }

    private static void processClassAnnotations(Class<?> clazz) {
        Annotation[] annotations = clazz.getAnnotations();
        for (Annotation annotation : annotations) {
            invokeAnnotationMethods(annotation);
        }
    }

    private static void processMethodAnnotations(Method method, Object obj) {
        Annotation[] annotations = method.getAnnotations();
        for (Annotation annotation : annotations) {
            invokeAnnotationMethods(annotation);
        }
    }

    private static void invokeAnnotationMethods(Annotation annotation) {
        Class<? extends Annotation> annotationType = annotation.annotationType();
        try {
            Method[] methods = annotationType.getDeclaredMethods();
            for (Method method : methods) {
                Object value = method.invoke(annotation);
                System.out.println("Annotation: " + annotationType.getSimpleName() +
                                   ", Method: " + method.getName() +
                                   ", Value: " + value);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
profile
컴퓨터공학과에 재학중이며, 백엔드를 지향하고 있습니다. 많이 부족하지만 열심히 노력해서 실력을 갈고 닦겠습니다. 부족하고 틀린 부분이 있을 수도 있지만 이쁘게 봐주시면 감사하겠습니다. 틀린 부분은 댓글 남겨주시면 제가 따로 학습 및 자료를 찾아봐서 제 것으로 만들도록 하겠습니다. 귀중한 시간 방문해주셔서 감사합니다.

0개의 댓글