자바 백엔드 입문 4편 — 어노테이션, Spring의 언어

2026-05-16자바 백엔드 입문

자바 백엔드 입문 4편. @Component·@Override·@Autowired 같은 어노테이션이 어떻게 동작하는지, 그리고 왜 Spring이 이 기호로 모든 것을 처리하는지 포스트잇 비유로 풀어쓴 학습 노트.

📚 자바 백엔드 입문 · 4편 — 어노테이션, Spring의 언어

이 글은 자바 백엔드 입문 시리즈 59편 중 4편이에요. 3편에서 인터페이스·다형성을 잡았다면, 4편은 Spring 코드에서 매번 마주치는 그 @ 기호 — 어노테이션을 잡습니다. Phase 0 자바 기초의 마지막에서 두 번째 글이고, 여기를 박아두면 5편부터 Spring 코드를 봤을 때 "이게 도대체 뭘 하는 거지?" 가 없어져요.

어노테이션이 어렵게 들리는 이유

자바 코드를 처음 펴면 @Override·@Component·@Autowired 같은 @ 가 붙은 줄이 매번 등장해요. 이게 처음엔 두 가지 이유로 어색합니다.

첫째, @ 기호가 코드처럼 안 보이고 "주석같이 생긴 무언가" 처럼 보여요. 함수도 아니고 변수도 아니고 — 도대체 이게 뭘 하는지 안 잡힙니다.

둘째, "이걸 박으면 뭐가 어떻게 달라지나" 가 안 보입니다. @Override 를 빼도 코드가 일단 도는 것 같고, @Component 를 박으면 "마법처럼" Spring이 객체를 만들어줘요. 그 "마법" 의 정체가 풀려야 어노테이션이 와닿아요.

이 글에서는 어노테이션을 포스트잇 메모 비유로 풀어 갑니다. 끝까지 따라오시면 Spring 코드의 절반을 차지하는 그 @ 기호들이 친근해져요.

어노테이션 = 코드에 붙이는 포스트잇 메모

어노테이션(Annotation)"이 코드에 이런 추가 정보를 붙여둔다" 는 메모예요. 코드 자체의 동작은 바꾸지 않고, 메타 데이터 — 즉 "이 코드에 대한 정보" 만 박아둡니다.

회사 비유로 풀어볼게요. 사무실 책상 위 서류에 노란 포스트잇으로 "수정 필요", "긴급", "검토 완료" 같은 메모가 붙어 있는 그림이에요. 서류 본문은 그대로지만, 누군가가 그 서류를 보고 "메모대로 처리" 하기로 약속한 거예요.

public class Sword implements Weapon {
    @Override                  // ← 포스트잇 1 — "이건 부모의 메서드 오버라이드야"
    public void attack() {
        System.out.println("칼을 휘두른다");
    }
}

@Component                     // ← 포스트잇 2 — "이 클래스로 객체 한 개 만들어 줘"
public class OrderService {
    @Autowired                 // ← 포스트잇 3 — "이 필드에 적절한 객체 끼워 넣어 줘"
    private PaymentGateway paymentGateway;
}

@Override"이 메서드는 부모의 메서드를 오버라이드한다" 는 메모예요. 컴파일러가 그 메모를 보고 "진짜 오버라이드가 맞는지" 검사해서 오타·시그니처 불일치 시 컴파일 에러로 알려줍니다.

@Component"이 클래스를 Spring 컨테이너에서 관리해 줘" 라는 메모예요. Spring이 시작 시점에 클래스 패스를 스캔하면서 이 메모가 박힌 클래스들을 모아서 객체로 찍어내요.

@Autowired"이 필드에 적절한 객체를 끼워 넣어 줘" 라는 메모예요. Spring 컨테이너가 객체를 조립할 때 이 메모를 보고 "PaymentGateway 인터페이스 구현체" 를 찾아 끼워 넣어줍니다.

한 줄 정리 — 어노테이션 = 코드에 붙인 메모. 누가 그 메모를 보느냐(컴파일러·Spring·런타임 라이브러리)에 따라 동작이 달라짐.

어노테이션 종류 세 가지 — 누가 읽느냐로 갈림

