백엔드/Spring

Spring의 어노테이션에 대해 알아보자🍀

삐삐에스 2024. 7. 17. 20:25

오늘은 스프링의 IoC와 DI 개념을 활용하여 3가지의 어노테이션의 의미를 알아보려고 한다.

 

우선 스프링의 기본 개념 4가지에 대해 복습을 해보면

1. IoC: 제어의 역전. 객체에 대한 제어권을 스프링이 가져가는 것.

2. DI: 의존성 주입. 개발자가 사용하고자 하는 객체를 스프링이 주는 것.

3. 스프링 빈: 스프링이 관리하고자 하는 객체들

4. 컨테이너: 스프링 빈이 담겨있는 일종의 공간. 스프링 빈 객체들을 모아놓고 쉽게 관리할 수 있다.

 

이제 간단히 복습도 했으니 어노테이션을 간단하게 알아보자!

 


@Component

@Component는 객체에 대한 제어권을 넘기는 것을 의미한다.

 

 

예시 코드로 더 자세히 알아보자.

@Component
public class House {
    public House() {
        System.out.println("객체가 생성됨");
    }
}

@SpringBootApplication
public class SummerApplication {
    public static void main(String[] args) {
        SpringApplication.run(SummerApplication.class, args);
    }
}

 

위처럼 코드를 작성하고 SummerApplication을 실행시켜보자.

그러면 아래와 같이 실행된다.

SummerApplication 실행 결과

여기서 보면 나는 분명 House라는 객체를 main에서 생성하지 않았는데 House 생성자에 있는 프린트 문이 실행된 것을 알 수 있다.

도대체 언제 어디서 House 객체가 생성된걸까?

 

여기서 House 클래스 위에 작성되어 있는 @Component 어노테이션에 주목해보자.

@Component 어노테이션은 쉽게 말해 "이 클래스 이제 스프링 네가 관리해줘." 라고 말하는 것과 같다.

즉, 앞서 말한 해당 객체에 대한 '제어권'을 개발자가 스프링에게 넘기는 것이다.

 

 

@ComponentScan

제어권이 넘어간 것까지는 OK.

그런데 그래서 객체를 언제 실행하는 건데? 가 궁금할 것이다.

 

아래 코드를 먼저 보자.

@SpringBootApplication
public class SummerApplication {
    public static void main(String[] args) {
        SpringApplication.run(SummerApplication.class, args);
    }
}

 

이 스프링 프로젝트는 SummerApplication을 실행함으로써 전체 프로젝트가 실행된다.

이 클래스의 상단을 보면 @SpringBootApplication이라는 어노테이션이 있다.

이 어노테이션을 한번 뜯어보자.

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(
    excludeFilters = {@Filter(
    type = FilterType.CUSTOM,
    classes = {TypeExcludeFilter.class}
), @Filter(
    type = FilterType.CUSTOM,
    classes = {AutoConfigurationExcludeFilter.class}
)}
)
public @interface SpringBootApplication {}

 

@SpringBootApplication을 뜯어 보니까 이건 인터페이스였다!(인터페이스 내부 구현부는 생략했다.)

이 인터페이스의 상단 어노테이션들을 보면 @ComponentScan이 있다.

 

 

@Autowired

나 생성잔데, 객체를 매개변수로 받을거야.
그리고 나는 내 안에서 필드값을 변경할거야.
나는 객체를 매개변수로 받아야되는데 네가 객체 관리하니까 네가 넣어줘!

 

 

@Autowired는 스프링에 객체를 달라고 요청을 보낼 때 사용하는 어노테이션이다.

즉, 생성자에 파라미터로 들어와야하는 객체를 스프링 네가 주입해줘야한다! 라고 표시하는 것이라고 생각하면 쉽다.

 

예시 코드를 보면서 더 쉽게 이해해보자.

@Component
public class Store {
    Menu menu;
    
    @Autowired
    public Store(Menu menu) {
        this.menu = menu;
        System.out.println(menu.getName() + "를 등록했습니다.");
    }
}

@Component
public class Menu {
    private String name;

    public Menu() {
        name = "hamburger";
        System.out.println(name + "을 만들었습니다!");
    }

    public String getName() {
        return name;
    }
}

 

위 코드를 보면 Store는 Menu 객체를 필드로 가지고 있고, 생성자로 하여금 Menu를 초기화하고 있다.

그리고 Menu 클래스의 생성자에서는 Menu의 필드인 name을 hamburger로 초기화하고 있다.

 

이렇게 코드를 작성하고 SummerApplication 클래스(@SpringBootApplication 어노테이션이 있는 클래스)에 있는 Main 메소드를 실행시켜보자.

SummerApplication 실행 결과

이 실행 결과를 보면 Menu 생성자에 있는 프린트문이 먼저 실행되고, 그 다음에 Store 생성자에 있는 프린트문이 생성되었다.

여기서 여러 가지 의문점이 생긴다.

 

스프링은 그러면 Menu를 먼저 생성한 것인가?

Store 생성자의 파라미터인 Menu를 어떻게 알고 주입시켰지?

등등

 

나는 여기서 두번째 질문에 대한 답을 해보겠다.

Store 클래스와 Menu 클래스에는 @Component 어노테이션이 붙어있다.

즉, 이 두 클래스의 제어권은 스프링에게로 갔고, 스프링은 프로젝트를 실행시키면서 @ComponentScan을 한다.

이때 스프링 빈들을 생성하고 이를 컨테이너에 담는다.

스프링 빈들을 생성할 때 @Autowired가 붙어있는 생성자의 경우, "스프링! 네가 여기에 필요한 객체 주입시켜줘!" 라는 메시지를 보고 @Component가 붙어있는 스프링 빈 중에 해당 타입(여기서는 Menu가 타입이 된다)을 찾아 주입시켜준다.

여기서 우리는 IoC와 DI 개념을 잘 확인할 수 있다.

 

그런데 여기서 @Autowired를 빼면 어떻게 될까?

에러가 뜨면서 프로젝트 실행이 되지 않는 것일까?

 

답은 "정상 실행된다." 이다.

 

음... 그러면 굳이 왜 @Autowired를 쓰는걸까..?

우선, 지금 @Autowired를 쓰지 않아도 스프링이 알아서 객체를 주입해주는 이유는 스프링 버전이 업그레이드되면서 @Autowired가 자동으로 등록되게끔 설정되어 있기 때문이다.

즉, 우리가 마치 @Autowired를 쓰지 않은 것같이 보이지만 생성자가 해당 클래스에 1개라면 스프링에서 자동으로 @Autowired를 넣어주고 있는 것이다.

그래서 위 코드에서도 Menu 클래스의 생성자에는 @Autowired가 생략되어 있을 것이다.

 

그래도 우리는 가독성이 좋은 클린코드를 지향하는 개발자들로서 @Autowired를 써주는걸로 하자!

반응형