어노테이션은 "누가 그 메모를 읽고 행동하는가" 로 세 가지로 갈려요. 이 분류만 잡으면 자바·Spring 코드에서 어노테이션을 만났을 때 "아, 누가 이걸 읽고 있구나" 감이 잡혀요.

1. 컴파일러가 읽는 어노테이션

대표 예 — @Override, @SuppressWarnings, @Deprecated. 컴파일 시점(.java.class 변환 중)에 컴파일러가 이 메모를 읽고 처리해요. 실행 시점에는 메모 자체가 사라지거나 무의미해질 수 있어요.

@Override
public void attack() { ... }   // 컴파일러: "어 이게 정말 부모 메서드 오버라이드 맞나?" 검사

2. 런타임에 다른 코드가 읽는 어노테이션

대표 예 — @Component, @Autowired, @RequestMapping, @Transactional. 컴파일 후 .class 파일에 메모가 그대로 남아 있어요. Spring 같은 프레임워크가 실행 시점에 이 메모를 리플렉션(Reflection) 이라는 기능으로 읽어서 행동해요.

@Component                                  // 메모: "Spring아 이거 관리해 줘"
public class OrderService {
    @Autowired                              // 메모: "여기 적절한 객체 끼워 줘"
    private PaymentGateway paymentGateway;
}

Spring이 시작될 때 "메모 박힌 클래스가 있나?" 를 모든 클래스에서 스캔해요. 메모가 있으면 정해진 동작(@Component → 객체 찍기, @Autowired → 의존성 주입)을 합니다.

3. 다른 어노테이션을 정의하는 메타 어노테이션

대표 예 — @Target, @Retention. 새 어노테이션을 만들 때 "이 어노테이션은 어디에 박을 수 있고, 언제까지 살아있나" 를 정의하는 어노테이션. 입문 단계에서는 "이런 게 있다" 정도만 알면 충분해요.

💡 리플렉션이 핵심

Spring이 어노테이션을 읽는 마법의 정체는 리플렉션(Reflection)이에요. 자바가 실행 중에 "내가 지금 어떤 클래스를 가지고 있고, 그 클래스에 어떤 메모가 박혀 있나" 를 스스로 들여다보는 기능. 이 능력 덕분에 Spring이 코드를 컴파일 시점에 안 보고도 런타임에 모든 어노테이션을 읽어 처리할 수 있어요.

Spring 어노테이션 5종 미리 만나기

5편부터 본격적으로 다룰 Spring 어노테이션 중 가장 자주 보이는 5개를 미리 소개해요. 지금 정확히 외울 필요는 없고, "이런 게 나오는구나" 만 잡아두면 충분해요.

어노테이션역할 한 줄
@Component"이 클래스 객체를 Spring 컨테이너에 등록해 줘"
@Service@Component 의 별칭. 서비스 레이어 클래스에 박음 (의미만 다름)
@Repository@Component 의 별칭. 데이터 접근 레이어 클래스에 박음
@Controller@Component 의 별칭. 웹 컨트롤러 레이어 클래스에 박음
@Autowired"이 필드/생성자에 적절한 Bean을 끼워 넣어 줘"

여기서 시험 함정 하나 있어요. "@Component·@Service·@Repository·@Controller가 어떻게 다른가요?" 가 면접 단골. 답은 내부 동작은 똑같고, 이름으로 "이 클래스의 역할이 무엇인지" 만 구분해요. Spring은 네 어노테이션 모두를 "객체 찍어 컨테이너에 등록" 으로 처리합니다. 의미만 다르고 동작은 같아요. "역할별 이름 분리" 의 이유는 코드 가독성과 일부 AOP(횡단 관심사) 처리의 편의성.

직접 어노테이션 만들기 — 짧게 미리보기

자바는 어노테이션을 직접 만들 수도 있어요. 지금 단계에서 코드를 외울 필요는 없지만 "이런 게 가능하다" 만 알아두세요.

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface LogExecutionTime {
    String description() default "";
}

이렇게 만든 @LogExecutionTime 을 메서드에 박으면, AOP 처리기가 "이 메서드는 실행 시간을 로깅해야 한다" 를 알아서 처리할 수 있어요. Spring AOP를 다룰 때(시리즈 17~18편) 다시 만날 그림이에요.

한 줄 정리 — 어노테이션은 단순한 메모가 아니라 "누군가가 행동하기로 약속된 메모". 그 누군가가 컴파일러·Spring·AOP·런타임 라이브러리.

왜 어노테이션이 Spring의 언어인가

5편부터 Spring 코드를 보면 거의 모든 줄에 어노테이션이 박혀 있어요. 한 컨트롤러 클래스에 7~8개 어노테이션이 들어가는 게 보통이에요.

@RestController
@RequestMapping("/api/orders")
public class OrderController {

    @Autowired
    private OrderService orderService;

    @GetMapping("/{id}")
    public ResponseEntity<Order> getOrder(@PathVariable Long id) {
        return ResponseEntity.ok(orderService.findById(id));
    }

    @PostMapping
    @Transactional
    public Order createOrder(@RequestBody @Valid OrderRequest req) {
        return orderService.create(req);
    }
}

이 한 컨트롤러에 어노테이션이 9개 박혀 있어요. 각 어노테이션 하나하나가 Spring에게 "이 줄을 어떻게 처리해라" 라고 알려주는 명령어예요. 이게 Spring을 "어노테이션 기반 프레임워크" 라고 부르는 이유.

어노테이션이 박혀 있어야 Spring이 "이 클래스는 컨트롤러구나", "이 메서드는 GET 요청 처리", "이 매개변수는 JSON 바디" 같은 정보를 알 수 있어요. 어노테이션을 다 빼면 Spring은 그냥 일반 자바 클래스로 봐서 아무것도 안 해줍니다.

시험 직전 한 번 더 — 어노테이션 입문자가 매번 헷갈리는 것

  • 어노테이션 = 코드에 붙이는 포스트잇 메모. 동작은 안 바꾸고 메타 정보만 박음
  • @ 기호로 시작 (@Override, @Component, @Autowired...)
  • 누가 메모를 읽느냐로 세 가지로 나뉨 — 컴파일러 / 런타임 프레임워크 / 메타 어노테이션
  • @Override = 컴파일러가 읽음. 부모 메서드 오버라이드 맞는지 검사
  • @Deprecated = 컴파일러가 읽음. "쓰지 마라" 경고 띄움
  • @Component·@Autowired = Spring이 런타임에 리플렉션으로 읽음
  • 리플렉션(Reflection) = 자바가 실행 중에 자기 자신을 들여다보는 기능
  • Spring의 마법 = 어노테이션 + 리플렉션의 조합
  • @Component·@Service·@Repository·@Controller — 내부 동작 동일, 의미만 다름
  • 의미 구분 이유 = 코드 가독성 + AOP 처리 편의성
  • @Service = 서비스 레이어, @Repository = 데이터 접근 레이어, @Controller = 웹 레이어
  • @Autowired = "이 자리에 적절한 Bean 끼워 넣어 줘" 메모
  • Spring 컨트롤러 한 클래스에 보통 7~10개 어노테이션 박힘
  • 어노테이션 빼면 Spring이 인식 못 함 — 그저 일반 자바 클래스로 봄
  • @Override 박는 이유 = 오타·시그니처 불일치 컴파일 시점에 잡힘
  • @Target = "이 어노테이션이 어디에 박힐 수 있나" 정의 (메서드·필드·클래스)
  • @Retention = "이 어노테이션이 언제까지 살아있나" 정의 (소스·클래스·런타임)
  • Spring 어노테이션은 거의 다 RUNTIME Retention (실행 시점까지 살아있음)
  • 직접 어노테이션 만드는 건 가능하지만 입문 단계에서 X. AOP에서 다시 만남
  • 자바 어노테이션 = 2004년 자바 5에서 도입. Spring이 이걸 적극 활용해 코드 짧아짐

시리즈 다른 편 (앞뒤 글 모음)

이전 글:

다음 글:

※ 이 포스팅은 쿠팡 파트너스 활동의 일환으로, 이에 따른 일정액의 수수료를 제공받습니다.

답글 남기기

error: Content is protected !!